1、socket
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
sys_socket->sock_create->__sock_create(入参有效性校验)->sock_alloc(申请分配新的inode,初始化inode->i_op=sockfs_inode_ops,创建socket)->[__sock_create]inet_create[pf->create]->[sys_socket]sock_map_fd[分配fd、创建file,并与sock之间相互关联]->sock_alloc_file(在sockfs中创建文件,设置sock->file = file、file->private_data = sock)->alloc_file(在sockfs中创建文件,设置file->f_op=socket_file_ops)->[sock_map_fd]fd_install(关联fd和file,current->files->fdt[fd]=file)
inet_create(sock类型为socket,sk为sock,从inetsw_array中查找对应的answer,sock->ops = answer->ops,创建初始化sk并与sock关联)->sk_alloc(从tcp_prot的slab中创建tcp_sock,初始化sk->sk_prot = sk->sk_prot_creator=answer->prot)->[inet_create]sock_init_data(sk->sk_socket = sock,sock->sk=sk,sk_data_ready等成员初始化工作)->[inet_create]tcp_v4_init_sock[sk->sk_prot->init](icsk->icsk_af_ops = &ipv4_specific、tcpmd5相关初始化tcp_sk(sk)->af_specific = &tcp_sock_ipv4_specific)->tcp_init_sock(初始化cwnd、sndbuf等TCP相关参数)
sock_mnt{sock_init}[sock_alloc]:sock_fs_type文件系统的superblock,文件系统相关操作 sockfs_ops sockfs_dentry_operations
inet_family_ops{inet_init}:对应famile为PF_INET的creat操作,sys_socket系统调用最终通过函数指针调用inet_create
2、bind
SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
sys_bind->sockfd_lookup_light(查找fd对应大的socket)->[sys_bind]move_addr_to_kernel(复制用户空间结构到sockaddr_storage)->inet_bind[sock->ops->bind]->inet_csk_get_port[sk->sk_prot->get_port]->inet_csk_bind_conflict[inet_csk(sk)->icsk_af_ops->bind_conflict]
sockfd_lookup_light(根据fd查找到socket)->sock_from_file(返回file->private_data)
inet_bind:
1、会根据ip_nonlocal_bind参数设置以及IP_TRANSPARENT、IP_FREEBIND选项决定是否允许绑定非合法IP
2、如果绑定端口号低于1024,判断是否有权绑定
3、TCP_CLOSE状态并且之前没有绑定过端口(inet->inet_num为0表示之前没有绑定过),才能重新绑定
4、绑定ip地址,inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr。inet_rcv_saddr用来做hash查找,inet_saddr用来做传输,广播和多播地址设置inet_saddr=0。
5、IP_BIND_ADDRESS_NO_PORT选项设置为1的时候不允许入参端口号为0,否则入参端口号为0代表由内核自动选择
6、调用inet_csk_get_port[sk->sk_prot->get_port]进行端口绑定操作
7、如果成功设置了有效的端口号,进行如下更新
if(inet->inet_rcv_saddr)
sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
if(snum)
sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
inet->inet_sport = htons(inet->inet_num);
inet->inet_daddr =0;
inet->inet_dport =0;
inet_csk_get_port:
SO_BINDTODEVICE选项可以设置绑定接口,即sk->sk_bound_dev_if,通过这个选项设置不同的接口后则可以绑定到相同的端口和ip地址上
SO_REUSEADDR:必要不为listen状态就可以重复绑定相同的地址端口。但是即使设置该选项,自动选择端口的时候仍然会尽量避免选定重复的IP和端口
SO_REUSEPORT:只要用户uid相同或已绑定的sk处于TW状态就可以抢占。 设置SO_REUSEPORT选项后,同一个用户两个套接字可以同时listen相同的端口和地址,而SO_REUSEADDR则不行。
inet_get_local_port_range:
通过顺序锁保护获取ip_local_port_range参数范围
inet_is_local_reserved_port:
ip_local_reserved_ports参数的设置保存在Bit arrays数据结构中,判断一个端口是否为预留端口(需要编译宏CONFIG_SYSCTL生效)
inet_bind_hash[inet_csk_get_port]:
更新inet_sk(sk)->inet_num = snum,并把sk添加到对应tb的owners队列中
Q:不进行bind操作 直接listen可以嘛?重复进行bind操作可以吗?listen状态进行重新绑定?
A:不进行bind直接进行listen的时候会自动选择端口。不能重复进行bind操作。只能在closed状态下进行bind操作。
3、listen
SYSCALL_DEFINE2(listen, int, fd, int, backlog)
sys_listen(会在入参backlog和somaxconn之间取小向后传递)->sockfd_lookup_light->[sys_listen]inet_listen[sock->ops->listen]->inet_csk_listen_start->reqsk_queue_alloc(初始化icsk_accept_queue中的参数,包括TFO队列)->[inet_listen]inet_csk_get_port[sk->sk_prot->get_port]->[inet_listen]inet_hash[sk->sk_prot->hash]->__inet_hash
sock_net(sock->sk)->core.sysctl_somaxconn:/proc/sys/net/core/somaxconn 默认为128
inet_listen:
socket状态只有为SS_UNCONNECTED且类型为SOCK_STREAM才能进行listen操作
TCP状态只能是CLOSE或者LISTEN才能进行listen操作,LISTEN状态下进行listen操作的时候重新更新backlog,sk->sk_max_ack_backlog = backlog,fastopen队列的maxlen在listen后不能更新。
tcp_fastopen设置:如果TFO_SERVER_ENABLE(2)有设置,且没有通过socket选项初始化fastopen队列长度的时候
如果TFO_SERVER_WO_SOCKOPT1(0x400)设置,则更新fastopenq.max_qlen=min(backlog, somaxconn)
如果TFO_SERVER_WO_SOCKOPT2(0x800)设置,则更新fastopenq.max_qlen=min(backlog, tcp_fastopen)
inet_csk_listen_start:
初始化accept队列、逻辑TFO队列、TFO RST队列、逻辑半连接队列、delay ACK等
通过inet_csk_get_port[sk->sk_prot->get_port]获取port成功的时候,通过inet_hash把sock添加到listen队列
__inet_hash
根据绑定的本地端口和net空间散列到hash桶中,sk->sk_prot->h.hashinfo->listening_hash[inet_sk_listen_hashfn(sk)],listening_hash哈希表大小为固定的INET_LHTABLE_SIZE(32)。
4、accept
SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr, int __user *, upeer_addrlen) ->sys_accept4(fd, upeer_sockaddr, upeer_addrlen, 0)
SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr, int __user *, upeer_addrlen, int, flags)
sys_accept4->sockfd_lookup_light->[sys_accept4]sock_alloc(分配分配inode,创建新的child socket)->[sys_accept4]get_unused_fd_flags(分配fd)->[sys_accept4]sock_alloc_file(在文件系统创建文件)->[sys_accept4]inet_accept[sock->ops->accept](最后设置newsock状态为SS_CONNECTED)->inet_csk_accept[sk1->sk_prot->accept]->inet_csk_wait_for_connect(根据超时时间等待连接)->reqsk_queue_remove(从accept队列取出head)->[inet_accept](关联newsock和sk2)
inet_csk_accept:
如果sock状态不为LISTEN,返回错误
如果accept队列为空,根据O_NONBLOCK选择超时等待或者立即返回。超时时间sk->sk_rcvtimeo默认为MAX_SCHEDULE_TIMEOUT(有符号的longmax),可以通过SO_RCVTIMEO选项来设置,设置值会向上取整到HZ精度。
accept后,普通连接直接释放req,TFO下如果连接还没完成三次握手则设置req->sk = NULL,否则同样释放req。
Q:多个进程或者线程同时accept先唤醒那个?
A:多个程序运行产生的进程accept的时候,会根据随机数进行随机选择,参考__inet_lookup_listener
多个线程accept的时候,多个线程实际对应一个sk,accept的时候会把自己的wait描述符添加到等待队列的尾部。唤醒的时候则是从head先唤醒,因此多线程accept的时候,先accept的线程先被唤醒。
通过fork产生的多个进程的accept实际对应一个sk,处理与多线程accept相同。
Q:进程accept后进行fork,其中只有子进程进行close操作,连接会关闭嘛?
A:不会,fork后父进程和子进程实际引用一个文件描述符,需要父进程和子进程都close后,TCP连接才会关闭。
5、connect
SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr, int, addrlen)
sys_connect->sockfd_lookup_light->[sys_connect]inet_stream_connect[sock->ops->connect]->__inet_stream_connect->tcp_v4_connect[sk->sk_prot->connect]->[__inet_stream_connect]inet_wait_for_connect
__inet_stream_connect:
连接目标地址不能为AF_UNSPEC
socket状态为SS_UNCONNECTED,TCP状态为CLOSED才能进行连接
根据O_NONBLOCK计算超时时间
连接成功后更新socket状态为SS_CONNECTED
tcp_v4_connect:
如过有ip选项,则初始化下一跳地址
获取到下一跳的路由,如果获取失败或者路由是多播或者广播,则返回错误 ,如果没有指定源地址根据查找到的路由初始化源地址
__inet_check_established->twsk_unique->tcp_twsk_unique[sk->sk_prot->twsk_prot->twsk_unique]
__inet_check_established:
判断选定的端口是否和ehash中连接冲突,
1、如果在ehsah中找到了源地址、目标地址、源端口、目标端口、接口、net命名空间都完全一样的sk2,
sk2如果为timewait状态且tcp_twsk_unique返回1,则把sk插入到ehash并从ehash中删除tw(sk2),并根据入参可能会从bhash中删除tw
其他情况则不能使用该端口,即不能重复连接
2、如果没有在ehash中找到相符的sk2,则同样端口分配成功,sk加入ehash。
tcp_twsk_unique:
如果tcptw有记录时间戳信息,并且记录时间距离当前超过1s,tcp_tw_reuse有效的时候,则把tcptw中记录的时间戳信息记录到sk中,并返回1
tcp_connect[tcp_v4_connect]:
初始化连接相关参数tcp_connect_init
分配SKB、初始化SKB、添加到写队列、获取连接的ECN信息
发送SKB,TFO下使用tcp_send_syn_data,普通连接使用tcp_transmit_skb
启动重传定时器ICSK_TIME_RETRANS
tcp_disconnect:
Q:connect的时候射设置连接到0.0.0.0:0怎么处理?
A:目的端口为0,目的地址和源地址会根据选出来的路由entry的目的地址和源地址进行设置,最终都会设置为127.0.0.1,源端口则会根据inet_hash_connect来选定,选定的时候会根据源地址、目的地址和目的端口先生成一个随机的offset,然后根据这个offset来随机选择源端口
Q:__inet_hash_connect中端口bhash冲突的场景下,怎么添加到ehash链表中的?
如果这个sk是第一个绑定这个端口的sk,那么直接在__inet_hash_connect中插入ehash,否则通过__inet_check_established插入到ehash