昨日我們看過了socket
如何產生,今日則主要聚焦在server端的兩個後續作業:bind
以及listen
上。
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.c
的inet_csk_get_port
函數中)的判斷。最後就會回到原先的sys_bind
中,並且回傳狀態。
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進入等待狀態。明日會加入客戶端的視角,觀看兩個角色之間的互動。感謝各位讀者,我們明日再會!