# Linux 端口绑定原理

我们在创建了socket fd后,需要绑定监听地址,代码如下,本节将详细介绍绑定过程,请读者务必弄懂socket创建原理后再观看,因为绑定时调用的函数将会在创建时设置。本节较为简单,没有过多的操作,仅仅只是将用户层传递的数据保存到对应结构而已。

 int serverFD = socket(AF_INET, SOCK_STREAM, 0);

sockaddr_in addr; // 端口绑定信息

addr.sin_family = AF_INET; // 指定协议簇

addr.sin_addr.s_addr = inet_addr("127.0.0.1");

addr.sin_port = htons(8080); // 将端口信息转为网络字节序(大端序)

int ret = bind(serverFD, (sockaddr*)&addr, sizeof(addr)); // 开始绑定
1
2
3
4
5
6
7
8
9
10
11

sys_bind 函数

该函数用于根据fd找到socket,然后调用socket的bind操作完成绑定。源码如下。

asmlinkage long sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen)

{

  struct socket *sock;

  char address[MAX_SOCK_ADDR];// 保存用户空间传递的绑定地址信息

  int err;

  if((sock = sockfd_lookup(fd,&err))!=NULL) // 根据fd描述符找到socket

 {

    if((err=move_addr_to_kernel(umyaddr,addrlen,address))>=0) { // 将用户层传递的 sockaddr 信息复制到 char address[MAX_SOCK_ADDR] 中

     ...

      err = sock->ops->bind(sock, (struct sockaddr *)address, addrlen); // 执行 bind 操作

   }

   ...

 }   

  return err;

}// 根据fd描述符找到socket

struct socket *sockfd_lookup(int fd, int *err){

 struct file *file;

 struct inode *inode;

 struct socket *sock;

 if (!(file = fget(fd))) // 获取fd下标处的file结构

 {

  *err = -EBADF;

  return NULL;

 }

 inode = file->f_dentry->d_inode; // 从file结构中获取inode信息

 if (!inode->i_sock || !(sock = SOCKET_I(inode))) // 前面我们说到socket 和 inode 放在一起(struct socket_alloc结构)所以我们只需要inode地址 就可以根据偏移量获取 socket的地址

 {

  *err = -ENOTSOCK;

  fput(file);

  return NULL;

 }

 ...

 return sock;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

inet_bind 函数

在创建 tcp 协议时,将会把该函数作为 bind 函数指针的实现,所以我们跟进该函数即可。我们看到,在该函数中将会通过 socket 获取到 sock 结构,然后将该结构强转为 inet_sock 结构 ,随后将用户态传递的绑定 IP 和 端口 信息保存在 inet_sock 的 inet_opt 信息 中。源码如下。

// 描述 IP socket 地址结构。注意:该结构为内核绑定时使用

#define __SOCK_SIZE__ 16  /* sizeof(struct sockaddr) */

struct sockaddr_in {

 sa_family_t  sin_family; /* Address family  */

 unsigned short int sin_port; /* Port number   */

 struct in_addr sin_addr; /* Internet address  *//* Pad to size of `struct sockaddr'. */

 unsigned char  __pad[__SOCK_SIZE__ - sizeof(short int) -

   sizeof(unsigned short int) - sizeof(struct in_addr)];

};// 描述 inet sock 的信息(在sock 结构基础上增加 inet_opt,用于表示socket套接字的元数据)

struct inet_sock {

 struct sock  sk;

 struct inet_opt inet;

};// 宏定义,用于从sock结构中获取到inet_sock结构指针

#define inet_sk(__sk) (&((struct inet_sock *)__sk)->inet)

​

​

int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len){

 struct sockaddr_in *addr = (struct sockaddr_in *)uaddr; // 指针强转,还记得混沌学堂说的么?对内存不同的解释

 struct sock *sk = sock->sk; // 从socket 中获取 底层网络操作 sock 

 struct inet_opt *inet = inet_sk(sk); // 从socket中获取 inet_opt socket 描述结构

 unsigned short snum;

 int chk_addr_ret;

 int err;

 // 对于RAW格式而言,存在自己的绑定操作,那么执行它自己的

 if (sk->sk_prot->bind) {

  err = sk->sk_prot->bind(sk, uaddr, addr_len);

  goto out;

 }

 ...

 snum = ntohs(addr->sin_port); // 端口号设置为 用户空间传递的 portNum

 ...

  // 否则执行通用绑定操作

 if (sk->sk_prot->get_port(sk, snum)) { // 若端口信息存在,那么绑定失败

		inet->saddr = inet->rcv_saddr = 0;

		err = -EADDRINUSE;

		goto out_release_sock;

 }

 ...

 inet->rcv_saddr = inet->saddr = addr->sin_addr.s_addr; // 保存监听IP地址

 ...

 inet->sport = htons(inet->num); // 保存端口信息(source port 源端口,也即输入端口,这里为server 的监听端口)

  // 服务端不需要连接,所以没有目的IP和目的端口,也即对端信息

 inet->daddr = 0;

 inet->dport = 0;

 ...

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103

sock 与 inet_sock 内存转换原理

读者可能会好奇:为啥 sock 可以直接强转为 inet_sock 。我们来看以下代码。可以看到其实开辟的 sock 内存后面已经包含了 ip 和 tcp 层的信息空间,所以我们可以随意强转,这时,是不是更能体现:C语言中指针的强转变化,仅仅只是对内存空间的数据不同解释罢了。

static int inet_create(struct socket *sock, int protocol){ // 前面描述sys_socket时讲过

 ...

  sk = sk_alloc(PF_INET, GFP_KERNEL, inet_sk_size(protocol),

     inet_sk_slab(protocol));

 ...

}// 表示TCP协议的 sock 元信息

struct tcp_sock {

 struct sock  sk; // sock 信息

 struct inet_opt  inet; // ip 层信息

 struct tcp_opt  tcp; // tcp 层信息

};static __inline__ int inet_sk_size(int protocol) // 通常我们指定为0,至于UDP和原始sock类型,读者自行查看

{

 int rc = sizeof(struct tcp_sock);

 if (protocol == IPPROTO_UDP)

  rc = sizeof(struct udp_sock);

 else if (protocol == IPPROTO_RAW)

  rc = sizeof(struct raw_sock);

 return rc;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45