-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 3d5ff3d
Showing
6 changed files
with
1,354 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.zig-cache/ | ||
zig-out/ |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
# ZealFirewall | ||
|
||
Linux firewall powered by eBPF and XDP written in Zig. | ||
|
||
# Requirements | ||
* zig-linux-x86_64-0.14.0-dev.3456+00a8742bb | ||
* Linux Kernel 6.6+ | ||
|
||
# Support feature | ||
|
||
* IPv4 | ||
* IPv6 | ||
* TCP | ||
* UDP | ||
* ICMP | ||
|
||
# Usage | ||
|
||
Clone zbpf. | ||
|
||
Put this repo in zbpf. | ||
|
||
Add dependencies in `build.zig.zon` and `build.zig`. | ||
|
||
```shell | ||
zig build ZealFirewall | ||
# change lo if you need | ||
sudo ./ZealFirewall -i lo | ||
``` | ||
|
||
# API | ||
|
||
## Get all rules | ||
|
||
GET /api/v1/rules | ||
|
||
## Add rule | ||
|
||
POST /api/v1/rule | ||
|
||
```json | ||
{ | ||
"src_addr": "127.0.0.1", | ||
"dst_port": 8000, | ||
"ip_proto": 6 | ||
} | ||
``` | ||
|
||
> Proto 6 is TCP | ||
```json | ||
{ | ||
"src_addr": "127.0.0.1", | ||
"dst_port": 8000, | ||
"ip_proto": 17 | ||
} | ||
``` | ||
|
||
> Proto 17 is UDP | ||
## Get rule | ||
|
||
GET /api/v1/rule | ||
|
||
```json | ||
{ | ||
"src_addr": "127.0.0.1", | ||
"dst_port": 8000, | ||
"ip_proto": 6 | ||
} | ||
``` | ||
|
||
## Delete rule | ||
|
||
DELETE /api/v1/rule | ||
|
||
```json | ||
{ | ||
"src_addr": "127.0.0.1", | ||
"dst_port": 8000, | ||
"ip_proto": 6 | ||
} | ||
``` | ||
|
||
# Reference | ||
|
||
[EtherType](https://zh.wikipedia.org/wiki/%E4%BB%A5%E5%A4%AA%E7%B1%BB%E5%9E%8B) | ||
|
||
[IPv4](https://zh.wikipedia.org/wiki/IPv4) | ||
|
||
[IPv6](https://zh.wikipedia.org/wiki/IPv6) | ||
|
||
[IP protocol numbers](https://zh.wikipedia.org/wiki/IP%E5%8D%8F%E8%AE%AE%E5%8F%B7%E5%88%97%E8%A1%A8) | ||
|
||
[TCP](https://zh.wikipedia.org/wiki/%E4%BC%A0%E8%BE%93%E6%8E%A7%E5%88%B6%E5%8D%8F%E8%AE%AE) | ||
|
||
[UDP](https://zh.wikipedia.org/wiki/%E7%94%A8%E6%88%B7%E6%95%B0%E6%8D%AE%E6%8A%A5%E5%8D%8F%E8%AE%AE) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
const __u8 = u8; | ||
const __be16 = u16; | ||
const __be32 = u32; | ||
const __sum16 = u16; | ||
const __u16 = u16; | ||
|
||
pub const In6Addr = extern struct { | ||
in6_u: extern union { | ||
u6_addr8: [16]__u8, | ||
u6_addr16: [8]__be16, | ||
u6_addr32: [4]__be32, | ||
}, | ||
}; | ||
|
||
pub const AclKey = struct { | ||
ip_src_addr: In6Addr, | ||
dst_port: u16, | ||
ip_proto: u8, | ||
reserved: u8, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
const std = @import("std"); | ||
|
||
const bpf = @import("bpf"); | ||
const Xdp = bpf.Xdp; | ||
const BPF = std.os.linux.BPF; | ||
const helpers = BPF.kern.helpers; | ||
const trace_printk = std.os.linux.BPF.kern.helpers.trace_printk; | ||
|
||
const __u8 = u8; | ||
const __be16 = u16; | ||
const __be32 = u32; | ||
const __sum16 = u16; | ||
const __u16 = u16; | ||
|
||
const EthHdr = extern struct { | ||
dest: [6]u8, | ||
src: [6]u8, | ||
proto: u16, | ||
}; | ||
|
||
const IPv4Hdr = extern struct { | ||
ver_ihl: u8, | ||
tos: u8, | ||
tot_len: u16, | ||
id: u16, | ||
frag_off: u16, | ||
ttl: u8, | ||
proto: u8, | ||
check: u16, | ||
src: u32, | ||
dst: u32, | ||
}; | ||
|
||
const IPv6Hdr = extern struct { | ||
flow: u32, | ||
plen: u16, | ||
nxt: u8, | ||
hlim: u8, | ||
src: [16]u8, | ||
dst: [16]u8, | ||
}; | ||
|
||
const TcpHdr = packed struct { | ||
source: __be16, | ||
dest: __be16, | ||
seq: __be32, | ||
ack_seq: __be32, | ||
res1: u4, | ||
doff: u4, | ||
fin: u1, | ||
syn: u1, | ||
rst: u1, | ||
psh: u1, | ||
ack: u1, | ||
urg: u1, | ||
ece: u1, | ||
cwr: u1, | ||
window: __be16, | ||
check: __sum16, | ||
urg_ptr: __be16, | ||
}; | ||
|
||
const UdpHdr = extern struct { | ||
source: __be16, | ||
dest: __be16, | ||
len: __be16, | ||
check: __sum16, | ||
}; | ||
|
||
const In6Addr = extern struct { | ||
in6_u: extern union { | ||
u6_addr8: [16]__u8, | ||
u6_addr16: [8]__be16, | ||
u6_addr32: [4]__be32, | ||
}, | ||
}; | ||
|
||
const AclKey = struct { | ||
ip_src_addr: In6Addr, | ||
dst_port: u16, | ||
ip_proto: u8, | ||
reserved: u8, | ||
}; | ||
|
||
var acl_map = bpf.Map.HashMap("acl_map", AclKey, u64, 64, 0).init(); | ||
|
||
export fn firewall(ctx: *Xdp.Meta) linksection("xdp") c_int { | ||
const eth_hdr: *const EthHdr = ctx.get_ptr(EthHdr, 0) orelse return @intFromEnum(Xdp.RET.drop); | ||
|
||
const proto_ip4 = 0x0800; | ||
const proto_ip6 = 0x86DD; | ||
|
||
const ret = switch (eth_hdr.proto) { | ||
std.mem.nativeTo(u16, proto_ip4, .big) => handle_ipv4(ctx), | ||
std.mem.nativeTo(u16, proto_ip6, .big) => handle_ipv6(ctx), | ||
else => { | ||
return @intFromEnum(Xdp.RET.aborted); | ||
}, | ||
}; | ||
return ret; | ||
} | ||
|
||
fn handle_ipv4(ctx: *Xdp.Meta) c_int { | ||
const iphdr_offset = @sizeOf(EthHdr); | ||
const ipv4hdr: *const IPv4Hdr = ctx.get_ptr(IPv4Hdr, iphdr_offset) orelse return @intFromEnum(Xdp.RET.aborted); | ||
|
||
const IPPROTO_ICMP = 1; | ||
const IPPROTO_TCP = 6; | ||
const IPPROTO_UDP = 17; | ||
if (ipv4hdr.proto == IPPROTO_ICMP) { | ||
return @intFromEnum(Xdp.RET.pass); | ||
} else if (ipv4hdr.proto == IPPROTO_TCP) { | ||
const tcpHdr: *const TcpHdr = ctx.get_ptr(TcpHdr, iphdr_offset + (ipv4hdr.ver_ihl * 4)) orelse return @intFromEnum(Xdp.RET.aborted); | ||
|
||
if (!(tcpHdr.syn == 1 and tcpHdr.ack == 0)) { | ||
return @intFromEnum(Xdp.RET.pass); | ||
} | ||
|
||
const ipv4addr = u32_to_bytes(ipv4hdr.src); | ||
|
||
const ipv6addr = In6Addr{ | ||
.in6_u = .{ | ||
.u6_addr8 = [16]__u8{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, ipv4addr[0], ipv4addr[1], ipv4addr[2], ipv4addr[3] }, | ||
}, | ||
}; | ||
|
||
const acl_key = AclKey{ | ||
.ip_src_addr = ipv6addr, | ||
.ip_proto = ipv4hdr.proto, | ||
.dst_port = tcpHdr.dest, | ||
.reserved = 0, | ||
}; | ||
|
||
const acl_val = acl_map.lookup(acl_key); | ||
if (acl_val) |val| { | ||
val.* += 1; | ||
return @intFromEnum(Xdp.RET.pass); | ||
} else { | ||
return @intFromEnum(Xdp.RET.drop); | ||
} | ||
} else if (ipv4hdr.proto == IPPROTO_UDP) { | ||
const udpHdr: *const UdpHdr = ctx.get_ptr(UdpHdr, iphdr_offset + (ipv4hdr.ver_ihl * 4)) orelse return @intFromEnum(Xdp.RET.aborted); | ||
const ipv4addr = u32_to_bytes(ipv4hdr.src); | ||
const ipv6addr = In6Addr{ | ||
.in6_u = .{ | ||
.u6_addr8 = [16]__u8{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, ipv4addr[0], ipv4addr[1], ipv4addr[2], ipv4addr[3] }, | ||
}, | ||
}; | ||
|
||
const acl_key = AclKey{ | ||
.ip_src_addr = ipv6addr, | ||
.ip_proto = ipv4hdr.proto, | ||
.dst_port = udpHdr.dest, | ||
.reserved = 0, | ||
}; | ||
|
||
const acl_val = acl_map.lookup(acl_key); | ||
if (acl_val) |val| { | ||
val.* += 1; | ||
return @intFromEnum(Xdp.RET.pass); | ||
} else { | ||
return @intFromEnum(Xdp.RET.drop); | ||
} | ||
} else { | ||
return @intFromEnum(Xdp.RET.drop); | ||
} | ||
} | ||
|
||
fn handle_ipv6(ctx: *Xdp.Meta) c_int { | ||
const iphdr_offset = @sizeOf(EthHdr); | ||
const ipv6hdr: *const IPv6Hdr = ctx.get_ptr(IPv6Hdr, iphdr_offset) orelse return @intFromEnum(Xdp.RET.aborted); | ||
|
||
const IPPROTO_ICMPV6 = 58; | ||
const IPPROTO_TCP = 6; | ||
const IPPROTO_UDP = 17; | ||
if (ipv6hdr.nxt == IPPROTO_ICMPV6) { | ||
return @intFromEnum(Xdp.RET.pass); | ||
} else if (ipv6hdr.nxt == IPPROTO_TCP) { | ||
const tcpHdr: *const TcpHdr = ctx.get_ptr(TcpHdr, iphdr_offset + (10 * 4)) orelse return @intFromEnum(Xdp.RET.aborted); | ||
if (!(tcpHdr.syn == 1 and tcpHdr.ack == 0)) { | ||
return @intFromEnum(Xdp.RET.pass); | ||
} | ||
|
||
const acl_key = AclKey{ | ||
.ip_src_addr = In6Addr{ .in6_u = .{ .u6_addr8 = ipv6hdr.src } }, | ||
.ip_proto = ipv6hdr.nxt, | ||
.dst_port = tcpHdr.dest, | ||
.reserved = 0, | ||
}; | ||
|
||
const acl_val = acl_map.lookup(acl_key); | ||
if (acl_val) |val| { | ||
val.* += 1; | ||
return @intFromEnum(Xdp.RET.pass); | ||
} else { | ||
return @intFromEnum(Xdp.RET.drop); | ||
} | ||
} else if (ipv6hdr.nxt == IPPROTO_UDP) { | ||
const udpHdr: *const UdpHdr = ctx.get_ptr(UdpHdr, iphdr_offset + (10 * 4)) orelse return @intFromEnum(Xdp.RET.aborted); | ||
|
||
const acl_key = AclKey{ | ||
.ip_src_addr = In6Addr{ .in6_u = .{ .u6_addr8 = ipv6hdr.src } }, | ||
.ip_proto = ipv6hdr.nxt, | ||
.dst_port = udpHdr.dest, | ||
.reserved = 0, | ||
}; | ||
|
||
const acl_val = acl_map.lookup(acl_key); | ||
if (acl_val) |val| { | ||
val.* += 1; | ||
return @intFromEnum(Xdp.RET.pass); | ||
} else { | ||
return @intFromEnum(Xdp.RET.drop); | ||
} | ||
} else { | ||
return @intFromEnum(Xdp.RET.drop); | ||
} | ||
} | ||
|
||
pub fn u32_to_bytes(num: u32) [4]u8 { | ||
return [4]u8{ | ||
@truncate(num), | ||
@truncate(num >> 8), | ||
@truncate(num >> 16), | ||
@truncate(num >> 24), | ||
}; | ||
} |
Oops, something went wrong.