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;
char *title;
};
struct entry Iptab[]
struct entry Icmptab[]
struct entry Tcptab[]
struct entry Udptab[]
struct entry Tcpexttab[]
tabtab结构体,合入了entry。
struct tabtab {
char *title;
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}
};
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为回调函数。
#define _PATH_PROCNET_TCP "/proc/net/tcp"
函数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
xxxdebug: tcp_do_one
xxxdebug: tcp_do_one
tcp 401 0 xxxxxxxxxx CLOSE_WAIT
xxxdebug: tcp_do_one
tcp 0 0 xxxxxxxxxxx ESTABLISHED
xxxdebug: tcp_do_one
tcp 0 0 xxxxxxxxxxxx ESTABLISHED
xxxdebug: tcp_do_one
tcp 0 0 xxxxxxxxxxxx ESTABLISHED
xxxdebug: tcp_do_one
tcp 361 0 xxxxxxxxxxxx CLOSE_WAIT
xxxdebug: tcp_do_one
这里看到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:显示指令执行过程;
-V或--version:显示版本信息;
-w或--raw:显示RAW传输协议的连线状况;
-x或--unix:此参数的效果和指定"-A unix"参数相同;
--ip 或 --inet :此参数的效果和指定 "-A inet" 参数相同。