netstat 使用 go 語(yǔ)言實(shí)現(xiàn)是什么操作?本文從 netstat 原理出發(fā)詳細(xì)解讀了這一實(shí)踐。
netstat 工作原理
netstat 命令是 linux 系統(tǒng)中查看網(wǎng)絡(luò)情況的一個(gè)命令。比如我們可以通過netstat -ntlp | grep 8080查看監(jiān)聽 8080 端口的進(jìn)程。
netstat 工作原理如下:
通過讀取/proc/net/tcp 、/proc/net/tcp6 文件,獲取 socket 本地地址,本地端口,遠(yuǎn)程地址,遠(yuǎn)程端口,狀態(tài),inode 等信息
接著掃描所有/proc/[pid]/fd 目錄下的的 socket 文件描述符,建立 inode 到進(jìn)程 pid 映射
根據(jù) pid 讀取/proc/[pid]/cmdline 文件,獲取進(jìn)程命令和啟動(dòng)參數(shù)
根據(jù) 2,3 步驟,即可以獲得 1 中對(duì)應(yīng) socket 的相關(guān)進(jìn)程信息
我們可以做個(gè)測(cè)試驗(yàn)證整個(gè)流程。先使用 nc 命令監(jiān)聽 8090 端口:
nc -l 8090
找到上面 nc 進(jìn)程的 pid,查看該進(jìn)程所有打開的文件描述符:
vagrant@vagrant:/proc/25556/fd$ ls -alh
total 0
dr-x------ 2 vagrant vagrant 0 Nov 18 12:21 。
dr-xr-xr-x 9 vagrant vagrant 0 Nov 18 12:20 。。
lrwx------ 1 vagrant vagrant 64 Nov 18 12:21 0 -》 /dev/pts/1
lrwx------ 1 vagrant vagrant 64 Nov 18 12:21 1 -》 /dev/pts/1
lrwx------ 1 vagrant vagrant 64 Nov 18 12:21 2 -》 /dev/pts/1
lrwx------ 1 vagrant vagrant 64 Nov 18 12:21 3 -》 socket:[2226056]
上面列出的所有文件描述中,socket:[2226056]為 nc 命令監(jiān)聽 8090 端口所創(chuàng)建的 socket。其中2226056為該 socket 的 inode。
根據(jù)該 inode 號(hào),我們查看/proc/net/tcp對(duì)應(yīng)的記錄信息,其中1F9A為本地端口號(hào),轉(zhuǎn)換成十進(jìn)制恰好為 8090:
vagrant@vagrant:/proc/25556/fd$ cat /proc/net/tcp | grep 2226056
1: 00000000:1F9A 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 2226056 1 0000000000000000 100 0 0 10 0
根據(jù)進(jìn)程 id,我們查看進(jìn)程名稱和啟動(dòng)參數(shù):
vagrant@vagrant:/proc/25556/fd$ cat /proc/25556/cmdline
nc-l8090
下面我們看下/proc/net/tcp文件格式。
/proc/net/tcp 文件格式
/proc/net/tcp文件首先會(huì)列出所有監(jiān)聽狀態(tài)的 TCP 套接字,然后列出所有已建立的 TCP 套接字。我們通過head -n 5 /proc/net/tcp命令查看該文件頭五行:
sl local_address rem_address st tx_queue rx_queue tr tm-》when retrnsmt uid timeout inode
0: 0100007F:0019 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 22279 1 0000000000000000 100 0 0 10 0
1: 00000000:1FBB 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 21205 1 0000000000000000 100 0 0 10 0
2: 00000000:26FB 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 21203 1 0000000000000000 100 0 0 10 0
3: 00000000:26FD 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 21201 1 0000000000000000 100 0 0 10 0
每一行各個(gè)字段解釋說明如下,由于太長(zhǎng)分為三部分說明:
第一部分:
46: 010310AC:9C4C 030310AC:1770 01
| | | | | |--》 連接狀態(tài),16 進(jìn)制表示,具體值見下面說明
| | | | |------》 遠(yuǎn)程 TCP 端口號(hào),主機(jī)字節(jié)序,16 進(jìn)制表示
| | | |-------------》 遠(yuǎn)程 IPv4 地址,網(wǎng)絡(luò)字節(jié)序,16 進(jìn)制表示
| | |--------------------》 本地 TCP 端口號(hào),主機(jī)字節(jié)序,16 進(jìn)制表示
| |---------------------------》 本地 IPv4 地址,網(wǎng)絡(luò)字節(jié)序,16 進(jìn)制表示
|----------------------------------》 條目編號(hào),從 0 開始
上面連接狀態(tài)所有值如下,具體參見 linux 源碼 tcp\_states.h[1]:
enum {
TCP_ESTABLISHED = 1,
TCP_SYN_SENT,
TCP_SYN_RECV,
TCP_FIN_WAIT1,
TCP_FIN_WAIT2,
TCP_TIME_WAIT,
TCP_CLOSE,
TCP_CLOSE_WAIT,
TCP_LAST_ACK,
TCP_LISTEN,
TCP_CLOSING, /* Now a valid state */
TCP_NEW_SYN_RECV,
TCP_MAX_STATES /* Leave at the end! */
};
第二部分:
00000150:00000000 01:00000019 00000000
| | | | |--》 number of unrecovered RTO timeouts
| | | |----------》 number of jiffies until timer expires
| | |----------------》 timer_active,具體值見下面說明
| |----------------------》 receive-queue,當(dāng)狀態(tài)是 ESTABLISHED,表示接收隊(duì)列中數(shù)據(jù)長(zhǎng)度;狀態(tài)是 LISTEN,表示已經(jīng)完成連接隊(duì)列的長(zhǎng)度
|-------------------------------》 transmit-queue,發(fā)送隊(duì)列中數(shù)據(jù)長(zhǎng)度
timer_active 所有值與說明如下:
0 no timer is pending
1 retransmit-timer is pending
2 another timer (e.g. delayed ack or keepalive) is pending
3 this is a socket in TIME_WAIT state. Not all fields will contain data (or even exist)
4 zero window probe timer is pending
第三部分:
1000 0 54165785 4 cd1e6040 25 4 27 3 -1
| | | | | | | | | |--》 slow start size threshold,
| | | | | | | | | or -1 if the threshold
| | | | | | | | | is 》= 0xFFFF
| | | | | | | | |----》 sending congestion window
| | | | | | | |-------》 (ack.quick《《1)|ack.pingpong
| | | | | | |---------》 Predicted tick of soft clock
| | | | | | (delayed ACK control data)
| | | | | |------------》 retransmit timeout
| | | | |------------------》 location of socket in memory
| | | |-----------------------》 socket reference count
| | |-----------------------------》 socket 的 inode 號(hào)
| |----------------------------------》 unanswered 0-window probes
|---------------------------------------------》 socket 所屬用戶的 uid
Go 實(shí)現(xiàn)簡(jiǎn)易版本 netstat 命令
netstat 工作原理和/proc/net/tcp文件結(jié)構(gòu),我們都已經(jīng)了解了,現(xiàn)在可以使用據(jù)此使用 Go 實(shí)現(xiàn)一個(gè)簡(jiǎn)單版本的 netstat 命令。
核心代碼如下,完整代碼參加 go-netstat[2]:
// 狀態(tài)碼值const (
TCP_ESTABLISHED = iota + 1
TCP_SYN_SENT
TCP_SYN_RECV
TCP_FIN_WAIT1
TCP_FIN_WAIT2
TCP_TIME_WAIT
TCP_CLOSE
TCP_CLOSE_WAIT
TCP_LAST_ACK
TCP_LISTEN
TCP_CLOSING
//TCP_NEW_SYN_RECV
//TCP_MAX_STATES
)
// 狀態(tài)碼var states = map[int]string{
TCP_ESTABLISHED: “ESTABLISHED”,
TCP_SYN_SENT: “SYN_SENT”,
TCP_SYN_RECV: “SYN_RECV”,
TCP_FIN_WAIT1: “FIN_WAIT1”,
TCP_FIN_WAIT2: “FIN_WAIT2”,
TCP_TIME_WAIT: “TIME_WAIT”,
TCP_CLOSE: “CLOSE”,
TCP_CLOSE_WAIT: “CLOSE_WAIT”,
TCP_LAST_ACK: “LAST_ACK”,
TCP_LISTEN: “LISTEN”,
TCP_CLOSING: “CLOSING”,
//TCP_NEW_SYN_RECV: “NEW_SYN_RECV”,
//TCP_MAX_STATES: “MAX_STATES”,
}
// socketEntry 結(jié)構(gòu)體,用來存儲(chǔ)/proc/net/tcp 每一行解析后數(shù)據(jù)信息type socketEntry struct {
id int
srcIP net.IP
srcPort int
dstIP net.IP
dstPort int
state string
txQueue int
rxQueue int
timer int8
timerDuration time.Duration
rto time.Duration // retransmission timeout
uid int
uname string
timeout time.Duration
inode string
}
// 解析/proc/net/tcp 行記錄func parseRawSocketEntry(entry string) (*socketEntry, error) {
se := &socketEntry{}
entrys := strings.Split(strings.TrimSpace(entry), “ ”)
entryItems := make([]string, 0, 17)
for _, ent := range entrys {
if ent == “” {
continue
}
entryItems = append(entryItems, ent)
}
id, err := strconv.Atoi(string(entryItems[0][:len(entryItems[0])-1]))
if err != nil {
return nil, err
}
se.id = id // sockect entry id
localAddr := strings.Split(entryItems[1], “:”) // 本地 ip
se.srcIP = parseHexBigEndianIPStr(localAddr[0])
port, err := strconv.ParseInt(localAddr[1], 16, 32) // 本地 port
if err != nil {
return nil, err
}
se.srcPort = int(port)
remoteAddr := strings.Split(entryItems[2], “:”) // 遠(yuǎn)程 ip
se.dstIP = parseHexBigEndianIPStr(remoteAddr[0])
port, err = strconv.ParseInt(remoteAddr[1], 16, 32) // 遠(yuǎn)程 port
if err != nil {
return nil, err
}
se.dstPort = int(port)
state, _ := strconv.ParseInt(entryItems[3], 16, 32) // socket 狀態(tài)
se.state = states[int(state)]
tcpQueue := strings.Split(entryItems[4], “:”)
tQueue, err := strconv.ParseInt(tcpQueue[0], 16, 32) // 發(fā)送隊(duì)列數(shù)據(jù)長(zhǎng)度
if err != nil {
return nil, err
}
se.txQueue = int(tQueue)
sQueue, err := strconv.ParseInt(tcpQueue[1], 16, 32) // 接收隊(duì)列數(shù)據(jù)長(zhǎng)度
if err != nil {
return nil, err
}
se.rxQueue = int(sQueue)
se.uid, err = strconv.Atoi(entryItems[7]) // socket uid
if err != nil {
return nil, err
}
se.uname = systemUsers[entryItems[7]] // socket user name
se.inode = entryItems[9] // socket inode
return se, nil
}
// hexIP 是網(wǎng)絡(luò)字節(jié)序/大端法轉(zhuǎn)換成的 16 進(jìn)制的字符串func parseHexBigEndianIPStr(hexIP string) net.IP {
b := []byte(hexIP)
for i, j := 1, len(b)-2; i 《 j; i, j = i+2, j-2 { // 反轉(zhuǎn)字節(jié),轉(zhuǎn)換成小端法
b[i], b[i-1], b[j], b[j+1] = b[j+1], b[j], b[i-1], b[i]
}
l, _ := strconv.ParseInt(string(b), 16, 64)
return net.IPv4(byte(l》》24), byte(l》》16), byte(l》》8), byte(l))
}
編輯:jq
-
Linux
+關(guān)注
關(guān)注
87文章
11211瀏覽量
208721
原文標(biāo)題:Go 語(yǔ)言實(shí)現(xiàn)簡(jiǎn)易版 netstat 命令
文章出處:【微信號(hào):aming_linux,微信公眾號(hào):阿銘linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論