iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 27
0
自我挑戰組

跨界的追尋:trace 30個基本Linux系統呼叫系列 第 27

trace 30個基本Linux系統呼叫第二十七日:bind-listen

前情提要

昨日我們看過了socket如何產生,今日則主要聚焦在server端的兩個後續作業:bind以及listen上。


連結socket與網路資訊的實體:bind

NAME
       bind - bind a name to a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);

bind系統呼叫將一個已經指定了用途(協定家族、封包種類...)的檔案描述子與一個網路位址結構連結在一起。為了不懂socket程式的讀者這裡稍微解釋一下,為什麼標頭定義使用struct sockaddr型別,範例程式內卻使用struct sockaddr_in型別。其實前者是一個比較通用的型別,可以跨不同協定使用;而後者就筆者的使用經驗,是IPv4使用的型別。

這個呼叫一樣在net/socket.c之中:

1356 SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
1357 {
1358         struct socket *sock;
1359         struct sockaddr_storage address;
1360         int err, fput_needed;
1361 
1362         sock = sockfd_lookup_light(fd, &err, &fput_needed);
1363         if (sock) { 
1364                 err = move_addr_to_kernel(umyaddr, addrlen, &address);
1365                 if (err >= 0) {
1366                         err = security_socket_bind(sock,
1367                                                    (struct sockaddr *)&address,
1368                                                    addrlen);
1369                         if (!err)
1370                                 err = sock->ops->bind(sock,
1371                                                       (struct sockaddr *)
1372                                                       &address, addrlen);
1373                 }
1374                 fput_light(sock->file, fput_needed);
1375         }
1376         return err;
1377 }

sockfd_lookup_light函式首先透過fd取得struct fd的結構,這個結構就會含有對應的struct file結構,而這個檔案描述子對應的socket就以private_data成員的身份歸屬於這個對應的檔案。接著的move_addr_to_kernel呼叫可以視為一個copy_from_user的wrapper,就是比較方便呼叫還有做個audit紀錄而已。

audit開頭的函數在本系列中多半跳過,因為筆者的環境並沒有開啟這個在何心中收集額外資訊的組態設定。

接下來就呼叫sock結構定義的、特定協定的bind方法。這個也不例外,是在./net/ipv4/af_inet.c之中,

 426 int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
 427 {
 428         struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;
 429         struct sock *sk = sock->sk;
 430         struct inet_sock *inet = inet_sk(sk);
 431         struct net *net = sock_net(sk);
 432         unsigned short snum;
 433         int chk_addr_ret;
 434         u32 tb_id = RT_TABLE_LOCAL;
 435         int err;
 436 
 437         /* If the socket has its own bind function then use it. (RAW) */
 438         if (sk->sk_prot->bind) {
 439                 err = sk->sk_prot->bind(sk, uaddr, addr_len);
 440                 goto out;
 441         }

在開始解析之前,必須理解這些名子相近的結構是怎麼回事。

  • struct socket:應用層用來表達socket這個抽象物件的東西
  • struct sockaddr_in:專用於ipv4的位址型態
  • struct sock(在include/linux/sock.h):網路層用來表達socket的東西,有非常多成員,像是接收佇列、傳送佇列、同步用的鎖等等

從動態追蹤的結果發現,sk並沒有定義sk->sk_prot->bind的bind成員,所以這裡的判斷會被跳過。

...
 467         if (!net->ipv4.sysctl_ip_nonlocal_bind &&
 468             !(inet->freebind || inet->transparent) &&
 469             addr->sin_addr.s_addr != htonl(INADDR_ANY) &&
 470             chk_addr_ret != RTN_LOCAL &&
 471             chk_addr_ret != RTN_MULTICAST &&
 472             chk_addr_ret != RTN_BROADCAST)
 473                 goto out;
 474 
 475         snum = ntohs(addr->sin_port);
 476         err = -EACCES;
 477         if (snum && snum < PROT_SOCK &&
 478             !ns_capable(net->user_ns, CAP_NET_BIND_SERVICE))
 479                 goto out;
...

這裡拿傳入的addr結構開始解析,也透過ns_capable判斷是否允許(詳見capabilities(7))當前的使用者做bind的動作。之後的程式碼會做一些socket狀態的判斷,還有port是否能夠使用(在net/ipv4/inet_connection_sock.cinet_csk_get_port函數中)的判斷。最後就會回到原先的sys_bind中,並且回傳狀態。


令socket被動化:listen

listen的說明是這樣的:

NAME
       listen - listen for connections on a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int listen(int sockfd, int backlog);

這個呼叫讓socket成為被動的姿態,若是成功則socket進入準備狀態,它能夠在之後透過accept這樣的呼叫接收訊息並建立連線。backlog則規範了這個等待的socket能夠處理的連線的排隊長度。這個呼叫一樣在net/socket.c之中:

1385 SYSCALL_DEFINE2(listen, int, fd, int, backlog)
1386 {
1387         struct socket *sock;
1388         int err, fput_needed;
1389         int somaxconn;
1390 
1391         sock = sockfd_lookup_light(fd, &err, &fput_needed);
1392         if (sock) {
1393                 somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
1394                 if ((unsigned int)backlog > somaxconn)
1395                         backlog = somaxconn;
1396 
1397                 err = security_socket_listen(sock, backlog);
1398                 if (!err)
1399                         err = sock->ops->listen(sock, backlog);
1400 
1401                 fput_light(sock->file, fput_needed);
1402         }
1403         return err;
1404 }

上述的排隊長度體現在somaxconn這個系統組態裡面。backlog固然是一個使用者(伺服器開發者)期望的容納量,1394行的判斷顯示系統的socket max connection規定更為重要。然後是1399行的呼叫到inet_listen

 194 int inet_listen(struct socket *sock, int backlog)
 195 {
 196         struct sock *sk = sock->sk;
 197         unsigned char old_state;
 198         int err;
 199 
 200         lock_sock(sk);
 201 
 202         err = -EINVAL;
 203         if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
 204                 goto out;
 205 
 206         old_state = sk->sk_state;
 207         if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))
 208                 goto out;
...

因為之後這個函數預計要對sk(socket的網路層結構)的狀態做一番修改,所以先在200行鎖起來,然後分別就狀態做一些判斷,

 210         /* 要是這個socket早就在聆聽狀態
 211          * 那我們只能允許backlog的改變
 212          */
 213         if (old_state != TCP_LISTEN) {
 214                 /* Check special setups for testing purpose to enable TFO w/o
 215                  * requiring TCP_FASTOPEN sockopt.
 216                  * Note that only TCP sockets (SOCK_STREAM) will reach here.
 217                  * Also fastopenq may already been allocated because this
 218                  * socket was in TCP_LISTEN state previously but was
 219                  * shutdown() (rather than close()).
 220                  */
 221                 if ((sysctl_tcp_fastopen & TFO_SERVER_ENABLE) != 0 &&
 222                     !inet_csk(sk)->icsk_accept_queue.fastopenq.max_qlen) {
 223                         if ((sysctl_tcp_fastopen & TFO_SERVER_WO_SOCKOPT1) != 0)
 224                                 fastopen_queue_tune(sk, backlog);
 225                         else if ((sysctl_tcp_fastopen &
 226                                   TFO_SERVER_WO_SOCKOPT2) != 0)
 227                                 fastopen_queue_tune(sk,
 228                                     ((uint)sysctl_tcp_fastopen) >> 16);
 229 
 230                         tcp_fastopen_init_key_once(true);
 231                 }
 232                 err = inet_csk_listen_start(sk, backlog);
 233                 if (err)
 234                         goto out;
 235         }
 236         sk->sk_max_ack_backlog = backlog;
 237         err = 0;

只有狀態正確的TCP連線才會進入到213行之後的判斷區塊中,而這裡就有一些與TCP相關的函數呼叫,最後一個是inet_csk_listen_start,裡面會呼叫sk_state_store(sk, TCP_LISTEN);設定狀態,完成這個呼叫。


結論

本日我們看了在伺服器端的兩個前置動作,它們一個是綁定檔案描述子與位址資訊,令一個則是使得該socket進入等待狀態。明日會加入客戶端的視角,觀看兩個角色之間的互動。感謝各位讀者,我們明日再會!


上一篇
trace 30個基本Linux系統呼叫第二十六日:socket
下一篇
trace 30個基本Linux系統呼叫第二十八日:互相交握的accept與connect
系列文
跨界的追尋:trace 30個基本Linux系統呼叫30

尚未有邦友留言

立即登入留言