标签 netstat 下的文章

ss源代码调试&原理分析

源代码调试

ss是位于iproute2这个库中,可以从iproute2上面下载到源代码,配置其源代码调试的方式和netstat源代码调试这篇文章一样.

在根目录下创建CMakeLists.txt文件,内容如下:

cmake_minimum_required(VERSION 3.13)
project(test C)
 
set(BUILD_DIR .)
 
#add_executable()
add_custom_target(ss command -c ${BUILD_DIR})

同时修改Makefile文件中的45行的CCOPTS = -O2CCOPTS = -O0 -g3

在clion中配置Target:

clion-settings.png

Netid  State      Recv-Q Send-Q Local Address:Port             Peer Address:Port
tcp    ESTAB      0      0      127.0.0.1:57354                127.0.0.1:socks               
tcp    ESTAB      0      0      127.0.0.1:37350                127.0.0.1:socks               
tcp    ESTAB      0      0      172.16.40.154:43450                45.8.223.61:17250               
tcp    CLOSE-WAIT 1      0      127.0.0.1:57398                127.0.0.1:socks               
tcp    ESTAB      0      0      127.0.0.1:57062                127.0.0.1:socks

和直接运行ss命令得到的结果一样.接下来就是分析整个ss程序的执行流程

main

main函数就是用于对各种选项进行解析,并以此判断执行什么函数.

int main(int argc, char *argv[])
{
    int saw_states = 0;
    int saw_query = 0;
    int do_summary = 0;
    const char *dump_tcpdiag = NULL;
    FILE *filter_fp = NULL;
    int ch;
    int state_filter = 0;
    int addrp_width, screen_width = 80;
 
    while ((ch = getopt_long(argc, argv,
                 "dhaletuwxnro460spbEf:miA:D:F:vVzZN:KHS",
                 long_opts, NULL)) != EOF) {
        switch (ch) {
        case 'n':
            resolve_services = 0;
            break;
        ......
        }
        .....
    }

在默认情况下,会进入到如下代码中

if (do_default) {
    state_filter = state_filter ? state_filter : SS_CONN;
    filter_default_dbs(&current_filter);
}

程序会执行filter_default_dbs()函数,设置默认的过滤条件.

filter_default_dbs

static void filter_default_dbs(struct filter *f) {
    filter_db_set(f, UDP_DB);
    filter_db_set(f, DCCP_DB);
    filter_db_set(f, TCP_DB);
    filter_db_set(f, RAW_DB);
    filter_db_set(f, UNIX_ST_DB);
    filter_db_set(f, UNIX_DG_DB);
    filter_db_set(f, UNIX_SQ_DB);
    filter_db_set(f, PACKET_R_DB);
    filter_db_set(f, PACKET_DG_DB);
    filter_db_set(f, NETLINK_DB);
    filter_db_set(f, SCTP_DB);
}

ilter_default_dbs很简单就是在默认情况下设置的过滤条件.

之后程序会执行到unix_show(&current_filter);

unix_show

函数代码如下:

static void filter_default_dbs(struct filter *f) {
    filter_db_set(f, UDP_DB);
    filter_db_set(f, DCCP_DB);
    filter_db_set(f, TCP_DB);
    filter_db_set(f, RAW_DB);
    filter_db_set(f, UNIX_ST_DB);
    filter_db_set(f, UNIX_DG_DB);
    filter_db_set(f, UNIX_SQ_DB);
    filter_db_set(f, PACKET_R_DB);
    filter_db_set(f, PACKET_DG_DB);
    filter_db_set(f, NETLINK_DB);
    filter_db_set(f, SCTP_DB);
}
filter_default_dbs很简单就是在默认情况下设置的过滤条件.

之后程序会执行到unix_show(&current_filter);

unix_show
函数代码如下:

unix_show  Collapse source
static int unix_show(struct filter *f)
{
    FILE *fp;
    char buf[256];
    char name[128];
    int  newformat = 0;
    int  cnt;
    struct sockstat *list = NULL;
    const int unix_state_map[] = { SS_CLOSE, SS_SYN_SENT,
                       SS_ESTABLISHED, SS_CLOSING };
 
    if (!filter_af_get(f, AF_UNIX))
        return 0;
 
    if (!getenv("PROC_NET_UNIX") && !getenv("PROC_ROOT")
        && unix_show_netlink(f) == 0)
        return 0;
 
    if ((fp = net_unix_open()) == NULL)
        return -1;
    if (!fgets(buf, sizeof(buf), fp)) {
        fclose(fp);
        return -1;
    }
 
    if (memcmp(buf, "Peer", 4) == 0)
        newformat = 1;
    cnt = 0;
 
    while (fgets(buf, sizeof(buf), fp)) {
        struct sockstat *u, **insp;
        int flags;
 
        if (!(u = calloc(1, sizeof(*u))))
            break;
 
        if (sscanf(buf, "%x: %x %x %x %x %x %d %s",
               &u->rport, &u->rq, &u->wq, &flags, &u->type,
               &u->state, &u->ino, name) < 8)
            name[0] = 0;
 
        u->lport = u->ino;
        u->local.family = u->remote.family = AF_UNIX;
 
        if (flags & (1 << 16)) {
            u->state = SS_LISTEN;
        } else if (u->state > 0 &&
               u->state <= ARRAY_SIZE(unix_state_map)) {
            u->state = unix_state_map[u->state-1];
            if (u->type == SOCK_DGRAM && u->state == SS_CLOSE && u->rport)
                u->state = SS_ESTABLISHED;
        }
        if (unix_type_skip(u, f) ||
            !(f->states & (1 << u->state))) {
            free(u);
            continue;
        }
 
        if (!newformat) {
            u->rport = 0;
            u->rq = 0;
            u->wq = 0;
        }
 
        if (name[0]) {
            u->name = strdup(name);
            if (!u->name) {
                free(u);
                break;
            }
        }
 
        if (u->rport) {
            struct sockstat *p;
 
            for (p = list; p; p = p->next) {
                if (u->rport == p->lport)
                    break;
            }
            if (!p)
                u->peer_name = "?";
            else
                u->peer_name = p->name ? : "*";
        }
 
        if (f->f) {
            struct sockstat st = {
                .local.family = AF_UNIX,
                .remote.family = AF_UNIX,
            };
 
            memcpy(st.local.data, &u->name, sizeof(u->name));
            if (strcmp(u->peer_name, "*"))
                memcpy(st.remote.data, &u->peer_name,
                       sizeof(u->peer_name));
            if (run_ssfilter(f->f, &st) == 0) {
                free(u->name);
                free(u);
                continue;
            }
        }
 
        insp = &list;
        while (*insp) {
            if (u->type < (*insp)->type ||
                (u->type == (*insp)->type &&
                 u->ino < (*insp)->ino))
                break;
            insp = &(*insp)->next;
        }
        u->next = *insp;
        *insp = u;
 
        if (++cnt > MAX_UNIX_REMEMBER) {
            while (list) {
                unix_stats_print(list, f);
                printf("\n");
 
                unix_list_drop_first(&list);
            }
            cnt = 0;
        }
    }
    fclose(fp);
    while (list) {
        unix_stats_print(list, f);
        printf("\n");
 
        unix_list_drop_first(&list);
    }
 
    return 0;
}

这个函数就是解析网络数据的核心函数.代码较多,还是分布分析这些代码.

unix_show_netlink

if (!getenv("PROC_NET_UNIX") && !getenv("PROC_ROOT")
       && unix_show_netlink(f) == 0)
       return 0;
  • getenv判断PROC_NET_UNIXPROC_ROOT是否存在
  • unix_show_netlink(f)创建netlink

追踪进入到unix_show_netlink()

static int unix_show_netlink(struct filter *f)
{
    DIAG_REQUEST(req, struct unix_diag_req r);
 
    req.r.sdiag_family = AF_UNIX;
    req.r.udiag_states = f->states;
    req.r.udiag_show = UDIAG_SHOW_NAME | UDIAG_SHOW_PEER | UDIAG_SHOW_RQLEN;
    if (show_mem)
        req.r.udiag_show |= UDIAG_SHOW_MEMINFO;
 
    return handle_netlink_request(f, &req.nlh, sizeof(req), unix_show_sock);
}

f是一个filter,用于设置一些简单的过滤条件.

req.r.sdiag_family = AF_UNIX;
req.r.udiag_states = f->states;
req.r.udiag_show = UDIAG_SHOW_NAME | UDIAG_SHOW_PEER | UDIAG_SHOW_RQLEN;

是用于设置diag_netnetlink的请求头,之后调用handle_netlink_request(f, &req.nlh, sizeof(req),unix_show_sock);

handle_netlink_request

跟踪进入到handle_netlink_request的实现

static int handle_netlink_request(struct filter *f, struct nlmsghdr *req,
        size_t size, rtnl_filter_t show_one_sock)
{
    int ret = -1;
    struct rtnl_handle rth;
 
    if (rtnl_open_byproto(&rth, 0, NETLINK_SOCK_DIAG))
        return -1;
 
    rth.dump = MAGIC_SEQ;
 
    if (rtnl_send(&rth, req, size) < 0)
        goto Exit;
 
    if (rtnl_dump_filter(&rth, show_one_sock, f))
        goto Exit;
 
    ret = 0;
Exit:
    rtnl_close(&rth);
    return ret;
}
  • 调用rtnl_send(&rth, req, size)用于发送diag_netnetlink的消息头.
  • rtnl_dump_filter(&rth, show_one_sock,f)获取netlink的返回消息,回调show_one_sock()函数.

rtnl_send

跟踪进入到lib/libnetlink.c

int rtnl_send(struct rtnl_handle *rth, const void *buf, int len)
{
    return send(rth->fd, buf, len, 0);
}

rtnl_send直接调用send()方法发送信息.

rtnl_dump_filter

跟踪进入到lib/libnetlink.c

int rtnl_dump_filter_nc(struct rtnl_handle *rth,
             rtnl_filter_t filter,
             void *arg1, __u16 nc_flags)
{
    const struct rtnl_dump_filter_arg a[2] = {
        { .filter = filter, .arg1 = arg1, .nc_flags = nc_flags, },
        { .filter = NULL,   .arg1 = NULL, .nc_flags = 0, },
    };
 
    return rtnl_dump_filter_l(rth, a);
}

rtnl_dump_filter_nc()中设置rtnl_dump_filter_arg过滤函数,之后调用rtnl_dump_filter_l()

int rtnl_dump_filter_l(struct rtnl_handle *rth,
               const struct rtnl_dump_filter_arg *arg)
{
    struct sockaddr_nl nladdr;
    struct iovec iov;
    struct msghdr msg = {
        .msg_name = &nladdr,
        .msg_namelen = sizeof(nladdr),
        .msg_iov = &iov,
        .msg_iovlen = 1,
    };
    char buf[32768];
    int dump_intr = 0;
 
    iov.iov_base = buf;
    while (1) {
        int status;
        const struct rtnl_dump_filter_arg *a;
        int found_done = 0;
        int msglen = 0;
 
        iov.iov_len = sizeof(buf);
        status = recvmsg(rth->fd, &msg, 0);
 
        if (status < 0) {
            if (errno == EINTR || errno == EAGAIN)
                continue;
            fprintf(stderr, "netlink receive error %s (%d)\n",
                strerror(errno), errno);
            return -1;
        }
 
        if (status == 0) {
            fprintf(stderr, "EOF on netlink\n");
            return -1;
        }
 
        if (rth->dump_fp)
            fwrite(buf, 1, NLMSG_ALIGN(status), rth->dump_fp);
 
        for (a = arg; a->filter; a++) {
            struct nlmsghdr *h = (struct nlmsghdr *)buf;
 
            msglen = status;
 
            while (NLMSG_OK(h, msglen)) {
                int err = 0;
 
                h->nlmsg_flags &= ~a->nc_flags;
 
                if (nladdr.nl_pid != 0 ||
                    h->nlmsg_pid != rth->local.nl_pid ||
                    h->nlmsg_seq != rth->dump)
                    goto skip_it;
 
                if (h->nlmsg_flags & NLM_F_DUMP_INTR)
                    dump_intr = 1;
 
                if (h->nlmsg_type == NLMSG_DONE) {
                    err = rtnl_dump_done(h);
                    if (err < 0)
                        return -1;
 
                    found_done = 1;
                    break; /* process next filter */
                }
 
                if (h->nlmsg_type == NLMSG_ERROR) {
                    rtnl_dump_error(rth, h);
                    return -1;
                }
 
                if (!rth->dump_fp) {
                    err = a->filter(&nladdr, h, a->arg1);
                    if (err < 0)
                        return err;
                }
 
skip_it:
                h = NLMSG_NEXT(h, msglen);
            }
        }
 
        if (found_done) {
            if (dump_intr)
                fprintf(stderr,
                    "Dump was interrupted and may be inconsistent.\n");
            return 0;
        }
 
        if (msg.msg_flags & MSG_TRUNC) {
            fprintf(stderr, "Message truncated\n");
            continue;
        }
        if (msglen) {
            fprintf(stderr, "!!!Remnant of size %d\n", msglen);
            exit(1);
        }
    }
}

rtnl_dump_filter_l()实现了通过netlink获取数据,然后根据rtnl_dump_filter_arg过滤数据.

获取数据:

struct sockaddr_nl nladdr;
struct iovec iov;
struct msghdr msg = {
    .msg_name = &nladdr,
    .msg_namelen = sizeof(nladdr),
    .msg_iov = &iov,
    .msg_iovlen = 1,
};
.....
status = recvmsg(rth->fd, &msg, 0);

过滤数据:

for (a = arg; a->filter; a++) {
    struct nlmsghdr *h = (struct nlmsghdr *)buf;
    .....
    h->nlmsg_flags &= ~a->nc_flags;
    if (nladdr.nl_pid != 0 ||
                h->nlmsg_pid != rth->local.nl_pid ||
                h->nlmsg_seq != rth->dump)
                goto skip_it;
 
            if (h->nlmsg_flags & NLM_F_DUMP_INTR)
                dump_intr = 1;
 
            if (h->nlmsg_type == NLMSG_DONE) {
                err = rtnl_dump_done(h);
                if (err < 0)
                    return -1;
 
                found_done = 1;
                break; /* process next filter */
            }
            .......

之前说过,handle_netlink_request(f, &req.nlh, sizeof(req), unix_show_sock);程序最终会回调unix_show_sock函数.

unix_show_sock

跟踪unix_show_sock的实现

static int unix_show_sock(const struct sockaddr_nl *addr, struct nlmsghdr *nlh,
        void *arg)
{
    struct filter *f = (struct filter *)arg;
    struct unix_diag_msg *r = NLMSG_DATA(nlh);
    struct rtattr *tb[UNIX_DIAG_MAX+1];
    char name[128];
    struct sockstat stat = { .name = "*", .peer_name = "*" };
 
    parse_rtattr(tb, UNIX_DIAG_MAX, (struct rtattr *)(r+1),
             nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));
 
    stat.type  = r->udiag_type;
    stat.state = r->udiag_state;
    stat.ino   = stat.lport = r->udiag_ino;
    stat.local.family = stat.remote.family = AF_UNIX;
 
    if (unix_type_skip(&stat, f))
        return 0;
 
    if (tb[UNIX_DIAG_RQLEN]) {
        struct unix_diag_rqlen *rql = RTA_DATA(tb[UNIX_DIAG_RQLEN]);
 
        stat.rq = rql->udiag_rqueue;
        stat.wq = rql->udiag_wqueue;
    }
    if (tb[UNIX_DIAG_NAME]) {
        int len = RTA_PAYLOAD(tb[UNIX_DIAG_NAME]);
 
        memcpy(name, RTA_DATA(tb[UNIX_DIAG_NAME]), len);
        name[len] = '\0';
        if (name[0] == '\0') {
            int i;
            for (i = 0; i < len; i++)
                if (name[i] == '\0')
                    name[i] = '@';
        }
        stat.name = &name[0];
        memcpy(stat.local.data, &stat.name, sizeof(stat.name));
    }
    if (tb[UNIX_DIAG_PEER])
        stat.rport = rta_getattr_u32(tb[UNIX_DIAG_PEER]);
 
    if (f->f && run_ssfilter(f->f, &stat) == 0)
        return 0;
 
    unix_stats_print(&stat, f);
 
    if (show_mem)
        print_skmeminfo(tb, UNIX_DIAG_MEMINFO);
    if (show_details) {
        if (tb[UNIX_DIAG_SHUTDOWN]) {
            unsigned char mask;
 
            mask = rta_getattr_u8(tb[UNIX_DIAG_SHUTDOWN]);
            printf(" %c-%c", mask & 1 ? '-' : '<', mask & 2 ? '-' : '>');
        }
    }
    printf("\n");
 
    return 0;
}

1.struct unix_diag_msg *r = NLMSG_DATA(nlh); parse_rtattr(tb, UNIX_DIAG_MAX, (struct rtattr *)(r+1),nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*r)));获取netlink的数据

2.解析数据并赋值

stat.type  = r->udiag_type;
stat.state = r->udiag_state;
stat.ino   = stat.lport = r->udiag_ino;
stat.local.family = stat.remote.family = AF_UNIX;
-------------------------------------------------
stat.rq = rql->udiag_rqueue;
stat.wq = rql->udiag_wqueue;

unix_stats_print

unix_stats_print(&stat, f);获取网络的连接状态

static void unix_stats_print(struct sockstat *s, struct filter *f)
{
    char port_name[30] = {};
 
    sock_state_print(s);
 
    sock_addr_print(s->name ?: "*", " ",
            int_to_str(s->lport, port_name), NULL);
    sock_addr_print(s->peer_name ?: "*", " ",
            int_to_str(s->rport, port_name), NULL);
 
    proc_ctx_print(s);
}

sock_state_print

跟踪进入到sock_state_print()

static void sock_state_print(struct sockstat *s)
{
    const char *sock_name;
    static const char * const sstate_name[] = {
        "UNKNOWN",
        [SS_ESTABLISHED] = "ESTAB",
        [SS_SYN_SENT] = "SYN-SENT",
        [SS_SYN_RECV] = "SYN-RECV",
        [SS_FIN_WAIT1] = "FIN-WAIT-1",
        [SS_FIN_WAIT2] = "FIN-WAIT-2",
        [SS_TIME_WAIT] = "TIME-WAIT",
        [SS_CLOSE] = "UNCONN",
        [SS_CLOSE_WAIT] = "CLOSE-WAIT",
        [SS_LAST_ACK] = "LAST-ACK",
        [SS_LISTEN] =   "LISTEN",
        [SS_CLOSING] = "CLOSING",
    };
 
    switch (s->local.family) {
    case AF_UNIX:
        sock_name = unix_netid_name(s->type);
        break;
    case AF_INET:
    case AF_INET6:
        sock_name = proto_name(s->type);
        break;
    case AF_PACKET:
        sock_name = s->type == SOCK_RAW ? "p_raw" : "p_dgr";
        break;
    case AF_NETLINK:
        sock_name = "nl";
        break;
    default:
        sock_name = "unknown";
    }
 
    if (netid_width)
        printf("%-*s ", netid_width,
               is_sctp_assoc(s, sock_name) ? "" : sock_name);
    if (state_width) {
        if (is_sctp_assoc(s, sock_name))
            printf("`- %-*s ", state_width - 3,
                   sctp_sstate_name[s->state]);
        else
            printf("%-*s ", state_width, sstate_name[s->state]);
    }
 
    printf("%-6d %-6d ", s->rq, s->wq);
}

根据s→local.family分别输出对应的内容,代码就不做过多的解释了,就是简单的switch case的判断.全部执行完毕之后,输出的结果是:

Netid  State      Recv-Q Send-Q Local Address:Port                 Peer Address:Port               
u_seq  ESTAB      0      0      @00017 309855                * 309856

可以发现其实在ss的默认输出情况下也是没有pid信息.如果我们采用ss -p,结果是:

etid  State      Recv-Q Send-Q Local Address:Port                 Peer Address:Port               
u_seq  ESTAB      0      0      @00017 309855                * 309856                users:(("code",pid=17009,fd=17))
u_seq  ESTAB      0      0      @00012 157444                * 157445                users:(("chrome",pid=5834,fd=10))

user_ent_hash_build

当我们加了-p参数之后,程序运行的结果:

case 'p':
    show_users++;
    user_ent_hash_build();
    break;

show_users的值变为1,程序接着执行user_ent_hash_build()

static void user_ent_hash_build(void)
{
    const char *root = getenv("PROC_ROOT") ? : "/proc/";
    struct dirent *d;
    char name[1024];
    int nameoff;
    DIR *dir;
    char *pid_context;
    char *sock_context;
    const char *no_ctx = "unavailable";
    static int user_ent_hash_build_init;
 
    /* If show_users & show_proc_ctx set only do this once */
    if (user_ent_hash_build_init != 0)
        return;
 
    user_ent_hash_build_init = 1;
 
    strlcpy(name, root, sizeof(name));
 
    if (strlen(name) == 0 || name[strlen(name)-1] != '/')
        strcat(name, "/");
 
    nameoff = strlen(name);
 
    dir = opendir(name);
    if (!dir)
        return;
 
    while ((d = readdir(dir)) != NULL) {
        struct dirent *d1;
        char process[16];
        char *p;
        int pid, pos;
        DIR *dir1;
        char crap;
 
        if (sscanf(d->d_name, "%d%c", &pid, &crap) != 1)
            continue;
 
        if (getpidcon(pid, &pid_context) != 0)
            pid_context = strdup(no_ctx);
 
        snprintf(name + nameoff, sizeof(name) - nameoff, "%d/fd/", pid);
        pos = strlen(name);
        if ((dir1 = opendir(name)) == NULL) {
            free(pid_context);
            continue;
        }
 
        process[0] = '\0';
        p = process;
 
        while ((d1 = readdir(dir1)) != NULL) {
            const char *pattern = "socket:[";
            unsigned int ino;
            char lnk[64];
            int fd;
            ssize_t link_len;
            char tmp[1024];
 
            if (sscanf(d1->d_name, "%d%c", &fd, &crap) != 1)
                continue;
 
            snprintf(name+pos, sizeof(name) - pos, "%d", fd);
 
            link_len = readlink(name, lnk, sizeof(lnk)-1);
            if (link_len == -1)
                continue;
            lnk[link_len] = '\0';
 
            if (strncmp(lnk, pattern, strlen(pattern)))
                continue;
 
            sscanf(lnk, "socket:[%u]", &ino);
 
            snprintf(tmp, sizeof(tmp), "%s/%d/fd/%s",
                    root, pid, d1->d_name);
 
            if (getfilecon(tmp, &sock_context) <= 0)
                sock_context = strdup(no_ctx);
 
            if (*p == '\0') {
                FILE *fp;
 
                snprintf(tmp, sizeof(tmp), "%s/%d/stat",
                    root, pid);
                if ((fp = fopen(tmp, "r")) != NULL) {
                    if (fscanf(fp, "%*d (%[^)])", p) < 1)
                        ; /* ignore */
                    fclose(fp);
                }
            }
            user_ent_add(ino, p, pid, fd,
                    pid_context, sock_context);
            free(sock_context);
        }
        free(pid_context);
        closedir(dir1);
    }
    closedir(dir);
}

这个解析方法与netstat中的prg_cache_load的方式类似.都是解析/proc/pid/fd下面的内容获得socketinode编号.得到pid,inodefd之后,调用user_ent_add()方法.

user_ent_add

static void user_ent_add(unsigned int ino, char *process,
                    int pid, int fd,
                    char *proc_ctx,
                    char *sock_ctx)
{
    struct user_ent *p, **pp;
 
    p = malloc(sizeof(struct user_ent));
    if (!p) {
        fprintf(stderr, "ss: failed to malloc buffer\n");
        abort();
    }
    p->next = NULL;
    p->ino = ino;
    p->pid = pid;
    p->fd = fd;
    p->process = strdup(process);
    p->process_ctx = strdup(proc_ctx);
    p->socket_ctx = strdup(sock_ctx);
 
    pp = &user_ent_hash[user_ent_hashfn(ino)];
    p->next = *pp;
    *pp = p;
}

获取inode,pidfd信息,最终组成一个链表.

proc_ctx_print

程序在输出结果的时候,调用proc_ctx_print()

static void proc_ctx_print(struct sockstat *s)
{
    char *buf;
 
    if (show_proc_ctx || show_sock_ctx) {
        if (find_entry(s->ino, &buf,
                (show_proc_ctx & show_sock_ctx) ?
                PROC_SOCK_CTX : PROC_CTX) > 0) {
            printf(" users:(%s)", buf);
            free(buf);
        }
    } else if (show_users) {
        if (find_entry(s->ino, &buf, USERS) > 0) {
            printf(" users:(%s)", buf);
            free(buf);
        }
    }
}

如果show_users>0,执行find_entry(0,根据inode编号找到对应进程的信息:

find_entry

static int find_entry(unsigned int ino, char **buf, int type)
{
    struct user_ent *p;
    int cnt = 0;
    char *ptr;
    char *new_buf;
    int len, new_buf_len;
    int buf_used = 0;
    int buf_len = 0;
 
    if (!ino)
        return 0;
 
    p = user_ent_hash[user_ent_hashfn(ino)];
    ptr = *buf = NULL;
    while (p) {
        if (p->ino != ino)
            goto next;
 
        while (1) {
            ptr = *buf + buf_used;
            switch (type) {
            case USERS:
                len = snprintf(ptr, buf_len - buf_used,
                    "(\"%s\",pid=%d,fd=%d),",
                    p->process, p->pid, p->fd);
                break;
            case PROC_CTX:
                len = snprintf(ptr, buf_len - buf_used,
                    "(\"%s\",pid=%d,proc_ctx=%s,fd=%d),",
                    p->process, p->pid,
                    p->process_ctx, p->fd);
                break;
            case PROC_SOCK_CTX:
                len = snprintf(ptr, buf_len - buf_used,
                    "(\"%s\",pid=%d,proc_ctx=%s,fd=%d,sock_ctx=%s),",
                    p->process, p->pid,
                    p->process_ctx, p->fd,
                    p->socket_ctx);
                break;
            default:
                fprintf(stderr, "ss: invalid type: %d\n", type);
                abort();
            }
 
            if (len < 0 || len >= buf_len - buf_used) {
                new_buf_len = buf_len + ENTRY_BUF_SIZE;
                new_buf = realloc(*buf, new_buf_len);
                if (!new_buf) {
                    fprintf(stderr, "ss: failed to malloc buffer\n");
                    abort();
                }
                *buf = new_buf;
                buf_len = new_buf_len;
                continue;
            } else {
                buf_used += len;
                break;
            }
        }
        cnt++;
next:
        p = p->next;
    }
    if (buf_used) {
        ptr = *buf + buf_used;
        ptr[-1] = '\0';
    }
    return cnt;
}

通过遍历p = user_ent_hash[user_ent_hashfn(ino)];这个链表得到得到所有的节点.然后利用

p = user_ent_hash[user_ent_hashfn(ino)];
ptr = *buf = NULL;
while (p) {
    if (p->ino != ino)
        goto next;

如果遍历得到inode相等,那么就说明找到了pid,最终输出的结果如下:

switch (type) {
            case USERS:
                len = snprintf(ptr, buf_len - buf_used,
                    "(\"%s\",pid=%d,fd=%d),",
                    p->process, p->pid, p->fd);
                break;

最终输出的结果是:

Netid  State      Recv-Q Send-Q Local Address:Port                 Peer Address:Port               
u_seq  ESTAB      0      0      @00017 309855                * 309856                users:(("code",pid=17009,fd=17))

总结

由于ssnetstat数据获取的方式不同,导致在执行效率上面存在很大的差别.ssnetstat这两种方式也给我们需要获取主机上面的网络数据提供了一个很好的思路.

netstat源代码调试&原理分析

说明

估计平时大部分人都是通过netstat来查看网络状态,但是事实是netstat已经逐渐被其他的命令替代,很多新的Linux发行版本中很多都不支持了netstat。以ubuntu 18.04为例来进行说明:

~ netstat 
zsh: command not found: netstat

按照difference between netstat and ss in linux?这篇文章的说法:

NOTE This program is obsolete. Replacement for netstat is ss.
Replacement for netstat -r is ip route. Replacement for netstat -i is
ip -s link. Replacement for netstat -g is ip maddr.

中文含义就是:netstat已经过时了,netstat的部分命令已经被ip这个命令取代了,当然还有更为强大的ssss命令用来显示处于活动状态的套接字信息。ss命令可以用来获取socket统计信息,它可以显示和netstat类似的内容。但ss的优势在于它能够显示更多更详细的有关TCP和连接状态的信息,而且比netstat更快速更高效。netstat的原理显示网络的原理仅仅只是解析/proc/net/tcp,所以如果服务器的socket连接数量变得非常大,那么通过netstat执行速度是非常慢。而ss采用的是通过tcp_diag的方式来获取网络信息,tcp_diag通过netlink的方式从内核拿到网络信息,这也是ss更高效更全面的原因。

下图就展示了ssnetstat在监控上面的区别。

ss.png

ss是获取的socket的信息,而netstat是通过解析/proc/net/下面的文件来获取信息包括Sockets,TCP/UDPIPEthernet信息。

netstatss的效率的对比,找同一台机器执行:

time ss
........
real    0m0.016s
user    0m0.001s
sys        0m0.001s
--------------------------------
time netstat
real    0m0.198s
user    0m0.009s
sys        0m0.011s

ss明显比netstat更加高效.

netstat简介

netstat是在net-tools工具包下面的一个工具集,net-tools提供了一份net-tools的源码,我们通过net-tools来看看netstat的实现原理。

netstat源代码调试

下载net-tools之后,导入到Clion中,创建CMakeLists.txt文件,内容如下:

cmake_minimum_required(VERSION 3.13)
project(test C)

set(BUILD_DIR .)

#add_executable()
add_custom_target(netstat command -c ${BUILD_DIR})

修改根目录下的Makefile中的59行的编译配置为:

CFLAGS ?= -O0 -g3

netstat.png

按照如上图设置自己的编译选项

以上就是搭建netstat的源代码调试过程。

tcp show

在netstat不需要任何参数的情况,程序首先会运行到2317行的tcp_info()

#if HAVE_AFINET
    if (!flag_arg || flag_tcp) {
        i = tcp_info();
        if (i)
        return (i);
    }

    if (!flag_arg || flag_sctp) {
        i = sctp_info();
        if (i)
        return (i);
    }
.........

跟踪进入到tcp_info():

static int tcp_info(void)
{
    INFO_GUTS6(_PATH_PROCNET_TCP, _PATH_PROCNET_TCP6, "AF INET (tcp)",
           tcp_do_one, "tcp", "tcp6");
}

参数的情况如下:

_PATH_PROCNET_TCP,在lib/pathnames.h中定义,是#define _PATH_PROCNET_TCP "/proc/net/tcp"

_PATH_PROCNET_TCP6, 在lib/pathnames.h中定义, 是#define _PATH_PROCNET_TCP6 "/proc/net/tcp6"

tcp_do_one,函数指针,位于1100行,部分代码如下:

static void tcp_do_one(int lnr, const char *line, const char *prot)
{
unsigned long rxq, txq, time_len, retr, inode;
int num, local_port, rem_port, d, state, uid, timer_run, timeout;
char rem_addr[128], local_addr[128], timers[64];
const struct aftype *ap;
struct sockaddr_storage localsas, remsas;
struct sockaddr_in *localaddr = (struct sockaddr_in *)&localsas;
struct sockaddr_in *remaddr = (struct sockaddr_in *)&remsas;
......

tcp_do_one()就是用来解析/proc/net/tcp/proc/net/tcp6每一行的含义的,关于/proc/net/tcp的每一行的含义可以参考之前写过的osquery源码解读之分析process_open_socket中的扩展章节。

INFO_GUTS6

#define INFO_GUTS6(file,file6,name,proc,prot4,prot6)    \
 char buffer[8192];                    \
 int rc = 0;                        \
 int lnr = 0;                        \
 if (!flag_arg || flag_inet) {                \
    INFO_GUTS1(file,name,proc,prot4)            \
 }                            \
 if (!flag_arg || flag_inet6) {                \
    INFO_GUTS2(file6,proc,prot6)            \
 }                            \
 INFO_GUTS3

INFO_GUTS6采用了#define的方式进行定义,最终根据是flag_inet(IPv4)或者flag_inet6(IPv6)的选项分别调用不同的函数,我们以INFO_GUTS1(file,name,proc,prot4)进一步分析。

INFO_GUTS1

#define INFO_GUTS1(file,name,proc,prot)            \
  procinfo = proc_fopen((file));            \
  if (procinfo == NULL) {                \
    if (errno != ENOENT && errno != EACCES) {        \
      perror((file));                    \
      return -1;                    \
    }                            \
    if (!flag_noprot && (flag_arg || flag_ver))        \
      ESYSNOT("netstat", (name));            \
    if (!flag_noprot && flag_arg)            \
      rc = 1;                        \
  } else {                        \
    do {                        \
      if (fgets(buffer, sizeof(buffer), procinfo))    \
        (proc)(lnr++, buffer,prot);            \
    } while (!feof(procinfo));                \
    fclose(procinfo);                    \
  }

rocinfo = proc_fopen((file)) 获取/proc/net/tcp的文件句柄

fgets(buffer, sizeof(buffer), procinfo) 解析文件内容并将每一行的内容存储在buffer

(proc)(lnr++, buffer,prot),利用(proc)函数解析buffer(proc)就是前面说明的tcp_do_one()函数

tcp_do_one

" 14: 020110AC:B498 CF0DE1B9:4362 06 00000000:00000000 03:000001B2 00000000 0 0 0 3 0000000000000000这一行为例来说明tcp_do_one()函数的执行过程。

tcp_do_one_1.png

由于分析是Ipv4,所以会跳过#if HAVE_AFINET6这段代码。之后执行:

num = sscanf(line,
    "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X %lX:%lX %X:%lX %lX %d %d %lu %*s\n",
         &d, local_addr, &local_port, rem_addr, &rem_port, &state,
         &txq, &rxq, &timer_run, &time_len, &retr, &uid, &timeout, &inode);
if (num < 11) {
    fprintf(stderr, _("warning, got bogus tcp line.\n"));
    return;
}

解析数据,并将每一列的数据分别填充到对应的字段上面。分析一下其中的每个字段的定义:

char rem_addr[128], local_addr[128], timers[64];
struct sockaddr_storage localsas, remsas;
struct sockaddr_in *localaddr = (struct sockaddr_in *)&localsas;
struct sockaddr_in *remaddr = (struct sockaddr_in *)&remsas;

在Linux中sockaddr_insockaddr_storage的定义如下:

struct sockaddr {
   unsigned short    sa_family;    // address family, AF_xxx
   char              sa_data[14];  // 14 bytes of protocol address
};


struct  sockaddr_in {
    short  int  sin_family;                      /* Address family */
    unsigned  short  int  sin_port;       /* Port number */
    struct  in_addr  sin_addr;              /* Internet address */
    unsigned  char  sin_zero[8];         /* Same size as struct sockaddr */
};
/* Internet address. */
struct in_addr {
  uint32_t       s_addr;     /* address in network byte order */
};

struct sockaddr_storage {
    sa_family_t  ss_family;     // address family

    // all this is padding, implementation specific, ignore it:
    char      __ss_pad1[_SS_PAD1SIZE];
    int64_t   __ss_align;
    char      __ss_pad2[_SS_PAD2SIZE];
};

之后代码继续执行:

sscanf(local_addr, "%X", &localaddr->sin_addr.s_addr);
sscanf(rem_addr, "%X", &remaddr->sin_addr.s_addr);
localsas.ss_family = AF_INET;
remsas.ss_family = AF_INET;

local_addr使用sscanf(,"%X")得到对应的十六进制,保存到&localaddr->sin_addr.s_addr(即in_addr结构体中的s_addr)中,同理&remaddr->sin_addr.s_addr。运行结果如下所示:

saddr.png

addr_do_one

addr_do_one(local_addr, sizeof(local_addr), 22, ap, &localsas, local_port, "tcp");
addr_do_one(rem_addr, sizeof(rem_addr), 22, ap, &remsas, rem_port, "tcp");

程序继续执行,最终会执行到addr_do_one()函数,用于解析本地IP地址和端口,以及远程IP地址和端口。

static void addr_do_one(char *buf, size_t buf_len, size_t short_len, const struct aftype *ap,
            const struct sockaddr_storage *addr,
            int port, const char *proto
)
{
    const char *sport, *saddr;
    size_t port_len, addr_len;

    saddr = ap->sprint(addr, flag_not & FLAG_NUM_HOST);
    sport = get_sname(htons(port), proto, flag_not & FLAG_NUM_PORT);
    addr_len = strlen(saddr);
    port_len = strlen(sport);
    if (!flag_wide && (addr_len + port_len > short_len)) {
        /* Assume port name is short */
        port_len = netmin(port_len, short_len - 4);
        addr_len = short_len - port_len;
        strncpy(buf, saddr, addr_len);
        buf[addr_len] = '\0';
        strcat(buf, ":");
        strncat(buf, sport, port_len);
    } else
          snprintf(buf, buf_len, "%s:%s", saddr, sport);
}

1.saddr = ap->sprint(addr, flag_not & FLAG_NUM_HOST); 这个表示是否需要将addr转换为域名的形式。由于addr值是127.0.0.1,转换之后得到的就是localhost,其中FLAG_NUM_HOST的就等价于--numeric-hosts的选项。

2.sport = get_sname(htons(port), proto, flag_not & FLAG_NUM_PORT);,port无法无法转换,其中的FLAG_NUM_PORT就等价于--numeric-ports这个选项。

3.!flag_wide && (addr_len + port_len > short_len 这个代码的含义是判断是否需要对IP和PORT进行截断。其中flag_wide的等同于-W, --wide don't truncate IP addresses。而short_len长度是22.

4.snprintf(buf, buf_len, "%s:%s", saddr, sport);,将IP:PORT赋值给buf.

output

最终程序执行

printf("%-4s  %6ld %6ld %-*s %-*s %-11s",
           prot, rxq, txq, (int)netmax(23,strlen(local_addr)), local_addr, (int)netmax(23,strlen(rem_addr)), rem_addr, _(tcp_state[state]));

按照制定的格式解析,输出结果

finish_this_one

最终程序会执行finish_this_one(uid,inode,timers);.

static void finish_this_one(int uid, unsigned long inode, const char *timers)
{
    struct passwd *pw;

    if (flag_exp > 1) {
    if (!(flag_not & FLAG_NUM_USER) && ((pw = getpwuid(uid)) != NULL))
        printf(" %-10s ", pw->pw_name);
    else
        printf(" %-10d ", uid);
    printf("%-10lu",inode);
    }
    if (flag_prg)
    printf(" %-" PROGNAME_WIDTHs "s",prg_cache_get(inode));
    if (flag_selinux)
    printf(" %-" SELINUX_WIDTHs "s",prg_cache_get_con(inode));

    if (flag_opt)
    printf(" %s", timers);
    putchar('\n');
}

1.flag_exp 等同于-e的参数。-e, --extend display other/more information.举例如下:

netstat -e 
Proto Recv-Q Send-Q Local Address           Foreign Address         State       User       Inode
tcp        0      0 localhost:6379          172.16.1.200:46702    ESTABLISHED redis      437788048

netstat
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 localhost:6379          172.16.1.200:46702    ESTABLISHED

发现使用-e参数会多显示UserInode号码。而在本例中还可以如果用户名不存在,则显示uid
getpwuid

2.flag_prg等同于-p, --programs display PID/Program name for sockets.举例如下:

netstat -pe
Proto Recv-Q Send-Q Local Address           Foreign Address         State       User       Inode      PID/Program name
tcp        0      0 localhost:6379          172.16.1.200:34062      ESTABLISHED redis      437672000  6017/redis-server *

netstat -e
Proto Recv-Q Send-Q Local Address           Foreign Address         State       User       Inode
tcp        0      0 localhost:6379          172.16.1.200:46702    ESTABLISHED redis      437788048

可以看到是通过prg_cache_get(inode)inode来找到对应的PID和进程信息;

3.flag_selinux等同于-Z, --context display SELinux security context for sockets

prg_cache_get

对于上面的通过inode找到对应进程的方法非常的好奇,于是去追踪prg_cache_get()函数的实现。

#define PRG_HASH_SIZE 211

#define PRG_HASHIT(x) ((x) % PRG_HASH_SIZE)

static struct prg_node {
    struct prg_node *next;
    unsigned long inode;
    char name[PROGNAME_WIDTH];
    char scon[SELINUX_WIDTH];
} *prg_hash[PRG_HASH_SIZE];

static const char *prg_cache_get(unsigned long inode)
{
    unsigned hi = PRG_HASHIT(inode);
    struct prg_node *pn;

    for (pn = prg_hash[hi]; pn; pn = pn->next)
    if (pn->inode == inode)
        return (pn->name);
    return ("-");
}

prg_hash中存储了所有的inode编号与program的对应关系,所以当给定一个inode编号时就能够找到对应的程序名称。那么prg_hash又是如何初始化的呢?

prg_cache_load

我们使用debug模式,加入-p的运行参数:

netstat-p.png

程序会运行到2289行的prg_cache_load(); 进入到prg_cache_load()函数中.

由于整个函数的代码较长,拆分来分析.

一、获取fd

#define PATH_PROC      "/proc"
#define PATH_FD_SUFF    "fd"
#define PATH_FD_SUFFl       strlen(PATH_FD_SUFF)
#define PATH_PROC_X_FD      PATH_PROC "/%s/" PATH_FD_SUFF
#define PATH_CMDLINE    "cmdline"
#define PATH_CMDLINEl       strlen(PATH_CMDLINE)
 
if (!(dirproc=opendir(PATH_PROC))) goto fail;
    while (errno = 0, direproc = readdir(dirproc)) {
    for (cs = direproc->d_name; *cs; cs++)
        if (!isdigit(*cs))
        break;
    if (*cs)
        continue;
    procfdlen = snprintf(line,sizeof(line),PATH_PROC_X_FD,direproc->d_name);
    if (procfdlen <= 0 || procfdlen >= sizeof(line) - 5)
        continue;
    errno = 0;
    dirfd = opendir(line);
    if (! dirfd) {
        if (errno == EACCES)
        eacces = 1;
        continue;
    }
    line[procfdlen] = '/';
    cmdlp = NULL;

1.dirproc=opendir(PATH_PROC);errno = 0, direproc = readdir(dirproc) 遍历/proc拿到所有的pid

2.procfdlen = snprintf(line,sizeof(line),PATH_PROC_X_FD,direproc→d_name); 遍历所有的/proc/pid拿到所有进程的fd

3.dirfd = opendir(line); 得到/proc/pid/fd的文件句柄

二、获取inode

while ((direfd = readdir(dirfd))) {
        /* Skip . and .. */
        if (!isdigit(direfd->d_name[0]))
            continue;
    if (procfdlen + 1 + strlen(direfd->d_name) + 1 > sizeof(line))
       continue;
    memcpy(line + procfdlen - PATH_FD_SUFFl, PATH_FD_SUFF "/",
        PATH_FD_SUFFl + 1);
    safe_strncpy(line + procfdlen + 1, direfd->d_name,
                    sizeof(line) - procfdlen - 1);
    lnamelen = readlink(line, lname, sizeof(lname) - 1);
    if (lnamelen == -1)
        continue;
        lname[lnamelen] = '\0';  /*make it a null-terminated string*/
 
        if (extract_type_1_socket_inode(lname, &inode) < 0)
            if (extract_type_2_socket_inode(lname, &inode) < 0)
            continue;

1.memcpy(line + procfdlen - PATH_FD_SUFFl, PATH_FD_SUFF "/",PATH_FD_SUFFl + 1);safe_strncpy(line + procfdlen + 1, direfd->d_name, sizeof(line) - procfdlen - 1); 得到遍历之后的fd信息,比如/proc/pid/fd

2.lnamelen = readlink(line, lname, sizeof(lname) - 1); 得到fd所指向的link,因为通常情况下fd一般都是链接,要么是socket链接要么是pipe链接.如下所示:

$ ls -al /proc/1289/fd
total 0
dr-x------ 2 username username  0 May 25 15:45 .
dr-xr-xr-x 9 username username  0 May 25 09:11 ..
lr-x------ 1 username username 64 May 25 16:23 0 -> 'pipe:[365366]'
l-wx------ 1 username username 64 May 25 16:23 1 -> 'pipe:[365367]'
l-wx------ 1 username username 64 May 25 16:23 2 -> 'pipe:[365368]'
lr-x------ 1 username username 64 May 25 16:23 3 -> /proc/uptime

3.通过extract_type_1_socket_inode获取到link中对应的inode编号.

#define PRG_SOCKET_PFX    "socket:["
#define PRG_SOCKET_PFXl (strlen(PRG_SOCKET_PFX))
static int extract_type_1_socket_inode(const char lname[], unsigned long * inode_p) {
 
/* If lname is of the form "socket:[12345]", extract the "12345"
   as *inode_p.  Otherwise, return -1 as *inode_p.
   */
// 判断长度是否小于 strlen(socket:[)+3
if (strlen(lname) < PRG_SOCKET_PFXl+3) return(-1);
 
//函数说明:memcmp()用来比较s1 和s2 所指的内存区间前n 个字符。
// 判断lname是否以 socket:[ 开头
if (memcmp(lname, PRG_SOCKET_PFX, PRG_SOCKET_PFXl)) return(-1);
if (lname[strlen(lname)-1] != ']') return(-1);  {
    char inode_str[strlen(lname + 1)];  /* e.g. "12345" */
    const int inode_str_len = strlen(lname) - PRG_SOCKET_PFXl - 1;
    char *serr;
 
    // 获取到inode的编号
    strncpy(inode_str, lname+PRG_SOCKET_PFXl, inode_str_len);
    inode_str[inode_str_len] = '\0';
    *inode_p = strtoul(inode_str, &serr, 0);
    if (!serr || *serr || *inode_p == ~0)
        return(-1);
}

4.获取程序对应的cmdline

if (!cmdlp) {
    if (procfdlen - PATH_FD_SUFFl + PATH_CMDLINEl >=sizeof(line) - 5)
        continue;
    safe_strncpy(line + procfdlen - PATH_FD_SUFFl, PATH_CMDLINE,sizeof(line) - procfdlen + PATH_FD_SUFFl);
fd = open(line, O_RDONLY);
if (fd < 0)
    continue;
cmdllen = read(fd, cmdlbuf, sizeof(cmdlbuf) - 1);
if (close(fd))
    continue;
if (cmdllen == -1)
    continue;
if (cmdllen < sizeof(cmdlbuf) - 1)
    cmdlbuf[cmdllen]='\0';
if (cmdlbuf[0] == '/' && (cmdlp = strrchr(cmdlbuf, '/')))
    cmdlp++;
else
    cmdlp = cmdlbuf;
}

由于cmdline是可以直接读取的,所以并不需要像读取fd那样借助与readlink()函数,直接通过read(fd, cmdlbuf, sizeof(cmdlbuf) - 1)即可读取文件内容.

5.snprintf(finbuf, sizeof(finbuf), "%s/%s", direproc->d_name, cmdlp); 拼接pidcmdlp,最终得到的就是类似与6017/redis-server *这样的效果 

6.最终程序调用prg_cache_add(inode, finbuf, "-");将解析得到的inodefinbuf加入到缓存中.

prg_cache_add

#define PRG_HASH_SIZE 211
#define PRG_HASHIT(x) ((x) % PRG_HASH_SIZE)
static struct prg_node {
    struct prg_node *next;
    unsigned long inode;
    char name[PROGNAME_WIDTH];
    char scon[SELINUX_WIDTH];
} *prg_hash[ ];
 
static void prg_cache_add(unsigned long inode, char *name, const char *scon)
{
    unsigned hi = PRG_HASHIT(inode);
    struct prg_node **pnp,*pn;
 
    prg_cache_loaded = 2;
    for (pnp = prg_hash + hi; (pn = *pnp); pnp = &pn->next) {
    if (pn->inode == inode) {
        /* Some warning should be appropriate here
           as we got multiple processes for one i-node */
        return;
    }
    }
    if (!(*pnp = malloc(sizeof(**pnp))))
    return;
    pn = *pnp;
    pn->next = NULL;
    pn->inode = inode;
    safe_strncpy(pn->name, name, sizeof(pn->name));
 
    {
    int len = (strlen(scon) - sizeof(pn->scon)) + 1;
    if (len > 0)
            safe_strncpy(pn->scon, &scon[len + 1], sizeof(pn->scon));
    else
            safe_strncpy(pn->scon, scon, sizeof(pn->scon));
    }
 
}

1.unsigned hi = PRG_HASHIT(inode); 使用inode整除211得到作为hash

2.for (pnp = prg_hash + hi; (pn = *pnp); pnp = &pn->next) 由于prg_hash是一个链表结构,所以通过for循环找到链表的结尾;

3.pn = *pnp;pn->next = NULL;pn->inode = inode;safe_strncpy(pn->name, name, sizeof(pn→name)); 为新的inode赋值并将其加入到链表的末尾;

所以prg_node是一个全局变量,是一个链表结果,保存了inode编号与pid/cmdline之间的对应关系;

prg_cache_get

static const char *prg_cache_get(unsigned long inode)
{
    unsigned hi = PRG_HASHIT(inode);
    struct prg_node *pn;
 
    for (pn = prg_hash[hi]; pn; pn = pn->next)
    if (pn->inode == inode)
        return (pn->name);
    return ("-");
}

分析完毕prg_cache_add()之后,看prg_cache_get()就很简单了.

1.unsigned hi = PRG_HASHIT(inode);通过inode号拿到hash

2.for (pn = prg_hash[hi]; pn; pn = pn->next) 遍历prg_hash链表中的每一个节点,如果遍历的inode与目标的inode相符就返回对应的信息.

总结

通过对netstat的一个简单的分析,可以发现其实netstat就是通过遍历/proc目录下的目录或者是文件来获取对应的信息.如果在一个网络进程频繁关闭打开关闭,那么使用netstat显然是相当耗时的.