Linux下使用ss命令查看网络连接
2013-10-11 22:26:38 阿炯

ss(Socket Statistics的缩写)命令可以用来获取socket统计信息,它的输出的结果类似于netstat输出的内容,但它能显示更详细的连接状态的信息,且比netstat更快速高效。它使用了TCP协议栈中tcp_diag(是一个用于分析统计的模块),能直接从获得第一手内核信息,这就使得ss命令快捷高效。

ss is one another utility to investigate sockets. Functionally it is NOT better than netstat combined with some perl/awk scripts and though it is surely faster it is not enough to make it much better.

它与netstat有一些不同之处,netstat是遍历/proc下面每个PID目录,ss直接读/proc/net下面的统计信息;所以ss执行的时候消耗资源以及消耗的时间都比netstat少很多。ss命令用于显示socket状态,可以显示PACKET, TCP, UDP, DCCP, RAW, Unix domain sockets等等统计。它比其他工具显示更多tcp和state信息,是一个非常实用、快速、有效的跟踪IP连接和sockets的新工具。ss命令可以提供如下信息:
所有的TCP sockets
所有的UDP sockets
所有ssh/ftp/ttp/https持久连接
所有连接到Xserver的本地进程
使用state(例如:connected, synchronized, SYN-RECV, SYN-SENT,TIME-WAIT)、地址、端口过滤
所有的state FIN-WAIT-1 tcpsocket连接以及更多


一些选项:
-h, --help     帮助信息
-V, --version    程序版本信息
-n, --numeric    不解析服务名称
-r, --resolve 解析主机名
-a, --all     显示所有套接字(sockets)
-l, --listening    显示监听状态的套接字(sockets)
-o, --options 显示计时器信息
-e, --extended 显示详细的套接字(sockets)信息
-m, --memory 显示套接字(socket)的内存使用情况
-p, --processes    显示使用套接字(socket)的进程
-i, --info     显示 TCP内部信息
-s, --summary    显示套接字(socket)使用概况
-4, --ipv4 仅显示IPv4的套接字(sockets)
-6, --ipv6 仅显示IPv6的套接字(sockets)
-0, --packet     显示 PACKET 套接字(socket)
-t, --tcp     仅显示 TCP套接字(sockets)
-u, --udp     仅显示 UCP套接字(sockets)
-d, --dccp     仅显示 DCCP套接字(sockets)
-w, --raw     仅显示 RAW套接字(sockets)
-x, --unix     仅显示 Unix套接字(sockets)
-f, --family=FAMILY 显示 FAMILY类型的套接字(sockets),FAMILY可选,支持 unix, inet, inet6, link, netlink
-A, --query=QUERY, --socket=QUERY
QUERY := {all|inet|tcp|udp|raw|unix|packet|netlink}[,QUERY]
-D, --diag=FILE 将原始TCP套接字(sockets)信息转储到文件
-F, --filter=FILE 从文件中都去过滤器信息
FILTER := [ state TCP-STATE ] [ EXPRESSION ]
f文件, filter =文件
从文件读取筛选信息,每行单独的文件都被解释的命令行选项,如果文件是stdin。
过滤器 :=[tcp 状态][expression]
请看一下官方文档(debian包 iproute doc)有关过滤的细节。

常用ss命令:
ss -l 显示本地打开的所有端口
ss -pl 显示每个进程具体打开的socket
ss -t -a 显示所有tcp socket
ss -u -a 显示所有的UDP Socekt
ss -o state established '( dport = :smtp or sport = :smtp )' 显示所有已建立的SMTP连接
ss -o state established '( dport = :http or sport = :http )' 显示所有已建立的HTTP连接
ss -x src /tmp/.X11-unix/* 找出所有连接X服务器的进程

#举例:
#显示所有TCP套接字
ss -t -a
#显示所有UDP套接字
ss -u -a
#显示所有已建立连接的 ssh连接
ss -o state established '(dport = :ssh or sport = :ssh)'
#找出所有连接到X服务器的本地进程
ss -x src /tmp/.X11-unix/*
Find all local processes connected to X server.
#列举出处于 FIN-WAIT-1状态的源端口为 80或者 443,目标网络为 web.freeoa.net/24所有 tcp套接字
ss -o state fin-wait-1 '(sport = :http or sport = :https)' dst web.freeoa.net/24
ss -o state established '(dport = :smtp or sport = :smtp)'   显示所有已建立的SMTP连接
ss -o state established '(dport = :http or sport = :http)'  显示所有已建立的HTTP连接
#查看所有打开的网络端口,本地打开的所有端口
ss -l
#查看当前服务器的网络连接统计,当前socket详细信息
ss -s
#显示每个进程具体打开的socket
ss -pl  

列出所有连接
ss | more

列出tcp,udp or unix套接字
ss -t
OR
ss -A tcp

ss -ua
OR
ss -a -A udp

禁用域名解析
ss -nt

查看侦听的sockets
ss -ltn

ss -lun

列出进程名称及id
ss -ltp

汇总统计连接信息
ss -s

列出连接的时间信息
ss -tn -o

查看IPv4 or IPv6 socket连接
ss -tl -f inet (for ipv4)
ss -tl6 (for ipv6)

ss -s 列出当前socket详细信息,显示sockets简要信息:列出当前已经连接,关闭,等待的tcp连接
# ss -s

列出当前监听端口
# ss -l

ss列出每个进程名及其监听的端口
# ss -pl

ss列所有的tcp sockets
# ss -t -a

ss列出所有udp sockets
# ss -u -a

ss列出所有http连接中的连接
# ss -o state established '( dport = :http or sport = :http )'
·以上包含对外提供的80,以及访问外部的80
·用以上命令完美的替代netstat获取http并发连接数,监控中常用到

ss列出本地哪个进程连接到x server
# ss -x src /tmp/.X11-unix/*

ss列出处在FIN-WAIT-1状态的http、https连接
# ss -o state fin-wait-1 '( sport = :http or sport = :https )'

ss常用的state状态:
established
syn-sent
syn-recv
fin-wait-1
fin-wait-2
time-wait
closed
close-wait
last-ack
listen
closing
all : All of the above states
connected : All the states except for listen and closed
synchronized : All the connected states except for syn-sent
bucket : Show states, which are maintained as minisockets, i.e. time-wait and syn-recv.
big : Opposite to bucket state.

ss使用IP地址筛选
ss src ADDRESS_PATTERN
src:表示来源
ADDRESS_PATTERN:表示地址规则

如下:
ss src 120.33.31.1 #列出来自20.33.31.1的连接

#列出来至120.33.31.1,80端口的连接
ss src 120.33.31.1:http
ss src 120.33.31.1:80

ss使用端口筛选
ss dport OP PORT
OP:是运算符
PORT:表示端口
dport:表示过滤目标端口、相反的有sport

OP运算符如下:
<= or le : 小于等于 >= or ge : 大于等于
== or eq : 等于
!= or ne : 不等于端口
< or lt : 小于这个端口 > or gt : 大于端口

OP实例
ss sport = :http 也可以是 ss sport = :80
ss dport = :http
ss dport \> :1024
ss sport \> :1024
ss sport \< :32000
ss sport eq :22
ss dport != :22
ss state connected sport = :http
ss \( sport = :http or sport = :https \)
ss -o state fin-wait-1 \( sport = :http or sport = :https \) dst 192.168.1/24

按不同的条件来过滤连接信息

按tcp state来过滤连接信息

ss [ OPTIONS ] [ STATE-FILTER ] [ ADDRESS-FILTER ]

List Ipv4 tcp sockets that are in "connected" state.
ss -t4 state established

Display sockets with state time-wait
ss -t4 state time-wait

有如下的一些tcp state:
1. established
2. syn-sent
3. syn-recv
4. fin-wait-1
5. fin-wait-2
6. time-wait
7. closed
8. close-wait
9. last-ack
10. closing
11. all - All of the above states
12. connected - All the states except for listen and closed
13. synchronized - All the connected states except for syn-sent
14. bucket - Show states, which are maintained as minisockets, i.e. time-wait and syn-recv.
15. big - Opposite to bucket state.


每秒列出state为syn-sent的连接:
watch -n 1 "ss -t4 state syn-sent"

按address and port number来过滤连接

Display all socket connections with source or destination port of ssh.
ss -at '( dport = :ssh or sport = :ssh )'

Sockets with destination port by http(s)
ss -nt '( dst :443 or dst :80 )'
OR
ss -nt dst :443 or dst :80

更多示例:
# Filter by address
ss -nt dst 74.125.236.178

# CIDR notation is also supported
ss -nt dst 74.125.236.178/16

# Address and Port combined
ss -nt dst 74.125.236.178:80

Ports can also be filtered with dport/sport options. Port numbers must be prefixed with a ":".
ss -nt dport = :80
OR
ss -nt dst :80

# source address is 127.0.0.1 and source port is greater than 5000
ss -nt src 127.0.0.1 sport gt :5000

# local smtp (port 25) sockets
sudo ss -ntlp sport eq :smtp

# port numbers greater than 25
sudo ss -nt sport gt :25

# sockets with remote ports less than 100
sudo ss -nt dport \< :100

# connections to remote port 80
sudo ss -nt state connected dport = :80

端口大小比较运算符:
<= or le : Less than or equal to port >= or ge : Greater than or equal to port
== or eq : Equal to port
!= or ne : Not equal to port
< or gt : Less than to port > or lt : Greater than to port



TCP连接状态回顾

CLOSED:初始状态,表示没有任何连接。

LISTEN:Server端的某个Socket正在监听来自远方的TCP端口的连接请求。

SYN_SENT:发送连接请求后等待确认信息。当客户端Socket进行Connect连接时,会首先发送SYN包,随即进入SYN_SENT状态,然后等待Server端发送三次握手中的第2个包。

SYN_RECEIVED:收到一个连接请求后回送确认信息和对等的连接请求,然后等待确认信息。通常是建立TCP连接的三次握手过程中的一个中间状态,表示Server端的Socket接收到来自Client的SYN包,并作出回应。

ESTABLISHED:表示连接已经建立,可以进行数据传输。

FIN_WAIT_1:主动关闭连接的一方等待对方返回ACK包。若Socket在ESTABLISHED状态下主动关闭连接并向对方发送FIN包(表示己方不再有数据需要发送),则进入FIN_WAIT_1状态,等待对方返回ACK包,此后还能读取数据,但不能发送数据。在正常情况下,无论对方处于何种状态,都应该马上返回ACK包,所以FIN_WAIT_1状态一般很难见到。

FIN_WAIT_2:主动关闭连接的一方收到对方返回的ACK包后,等待对方发送FIN包。处于FIN_WAIT_1状态下的Socket收到了对方返回的ACK包后,便进入FIN_WAIT_2状态。由于FIN_WAIT_2状态下的Socket需要等待对方发送的FIN包,所有常常可以看到。若在FIN_WAIT_1状态下收到对方发送的同时带有FIN和ACK的包时,则直接进入TIME_WAIT状态,无须经过FIN_WAIT_2状态。

TIME_WAIT:主动关闭连接的一方收到对方发送的FIN包后返回ACK包(表示对方也不再有数据需要发送,此后不能再读取或发送数据),然后等待足够长的时间(2MSL)以确保对方接收到ACK包(考虑到丢失ACK包的可能和迷路重复数据包的影响),最后回到CLOSED状态,释放网络资源。

CLOSE_WAIT:表示被动关闭连接的一方在等待关闭连接。当收到对方发送的FIN包后(表示对方不再有数据需要发送),相应的返回ACK包,然后进入CLOSE_WAIT状态。在该状态下,若己方还有数据未发送,则可以继续向对方进行发送,但不能再读取数据,直到数据发送完毕。

LAST_ACK:被动关闭连接的一方在CLOSE_WAIT状态下完成数据的发送后便可向对方发送FIN包(表示己方不再有数据需要发送),然后等待对方返回ACK包。收到ACK包后便回到CLOSED状态,释放网络资源。

CLOSING:比较罕见的例外状态。正常情况下,发送FIN包后应该先收到(或同时收到)对方的ACK包,再收到对方的FIN包,而CLOSING状态表示发送FIN包后并没有收到对方的ACK包,却已收到了对方的FIN包。有两种情况可能导致这种状态:其一,如果双方几乎在同时关闭连接,那么就可能出现双方同时发送FIN包的情况;其二,如果ACK包丢失而对方的FIN包很快发出,也会出现FIN先于ACK到达。


网络连接中有太多的关闭状态计数却查看不到

在ss的'-s'输出中能看到,但直接查看时(-natu)在数量上相去甚远。

# ss -s
Total: 194738 (kernel 194975)
TCP:  201128 (estab 13052, closed 174321, orphaned 6477, synrecv 0, timewait 3/0), ports 0

# ss -s
Total: 65957 (kernel 66625)
TCP:  65599 (estab 36, closed 65534, orphaned 0, synrecv 0, timewait 3/0), ports 0

此种情暗示了很多socket是处于closed状态。其文件描述符存在泄漏的可能,没有能及时被内核回收,可使用lsof命令检查系统sock的文件句柄:
lsof | grep sock
lsof | perl -anle 'print if($F[5] eq "sock")'

可以发现Name列的值为"can't identify protocol",socket找不到打开的文件,依据进程号找到对应的进程,分析出具体的情况再处理。单纯地增加操作系统文件句柄数量(max open files),并不能解决问题(实际上也没有解决还可能让问题恶化),应该以解决问题为优先,正本还需清源。

1)what are they exactly?
They are sockets that either never connected or disconnected and not closed.In Linux, an outgoing TCP socket goes through the following stages (roughly):
You create the socket (unconnected), and kernel allocates a file descriptor for it.
You connect() it to the remote side, establishing a network connection.
You do data transfer (read/write).

When you are done with reading/writing, you shutdown() the socket for both reading and writing, closing the network connection. close() the socket, and kernel frees the file descriptor.

2)why do they keep dangling without being destroyed?
Because nobody called close() on them. I would call it a "file descriptor leak" or a "socket descriptor leak".

3)Is there any setting(timeout or whatever) which can help to keep them under a reasonable limit?
From the Linux point of view, no. You have to explicitly call close() on them (or terminate the process that owns them so the kernel knows they aren't used anymore).

The closed connections do exist and are sockets which have never been closed properly.
 
来看看ss的源代码中对计算过程

The column named Closed is particularly mind-blowing.

Total: 246
TCP:   129 (estab 87, closed 28, orphaned 4, timewait 28)Transport Total     IP        IPv6
RAW       1        0         1
UDP       6        4         2
TCP       101      94        7
INET      108      98        10
FRAG      0        0         0

This output is from my test server. The test server’s OS is Ubuntu 20.04.1 LTS, Linux Kernel version is 5.4.0 and iproute2 package version is 5.5.0

You may get different output in older/newer Linux servers.

The ss stands for socket statistics.

How does ss collect/calculate socket statistics?

A screenshot from ss source code

As you can see, ss calculates the number of closed connections with this simple mathematical operation:
s.tcp_total - (s.tcp4_hashed + s.tcp6_hashed - s.tcp_tws)

Let’s stop here.

ss is actually parsing the output of /proc/net/sockstat and /proc/net/sockstat6:
ss is parsing the output of the /proc/net/sockstat and proc/net/sockstat6

tcp_total is actually “alloc” in the output of /proc/net/sockstat.
tcp4_hashed is actually “inuse” in the output of /proc/net/sockstat
tcp6_hashed is actually “inuse” in the output of /proc/net/sockstat6
tcp_tws is actually “tw” in the output of /proc/net/sockstat

TWS stands for timewaits

So, the output of /proc/net/sockstat and sockstat6 must be consistent with the output of ss -s:
# cat /proc/net/sockstat && echo "---" && cat /proc/net/sockstat6 && echo "---" && ss -ssockets: used 400
TCP:      inuse 143 orphan 1 tw 38 alloc 247 mem 86
UDP:      inuse 4 mem 3
UDPLITE:  inuse 0
RAW:      inuse 0
FRAG:     inuse 0 memory 0
---
TCP6:     inuse 4
UDP6:     inuse 2
UDPLITE6: inuse 0
RAW6:     inuse 1
FRAG6:    inuse 0 memory 0
---
Total: 400
TCP:   285 (estab 136, closed 138, orphaned 1, timewait 38)Transport Total     IP        IPv6
RAW       1         0         1
UDP       6         4         2
TCP       147       143       4
INET      154       147       7
FRAG      0         0         0

Let’s manually parse the output of /proc/net/sockstat and sockstat6:
s.tcp_total   = 247
s.tcp4_hashed = 143
s.tcp6_hashed = 4
s.tcp_tws     = 38

Let’s recall the mathematical operation that calculates the number of closed connections:
s.tcp_total - (s.tcp4_hashed + s.tcp6_hashed - s.tcp_tws)

Let’s put the values in the right places:
247 - (143 + 4 - 38)

Result (the number of closed connections): 138

38 out of 138 closed TCP sockets are actually in the TIME_WAIT state.

What about the other 100 TCP sockets?

We know that the closed connections are calculated based on the “alloc” column. So now we need to examine the “alloc” column in the output of /proc/net/sockstat now.

What is the alloc column in the sockstat output?

In the socket statistics, there are 2 types of TCP sockets: allocated and in use.
All TCP socket states are counted as alloc.
All TCP socket states except TCP_CLOSE are counted as inuse.

So, we found out that the other 100 TCP sockets are in TCP_CLOSE state.

A TCP socket can be marked as TCP_CLOSE in many cases. However, I’d like to point out a common scenario.

In TCP socket creation, the required fields are set in the sock_init_data function in the source code

内核将(新建)TCP套接字的初始状态设置为“TCP_CLOSE”。

使用一个简单perl script来验证一下:
use v5.16;
use Data::Dumper;
use IO::Socket qw(AF_INET AF_UNIX);

my @socks;
# create a new AF_INET socket
# my $sock = IO::Socket->new(Domain => AF_INET);
# # which is the same as
for(my $i=0;$i<100;$i++){
        $socks[$i]=IO::Socket->new(Domain=>AF_INET);
}

END{
        say Dumper(\@socks);
        sleep(9);
        say 'End here...';
}

如上所见,创建了100个套接字,其它什么都没有做。

运行此脚本之前,ss-s的输出如下:
# ss -s
Total: 145
TCP: 4 (estab 2, closed 0, orphaned 0, timewait 0)Transport Total   IP   IPv6
RAW       1       0    1
UDP       1       1    0
TCP       4       3    1
INET      6       4    2
FRAG      0       0    0

脚本运行结束后,ss-s的输出发生了变化:
# ss -s
Total: 245
TCP:   104 (estab 2, closed 100, orphaned 0, timewait 0)Transport Total     IP        IPv6
RAW       1         0         1
UDP       1         1         0
TCP       4         3         1
INET      6         4         2
FRAG      0         0         0

可以看到内核创建并维持了TCP_CLOSE状态的TCP套接字。

因此,如果closed的列具有较高的数字,而timewait的列具有较低的数字,则应用程序可能创建TCP套接字,但没有为其执行其它操作,且没有进行相关的回收。


参考文档
http://www.cyberciti.biz/files/ss.html