免责声明
本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者及本公众号不为此承担任何责任。
TCP防火墙
BCC作为一个流行的ebpf开发方案,提供了很多案例供开发者学习,其中/examples/networking/tcp_mon_block/是BCC官方提供的TCP防火墙的案例。过滤TCP请求对于入侵检测和防火墙很有帮助,值得学习。
内核态初始化
- 首先定义必要的结构体,包括:完整的网络数据包信息、输出事件、连接关系
typedef
struct
{
u32
src_ip;
u16
src_port;
u32
dst_ip;
u16
dst_port;
u32
pid;
u8
tcp_flags;
char
comm[TASK_COMM_LEN];
full_packet;
typedef
struct
{
u8
state;
u32
src_ip;
u16
src_port;
u32
dst_ip;
u16
dst_port;
u32
pid;
char
comm[TASK_COMM_LEN];
verbose_event;
typedef
struct
{
u32
src_ip;
u16
src_port;
u32
dst_ip;
u16
dst_port;
key_hash;
2. 定义必要的散列表和性能事件输出:
BPF_HASH(monitored_connections, key_hash, full_packet);
BPF_HASH(allow_list, u32, u32);
BPF_HASH(pid_list, u32, u32);
BPF_PERF_OUTPUT(blocked_events);
BPF_PERF_OUTPUT(verbose_events);
3. 定义检查TCP头是否越界的函数:
static
__
always_inline
int
tcp_header_bound_check
(
struct
tcphdr* tcp,
void
* data_end
)
{
if
((
void
*)tcp +
sizeof
(*tcp) > data_end)
{
return
-1
;
}
return
0
;
}
4. 定义构建详细事件的函数:
static
void make_verbose_event(verbose_event *v, u32 src_ip, u32 dst_ip, u16 src_port, u16 dst_port, u32 pid, u8 state)
{
v->src_ip = src_ip;
v->src_port = src_port;
v->dst_ip = dst_ip;
v->dst_port = dst_port;
v->pid = pid;
v->state = state;
bpf_get_current_comm(&v->comm, sizeof(v->comm));
}
检测出站流量
1. 定义检测网卡出站数据包流量,首先定义变量:
int
handle_egress
(struct __sk_buff *ctx)
{
void
* data_end = (
void
*)(
long
)ctx->data_end;
void
* data = (
void
*)(
long
)ctx->data;
struct
ethhdr
*
eth
=
data
;
struct
iphdr
*
ip
=
data
+
sizeof
(*
eth
);
struct
tcphdr
*
tcp
;
key_hash key = {};
其中:
-
data_end:代表网络数据包的边界,用于确保访问数据包内容时不越界。 -
data:捕获到的以太网帧。 -
eth:指向以太网头部的结构体指针。 -
ip:指向IP头部的结构体指针,它紧跟在以太网头后面。 -
tcp:用于稍后存储指向TCP头部的指针。 -
key:类型为 key_hash 的变量,用于作为查找监控连接散列表的键。
2. 检查数据包长度是否正确:
/* length check */
if
(data +
sizeof
(*eth) +
sizeof
(*ip) > data_end)
{
return
TC_ACT_OK;
}
3. 接下来检查是否为TCP协议:
if
(eth->h_proto != htons(ETH_P_IP))
{
return
TC_ACT_OK;
}
if
(ip->protocol != IPPROTO_TCP)
{
return
TC_ACT_OK;
}
4. 然后可以提取出源和目的的ip和port:
tcp = (void *)ip + sizeof(*ip);
if
(tcp_header_bound_check(tcp, data_end))
{
return
TC_ACT_OK;
}
u8 tcpflags = ((u_int8_t *)tcp)[
13
];
u16 src_port = bpf_ntohs(tcp->source);
u16 dst_port = bpf_ntohs(tcp->dest);
key.src_ip = ip->saddr;
key.src_port = src_port;
key.dst_ip = ip->daddr;
key.dst_port = dst_port;
5. 使用前面创建的key在monitored_connections散列表中查找。如果找到对应条目,说明此连接被监控:
full_packet
*packet_value;
packet_value
= monitored_connections.lookup(&key);
6. 如果找到相应条目,即packet_value不为0,则更新条目内的TCP标志,并使用blocked_events.perf_submit()函数向用户空间发送数据包相关信息的事件。随后,返回 TC_ACT_SHOT 告诉内核丢弃该数据包:
if
(packet_value !=
0
)
{
packet_value->tcp_flags = tcpflags;
blocked_events.perf_submit(ctx, packet_value,
sizeof
(full_packet));
return
TC_ACT_SHOT;
}
(未完待续)
原文始发于微信公众号(赛博安全狗):【eBPF】BCC实现tcp防火墙
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论