天天看點

netstat工具源碼剖析

netstat工具源碼剖析

netstat是監控TCP/IP網絡的工具,可以顯示路由表、實際的網絡連接配接以及每一個網絡接口裝置的狀态資訊。用于顯示與IP、TCP、UDP和ICMP協定相關的統計資料,一般用于檢驗本機各端口的網絡連接配接情況。

網上關于netstat工具使用的文章多如牛毛,但是針對工具剖析的卻沒有,一篇難找。古人說:工欲善其事必先利其器。那麼我們一起來分析吧。

netstat的源碼位于net-tools工具包中,這是linux網絡的基本工具包,此外還包括arp,hostname,route等指令。

  項目連結:http://net-tools.sourceforge.net/

            下載下傳位址:https://sourceforge.net/projects/net-tools/files/latest/download

1.1.1 編譯

下載下傳後解壓,可以直接進行編譯了。

make netstat;可以進行netstat編譯。

make ifconfig;可以編譯ifconfig。

編譯過程中可以選擇部分選項,例如需要使用-M參數,那麼在config.h檔案中将#define HAVE_FW_MASQUERADE 0,如果要編譯就設定為1就好了。

編譯時候出錯需要進行如下修改:

1、在lib/inet_src.c中,108行加入break;

    107     default:

108        break;

2、在lib/x25_sr.c  

80 memcpy(&rt.address, &sx25.sx25_addr, sizeof(x25_address));

為:

memcpy(&rt.address, &sx25.sx25_addr, sizeof(sx25.sx25_addr));

     我們檢視makefile檔案如下:

NET_LIB = $(NET_LIB_PATH)/lib$(NET_LIB_NAME).a

NET_LIB_PATH = lib

NET_LIB_NAME = net-tools

CC      = gcc

LDFLAGS = $(LOPTS) -L$(NET_LIB_PATH)

netstat:        $(NET_LIB) netstat.o statistics.o

                $(CC) $(LDFLAGS) -o netstat netstat.o statistics.o $(NLIB) $(RESLIB)

     其主要兩個程式檔案是statistics.c和netstat.c。

     當然還有一些相關的函數都是位于lib檔案夾中的。

     相關的系統檔案目錄在lib/pathnames.h檔案中

     列印僞連結資訊的實作函數位于lib/masq_info.c中,有print_masq函數、read_masqinfo函數、ip_masq_info函數三個函數。

1.1.2 statistics中資料結構和部分函數

statistics.c檔案中定義了統計資訊相關的資料結構和函數,提供為netstat.c檔案使用。

enum State {

    number = 0, opt_number, i_forward, i_inp_icmp, i_outp_icmp, i_rto_alg,

    MaxState

};

struct entry {

    char *title;

    char *out;

    enum State type;

struct statedesc {

    int indent;

struct entry Iptab[]

struct entry Icmptab[]

struct entry Tcptab[]

struct entry Udptab[]

struct entry Tcpexttab[]

tabtab結構體,合入了entry。

struct tabtab {

    struct entry *tab;

    size_t size;

    int *flag;

struct tabtab snmptabs[]為tabtab結構體的數組。

struct tabtab snmptabs[] =

{

    {"Ip", Iptab, sizeof(Iptab), &f_raw},

    {"Icmp", Icmptab, sizeof(Icmptab), &f_raw},

    {"Tcp", Tcptab, sizeof(Tcptab), &f_tcp},

    {"Udp", Udptab, sizeof(Udptab), &f_udp},

    {"TcpExt", Tcpexttab, sizeof(Tcpexttab), &f_tcp},

    {NULL}

netstat工具源碼剖析

1.1.2.1  函數

inittab函數通過qsort函數将Iptab,Icmptab,Tcptab,Udptab,Tcpexttab進行排序.

Parsesnmp函數入參為三個整數,主要用于打開/proc/net/snmp檔案和/proc/net/netstat,将句柄交給process_fd函數進行處理。通過/proc/net/snmp檔案可以得到各層網絡協定的收發包的情況。

     Printval函數将值進行列印.

1.1.3 main主程式邏輯

位于netstat.c檔案的main函數中,

     先定義了一個選擇結構體數組,結構體由系統提供:

struct option

  const char *name;

  /* has_arg can't be an enum because some compilers complain about

     type mismatches in all the code that assumes it is an int.  */

  int has_arg;

  int *flag;

  int val;

然後通過getopt_long函數來擷取指令行參數。設定相關變量,例如flag_tcp++,flag_udp++等等。

判斷變量設定合理性,有些參數不能同時設定,例如flag_int 、 flag_rou、flag_mas、flag_sta分别是接口表、路由表、僞ip、靜态統計資料等。

    if (flag_int + flag_rou + flag_mas + flag_sta > 1)

        usage();

     然後根據這些相關變量,執行對應的函數。

     例如:

    if (flag_int) {

        for (;;) {

            i = iface_info();

            if (!flag_cnt || i)

                break;

            sleep(1);

        }   

        return (i);

    }  

     每個變量判斷其是否被設定,最後會去系統的相應路徑檔案中擷取資訊。

#define _PATH_PROCNET_IGMP              "/proc/net/igmp"

#define _PATH_PROCNET_IGMP6             "/proc/net/igmp6"

#define _PATH_PROCNET_TCP               "/proc/net/tcp"

#define _PATH_PROCNET_TCP6              "/proc/net/tcp6"

#define _PATH_PROCNET_UDP               "/proc/net/udp"

#define _PATH_PROCNET_UDP6              "/proc/net/udp6"

#define _PATH_PROCNET_RAW               "/proc/net/raw"

#define _PATH_PROCNET_RAW6              "/proc/net/raw6"

#define _PATH_PROCNET_UNIX              "/proc/net/unix"

#define _PATH_PROCNET_ROUTE             "/proc/net/route"

#define _PATH_PROCNET_ROUTE6            "/proc/net/ipv6_route"

#define _PATH_PROCNET_RTCACHE           "/proc/net/rt_cache"

#define _PATH_PROCNET_AX25_ROUTE        "/proc/net/ax25_route"

#define _PATH_PROCNET_NR                "/proc/net/nr"

#define _PATH_PROCNET_NR_NEIGH          "/proc/net/nr_neigh"

#define _PATH_PROCNET_NR_NODES          "/proc/net/nr_nodes"

#define _PATH_PROCNET_ARP               "/proc/net/arp"

#define _PATH_PROCNET_AX25              "/proc/net/ax25"

#define _PATH_PROCNET_IPX               "/proc/net/ipx"

#define _PATH_PROCNET_IPX_ROUTE         "/proc/net/ipx_route"

#define _PATH_PROCNET_ATALK             "/proc/net/appletalk"

#define _PATH_PROCNET_IP_BLK            "/proc/net/ip_block"

#define _PATH_PROCNET_IP_FWD            "/proc/net/ip_forward"

#define _PATH_PROCNET_IP_ACC            "/proc/net/ip_acct"

#define _PATH_PROCNET_IP_MASQ           "/proc/net/ip_masquerade"

#define _PATH_PROCNET_NDISC             "/proc/net/ndisc"

#define _PATH_PROCNET_IFINET6           "/proc/net/if_inet6"

#define _PATH_PROCNET_DEV               "/proc/net/dev"

#define _PATH_PROCNET_RARP              "/proc/net/rarp"

#define _PATH_ETHERS                    "/etc/ethers"

#define _PATH_PROCNET_ROSE_ROUTE        "/proc/net/rose_routes"

#define _PATH_PROCNET_X25              "/proc/net/x25"

#define _PATH_PROCNET_X25_ROUTE                "/proc/net/x25_routes"

#define _PATH_PROCNET_DEV_MCAST         "/proc/net/dev_mcast"

/* pathname for the netlink device */

#define _PATH_DEV_ROUTE "/dev/route"

1.1.4 tcp連結顯示

以netstat中-t參數為例,在main函數中看到是tcp_info()函數,我們調試下這個函數,為其添加列印資訊:

static int tcp_info(void)

    printf("xxxdebug: tcp_info\n");

    INFO_GUTS6(_PATH_PROCNET_TCP, _PATH_PROCNET_TCP6, "AF INET (tcp)",

               tcp_do_one);

}

其打開的路徑為_PATH_PROCNET_TCP或_PATH_PROCNET_TCP6,其中tcp_do_one為回調函數。

函數INFO_GUTS6定義如下:

#define INFO_GUTS6(file,file6,name,proc)                \

 char buffer[8192];                                     \

 int rc = 0;                                            \

 int lnr = 0;                                           \

 if (!flag_arg || flag_inet) {                          \

    INFO_GUTS1(file,name,proc)                          \

 }                                                      \

 if (!flag_arg || flag_inet6) {                         \

    INFO_GUTS2(file6,proc)                              \

 INFO_GUTS3

根據變量情況,調用INFO_GUTS1或者INFO_GUTS2。

 最後調用tcp_do_one函數。

在 tcp_do_one函數最後加入一行列印:

printf("xxxdebug: tcp_do_one\n");

編譯後運作:#netstat -t

# ./netstat -t

Active Internet connections (w/o servers)

Proto Recv-Q Send-Q Local Address           Foreign Address         State     

xxxdebug: tcp_info

xxxdebug: tcp_do_one

tcp      401      0 xxxxxxxxxx    CLOSE_WAIT 

tcp        0      0 xxxxxxxxxxx     ESTABLISHED

tcp        0      0 xxxxxxxxxxxx    ESTABLISHED

tcp        0      0 xxxxxxxxxxxx      ESTABLISHED

tcp      361      0 xxxxxxxxxxxx      CLOSE_WAIT 

這裡看到tcp_do_one執行了8次,因為在/proc/net/tcp檔案有8行,

# cat /proc/net/tcp

  sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode                                                    

   0: 0100007F:0CEA 00000000:0000 0A 00000000:00000000 00:00000000 00000000   110        0 32096415 1 ffff880078c04000 100 0 0 10 0                 

   1: 00000000:006F 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 47126999 1 ffff880078ef0780 100 0 0 10 0                 

   2: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000     0        0 35410755 1 ffff88000477f480 100 0 0 10 0                 

   3: 98CD13AC:E05A CD8CCD8C:0050 08 00000000:00000191 00:00000000 00000000     0        0 63139333 1 ffff8800369ac780 20 4 28 10 -1                 

   4: 98CD13AC:0016 FF47643A:C8FA 01 00000000:00000000 02:0007E41B 00000000     0        0 63635147 3 ffff88007c1e4f00 22 4 29 10 7                 

   5: 98CD13AC:0016 FF47643A:C6B8 01 00000000:00000000 02:0006FDB5 00000000     0        0 63634584 2 ffff88007c1e4000 21 4 27 6 4                  

   6: 98CD13AC:C88E 0D440B6A:0050 01 00000000:00000000 00:00000000 00000000     0        0 63158811 1 ffff8800369af480 20 4 31 10 18                

   7: 98CD13AC:DF24 0D440B6A:0050 08 00000000:00000169 00:00000000 00000000     0        0 17490053 1 ffff880005547480 20 4 28 10 13 

而在INFO_GUTS1函數中會根據/proc/net/tcp内容進行循環:

#define INFO_GUTS1(file,name,proc)                      \

  procinfo = fopen((file), "r");                        \

  if (procinfo == NULL) {                               \

    if (errno != ENOENT) {                              \

      perror((file));                                   \

      return -1;                                        \

    }                                                   \

    if (flag_arg || flag_ver)                           \

      ESYSNOT("netstat", (name));                       \

    if (flag_arg)                                       \

      rc = 1;                                           \

  } else {                                              \

    do {                                                \

      if (fgets(buffer, sizeof(buffer), procinfo))      \

        (proc)(lnr++, buffer);                          \

    } while (!feof(procinfo));                          \

    fclose(procinfo);                                   \

  }

            好了,後面大家有對某個參數感興趣的也可以同理的去檢視源碼,看看執行路徑。

            netstat主要是對/proc/net/下面的檔案進行解析顯示,讓人可以更加直覺的看到目前網絡的使用情況。

1.1.5 參數選項

最後将netstat常用的參數附上。具體詳細的使用下一章會繼續。

-a或--all:顯示所有連線中的Socket;

-A<網絡類型>或--<網絡類型>:列出該網絡類型連線中的相關位址;

-c或--continuous:持續列出網絡狀态;

-C或--cache:顯示路由器配置的快取資訊;

-e或--extend:顯示網絡其他相關資訊;

-F或--fib:顯示FIB;

-g或--groups:顯示多重廣播功能群組組員名單;

-h或--help:線上幫助;

-i或--interfaces:顯示網絡界面資訊表單;

-l或--listening:顯示監控中的伺服器的Socket;

-M或--masquerade:顯示僞裝的網絡連線;

-n或--numeric:直接使用ip位址,而不通過域名伺服器;

-N或--netlink或--symbolic:顯示網絡硬體外圍裝置的符号連接配接名稱;

-o或--timers:顯示計時器;

-p或--programs:顯示正在使用Socket的程式識别碼和程式名稱;

-r或--route:顯示Routing Table;

-s或--statistice:顯示網絡工作資訊統計表;

-t或--tcp:顯示TCP傳輸協定的連線狀況;

-u或--udp:顯示UDP傳輸協定的連線狀況;

-v或--verbose:顯示指令執行過程;

--ip或--inet:此參數的效果和指定"-A inet"參數相同。