内容简介:上篇文章主要是对首先查看其中有几个列名需要说明一下:
上篇文章主要是对 shell_history
的实现进行了分析。通过分析可以发现, osquery
良好的设计使得源码简单易读。 shell_history
的整体实现也比较简单,通过读取并解析 .bash_history
中的内容,获得用户输入的历史命令。本文分析的是 process_open_sockets
,相比较而言实现更加复杂,对 Linux 也需要有更深的了解。
使用说明
首先查看 process_open_sockets
表的定义:
table_name("process_open_sockets") description("Processes which have open network sockets on the system.") schema([ Column("pid", INTEGER, "Process (or thread) ID", index=True), Column("fd", BIGINT, "Socket file descriptor number"), Column("socket", BIGINT, "Socket handle or inode number"), Column("family", INTEGER, "Network protocol (IPv4, IPv6)"), Column("protocol", INTEGER, "Transport protocol (TCP/UDP)"), Column("local_address", TEXT, "Socket local address"), Column("remote_address", TEXT, "Socket remote address"), Column("local_port", INTEGER, "Socket local port"), Column("remote_port", INTEGER, "Socket remote port"), Column("path", TEXT, "For UNIX sockets (family=AF_UNIX), the domain path"), ]) extended_schema(lambda: LINUX() or DARWIN(), [ Column("state", TEXT, "TCP socket state"), ]) extended_schema(LINUX, [ Column("net_namespace", TEXT, "The inode number of the network namespace"), ]) implementation("system/process_open_sockets@genOpenSockets") examples([ "select * from process_open_sockets where pid = 1", ])
其中有几个列名需要说明一下:
socket
我们进行一个简单的反弹 shell 的操作,然后使用查询 process_open_sockets
表的信息。
osquery> select pos.*,p.cwd,p.cmdline from process_open_sockets pos left join processes p where pos.family=2 and pos.pid=p.pid and net_namespace<>0; +-------+----+----------+--------+----------+---------------+----------------+------------+-------------+------+-------------+---------------+-----------------+-----------+ | pid | fd | socket | family | protocol | local_address | remote_address | local_port | remote_port | path | state | net_namespace | cwd | cmdline | +-------+----+----------+--------+----------+---------------+----------------+------------+-------------+------+-------------+---------------+-----------------+-----------+ | 37272 | 15 | 52319299 | 2 | 6 | 192.168.2.142 | 172.22.0.176 | 43522 | 9091 | | ESTABLISHED | 4026531956 | /home/xingjun | osqueryi | | 91155 | 2 | 56651533 | 2 | 6 | 192.168.2.142 | 192.168.2.150 | 53486 | 8888 | | ESTABLISHED | 4026531956 | /proc/79036/net | /bin/bash | +-------+----+----------+--------+----------+---------------+----------------+------------+-------------+------+-------------+---------------+-----------------+-----------+
process_open_sockets
表的实现是位于 osquery/tables/networking/linux/process_open_sockets.cpp 中。
分析
process_open_sockets
的实现全部是在 QueryData genOpenSockets(QueryContext &context)
一个方法中。
官方给出的分析步骤是:
Data for this table is fetched from 3 different sources and correlated. 1. Collect all sockets associated with each pid by going through all files under /proc/<pid>/fd and search for links of the type socket:[<inode>]. Extract the inode and fd (filename) and index it by inode number. The inode can then be used to correlate pid and fd with the socket information collected on step 3. The map generated in this step will only contain sockets associated with pids in the list, so it will also be used to filter the sockets later if pid_filter is set. 2. Collect the inode for the network namespace associated with each pid. Every time a new namespace is found execute step 3 to get socket basic information. 3. Collect basic socket information for all sockets under a specifc network namespace. This is done by reading through files under /proc/<pid>/net for the first pid we find in a certain namespace. Notice this will collect information for all sockets on the namespace not only for sockets associated with the specific pid, therefore only needs to be run once. From this step we collect the inodes of each of the sockets, and will use that to correlate the socket information with the information collect on steps 1 and 2.
其实大致步骤就是:
/proc/<pid>/net
为了方便说明,我对整个函数的代码进行切割,分步说明。
获取pid信息
std::set <std::string> pids; if (context.constraints["pid"].exists(EQUALS)) { pids = context.constraints["pid"].getAll(EQUALS); } bool pid_filter = !(pids.empty() || std::find(pids.begin(), pids.end(), "-1") != pids.end()); if (!pid_filter) { pids.clear(); status = osquery::procProcesses(pids); if (!status.ok()) { VLOG(1) << "Failed to acquire pid list: " << status.what(); return results; } }
- 前面的
context.constraints["pid"].exists(EQUALS)
、pid_filter
为了判断在 SQL 语句中是否存在where
子句以此拿到选择的pid。 - 调用
status = osquery::procProcesses(pids);
拿到对应的PID信息。
跟踪进入到 osquery/filesystem/linux/proc.cpp:procProcesses(std::set<std::string>& processes)
:
Status procProcesses(std::set<std::string>& processes) { auto callback = [](const std::string& pid, std::set<std::string>& _processes) -> bool { _processes.insert(pid); return true; }; return procEnumerateProcesses<decltype(processes)>(processes, callback); }
继续跟踪进入到 osquery/filesystem/linux/proc.h:procEnumerateProcesses(UserData& user_data,bool (*callback)(const std::string&, UserData&))
const std::string kLinuxProcPath = "/proc"; ..... template<typename UserData> Status procEnumerateProcesses(UserData &user_data,bool (*callback)(const std::string &, UserData &)) { boost::filesystem::directory_iterator it(kLinuxProcPath), end; try { for (; it != end; ++it) { if (!boost::filesystem::is_directory(it->status())) { continue; } // See #792: std::regex is incomplete until GCC 4.9 const auto &pid = it->path().leaf().string(); if (std::atoll(pid.data()) <= 0) { continue; } bool ret = callback(pid, user_data); if (ret == false) { break; } } } catch (const boost::filesystem::filesystem_error &e) { VLOG(1) << "Exception iterating Linux processes: " << e.what(); return Status(1, e.what()); } return Status(0); }
-
boost::filesystem::directory_iterator it(kLinuxProcPath), end;
遍历/proc
目录下面所有的文件, -
const auto &pid = it->path().leaf().string();..; bool ret = callback(pid, user_data);
,通过it->path().leaf().string()
判断是否为数字,之后调用bool ret = callback(pid, user_data);
- callback 方法
_processes.insert(pid);return true;
将查询到的pid全部记录到user_data
中。
以一个反弹shell的例子为例,使用 osqueryi
查询到的信息如下:
osquery> select * from process_open_sockets where pid=14960; +-------+----+--------+--------+----------+---------------+----------------+------------+-------------+------+-------------+---------------+ | pid | fd | socket | family | protocol | local_address | remote_address | local_port | remote_port | path | state | net_namespace | +-------+----+--------+--------+----------+---------------+----------------+------------+-------------+------+-------------+---------------+ | 14960 | 2 | 307410 | 2 | 6 | 192.168.2.156 | 192.168.2.145 | 51118 | 8888 | | ESTABLISHED | 4026531956 | +-------+----+--------+--------+----------+---------------+----------------+------------+-------------+------+-------------+---------------+
获取进程对应的pid和fd信息
/* Use a set to record the namespaces already processed */ std::set <ino_t> netns_list; SocketInodeToProcessInfoMap inode_proc_map; SocketInfoList socket_list; for (const auto &pid : pids) { /* Step 1 */ status = procGetSocketInodeToProcessInfoMap(pid, inode_proc_map); if (!status.ok()) { VLOG(1) << "Results for process_open_sockets might be incomplete. Failed " "to acquire socket inode to process map for pid " << pid << ": " << status.what(); }
在拿到所有的需要查询的pid信息之后,调用 status = procGetSocketInodeToProcessInfoMap(pid, inode_proc_map);
,顾名思义就是用于获取进程所对应的socket inode编号。进入到 osquery/filesystem/linux/proc.cpp:procGetSocketInodeToProcessInfoMap()
中:
Status procGetSocketInodeToProcessInfoMap(const std::string &pid,SocketInodeToProcessInfoMap &result) { auto callback = [](const std::string &_pid, const std::string &fd, const std::string &link, SocketInodeToProcessInfoMap &_result) -> bool { /* We only care about sockets. But there will be other descriptors. */ if (link.find("socket:[") != 0) { return true; } std::string inode = link.substr(8, link.size() - 9); _result[inode] = {_pid, fd}; return true; }; return procEnumerateProcessDescriptors<decltype(result)>( pid, result, callback); }
其中的 auto callback
定义的是一个回调函数,进入到 procEnumerateProcessDescriptors()
中分析:
const std::string kLinuxProcPath = "/proc"; .... template<typename UserData> Status procEnumerateProcessDescriptors(const std::string &pid, UserData &user_data, bool (*callback)(const std::string &pid, const std::string &fd, const std::string &link, UserData &user_data)) { std::string descriptors_path = kLinuxProcPath + "/" + pid + "/fd"; try { boost::filesystem::directory_iterator it(descriptors_path), end; for (; it != end; ++it) { auto fd = it->path().leaf().string(); std::string link; Status status = procReadDescriptor(pid, fd, link); if (!status.ok()) { VLOG(1) << "Failed to read the link for file descriptor " << fd << " of pid " << pid << ". Data might be incomplete."; } bool ret = callback(pid, fd, link, user_data); if (ret == false) { break; } } } catch (boost::filesystem::filesystem_error &e) { VLOG(1) << "Exception iterating process file descriptors: " << e.what(); return Status(1, e.what()); } return Status(0); }
这个代码写得十分清晰。
- 遍历
/proc/pid/fd
,拿到所有的文件描述符。在本例中即为/proc/14960/fd
。
-
回调
bool ret = callback(pid, fd, link, user_data);
,即之前在procGetSocketInodeToProcessInfoMap
中定义的:auto callback = [](const std::string &_pid, const std::string &fd, const std::string &link, SocketInodeToProcessInfoMap &_result) -> bool { /* We only care about sockets. But there will be other descriptors. */ if (link.find("socket:[") != 0) { return true; } std::string inode = link.substr(8, link.size() - 9); _result[inode] = {_pid, fd}; return true; };
代码也十分地简单,拿到fd所对应的link,检查是否存在
socket:[
,如果存在获取对应的inode
。由于查询的是process_open_sockets
,所以我们仅仅只关心存在socket
的link,在本例中就是307410
。最终在SocketInodeToProcessInfoMap
中的结构就是_result[inode] = {_pid, fd};
。以inode
作为key,包含了pid
和fd
的信息。
获取进程对应的ns信息
在上一步 status = procGetSocketInodeToProcessInfoMap(pid, inode_proc_map);
执行完毕之后,得到 _result[inode] = {_pid, fd};
。将inode与pid和fd进行了关联。接下里就是解析进程对应的ns信息。
ino_t ns; ProcessNamespaceList namespaces; status = procGetProcessNamespaces(pid, namespaces, {"net"}); if (status.ok()) { ns = namespaces["net"]; } else { /* If namespaces are not available we allways set ns to 0 and step 3 will * run once for the first pid in the list. */ ns = 0; VLOG(1) << "Results for the process_open_sockets might be incomplete." "Failed to acquire network namespace information for process " "with pid " << pid << ": " << status.what(); } ``` 跟踪进入到`status = procGetProcessNamespaces(pid, namespaces, {"net"});`,进入到`osquery/filesystem/linux/proc.cpp:procGetProcessNamespaces()` ```CPP const std::string kLinuxProcPath = "/proc"; ... Status procGetProcessNamespaces(const std::string &process_id,ProcessNamespaceList &namespace_list,std::vector <std::string> namespaces) { namespace_list.clear(); if (namespaces.empty()) { namespaces = kUserNamespaceList; } auto process_namespace_root = kLinuxProcPath + "/" + process_id + "/ns"; for (const auto &namespace_name : namespaces) { ino_t namespace_inode; auto status = procGetNamespaceInode(namespace_inode, namespace_name, process_namespace_root); if (!status.ok()) { continue; } namespace_list[namespace_name] = namespace_inode; } return Status(0, "OK"); }
遍历 const auto &namespace_name : namespaces
,之后进入到 process_namespace_root
中,调用 procGetNamespaceInode(namespace_inode, namespace_name, process_namespace_root);
进行查询。在本例中 namespaces
是 {"net"}
, process_namespace_root
是 /proc/14960/ns
。
分析 procGetNamespaceInode(namespace_inode, namespace_name, process_namespace_root)
:
Status procGetNamespaceInode(ino_t &inode,const std::string &namespace_name,const std::string &process_namespace_root) { inode = 0; auto path = process_namespace_root + "/" + namespace_name; char link_destination[PATH_MAX] = {}; auto link_dest_length = readlink(path.data(), link_destination, PATH_MAX - 1); if (link_dest_length < 0) { return Status(1, "Failed to retrieve the inode for namespace " + path); } // The link destination must be in the following form: namespace:[inode] if (std::strncmp(link_destination, namespace_name.data(), namespace_name.size()) != 0 || std::strncmp(link_destination + namespace_name.size(), ":[", 2) != 0) { return Status(1, "Invalid descriptor for namespace " + path); } // Parse the inode part of the string; strtoull should return us a pointer // to the closing square bracket const char *inode_string_ptr = link_destination + namespace_name.size() + 2; char *square_bracket_ptr = nullptr; inode = static_cast<ino_t>( std::strtoull(inode_string_ptr, □_bracket_ptr, 10)); if (inode == 0 || square_bracket_ptr == nullptr || *square_bracket_ptr != ']') { return Status(1, "Invalid inode value in descriptor for namespace " + path); } return Status(0, "OK"); }
根据 procGetProcessNamespaces()
中定义的相关变量,得到 path
是 /proc/pid/ns/net
,在本例中是 /proc/14960/ns/net
。通过 inode = static_cast<ino_t>(std::strtoull(inode_string_ptr, &square_bracket_ptr, 10));
,解析 /proc/pid/ns/net
所对应的 inode
。在本例中:
所以取到的 inode
是 4026531956
。之后在 procGetProcessNamespaces()
中执行 namespace_list[namespace_name] = namespace_inode;
,所以 namespace_list['net']=4026531956
。最终 ns = namespaces["net"];
,所以得到的 ns=4026531956
。
解析进程的net信息
// Linux proc protocol define to net stats file name. const std::map<int, std::string> kLinuxProtocolNames = { {IPPROTO_ICMP, "icmp"}, {IPPROTO_TCP, "tcp"}, {IPPROTO_UDP, "udp"}, {IPPROTO_UDPLITE, "udplite"}, {IPPROTO_RAW, "raw"}, }; ... if (netns_list.count(ns) == 0) { netns_list.insert(ns); /* Step 3 */ for (const auto &pair : kLinuxProtocolNames) { status = procGetSocketList(AF_INET, pair.first, ns, pid, socket_list); if (!status.ok()) { VLOG(1) << "Results for process_open_sockets might be incomplete. Failed " "to acquire basic socket information for AF_INET " << pair.second << ": " << status.what(); } status = procGetSocketList(AF_INET6, pair.first, ns, pid, socket_list); if (!status.ok()) { VLOG(1) << "Results for process_open_sockets might be incomplete. Failed " "to acquire basic socket information for AF_INET6 " << pair.second << ": " << status.what(); } } status = procGetSocketList(AF_UNIX, IPPROTO_IP, ns, pid, socket_list); if (!status.ok()) { VLOG(1) << "Results for process_open_sockets might be incomplete. Failed " "to acquire basic socket information for AF_UNIX: " << status.what(); } }
对于 icmp/tcp/udp/udplite/raw
会调用 status = procGetSocketList(AF_INET|AF_INET6|AF_UNIX, pair.first, ns, pid, socket_list);
。我们这里仅仅以 procGetSocketList(AF_INET, pair.first, ns, pid, socket_list);
进行说明(其中的 ns
就是 4026531956
)。
Status procGetSocketList(int family, int protocol,ino_t net_ns,const std::string &pid, SocketInfoList &result) { std::string path = kLinuxProcPath + "/" + pid + "/net/"; switch (family) { case AF_INET: if (kLinuxProtocolNames.count(protocol) == 0) { return Status(1,"Invalid family " + std::to_string(protocol) +" for AF_INET familiy"); } else { path += kLinuxProtocolNames.at(protocol); } break; case AF_INET6: if (kLinuxProtocolNames.count(protocol) == 0) { return Status(1,"Invalid protocol " + std::to_string(protocol) +" for AF_INET6 familiy"); } else { path += kLinuxProtocolNames.at(protocol) + "6"; } break; case AF_UNIX: if (protocol != IPPROTO_IP) { return Status(1, "Invalid protocol " + std::to_string(protocol) + " for AF_UNIX familiy"); } else { path += "unix"; } break; default: return Status(1, "Invalid family " + std::to_string(family)); } std::string content; if (!osquery::readFile(path, content).ok()) { return Status(1, "Could not open socket information from " + path); } Status status(0); switch (family) { case AF_INET: case AF_INET6: status = procGetSocketListInet(family, protocol, net_ns, path, content, result); break; case AF_UNIX: status = procGetSocketListUnix(net_ns, path, content, result); break; } return status; }
由于我们的传参是 family=AF_INET,protocol=tcp,net_ns=4026531956,pid=14960
。执行流程如下:
-
path += kLinuxProtocolNames.at(protocol);
,得到path
是/proc/14960/net/tcp
-
osquery::readFile(path, content).ok()
,读取文件内容,即/proc/14960/net/tcp
所对应的文件内容。在本例中是:sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode 0: 00000000:1538 00000000:0000 0A 00000000:00000000 00:00000000 00000000 26 0 26488 1 ffff912c69c21740 100 0 0 10 0 1: 0100007F:0019 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 28721 1 ffff912c69c23640 100 0 0 10 0 2: 00000000:01BB 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 27739 1 ffff912c69c21f00 100 0 0 10 0 3: 0100007F:18EB 00000000:0000 0A 00000000:00000000 00:00000000 00000000 988 0 25611 1 ffff912c69c207c0 100 0 0 10 0 4: 00000000:0050 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 27737 1 ffff912c69c226c0 100 0 0 10 0 5: 017AA8C0:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 29031 1 ffff912c69c23e00 100 0 0 10 0 6: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 25754 1 ffff912c69c20f80 100 0 0 10 0 7: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 25590 1 ffff912c69c20000 100 0 0 10 0 8: 9C02A8C0:C7AE 9102A8C0:22B8 01 00000000:00000000 00:00000000 00000000 1000 0 307410 1 ffff912c374887c0 20 0 0 10 -1
-
执行
procGetSocketListInet(family, protocol, net_ns, path, content, result);
分析
static Status procGetSocketListInet(int family,int protocol,ino_t net_ns,const std::string &path,const std::string &content,SocketInfoList &result) { // The system's socket information is tokenized by line. bool header = true; for (const auto &line : osquery::split(content, "\n")) { if (header) { if (line.find("sl") != 0 && line.find("sk") != 0) { return Status(1, std::string("Invalid file header for ") + path); } header = false; continue; } // The socket information is tokenized by spaces, each a field. auto fields = osquery::split(line, " "); if (fields.size() < 10) { VLOG(1) << "Invalid socket descriptor found: '" << line << "'. Skipping this entry"; continue; } // Two of the fields are the local/remote address/port pairs. auto locals = osquery::split(fields[1], ":"); auto remotes = osquery::split(fields[2], ":"); if (locals.size() != 2 || remotes.size() != 2) { VLOG(1) << "Invalid socket descriptor found: '" << line << "'. Skipping this entry"; continue; } SocketInfo socket_info = {}; socket_info.socket = fields[9]; socket_info.net_ns = net_ns; socket_info.family = family; socket_info.protocol = protocol; socket_info.local_address = procDecodeAddressFromHex(locals[0], family); socket_info.local_port = procDecodePortFromHex(locals[1]); socket_info.remote_address = procDecodeAddressFromHex(remotes[0], family); socket_info.remote_port = procDecodePortFromHex(remotes[1]); if (protocol == IPPROTO_TCP) { char *null_terminator_ptr = nullptr; auto integer_socket_state = std::strtoull(fields[3].data(), &null_terminator_ptr, 16); if (integer_socket_state == 0 || integer_socket_state >= tcp_states.size() || null_terminator_ptr == nullptr || *null_terminator_ptr != 0) { socket_info.state = "UNKNOWN"; } else { socket_info.state = tcp_states[integer_socket_state]; } } result.push_back(std::move(socket_info)); } return Status(0); }
整个执行流程如下:
-
const auto &line : osquery::split(content, "\n");.. auto fields = osquery::split(line, " ");
解析文件,读取每一行的内容。对每一行采用空格分割; -
解析信息
SocketInfo socket_info = {}; socket_info.socket = fields[9]; socket_info.net_ns = net_ns; socket_info.family = family; socket_info.protocol = protocol; socket_info.local_address = procDecodeAddressFromHex(locals[0], family); socket_info.local_port = procDecodePortFromHex(locals[1]); socket_info.remote_address = procDecodeAddressFromHex(remotes[0], family); socket_info.remote_port = procDecodePortFromHex(remotes[1]);
解析
/proc/14960/net/tcp
文件中的每一行,分别填充至socket_info
结构中。但是在/proc/14960/net/tcp
并不是所有的信息都是我们需要的,我们还需要对信息进行过滤。可以看到最后一条的inode
是307410
才是我们需要的。
获取进程连接信息
将解析完毕 /proc/14960/net/tcp
获取 socket_info
之后,继续执行 genOpenSockets()
中的代码。
for (const auto &info : socket_list) { Row r; auto proc_it = inode_proc_map.find(info.socket); if (proc_it != inode_proc_map.end()) { r["pid"] = proc_it->second.pid; r["fd"] = proc_it->second.fd; } else if (!pid_filter) { r["pid"] = "-1"; r["fd"] = "-1"; } else { /* If we're filtering by pid we only care about sockets associated with * pids on the list.*/ continue; } r["socket"] = info.socket; r["family"] = std::to_string(info.family); r["protocol"] = std::to_string(info.protocol); r["local_address"] = info.local_address; r["local_port"] = std::to_string(info.local_port); r["remote_address"] = info.remote_address; r["remote_port"] = std::to_string(info.remote_port); r["path"] = info.unix_socket_path; r["state"] = info.state; r["net_namespace"] = std::to_string(info.net_ns); results.push_back(std::move(r)); }
其中关键代码是:
auto proc_it = inode_proc_map.find(info.socket); if (proc_it != inode_proc_map.end()) {
通过遍历 socket_list
,判断在第一步保存在 inode_proc_map
中的inode信息与 info
中的inode信息是否一致,如果一致,说明就是我们需要的那个进程的网络连接的信息。最终保存我们查询到的信息 results.push_back(std::move(r));
。
到这里,我们就查询到了进程的所有的网络连接的信息。最终通过 osquery
展现。
osquery> select * from process_open_sockets where pid=14960; +-------+----+--------+--------+----------+---------------+----------------+------------+-------------+------+-------------+---------------+ | pid | fd | socket | family | protocol | local_address | remote_address | local_port | remote_port | path | state | net_namespace | +-------+----+--------+--------+----------+---------------+----------------+------------+-------------+------+-------------+---------------+ | 14960 | 2 | 307410 | 2 | 6 | 192.168.2.156 | 192.168.2.145 | 51118 | 8888 | | ESTABLISHED | 4026531956 | +-------+----+--------+--------+----------+---------------+----------------+------------+-------------+------+-------------+---------------+
以上就是整个osquery执行 process_open_sockets
表查询的整个流程。
扩展
Linux一些皆文件的特性,使得我们能够通过读取Linux下某些文件信息获取系统/进程所有的信息。在前面我们仅仅是从 osquery
的角度来分析的。本节主要是对Linux中的与网络有关、进程相关的信息进行说明。
在 /proc/net/tcp
、 /proc/net/udp
中保存了当前系统中所有的进程信息,与 /proc/pid/net/tcp
或者是 /proc/pid/net/udp
中保存的信息完全相同。
/proc/net/tcp
信息如下:
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode 0: 00000000:1538 00000000:0000 0A 00000000:00000000 00:00000000 00000000 26 0 26488 1 ffff912c69c21740 100 0 0 10 0 1: 0100007F:0019 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 28721 1 ffff912c69c23640 100 0 0 10 0 2: 00000000:01BB 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 27739 1 ffff912c69c21f00 100 0 0 10 0 3: 00000000:1F40 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 471681 1 ffff912c37488f80 100 0 0 10 0 4: 0100007F:18EB 00000000:0000 0A 00000000:00000000 00:00000000 00000000 988 0 25611 1 ffff912c69c207c0 100 0 0 10 0 5: 00000000:0050 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 27737 1 ffff912c69c226c0 100 0 0 10 0 6: 017AA8C0:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 29031 1 ffff912c69c23e00 100 0 0 10 0 7: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 25754 1 ffff912c69c20f80 100 0 0 10 0 8: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 25590 1 ffff912c69c20000 100 0 0 10 0 9: 9C02A8C0:C7AE 9102A8C0:22B8 01 00000000:00000000 00:00000000 00000000 1000 0 307410 1 ffff912c374887c0 20 0 0 10 -1
/proc/14960/net/tcp
信息如下:
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode 0: 00000000:1538 00000000:0000 0A 00000000:00000000 00:00000000 00000000 26 0 26488 1 ffff912c69c21740 100 0 0 10 0 1: 0100007F:0019 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 28721 1 ffff912c69c23640 100 0 0 10 0 2: 00000000:01BB 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 27739 1 ffff912c69c21f00 100 0 0 10 0 3: 0100007F:18EB 00000000:0000 0A 00000000:00000000 00:00000000 00000000 988 0 25611 1 ffff912c69c207c0 100 0 0 10 0 4: 00000000:0050 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 27737 1 ffff912c69c226c0 100 0 0 10 0 5: 017AA8C0:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 29031 1 ffff912c69c23e00 100 0 0 10 0 6: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 25754 1 ffff912c69c20f80 100 0 0 10 0 7: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 25590 1 ffff912c69c20000 100 0 0 10 0 8: 9C02A8C0:C7AE 9102A8C0:22B8 01 00000000:00000000 00:00000000 00000000 1000 0 307410 1 ffff912c374887c0 20 0 0 10 -1
我们每一列的含义都是固定的,我们以最终一列 9C02A8C0:C7AE 9102A8C0:22B8 01 00000000:00000000 00:00000000 00000000 1000 0 307410 1 ffff912c374887c0 20 0 0 10 -1
为例进行说明。
-
local_address
,本地通讯端口和IP,本例是9C02A8C0:C7AE
。9C02A8C0
,是本地IP。9C02A8C0
是十六进制,转换为十进制是2617419968
,将其转换为IP地址则是156.2.168.192
,倒装一下得到192.168.2.156
。C7AE
转化为十进制是51118
。所以当进行网络通信时,得到本地IP是192.168.2.156
,端口是51118
。 -
rem_address
,远程服务器通信端口和IP,本例是9102A8C0:22B8
。9102A8C0
是远程IP。分析方法和local_address
相同,得到远程IP是192.168.2.145
,端口是8888
。 -
st
,socket的状态,本例是01
。st
的不同的值表示不同的含义。- 01: ESTABLISHED,
- 02: SYN_SENT
- 03: SYN_RECV
- 04: FIN_WAIT1
- 05: FIN_WAIT2
- 06: TIME_WAIT
- 07: CLOSE
- 08: CLOSE_WAIT
- 09: LAST_ACK
- 0A: LISTEN
- 0B: CLOSING
所以在本例中01
则说明是ESTABLISHED
状态。
-
tx_queue
, 表示发送队列中的数据长度,本例是00000000
。 -
rx_queue
, 如果状态是ESTABLISHED
,表示接受队列中数据长度;如果是LISTEN
,表示已完成连接队列的长度; -
tr
,定时器类型。为0,表示没有启动计时器;为1,表示重传定时器;为2,表示连接定时器;为3,表示TIME_WAIT定时器;为4,表示持续定时器; -
tm->when
,超时时间。 -
retrnsmt
,超时重传次数 -
uid
,用户id -
timeout
,持续定时器或保洁定时器周期性发送出去但未被确认的TCP段数目,在收到ACK之后清零 -
inode
,socket连接对应的inode -
1
,没有显示header,表示的是socket的引用数目 -
ffff912c374887c0
,没有显示header,表示sock结构对应的地址 -
20
,没有显示header,表示RTO
,单位是clock_t
-
0
,用来计算延时确认的估值 -
0
,快速确认数和是否启用标志位的或元算结果 -
10
,当前拥塞窗口大小 -
-1
,如果慢启动阈值大于等于0x7fffffff显示-1,否则表示慢启动阈值
proc_net_tcp_decode 这篇文章对每个字段也进行了详细地说明。
通过查看某个具体的pid的fd信息,检查是否存在以 socket:
开头的文件描述符,如果存在则说明存在网络通信。
在得到了 socket
所对应的inode之后,就可以在 /proc/net/tcp
中查询对应的socket的信息,比如远程服务器的IP和端口信息。这样通过socket的inode就可以关联进程信息和它的网络信息。
总结
论读源代码的重要性
以上
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Phoenix解读 | Phoenix源码解读之索引
- Phoenix解读 | Phoenix源码解读之SQL
- Redux 源码解读 —— 从源码开始学 Redux
- AQS源码详细解读
- SDWebImage源码解读《一》
- MJExtension源码解读
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
形式感+:网页视觉设计创意拓展与快速表现
晋小彦 / 清华大学出版社 / 2014-1-1 / 59.00元
网页设计师从早年的综合性工作中分化出来,形成了相对独立的专业岗位,网页设计也不再是单纯的软件应用,它衍生出了许多独立的研究方向,当网站策划、交互体验都逐渐独立之后,形式感的突破和表现成为网页视觉设计的一项重要工作。随着时代的发展,网页设计更接近于一门艺术。网络带宽和硬件的发展为网页提供了使用更大图片、动画甚至视频的权利,而这些也为视觉设计师提供了更多表现的空间。另外多终端用户屏幕(主要是各种移动设......一起来看看 《形式感+:网页视觉设计创意拓展与快速表现》 这本书的介绍吧!