ss 详解

作者: 一天的 | 来源:发表于2019-04-11 22:51 被阅读0次

    ss

    用来显示处于活动状态的套接字信息。ss命令可以用来获取socket统计信息,它可以显示和netstat(参考https://www.jianshu.com/p/7630474c39b1)类似的内容。但ss的优势在于它能够显示更多更详细的有关TCP和连接状态的信息,而且比netstat更快速更高效。

    当服务器的socket连接数量变得非常大时,无论是使用netstat命令还是直接cat /proc/net/tcp,执行速度都会很慢。可能你不会有切身的感受,但请相信我,当服务器维持的连接达到上万个的时候,使用netstat等于浪费 生命,而用ss才是节省时间。

    天下武功唯快不破。ss快的秘诀在于,它利用到了TCP协议栈中tcp_diag。tcp_diag是一个用于分析统计的模块,可以获得Linux 内核中第一手的信息,这就确保了ss的快捷高效。当然,如果你的系统中没有tcp_diag,ss也可以正常运行,只是效率会变得稍慢。

    参数(和netstat类似)

    -h:显示帮助信息;

    -V:显示指令版本信息;

    -n:不解析服务名称,以数字方式显示;

    -a:显示所有的套接字;

    -l:显示处于监听状态的套接字;

    -o:显示计时器信息;

    -m:显示套接字的内存使用情况;

    -p:显示使用套接字的进程信息;

    -i:显示内部的TCP信息;

    -4:只显示ipv4的套接字;

    -6:只显示ipv6的套接字;

    -t:只显示tcp套接字;

    -u:只显示udp套接字;

    -d:只显示DCCP套接字;

    -w:仅显示RAW套接字;

    -x:仅显示UNIX域套接字。

    示例

    [admin@localhost ~]# ss -t -a

    State      Recv-Q Send-Q                            Local Address:Port                                Peer Address:Port 

    LISTEN      0      0                                            *:3306                                          *:*     

    LISTEN      0      0                                            *:http                                          *:*     

    LISTEN      0      0                                            *:ssh                                            *:*     

    LISTEN      0      0                                    127.0.0.1:smtp                                          *:*     

    ESTAB      0      0                                112.124.15.130:42071                              42.156.166.25:http   

    ESTAB      0      0                                112.124.15.130:ssh                              121.229.196.235:33398

    显示统计信息

    [admin@localhost ~]# ss -s

    Total: 172 (kernel 189)

    TCP:  10 (estab 2, closed 4, orphaned 0, synrecv 0, timewait 0/0), ports 5

    Transport Total    ip        IPv6

    *        189      -        -       

    RAW      0        0        0       

    UDP      5        5        0       

    TCP      6        6        0       

    INET      11        11        0       

    FRAG      0        0        0

    查看进程使用的socket

    [admin@localhost ~]# ss -pl

    State      Recv-Q Send-Q                                Local Address:Port                                    Peer Address:Port 

    LISTEN      0      128                                              :::ssh                                              :::*        users:(("sshd",1292,4))

    LISTEN      0      128                                              *:ssh                                                *:*        users:(("sshd",1292,3))

    LISTEN      0      128                                      127.0.0.1:ipp                                                *:*        users:(("cupsd",1165,7))

    LISTEN      0      128                                            ::1:ipp                                              :::*        users:(("cupsd",1165,6))

    LISTEN      0      128                                              *:32957                                              *:*        users:(("rpc.statd",1104,9))

    LISTEN      0      128                                              :::57637                                            :::*        users:(("rpc.statd",1104,11))

    LISTEN      0      80                                              :::mysql                                            :::*        users:(("mysqld",1528,17))

    LISTEN      0      128                                              *:6379                                              *:*        users:(("redis-server",1672,5))

    LISTEN      0      128                                              :::6379                                              :::*        users:(("redis-server",1672,4))

    LISTEN      0      128                                              :::sunrpc                                            :::*        users:(("rpcbind",1084,11))

    LISTEN      0      128                                              *:sunrpc                                            *:*        users:(("rpcbind",1084,8))

    LISTEN      0      128                                              *:http                                              *:*        users:(("nginx",1685,13),("nginx",3698,13),("nginx",3699,13))

    关于Recv-Q和Send-Q状态

    当 client 通过 connect 向 server 发出 SYN 包时,client 会维护一个 socket 等待队列,而 server 会维护一个 SYN 队列

    此时进入半链接的状态,如果 socket 等待队列满了,server 则会丢弃,而 client 也会由此返回 connection time out;只要是 client 没有收到 SYN+ACK,3s 之后,client 会再次发送,如果依然没有收到,9s 之后会继续发送

    半连接 syn 队列的长度为 max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog)  决定

    当 server 收到 client 的 SYN 包后,会返回 SYN, ACK 的包加以确认,client 的 TCP 协议栈会唤醒 socket 等待队列,发出 connect 调用

    client 返回 ACK 的包后,server 会进入一个新的叫 accept 的队列,该队列的长度为 min(backlog, somaxconn),默认情况下,somaxconn 的值为 128,表示最多有 129 的 ESTAB 的连接等待 accept(),而 backlog 的值则由int listen(int sockfd, int backlog) (网络编程的一个参数)中的第二个参数指定。

    当 accept 队列满了之后,即使 client 继续向 server 发送 ACK 的包,也会不被相应,此时,server 通过 /proc/sys/net/ipv4/tcp_abort_on_overflow 来决定如何返回,0 表示直接丢丢弃该 ACK,1 表示发送 RST 通知 client;相应的,client 则会分别返回 read timeout 或者 connection reset by peer。上面说的只是些理论,如果服务器不及时的调用 accept(),当 queue 满了之后,服务器并不会按照理论所述,不再对 SYN 进行应答,返回 ETIMEDOUT。

    可以看到,整个 TCP stack 有如下的两个 queue:

    1. 一个是 half open(syn queue) queue(max(tcp_max_syn_backlog, 64)),用来保存 SYN_SENT 以及 SYN_RECV 的信息。

    2. 另外一个是 accept queue(min(somaxconn, backlog)),保存 ESTAB 的状态,但是调用 accept()。

    使用 ss 获取到的 Recv-Q/Send-Q 在 LISTEN 状态以及非 LISTEN 状态所表达的含义是不同的。

    LISTEN 状态: Recv-Q 表示的当前等待服务端调用 accept 完成三次握手的 listen backlog 数值,也就是说,当客户端通过 connect() 去连接正在 listen() 的服务端时,这些连接会一直处于这个 queue 里面直到被服务端 accept();Send-Q 表示的则是最大的 listen backlog 数值,这就就是上面提到的 min(backlog, somaxconn) 的值。

    其余状态: 非 LISTEN 状态,代表缓冲区字节数量。Recv-Q 表示 receive queue 中的 bytes 数量;Send-Q 表示 send queue 中的 bytes 数值。

    https://www.jianshu.com/p/ff26312e67a9

    查看tcp_diag代码

    static void tcp_diag_get_info(struct sock *sk, struct inet_diag_msg *r, void *_info) { 

        const struct tcp_sock *tp = tcp_sk(sk); 

        struct tcp_info *info = _info; 

        if (sk->sk_state == TCP_LISTEN) { 

            r->idiag_rqueue = sk->sk_ack_backlog; 

            r->idiag_wqueue = sk->sk_max_ack_backlog; 

        } else { 

            r->idiag_rqueue = tp->rcv_nxt - tp->copied_seq; 

            r->idiag_wqueue = tp->write_seq - tp->snd_una; 

        } 

        if (info != NULL) 

            tcp_get_info(sk, info); 

    }

    显示更多的信息

    -o, --options 显示时间信息(定时器)

    -m, --memory 显示 socket 使用的内存 

    -i, --info 显示更多 TCP 内部的信息

    -s 显示统计信息

    ~ # ss -a -t -o -4

    State       Recv-Q Send-Q                                            Local Address:Port                                                Peer Address:Port      

    LISTEN      0      128                                                           *:openvms-sysipc                                                        *:*       

    ESTAB       0      0                                                172.16.132.189:ssh                                                172.16.132.93:hs-port  timer:(keepalive,65min,0)

    ESTAB       0      0                                                    10.0.64.19:49462                                                10.0.64.107:61616   

    ESTAB       0      0                                                172.16.132.189:ssh                                                172.16.132.85:63934    timer:(keepalive,38min,0)

    ESTAB       0      0                                                    10.0.64.19:60569                                                10.0.64.107:61616   

    这个输出更上次输出相比,多了一个timer输出。这个输出描述的是tcp socket上的定时器,在说明这个之前先了解一下linux对一个tcp socket可能设置的定时器。

    tcp socket总共有7个定时器,通过4个timer实现。分别是

    通过icsk_retransmit_timer实现的重传定时器、零窗口探测定时器;通过sk_timer实现的连接建立定时器、保活定时器和FIN_WAIT_2定时器;通过icsk_delack_timer实现的延时ack定时器;以及TIME_WAIT定时器。

    我们看一下ss的代码

    static const char *tmr_name[] = {

    "off",

    "on",

    "keepalive",

    "timewait",

    "persist",

    "unknown"

    };

    if (show_options) {

        if (r->idiag_timer) {

            if (r->idiag_timer > 4)

                r->idiag_timer = 5;

            printf(" timer:(%s,%s,%d)",

            tmr_name[r->idiag_timer],

            print_ms_timer(r->idiag_expires),

            r->idiag_retrans);

        }

    }

    对应的内核代码是

    if (icsk->icsk_pending == ICSK_TIME_RETRANS) { 

        r->idiag_timer = 1;

        r->idiag_retrans = icsk->icsk_retransmits;

        r->idiag_expires = EXPIRES_IN_MS(icsk->icsk_timeout); 

    } else if (icsk->icsk_pending == ICSK_TIME_PROBE0) { 

        r->idiag_timer = 4;

        r->idiag_retrans = icsk->icsk_probes_out;

        r->idiag_expires = EXPIRES_IN_MS(icsk->icsk_timeout); 

    } else if (timer_pending(&sk->sk_timer)) { 

        r->idiag_timer = 2;

        r->idiag_retrans = icsk->icsk_probes_out;

        r->idiag_expires = EXPIRES_IN_MS(sk->sk_timer.expires); 

    } else {

        r->idiag_timer = 0;

        r->idiag_expires = 0; 

    }

    static int inet_twsk_diag_fill(struct inet_timewait_sock *tw, struct sk_buff *skb, int ext, u32 pid,

                        u32 seq, u16 nlmsg_flags, const struct nlmsghdr *unlh){

        long tmo;

        struct inet_diag_msg *r;

        const unsigned char *previous_tail = skb_tail_pointer(skb);

        struct nlmsghdr *nlh = NLMSG_PUT(skb, pid, seq,

        unlh->nlmsg_type, sizeof(*r));

        r = NLMSG_DATA(nlh);

        BUG_ON(tw->tw_state != TCP_TIME_WAIT);

        nlh->nlmsg_flags = nlmsg_flags;

        tmo = tw->tw_ttd - jiffies;

        if (tmo < 0)

            tmo = 0;

        r->idiag_family = tw->tw_family;

        r->idiag_retrans = 0;

        r->id.idiag_if = tw->tw_bound_dev_if;

        r->id.idiag_cookie[0] = (u32)(unsigned long)tw;

        r->id.idiag_cookie[1] = (u32)(((unsigned long)tw >> 31) >> 1);

        r->id.idiag_sport = tw->tw_sport;

        r->id.idiag_dport = tw->tw_dport;

        r->id.idiag_src[0] = tw->tw_rcv_saddr;

        r->id.idiag_dst[0] = tw->tw_daddr;

        r->idiag_state = tw->tw_substate;

        r->idiag_timer = 3;

        r->idiag_expires = DIV_ROUND_UP(tmo * 1000, HZ);

        r->idiag_rqueue = 0;

        r->idiag_wqueue = 0;

        r->idiag_uid = 0;

        r->idiag_inode = 0;

    timer的输出含义就是(类型,过期时间,重试次数),这里说一下类型的含义:

    off: 当前socket没有timer

    on: 重传timer

    keepalive:连接建立timer or fin_wait_2 timer or 保活timer;具体是那个timer,可以根据连接的状态来确定。

    timewait: TIME_WAITtimer

    persist:零窗口探测timer 

    dst/src dport/sport 语法

    可以通过 dst/src/dport/sprot 语法来过滤连接的来源和目标,来源端口和目标端口。

    匹配远程地址和端口号

    $ ss dst 192.168.1.5

    $ ss dst 192.168.119.113:http

    $ ss dst 192.168.119.113:443

    将本地或者远程端口和一个数比较,可以使用下面的语法做端口号的过滤:

    $ ss dport OP PORT

    $ ss sport OP PORT

    OP 可以代表以下任意一个:

    <=le小于或等于某个端口号

    >=ge大于或等于某个端口号

    ==eq等于某个端口号

    !=ne不等于某个端口号

    >gt大于某个端口号

    <lt小于某个端口号

    下面是一个简单的 demo(注意,需要对尖括号使用转义符):

    $ ss -tunl sport lt 50

    $ ss -tunl sport \< 50

    $  ss dport = :8080 or sport = :8080

    ss 命令还可以通过 TCP 连接的状态进程过滤,支持的 TCP 协议中的状态有:

    established

    syn-sent

    syn-recv

    fin-wait-1

    fin-wait-2

    time-wait

    closed

    close-wait

    last-ack

    listening

    closing

    除了上面的 TCP 状态,还可以使用下面这些状态:

    all列出所有的 TCP 状态。

    connected列出除了 listening 和 closing 之外的所有 TCP 状态。

    synchronized列出除了 syn-sent 之外的所有 TCP 状态。

    bucket列出 maintained 的状态,如:time-wait 和 syn-recv。

    big列出和 bucket 相反的状态。

    使用 ipv4 时的过滤语法如下:

    使用 ipv6 时的过滤语法如下:

    1$ ss -6 state filter

    下面的两种写法是等价的,要有使用 \ 转义小括号,要么使用单引号括起来:

    $ ss -4n state listening \( dport = :ssh\)

    $ ss -4n state listening '( dport = :ssh )'

    找出打开套接字/端口应用程序

    [admin@localhost ~]# ss -pl | grep3306

    00*:3306*:* users:(("mysqld",1718,10))

    ss -o state established '( dport = :smtp or sport = :smtp )' 显示所有已建立的SMTP连接

    ss -o state established '( dport = :http or sport = :http )' 显示所有已建立的HTTP连接

    ss -o state established '( dport = :8080 or sport = :8080 )'

    相关文章

      网友评论

          本文标题:ss 详解

          本文链接:https://www.haomeiwen.com/subject/bnefwqtx.html