Skip to content

Commit

Permalink
Tc
Browse files Browse the repository at this point in the history
  • Loading branch information
msune committed Aug 26, 2024
1 parent 1603d15 commit 0d3c77b
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 37 deletions.
1 change: 1 addition & 0 deletions src/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rules.h
2 changes: 2 additions & 0 deletions src/Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
all: compile

compile:
python3 ../tools/gen.py rules.default > rules.h
clang -O2 -Wall -Werror -g -target bpf -c funnel.c -o tc_funnel.o
clang -O2 -Wall -Werror -g -target bpf -c unfunnel.c -o tc_unfunnel.o

clean:
rm -rf *.o || true
rm -rf rules.h || true
56 changes: 56 additions & 0 deletions src/common.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#ifndef FUNNEL_COMMON_H
#define FUNNEL_COMMON_H

#include <stdbool.h>

#if 0
#define PRINTK(...) bpf_printk( __VA_ARGS__ )
#else
Expand Down Expand Up @@ -45,6 +47,58 @@ static __always_inline __be16 csum_fold(__s64 csum)
char ____license[] __attribute__((section("license"), used)) = NAME
#endif


//Rules
typedef struct sfunnel_ip4_addr_match {
__be32 addr;
__u8 mask;
bool negate;
} sfunnel_ip4_addr_match_t;

typedef struct sfunnel_l4_port_match {
__be16 port;
bool negate;
} sfunnel_l4_port_match_t;

typedef struct sfunnel_action_params {
bool execute;
union {
struct {
__u8 funn_proto; //Funneling proto (TCP or UDP)
__be16 sport;
__be16 dport;
__u8 tcp_flags;
} funnel;

struct {
__u8 proto; //Inner pkt proto (TCP or UDP)
} unfunnel;

struct {
__be32 daddr;
//TODO: add more for LB.
} dnat;
}p;
}sfunnel_action_params_t;

typedef struct sfunnel_ip4_rule {
struct {
sfunnel_ip4_addr_match_t saddr;
sfunnel_ip4_addr_match_t daddr;
__u8 proto; //0 don't match
sfunnel_l4_port_match_t sport;
sfunnel_l4_port_match_t dport;
} matches;
struct {
sfunnel_action_params_t funnel;
sfunnel_action_params_t unfunnel;
sfunnel_action_params_t dnat;
sfunnel_action_params_t accept;
sfunnel_action_params_t drop;
} actions;
}sfunnel_ip4_rule_t;


//XXX: to be removed by dynamic config
#define TCP_FUNNEL_DST_PORT 179
#define TCP_FUNNEL_SRC_PORT 540 //E.g. UUCP
Expand All @@ -58,4 +112,6 @@ static __always_inline __be16 csum_fold(__s64 csum)

//XXX: end to be removed

#include "rules.h"

#endif //FUNNEL_COMMON_H
4 changes: 4 additions & 0 deletions src/rules.default
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ip udp dport 2055 actions funnel tcp dport 179 sport 540
ip udp dport 4739 actions funnel tcp dport 179 sport 540
ip udp dport 6343 actions funnel tcp dport 179 sport 540
ip tcp dport 4739 actions funnel tcp dport 179 sport 541
102 changes: 67 additions & 35 deletions tools/gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
# Filter support (*):
#
# TODO
#
# Action support:
# TODO

INDENT=" "
param_re="(\S+)"
Expand All @@ -31,15 +32,12 @@
f"(dport)\s*{neg_re}\s*{param_re}"
]

# Action support:

# dnat <address> : DNAT to an IPv4/IPv6 address
# drop : drop packet
action_keys = [
"funnel", # funnel <tcp | udp> sport <port> dport <port>
"unfunnel",# unfunnel <tcp | udp>: unfunnels traffic, TCP or UDP header
"dnat", # DNAT to an IPv4/IPv6 address
"drop" # Drop packet
action_patterns = [
f"(funnel)\s*{param_re}\s*(sport|dport)\s*{param_re}\s*(sport|dport)\s*{param_re}",
f"(unfunnel)\s*{param_re}",
f"(dnat)\s*{param_re}",
f"(accept)",
f"(drop)"
]

#Parsing routines
Expand Down Expand Up @@ -73,19 +71,42 @@ def extract_matches(string: str):
return matches

def extract_actions(string: str):
key_pattern = '|'.join(re.escape(key) for key in action_keys)
pattern = rf'\s*({key_pattern}|\w+)\s*([\w|\.|/]+)?\s*'
matches = re.findall(pattern, string)
result_dict = {}
for key, value in matches:
key = key.strip()
if key not in action_keys:
raise ValueError(f"ERROR: unknown action '{key}'!")

result_dict[key.strip()] = {
"value": value.strip()
}
return result_dict
s = string
actions={}
for a_it in action_patterns:
pattern = rf'\s*{a_it}\s*'
re_a = re.search(pattern, s)
if not re_a:
continue
grp_len = len(re_a.groups())
if grp_len not in [1,2,6]:
raise Exception(f"ERROR: parsing '{a_it}' in '{string}'")
a = {}
if grp_len == 2:
#Unfunnel/DNAT
if re_a.groups()[0] == "unfunnel" and re_a.groups()[1] not in ["tcp", "udp"]:
raise ValueError(f"ERROR: protocol following unfunnel action must be 'tcp' or 'udp' in '{string}'")
a["value"] = re_a.groups()[1]
elif grp_len == 6:
#Funnel
if re_a.groups()[1] not in ["tcp", "udp"]:
raise ValueError(f"ERROR: protocol following funnel action must be 'tcp' or 'udp' in '{string}'")
a["fun_proto"] = re_a.groups()[1]
if re_a.groups()[2] not in ["sport", "dport"]:
raise ValueError(f"ERROR: unknown heade field '{re_a.groups()[3]}' in '{string}'")
a[re_a.groups()[2]] = re_a.groups()[3]
if re_a.groups()[4] not in ["sport", "dport"]:
raise ValueError(f"ERROR: unknown heade field '{re_a.groups()[3]}' in '{string}'")
a[re_a.groups()[4]] = re_a.groups()[5]

actions[re_a.groups()[0]] = a
s = re.sub(pattern, "", s)

if s != "":
raise ValueError(f"ERROR: unable to parse '{s}' from '{string}'")

return actions

def extract_tuples(string):
r = string.split("actions")
if len(r) != 2:
Expand All @@ -105,7 +126,7 @@ def gen_ip_match(field: str, m: dict, indent: str):
s += f" .negate = {neg},"
s += f" .addr = 0x{ip:08x},"
s += f" .mask = 0x{mask:08x}"
s += f"}},\n"
s += f" }},\n"
return s

def gen_port_match(field: str, m: dict, indent: str, end:str =","):
Expand All @@ -115,9 +136,12 @@ def gen_port_match(field: str, m: dict, indent: str, end:str =","):
s = f"{indent}.{field} = {{"
s += f" .negate = {neg},"
s += f" .port = 0x{port_nbo:04x}"
s += f"}}{end}\n"
s += f" }}{end}\n"
return s

def __gen_ipproto(proto: str):
return "IPPROTO_" + proto.strip().rstrip().upper()

def gen_matches(matches: str, indent: str):
s = ""

Expand All @@ -133,7 +157,7 @@ def gen_matches(matches: str, indent: str):
if "tcp" not in matches and "udp" not in matches:
raise ValueError(f"ERROR: tcp nor udp specified. One must be selected")
proto = "tcp" if "tcp" in matches else "udp"
s += f"{indent}.proto = IPPROTO" + proto.upper() + ",\n"
s += f"{indent}.proto = {__gen_ipproto(proto)},\n"

#L4 ports
if "sport" not in matches:
Expand All @@ -147,9 +171,9 @@ def gen_matches(matches: str, indent: str):
return s

def gen_no_param_action(action: str, actions: dict, indent: str, end:str = ","):
s = f"{indent}.{action} {{"
s += f' .execute = { "1" if action in actions else "0"},'
s += f" .p = {{{{0}}}}"
s = f"{indent}.{action} = {{"
s += f' .execute = { "1" if action in actions else "0" },'
s += f" .p = {{{{0}}}} "
s += f"}}{end}\n"
return s

Expand All @@ -162,16 +186,22 @@ def gen_actions(actions: dict, indent: str):
s += gen_no_param_action("accept", actions, indent)

if "funnel" in actions:
proto = __gen_ipproto(actions["funnel"]["fun_proto"])
f = actions['funnel']
s += f"{indent}.funnel = {{ .execute = 1, .p = {{ .funnel = {{ .funn_proto = {proto}, .sport = {f['sport']}, .dport = {f['dport']} }} }} }},\n"
pass
else:
s += gen_no_param_action("funnel", actions, indent)

if "unfunnel" in actions:
pass
proto = f"IPPROTO" + actions["unfunnel"]["value"].upper().strip()
s += f"{indent}.unfunnel = {{ .execute = 1, .p = {{ .unfunnel = {{ .proto = {proto} }} }} }},\n"
else:
s += gen_no_param_action("unfunnel", actions, indent)
if "dnat" in actions:
pass
network = ipaddress.IPv4Network(actions["dnat"]["value"], strict=False)
addr_nbo = socket.htonl(int(network.network_address))
s += f"{indent}.dnat = {{ .execute = 1, .p = {{ .dnat = {{ .daddr = 0x{addr_nbo:08x} }} }} }}\n"
else:
s += gen_no_param_action("dnat", actions, indent, end="")

Expand All @@ -193,17 +223,18 @@ def gen_header(rules_str: str, rules: list):
s += f"{rules_str}"
s += f"*/\n\n"

s += "struct ip4_rule ip4_rules[] = {\n"
s += "struct sfunnel_ip4_rule ip4_rules[] = {\n"
for index, rule in enumerate(rules):
s += f"{INDENT}{{\n"
s += f'{2*INDENT}//{rule["__line__"]}\n'
s += f'{2*INDENT}.matches = {{\n'
s += gen_matches(rule["matches"], f"{3*INDENT}")
s += f'{2*INDENT}}},\n'
s += f'{2*INDENT}.actions = {{\n'
s += gen_actions(rule["actions"], f"{3*INDENT}")
s += f'{INDENT}{INDENT}}}\n'
s += f"{INDENT}}}"+ ("," if index != (len(rules) -1) else "") + "\n"
s += "} //ip4_rules\n"
s += "}; //ip4_rules\n"

#End guard
return s + "#endif //{guard}"
Expand All @@ -227,9 +258,10 @@ def usage():
matches, actions = extract_tuples(line)
rule["matches"] = matches
rule["actions"] = actions
rule["__line__"] = line
rules.append(rule)

print(f"Parsed { len(rules) } rules:")
print(f"{json.dumps(rules, indent=2)}")
#print(f"Parsed { len(rules) } rules:")
#print(f"{json.dumps(rules, indent=2)}")

print(gen_header(file_content, rules))
7 changes: 5 additions & 2 deletions tools/rules
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
ip saddr 192.168.0.2/32 daddr != 10.0.0.2 tcp dport != 179 actions dnat
ip saddr 192.168.0.2/24 daddr 10.0.0.2 udp actions funnel test
ip saddr 192.168.0.2/32 daddr != 10.0.0.2 tcp dport != 179 actions dnat 172.16.0.1
ip saddr 192.168.0.2/24 daddr 10.0.0.2 udp actions funnel tcp dport 80 sport 70
ip saddr 192.168.0.2/24 daddr 10.0.0.2 udp actions funnel udp sport 80 dport 70
ip saddr 192.168.0.2/24 daddr 10.0.0.2 udp actions drop
ip saddr 192.168.1.2/24 daddr 10.0.0.2 udp actions unfunnel tcp

0 comments on commit 0d3c77b

Please sign in to comment.