Detailed explanation of setsockopt function and parameters

1. Introduction to setsockopt() function

The setsockopt() function is used to set option values ​​of sockets of any type and state. Its functions and instructions are as follows:

int setsockopt(SOCKET s, int level, int optname, const char FAR *optval, int optlen);

The first parameter socket is the socket descriptor. The second parameter level is the level of the option being set. If you want to set the option at the socket level, you must set the level to SOL_SOCKET. option_name specifies the option to be set. What values ​​option_name can have depends on the level. At the socket level (SOL_SOCKET), option_name can have the following values:

1. SO_DEBUG,打开或关闭调试信息。
    当option_value不等于0时,打开调试信息,否则,关闭调试信息。它实际所做的工作是在sock->sk->sk_flag中置 SOCK_DBG(10)位,或清SOCK_DBG位。
    
2. SO_REUSEADDR,打开或关闭地址复用功能。
    当option_value不等于0时,打开,否则,关闭。它实际所做的工作是置sock->sk->sk_reuse为103. SO_DONTROUTE,打开或关闭路由查找功能。
    当option_value不等于0时,打开,否则,关闭。它实际所做的工作是在sock->sk->sk_flag中置或清SOCK_LOCALROUTE位。
    
4. SO_BROADCAST,允许或禁止发送广播数据。
    当option_value不等于0时,允许,否则,禁止。它实际所做的工作是在sock->sk->sk_flag中置或清SOCK_BROADCAST位。
    
5. SO_SNDBUF,设置发送缓冲区的大小。
    发送缓冲区的大小是有上下限的,其上限为256 * (sizeof(struct sk_buff) + 256),下限为2048字节。该操作将sock->sk->sk_sndbuf设置为val * 2,之所以要乘以2,是防止大数据量的发送,突然导致缓冲区溢出。最后,该操作完成后,因为对发送缓冲的大小 作了改变,要检查sleep队列,如果有进程正在等待写,将它们唤醒。
    
6. SO_RCVBUF,设置接收缓冲区的大小。
    接收缓冲区大小的上下限分别是:256 * (sizeof(struct sk_buff) + 256)256字节。该操作将sock->sk->sk_rcvbuf设置为val * 27. SO_KEEPALIVE,套接字保活。
    如果协议是TCP,并且当前的套接字状态不是侦听(listen)或关闭(close),那么,当option_value不是零时,启用TCP保活定时 器,否则关闭保活定时器。对于所有协议,该操
    作都会根据option_value置或清 sock->sk->sk_flag中的 SOCK_KEEPOPEN位。

8. SO_OOBINLINE,紧急数据放入普通数据流。
    该操作根据option_value的值置或清sock->sk->sk_flag中的SOCK_URGINLINE位。
    
9. SO_NO_CHECK,打开或关闭校验和。
    该操作根据option_value的值,设置sock->sk->sk_no_check。
    
10. SO_PRIORITY,设置在套接字发送的所有包的协议定义优先权。Linux通过这一值来排列网络队列。
    这个值在06之间(包括06),由option_value指定。赋给sock->sk->sk_priority。
    
11. SO_LINGER,如果选择此选项, close或 shutdown将等到所有套接字里排队的消息成功发送或到达延迟时间后>才会返回. 否则, 调用将立即返回。
    该选项的参数(option_value)是一个linger结构:
        struct linger {
    
    
            int   l_onoff;   
            int   l_linger;  
        };
    如果linger.l_onoff值为0(关闭),则清 sock->sk->sk_flag中的SOCK_LINGER位;否则,置该位,并赋sk->sk_lingertime值为 linger.l_linger。
    
12. SO_PASSCRED,允许或禁止SCM_CREDENTIALS 控制消息的接收。
    该选项根据option_value的值,清或置sock->sk->sk_flag中的SOCK_PASSCRED位。
    
13. SO_TIMESTAMP,打开或关闭数据报中的时间戳接收。
    该选项根据option_value的值,清或置sock->sk->sk_flag中的SOCK_RCVTSTAMP位,如果打开,则还需设sock->sk->sk_flag中的SOCK_TIMESTAMP位,同时,将全局变量netstamp_needed加114. SO_RCVLOWAT,设置接收数据前的缓冲区内的最小字节数。
    在Linux中,缓冲区内的最小字节数是固定的,为1。即将sock->sk->sk_rcvlowat固定赋值为115. SO_RCVTIMEO,设置接收超时时间。
    该选项最终将接收超时时间赋给sock->sk->sk_rcvtimeo。
    
16. SO_SNDTIMEO,设置发送超时时间。
    该选项最终将发送超时时间赋给sock->sk->sk_sndtimeo。
  
17. SO_BINDTODEVICE,将套接字绑定到一个特定的设备上。
    该选项最终将设备赋给sock->sk->sk_bound_dev_if。
 
18. SO_ATTACH_FILTER和SO_DETACH_FILTER。
    关于数据包过滤,它们最终会影响sk->sk_filter。

The above are some socket options at the SOL_SOCKET layer. If it exceeds this range, give some options that are not at this level as parameters, and finally get the return value of ENOPROTOOPT. But the above analysis is limited.

2. Detailed explanation of SO_REUSEADDR and SO_REUSEPORT parameters

2.1 SO_REUSEADDR:

The usage scenario of SO_REUSEADDR: when the server side calls the bind function

setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,(const void *)&reuse , sizeof(int));

Purpose: To ensure that the server can restart successfully when a link in the timewait state appears on the server.

Note: SO_REUSEADDR is only for the time-wait connection (the time-wait connection duration of the linux system is 1min), to ensure the success of the server restart. As for the article on the Internet, it says: If there is a socket bound to 0.0.0.0:port; set After this parameter, other sockets can bind the local ip:port. After testing, the "Address already in use" error was displayed, and the binding failed.

For example:
The server listens to port 9980. Since the connection is actively closed, a time-wait connection is generated:
insert image description here
if the server restarts at this time, and the server does not set the SO_REUSEADDR parameter, the server fails to restart and reports an error: "Address already in use"

If SO_REUSEADDR is set, restart ok;

2.2 SO_REUSEPORT:

SO_REUSEPORT usage scenario: linux kernel 3.9 introduces the latest SO_REUSEPORT option, which enables multiple processes or threads to create multiple listening sockets bound to the same ip:port, which improves the concurrency of the server's connection receiving capabilities and improves the scalability of the program; At this time, you need to set SO_REUSEPORT (note that all processes must be set to take effect).

setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT,(const void *)&reuse , sizeof(int));

Purpose: Each process has an independent listening socket, and bind the same ip:port, independent listen() and accept(); improve the ability to receive connections. (For example, nginx multi-process listens to the same ip:port at the same time)

Problems solved:
(1) Avoiding the "shocking herd effect" of application layer multi-threading or processes listening to the same ip:port.
(2) Load balancing is implemented at the kernel level to ensure that each process or thread receives a balanced number of connections.
(3) Only server processes with the same effective-user-id can listen to the same ip:port (security considerations)

2.3 Code example: server_128.c

#include <stdio.h>                                                                                                               
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <sys/wait.h>
void work () {
    
    
        int listenfd = socket(AF_INET, SOCK_STREAM, 0);
        if (listenfd < 0) {
    
    
                perror("listen socket");
                _exit(-1);
        }
        int ret = 0;
        int reuse = 1;
        ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,(const void *)&reuse , sizeof(int));
        if (ret < 0) {
    
    
                perror("setsockopt");
                _exit(-1);
        }
        ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT,(const void *)&reuse , sizeof(int));
        if (ret < 0) {
    
    
            perror("setsockopt");
            _exit(-1);
        }
        struct sockaddr_in addr;
        memset(&addr, 0, sizeof(addr));
        addr.sin_family = AF_INET;
        //addr.sin_addr.s_addr = inet_addr("10.95.118.221");
        addr.sin_addr.s_addr = inet_addr("0.0.0.0");                                                                             
        addr.sin_port = htons(9980);
        ret = bind(listenfd, (struct sockaddr *)&addr, sizeof(addr));
        if (ret < 0) {
    
    
                perror("bind addr");
                _exit(-1);
       }
        printf("bind success\n");
        ret = listen(listenfd,10);
        if (ret < 0) {
    
    
                perror("listen");
                _exit(-1);
        }
        printf("listen success\n");
        struct sockaddr clientaddr;
        int len = 0;
        while(1) {
    
    
                printf("process:%d accept...\n", getpid());
                int clientfd = accept(listenfd, (struct sockaddr*)&clientaddr, &len);
                if (clientfd < 0) {
    
    
                        printf("accept:%d %s", getpid(),strerror(errno));
                        _exit(-1);
                }
                close(clientfd);
                printf("process:%d close socket\n", getpid());
        }
}

int main(){
    
    
        printf("uid:%d euid:%d\n", getuid(),geteuid());
        int i = 0;
        for (i = 0; i< 6; i++) {
    
    
                pid_t pid = fork();
                if (pid == 0) {
    
    
                        work();
                }
                if(pid < 0) {
    
    
                        perror("fork");
                        continue;
                }
        }
        int status,id;
        while((id=waitpid(-1, &status, 0)) > 0) {
    
    
                printf("%d exit\n", id);
        }
        if(errno == ECHILD) {
    
    
                printf("all child exit\n");
        }
        return 0;
}         

The above sample program starts 6 child processes, each child process creates its own listening socket, binds the same ip:port; independent listen(), accept(); if each child process does not set the SO_REUSEADDR option, it will prompt "Address already in use" error.

Security: As long as the server program with the SO_REUSEPORT option is set, can it monitor the same ip:port and obtain messages? Of course not, the current Linux kernel requires that the server program started later must have the same effective-user-id as the server program started earlier.

For example:
insert image description here
the two server programs server_127 and server128 in the above picture are both set to SO_REUSEPORT, listen to the same ip:port, and use the root user to start the two server programs. Because the effective-user-id is equal, both are 0, so the startup does not work. question.

Modify the permissions of server127: chmod u+s server_127
insert image description here
After starting server_128, start server_127, prompting an error:
insert image description here

Guess you like

Origin blog.csdn.net/weixin_40209493/article/details/129497776