How Netstat works
The netstat command is used to check the network status in Linux. For example, we can through the netstat – NTLP | grep 8080 to check the process of listen on port 8080.
Netstat works as follows:
- By reading /proc/net/tcp and /proc/net/tcp6 files, the socket local address, local port, remote address, remote port, status, inode and other information can be obtained
- Then scan all socket file descriptors in /proc/[pid]/fd to establish inode to pid mapping
- Read the /proc/[pid]/cmdline file based on pid to obtain the process command and start parameters
- According to step 2 and 3, you can obtain process information about the socket corresponding to step 1
We can run a test to validate the process. Run the nc command to listen on port 8090:
nc -l 8090
Copy the code
Find the PID of the above nc process and check all open file descriptors of the process:
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]
Copy the code
In all file descriptions listed above, socket:[2226056] is the socket created for the NC command to listen on port 8090. 2226056 is the inode of the socket.
1F9A is the local port number, which in decimal form is exactly 8090: /proc/net/tcp
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
Copy the code
Based on the process ID, we look at the process name and startup parameters:
vagrant@vagrant:/proc/25556/fd$ cat /proc/25556/cmdline
nc-l8090
Copy the code
Let’s take a look at the /proc/net/tcp file format.
The file format is /proc/net/tcp
The /proc/net/tcp file first lists all TCP sockets listening on state, and then all established TCP sockets. We look at the first five lines of the file with the head -n 5 /proc/net/tcp command:
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
Copy the code
The description of each field in each line is as follows. It is divided into three parts because it is too long:
Part I:
46: 010310 ac: 9 c4c 030310 ac: 1770 01 | | | | | | -- - > connection status, hexadecimal, said see below shows the specific value | | | | | -- -- -- -- -- - > remote TCP port number, host byte order, Hexadecimal said | | | | -- -- -- -- -- -- -- -- -- -- -- -- -- > remote IPv4 addresses, network byte order, hexadecimal said | | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- > local TCP port number, host byte order, Hexadecimal said | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - > local IPv4 addresses, network byte order, hexadecimal said | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - > item number, starting from 0Copy the code
All values of the connection status above are as follows, for details, refer to the Linux source code tcp_states.h:
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! * /
};
Copy the code
Part II:
00000150:00000000 01:00000019 00000000 | | | | |--> number of unrecovered RTO timeouts | | | |----------> number of Jiffies until the timer expires | | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - > timer_active, see below shows the specific value | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - > the receive queue, ESTABLISHED indicates the length of the data in the receiving queue. State is LISTEN, says it has complete connection queue length | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - > the transmit queue, send the queue length of dataCopy the code
All values and descriptions of timer_active are as follows:
- 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
Part III:
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 the memory | | | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- > socket reference count | | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - > the socket inode number | |----------------------------------> unanswered 0-window probes |---------------------------------------------> Uid of the user to which the socket belongsCopy the code
Go implements a simple version of the netstat command
Now that you know how netstat works and the /proc/net/tcp file structure, you can use it to implement a simple version of the netstat command using Go.
The core code is as follows, and the complete code is included in Go-netstat:
// Status code value
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
)
/ / status code
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",
}
The // socketEntry structure is used to store data information after each parsed row of /proc/net/TCP
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
}
// Parse /proc/net/tcp line records
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]))
iferr ! =nil {
return nil, err
}
se.id = id // sockect entry id
localAddr := strings.Split(entryItems[1].":") / / local IP
se.srcIP = parseHexBigEndianIPStr(localAddr[0])
port, err := strconv.ParseInt(localAddr[1].16.32) / / local port
iferr ! =nil {
return nil, err
}
se.srcPort = int(port)
remoteAddr := strings.Split(entryItems[2].":") / / remote IP
se.dstIP = parseHexBigEndianIPStr(remoteAddr[0])
port, err = strconv.ParseInt(remoteAddr[1].16.32) / / the remote port
iferr ! =nil {
return nil, err
}
se.dstPort = int(port)
state, _ := strconv.ParseInt(entryItems[3].16.32) / / state of the socket
se.state = states[int(state)]
tcpQueue := strings.Split(entryItems[4].":")
tQueue, err := strconv.ParseInt(tcpQueue[0].16.32) // Send queue data length
iferr ! =nil {
return nil, err
}
se.txQueue = int(tQueue)
sQueue, err := strconv.ParseInt(tcpQueue[1].16.32) // Receive queue data length
iferr ! =nil {
return nil, err
}
se.rxQueue = int(sQueue)
se.uid, err = strconv.Atoi(entryItems[7]) // socket uid
iferr ! =nil {
return nil, err
}
se.uname = systemUsers[entryItems[7]] // socket user name
se.inode = entryItems[9] // socket inode
return se, nil
}
// hexIP is a hexadecimal string converted from network byte order/big-endian
func parseHexBigEndianIPStr(hexIP string) net.IP {
b := []byte(hexIP)
for i, j := 1.len(b)2 -; i < j; i, j = i+2, j2 - { // Invert the byte and convert it to the small-endian method
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))
}
Copy the code
reference
- The original address
- /proc/net/tcp Official document