あるポートから別のポートに着信TC BPF
トラフィックをリダイレクトするために使用したいと思います。以下は私のコードですが、次の例も試しました。80
8080
人8tc-bpf(検索8080
)、同じ結果が表示されます。
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#include <linux/pkt_cls.h>
#include <linux/if_ether.h>
#include <linux/tcp.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/filter.h>
static inline void set_tcp_dport(struct __sk_buff *skb, int nh_off,
__u16 old_port, __u16 new_port)
{
bpf_l4_csum_replace(skb, nh_off + offsetof(struct tcphdr, check),
old_port, new_port, sizeof(new_port));
bpf_skb_store_bytes(skb, nh_off + offsetof(struct tcphdr, dest),
&new_port, sizeof(new_port), 0);
}
SEC("tc_my")
int tc_bpf_my(struct __sk_buff *skb)
{
struct iphdr ip;
struct tcphdr tcp;
if (0 != bpf_skb_load_bytes(skb, sizeof(struct ethhdr), &ip, sizeof(struct iphdr))) {
bpf_printk("bpf_skb_load_bytes iph failed");
return TC_ACT_OK;
}
if (0 != bpf_skb_load_bytes(skb, sizeof(struct ethhdr) + (ip.ihl << 2), &tcp, sizeof(struct tcphdr))) {
bpf_printk("bpf_skb_load_bytes ethh failed");
return TC_ACT_OK;
}
unsigned int src_port = bpf_ntohs(tcp.source);
unsigned int dst_port = bpf_ntohs(tcp.dest);
if (src_port == 80 || dst_port == 80 || src_port == 8080 || dst_port == 8080)
bpf_printk("%pI4:%u -> %pI4:%u", &ip.saddr, src_port, &ip.daddr, dst_port);
if (dst_port != 80)
return TC_ACT_OK;
set_tcp_dport(skb, ETH_HLEN + sizeof(struct iphdr), __constant_htons(80), __constant_htons(8080));
return TC_ACT_OK;
}
char LICENSE[] SEC("license") = "GPL";
マシンAでは、以下を実行しています。
clang -g -O2 -Wall -target bpf -c tc_my.c -o tc_my.o
tc qdisc add dev ens160 clsact
tc filter add dev ens160 ingress bpf da obj tc_my.o sec tc_my
nc -l 8080
マシンBから:
nc $IP_A 80
マシンBではnc
接続されているように見えますが、次のようにss
表示されます。
SYN-SENT 0 1 $IP_B:53442 $IP_A:80 users:(("nc",pid=30180,fd=3))
SYN-RECV
マシンAでは、接続が切断されるまで接続は維持されます。
私のプログラムは、次の規則を追加したように動作すると予想されますiptables
。
iptables -t nat -A PREROUTING -p tcp -m tcp --dport 80 -j REDIRECT --to-port 8080
私の期待が間違っているかもしれませんが、その理由を知りたいです。リダイレクトを機能させるにはどうすればよいですかTC BPF
?
解決策
私が受け入れた答えで説明したように、以下は受信NAT 90-> 8080と送信de-NAT 8080 -> 90を実行するTCP用のサンプルコードです。
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#include <linux/pkt_cls.h>
#include <linux/if_ether.h>
#include <linux/tcp.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/filter.h>
static inline void set_tcp_dport(struct __sk_buff *skb, int nh_off,
__u16 old_port, __u16 new_port)
{
bpf_l4_csum_replace(skb, nh_off + offsetof(struct tcphdr, check),
old_port, new_port, sizeof(new_port));
bpf_skb_store_bytes(skb, nh_off + offsetof(struct tcphdr, dest),
&new_port, sizeof(new_port), 0);
}
static inline void set_tcp_sport(struct __sk_buff *skb, int nh_off,
__u16 old_port, __u16 new_port)
{
bpf_l4_csum_replace(skb, nh_off + offsetof(struct tcphdr, check),
old_port, new_port, sizeof(new_port));
bpf_skb_store_bytes(skb, nh_off + offsetof(struct tcphdr, source),
&new_port, sizeof(new_port), 0);
}
SEC("tc_ingress")
int tc_ingress_(struct __sk_buff *skb)
{
struct iphdr ip;
struct tcphdr tcp;
if (0 != bpf_skb_load_bytes(skb, sizeof(struct ethhdr), &ip, sizeof(struct iphdr)))
{
bpf_printk("bpf_skb_load_bytes iph failed");
return TC_ACT_OK;
}
if (0 != bpf_skb_load_bytes(skb, sizeof(struct ethhdr) + (ip.ihl << 2), &tcp, sizeof(struct tcphdr)))
{
bpf_printk("bpf_skb_load_bytes ethh failed");
return TC_ACT_OK;
}
unsigned int src_port = bpf_ntohs(tcp.source);
unsigned int dst_port = bpf_ntohs(tcp.dest);
if (src_port == 90 || dst_port == 90 || src_port == 8080 || dst_port == 8080)
bpf_printk("INGRESS %pI4:%u -> %pI4:%u", &ip.saddr, src_port, &ip.daddr, dst_port);
if (dst_port != 90)
return TC_ACT_OK;
set_tcp_dport(skb, ETH_HLEN + sizeof(struct iphdr), __constant_htons(90), __constant_htons(8080));
return TC_ACT_OK;
}
SEC("tc_egress")
int tc_egress_(struct __sk_buff *skb)
{
struct iphdr ip;
struct tcphdr tcp;
if (0 != bpf_skb_load_bytes(skb, sizeof(struct ethhdr), &ip, sizeof(struct iphdr)))
{
bpf_printk("bpf_skb_load_bytes iph failed");
return TC_ACT_OK;
}
if (0 != bpf_skb_load_bytes(skb, sizeof(struct ethhdr) + (ip.ihl << 2), &tcp, sizeof(struct tcphdr)))
{
bpf_printk("bpf_skb_load_bytes ethh failed");
return TC_ACT_OK;
}
unsigned int src_port = bpf_ntohs(tcp.source);
unsigned int dst_port = bpf_ntohs(tcp.dest);
if (src_port == 90 || dst_port == 90 || src_port == 8080 || dst_port == 8080)
bpf_printk("EGRESS %pI4:%u -> %pI4:%u", &ip.saddr, src_port, &ip.daddr, dst_port);
if (src_port != 8080)
return TC_ACT_OK;
set_tcp_sport(skb, ETH_HLEN + sizeof(struct iphdr), __constant_htons(8080), __constant_htons(90));
return TC_ACT_OK;
}
char LICENSE[] SEC("license") = "GPL";
私のプログラムでさまざまな部分をビルドしてロードする方法は次のとおりです。
clang -g -O2 -Wall -target bpf -c tc_my.c -o tc_my.o
tc filter add dev ens32 ingress bpf da obj /tc_my.o sec tc_ingress
tc filter add dev ens32 egress bpf da obj /tc_my.o sec tc_egress
答え1
Netfilterとは異なり、Netfilterには以下が含まれます。状態を保存NATエンジン(使用つながるルックアップエントリ)明示的なルールなしで応答トラフィックを自動的にde-NATし、他の場所にNATを実装します。はい無国籍そして、処理すべき2つの方向があります。着信接続の場合、これはNATが次に処理されることを意味します。入り口 だけでなくde-NAT処理出口明らかに。
走りながら目撃したようにTCPダンプクライアント側から:
# tcpdump -ttt -l -n -s0 -p -i lxcbr0 tcp
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on lxcbr0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
00:00:00.000000 IP 10.0.3.1.52542 > 10.0.3.214.80: Flags [S], seq 3033230443, win 64240, options [mss 1460,sackOK,TS val 2154801903 ecr 0,nop,wscale 7], length 0
00:00:00.000058 IP 10.0.3.214.8080 > 10.0.3.1.52542: Flags [S.], seq 1400064141, ack 3033230444, win 65160, options [mss 1460,sackOK,TS val 3949758745 ecr 2154801903,nop,wscale 7], length 0
00:00:00.000013 IP 10.0.3.1.52542 > 10.0.3.214.8080: Flags [R], seq 3033230444, win 0, length 0
現在、eBPFコードは最初の部分だけを完成しています。したがって、ポート80に入るTCPパケットは実際にはネットワークスタックの他の部分がそれを知る前にポート8080に切り替わりますが、応答トラフィックはポート8080からのみ送信されます(すべてのポート80情報はeBPFコードの後に失われます))、クライアントはポート80からの回答も楽しみです。クライアントのカーネルはTCP RSTで応答し、クライアントは再試行しますが、同じ結果(接続なし)が表示されます。
同等の逆変換を実行する必要があります。出口。これらすべてがそうであるように無国籍つまり、これが完了すると、同じ理由でポート8080に直接接続できなくなります。これで同じ効果が得られます。これで、ポート 8080 への接続はポート 80 を使用して戻されます。
これとは対照的に、UDP に同じ設定を適用すると、UDP はトラフィックを受信したときに何も送信しなくて済むため、着信トラフィックにのみ適用されます。ただし、ICMPエラーを再送信すること(つまり、サーバーが受信しなくなったことをクライアントに通知する信号)は失敗します。 eBPF コードが UDP の異なる方向に対して実行されていても、ICMP エラーには、まだ UDP ペイロードの一部に無効な UDP ポートが含まれています。 NetfilterのNATもこの問題を解決できます。