SO_ATTACH_REUSEPORT_CBPF 套接字选项意外行为
SO_ATTACH_REUSEPORT_CBPF socket option unexpected behavior
我正在尝试使用来自两个应用程序的端口,并让每个应用程序从一组不同的 IP 地址接收数据包。为了实现这一点,我使用了 SO_REUSEPORT 和 SO_ATTACH_REUSEPORT_CBPF 套接字选项。我的代码如下:
parentfd = socket(AF_INET, SOCK_STREAM, 0);
if (parentfd < 0)
error( "ERROR opening socket");
struct sock_filter code[]={
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 3, 0x00000800 },
{ 0x20, 0, 0, 0x0000001a },
{ 0x15, 2, 0, 0xc0a8ff01 },
{ 0x6, 0, 0, 0x00000000 },
{ 0x6, 0, 0, 0x00040000 },
{ 0x6, 0, 0, 0x00000001 },
};
struct sock_fprog bpf = {
.len = ARRAY_SIZE(code),
.filter = code,
};
if (setsockopt(parentfd, SOL_SOCKET, SO_REUSEPORT, (const void *)&optval,sizeof(optval)))
error("ERROR setting SO_REUSEPORT");
if (setsockopt(parentfd, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, (const void *)&bpf, sizeof(bpf)))
error("ERROR setting SO_ATTACH_REUSEPORT_CBPF);
我还有一个不同的进程,它使用 仅 SO_REUSEPORT 标志侦听同一端口。来自一台 IP 地址为 192.168.255.1
的机器,我是 运行 echo 1234 | ncat 192.168.255.150 1234
。根据我的过滤器,我希望第二个进程接收来自该 IP 地址的所有流量。然而,这一切都被第一个收到了。当我将过滤器更改为简单时:
struct sock_filter code[]={
{ 0x6, 0, 0, 0x00000001 },
};
它按预期工作,所有数据包都被第二个进程接收。知道为什么会这样吗?
无效代码是:
l0: ldh [12] /* read EtherType (2 bytes), which is found at offset 12 (decimal) */
l1: jeq #0x800, l2, l5 /* if EtherType == `0x800` (IPv4), jump to `l2`, otherwise jump to `l5` */
l2: ld [26] /* read source IP address (4 bytes) */
l3: jeq #0xc0a8ff01, l6, l4 /* if source IP address == 192.168.255.1, jump to l6 (return 1), else jump to l4 (return 0) */
l4: ret #0
l5: ret #0x40000
l6: ret #0x1
工作代码是:
ret #0x1
socket (7)
说:
The BPF program must return an index between 0 and N-1 representing the socket which should receive the packet (where N is the number of sockets in the group). If the BPF program returns an invalid index, socket selection will fall back to the plain SO_REUSEPORT mechanism.
在我的机器上 tcpdump -i lo -ddd 'src host 192.168.255.1'
产生
10
40 0 0 12
21 0 2 2048
32 0 0 26
21 4 5 3232300801
21 1 0 2054
21 0 3 32821
32 0 0 28
21 0 1 3232300801
6 0 0 262144
6 0 0 0
这是
l0: ldh [12]
l1: jeq #0x800, l2, l4
l2: ld [26]
l3: jeq #0xc0a8ff01, l8, l9
l4: jeq #0x806, l6, l5
l5: jeq #0x8035, l6, l9
l6: ld [28]
l7: jeq #0xc0a8ff01, l8, l9
l8: ret #0x40000
l9: ret #0
我没有发现您的代码有任何明显的错误。
您是否在服务器上尝试过 运行 tcpdump?也许您忘记删除客户端上的额外 IP 地址,或者某处忘记了 SNAT 规则?
你是哪个内核版本运行?你能 post 一个最小的 C 应用程序来重现这个问题吗?
我发现了问题所在。过滤器适用于所有数据包,甚至是 TCP 握手数据包。此外,基指针指向数据包有效负载的第一个字节,而不是 headers。因此,当它执行时
ldh[12]
它超出了数据包的限制(SYN 数据包的有效负载为 0 字节),默认行为是 return 0。
我正在尝试使用来自两个应用程序的端口,并让每个应用程序从一组不同的 IP 地址接收数据包。为了实现这一点,我使用了 SO_REUSEPORT 和 SO_ATTACH_REUSEPORT_CBPF 套接字选项。我的代码如下:
parentfd = socket(AF_INET, SOCK_STREAM, 0);
if (parentfd < 0)
error( "ERROR opening socket");
struct sock_filter code[]={
{ 0x28, 0, 0, 0x0000000c },
{ 0x15, 0, 3, 0x00000800 },
{ 0x20, 0, 0, 0x0000001a },
{ 0x15, 2, 0, 0xc0a8ff01 },
{ 0x6, 0, 0, 0x00000000 },
{ 0x6, 0, 0, 0x00040000 },
{ 0x6, 0, 0, 0x00000001 },
};
struct sock_fprog bpf = {
.len = ARRAY_SIZE(code),
.filter = code,
};
if (setsockopt(parentfd, SOL_SOCKET, SO_REUSEPORT, (const void *)&optval,sizeof(optval)))
error("ERROR setting SO_REUSEPORT");
if (setsockopt(parentfd, SOL_SOCKET, SO_ATTACH_REUSEPORT_CBPF, (const void *)&bpf, sizeof(bpf)))
error("ERROR setting SO_ATTACH_REUSEPORT_CBPF);
我还有一个不同的进程,它使用 仅 SO_REUSEPORT 标志侦听同一端口。来自一台 IP 地址为 192.168.255.1
的机器,我是 运行 echo 1234 | ncat 192.168.255.150 1234
。根据我的过滤器,我希望第二个进程接收来自该 IP 地址的所有流量。然而,这一切都被第一个收到了。当我将过滤器更改为简单时:
struct sock_filter code[]={
{ 0x6, 0, 0, 0x00000001 },
};
它按预期工作,所有数据包都被第二个进程接收。知道为什么会这样吗?
无效代码是:
l0: ldh [12] /* read EtherType (2 bytes), which is found at offset 12 (decimal) */
l1: jeq #0x800, l2, l5 /* if EtherType == `0x800` (IPv4), jump to `l2`, otherwise jump to `l5` */
l2: ld [26] /* read source IP address (4 bytes) */
l3: jeq #0xc0a8ff01, l6, l4 /* if source IP address == 192.168.255.1, jump to l6 (return 1), else jump to l4 (return 0) */
l4: ret #0
l5: ret #0x40000
l6: ret #0x1
工作代码是:
ret #0x1
socket (7)
说:
The BPF program must return an index between 0 and N-1 representing the socket which should receive the packet (where N is the number of sockets in the group). If the BPF program returns an invalid index, socket selection will fall back to the plain SO_REUSEPORT mechanism.
在我的机器上 tcpdump -i lo -ddd 'src host 192.168.255.1'
产生
10
40 0 0 12
21 0 2 2048
32 0 0 26
21 4 5 3232300801
21 1 0 2054
21 0 3 32821
32 0 0 28
21 0 1 3232300801
6 0 0 262144
6 0 0 0
这是
l0: ldh [12]
l1: jeq #0x800, l2, l4
l2: ld [26]
l3: jeq #0xc0a8ff01, l8, l9
l4: jeq #0x806, l6, l5
l5: jeq #0x8035, l6, l9
l6: ld [28]
l7: jeq #0xc0a8ff01, l8, l9
l8: ret #0x40000
l9: ret #0
我没有发现您的代码有任何明显的错误。
您是否在服务器上尝试过 运行 tcpdump?也许您忘记删除客户端上的额外 IP 地址,或者某处忘记了 SNAT 规则?
你是哪个内核版本运行?你能 post 一个最小的 C 应用程序来重现这个问题吗?
我发现了问题所在。过滤器适用于所有数据包,甚至是 TCP 握手数据包。此外,基指针指向数据包有效负载的第一个字节,而不是 headers。因此,当它执行时
ldh[12]
它超出了数据包的限制(SYN 数据包的有效负载为 0 字节),默认行为是 return 0。