端口复用

多个进程绑定(bind)同一个端口,当客户断发起连接(connect)时,内核会通过一个hash算法决定分配到那个进程上。

Linux 4.5之前的reuseport查找实现(4.3内核)

以下是未优化前的Linux 4.3内核的实现,可见是多么地不直观。它采用了遍历HASH冲突链表的方式进行reuseport套接字的精确定位:
result = NULL;
badness = 0;
udp_portaddr_for_each_entry_rcu(sk, node, &hslot2->head) {
    score = compute_score2(sk, net, saddr, sport,
                  daddr, hnum, dif);
    if (score > badness) { // 冒泡排序
        // 找到了更加合适的socket,需要重新hash
        result = sk;
        badness = score;
        reuseport = sk->sk_reuseport;
        if (reuseport) {
            hash = udp_ehashfn(net, daddr, hnum,
                       saddr, sport);
            matches = 1;
        }
    } else if (score == badness && reuseport) { // reuseport套接字散列定位
        // 找到了同样reuseport的socket,进行定位
        matches++;
        if (reciprocal_scale(hash, matches) == 0)
            result = sk;
        hash = next_pseudo_random32(hash);
    }
}

  

Linux 4.5(针对UDP)/4.6(针对TCP)的reuseport查找实现

我们来看看在4.5和4.6内核中对于reuseport的查找增加了一些什么神奇的新东西:
result = NULL;
badness = 0;
udp_portaddr_for_each_entry_rcu(sk, node, &hslot2->head) {
    score = compute_score2(sk, net, saddr, sport,
                  daddr, hnum, dif);
    if (score > badness) {
        // 在reuseport情形下,意味着找到了更加合适的socket组,需要重新hash
        result = sk;
        badness = score;
        reuseport = sk->sk_reuseport;
        if (reuseport) {
            hash = udp_ehashfn(net, daddr, hnum,
                       saddr, sport);
            if (select_ok) {
                struct sock *sk2;
                // 找到了一个组,接着进行组内hash。
                sk2 = reuseport_select_sock(sk, hash, skb,
                        sizeof(struct udphdr));
                if (sk2) {
                    result = sk2;
                    select_ok = false;
                    goto found;
                }
            }
            matches = 1;
        }
    } else if (score == badness && reuseport) {
    // 这个else if分支的期待是,在分层查找不适用的时候,寻找更加匹配的reuseport组,注意4.5/4.6以后直接寻找的是一个reuseport组。
    // 在某种意义上,这回退到了4.5之前的算法。
        matches++;
        if (reciprocal_scale(hash, matches) == 0)
            result = sk;
        hash = next_pseudo_random32(hash);
    }
}

  

struct sock *reuseport_select_sock(struct sock *sk,
                   u32 hash,
                   struct sk_buff *skb,
                   int hdr_len)
{
    ...
    prog = rcu_dereference(reuse->prog);
    socks = READ_ONCE(reuse->num_socks);
    if (likely(socks)) {
        /* paired with smp_wmb() in reuseport_add_sock() */
        smp_rmb();  

        if (prog && skb) // 可以用BPF来从用户态注入自己的定位逻辑,更好实现基于策略的负载均衡
            sk2 = run_bpf(reuse, socks, prog, skb, hdr_len);
        else
            // reciprocal_scale简单地将结果限制在了[0,socks)这个区间内
            sk2 = reuse->socks[reciprocal_scale(hash, socks)];
    }
    ...
}

  单机上的 连接服务器 则可以用端口复用的方式实现负载均衡;也完美解决了nginx之前的惊群现象,也不需要像nginx后来的做法去避免惊群。

下面给出测试用的demo

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#define MAXLINE 100

void* thread_(void* agr)
{
	int listenfd,connfd;
	struct sockaddr_in servaddr;
	char buff[MAXLINE+1];
	time_t ticks;
	unsigned short port;
	int flag=1,len=sizeof(int);

	port=10013;
	if( (listenfd=socket(AF_INET,SOCK_STREAM,0)) == -1)
	{
		perror("socket");
		exit(1);
	}
	bzero(&servaddr,sizeof(servaddr));
	servaddr.sin_family=AF_INET;
	servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
	servaddr.sin_port=htons(port);
	//SO_REUSEPORT
	if( setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1)
	{
		perror("SO_REUSEADDR");
		exit(1);
	}
	if( setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, &flag, len) == -1)
	{
		perror("SO_REUSEPORT");
		exit(1);
	}
	if( bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) ==-1)
	{
		perror("bind");
		exit(1);
	}
	else
		printf("bind call OK!\n");
	if( listen(listenfd,5) == -1)
	{
		perror("listen");
		exit(1);
	}
	char buf[] = "hello world.";
	for(;;)	{
		if( (connfd=accept(listenfd,(struct sockaddr*)NULL,NULL)) == -1)
		{
			perror("accept");
			exit(1);
		}
		send(connfd,buf,sizeof(buf),0);
		close(connfd);
		printf("pid %d : once\n",pthread_self());
	}
}
int main(int argc, char** argv)
{

	pthread_t pt;
	if( 0!=pthread_create(&pt,NULL,thread_,(void*)0) )
	{
		perror("pthread_create");
	}
	thread_((void*)1);
	return 0;
}

  

时间: 2024-10-10 00:06:27

端口复用的相关文章

Linux网络编程——端口复用(多个套接字绑定同一个端口)

在<绑定( bind )端口需要注意的问题>提到:一个网络应用程序只能绑定一个端口( 一个套接字只能绑定一个端口 ). 实际上,默认的情况下,如果一个网络应用程序的一个套接字 绑定了一个端口( 占用了 8000 ),这时候,别的套接字就无法使用这个端口( 8000 ), 验证例子如下: [objc] view plaincopy #include <stdio.h> #include <stdlib.h> #include <string.h> #inclu

TCP套接字端口复用SO_REUSEADDR

下面建立的套接字都是tcp套接字 1.进程创建监听套接字socket1,邦定一个指定端口,并接受了若干连接.那么进程创建另外一个套接口socket2,并试图邦定同一个端口时候,bind错误返回“Address already in use”(即使使用了SO_REUSEADDR). 2.进程创建监听套接字,邦定一个指定端口,并接受了若干连接,为每个连接创建子进程为连接服务.杀死监听套接字所在进程,然后重新启动.重新启动的进程调用bind重新建立监听套接字.这次邦定只有在bind前指定了SO_REU

Web端口复用正向后门研究实现与防御

0×01背景 现在的很多远控/后门因为目前主流防火墙规则的限制,基本上都采用TCP/UDP反弹回连的通讯形式:但是在较高安全环境下,尤其负责web相关业务的环境,因为安防设备(防火墙,IDS,IPS等)规则的严格限制,TCP/UDP(HTTP/HTTPS/DNS)甚至ICMP等隧道都不能很轻易从内网访问Internet,只接受外部的请求.在这种场景下,攻击者在拿到了webshell的前提下,考虑植入除webshell以外的后门就需要考虑如何来绕过防火墙等安防设备的限制了. 实际上关于端口复用这一

Netscaler的超高端口复用助力应对公网地址紧张

经常会有人问一个IP只有65535(姑且不考虑预留端口),从Big-ip迁移到Netscaler后需不需要增加Ip地址数量来应对大吞吐的场景,尤其是链路负载均衡的应用? 众所周知,Netscaler对比Big-ip具有明显的节约地址的特点,基本上Netscaler可以用最少2个公网地址(管理地址为私网地址)就可以提供外网服务(NAT在Netscaler上而非防火墙),而Big-ip至少需要4个公网地址. 在Big-ip的环境下大家会预留很多Ip地址来应对端口不够用的情况,但如果在Netscale

Linux下的socket编程实践(三)端口复用和 P2P多进程服务器

Socket端口复用 先说为什么要使用socket端口复用?如果你遇到过这样的问题:server程序重启之后,无法连接,需要过一段时间才能连接上? 1.一个监听(listen)server已经启动 2.当有client有连接请求的时候,server产生一个子进程去处理该client的事物. 3.server主进程终止了,但是子进程还在占用该连接处理client的事情.虽然子进程终止了,但是由于子进程没有终止,该socket的引用计数不会为0,所以该socket不会被关闭. 4.server程序重

【 socke】C# socket端口复用-多主机头绑定

什么是端口复用: 因为在winsock的实现中,对于服务器的绑定是可以多重绑定的,在确定多重绑定使用谁的时候,根据一条原则是谁的指定最明确则将包递交给谁,而且没有权限之分.这种多重绑定便称之为端口复用. 二.我们如何实现Socket端口复用: 其实我们要实现端口复用很简单,我们只要使用SetSocketOption函数设置Socket选项就可以了.MSDN是这样解释的: Socket 选项确定当前 Socket 的行为.对于具有 Boolean 数据类型的选项,指定非零值可启用该选项,指定零值可

木马控制技术(二) -- 端口复用

本文转自:http://www.cnblogs.com/handt/archive/2012/08/10/2631728.html 端口复用,字面意思就是,重复使用一个端口 记得有一次同学的 sina 微博总是自动发消息,当时让他查看本地端口,再通过端口找到对应程序 学了端口复用和线程注入之后,发现这种方法对“高级点”的木马完全没用…… 还是需要一些强大的 HIP 工具来检查 回到正题 端口复用,使用到了一个重要的函数: intsetsockopt( __in SOCKET s, __in in

Linux网络编程:端口复用

在<绑定( bind )端口需要注意的问题>提到:一个网络应用程序只能绑定一个端口( 一个套接字只能绑定一个端口 ). 实际上,默认的情况下,如果一个网络应用程序的一个套接字 绑定了一个端口( 占用了 8000 ),这时候,别的套接字就无法使用这个端口( 8000 ), 验证例子如下: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #i

企业边界网络设备的一般配置:ACL、端口复用(PAT)、端口映射

一.概述: 企业的边界网络设备一般是路由器或者多层交换机,主要实现的功能如下:(1)实现内网部分设备访问外网:(2)客户从公网访问企业内网的Web服务器等:(3)运维人员从外网访问企业内部的部分设备进行远程维护.其中第一项功能需求通过ACL和端口复用(PAT)技术实现,第二.三项功能需求通过端口映射技术实现. 本文结合拓扑图讲述上述几项功能的实现技术及具体配置. 二.拓扑图说明: 如上图所示,绿色背景部分为企业内部网络环境(COMPANY-Network),蓝色背景部分为运营商网络环境(ISP-