From 916a55d480d537ed23999d2fbeb2ce78cb44f169 Mon Sep 17 00:00:00 2001 From: mzz2017 <2017@duck.com> Date: Wed, 25 Jan 2023 00:31:20 +0800 Subject: [PATCH] feat: support routing by l4proto, ipversion, mac, sip, port, sport --- README.md | 3 +- common/consts/ebpf.go | 10 ++-- common/consts/routing.go | 10 +++- common/utils.go | 42 +++++++++++++ component/control/kern/tproxy.c | 62 +++++++++++-------- component/control/routing_matcher_builder.go | 56 +++++++++++++++++ component/routing/matcher_builder.go | 63 ++++++++++++++++++-- component/routing/walker.go | 9 ++- main.go | 6 +- 9 files changed, 215 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index e73a1eb..615c541 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,6 @@ As a successor of [v2rayA](https://github.com/v2rayA/v2rayA), dae abandoned v2ra ## TODO -1. Control plane does not support MAC and other matching yet. 1. Dns upstream. Check dns upstream and source loop (whether upstream is also a client of us) and remind user to add source rule. -1. Routing performance optimization. +1. Domain routing performance optimization. 1. ... diff --git a/common/consts/ebpf.go b/common/consts/ebpf.go index 5aff192..f88af7b 100644 --- a/common/consts/ebpf.go +++ b/common/consts/ebpf.go @@ -40,7 +40,7 @@ const ( RoutingType_SourceIpSet RoutingType_Port RoutingType_SourcePort - RoutingType_Network + RoutingType_L4Proto RoutingType_IpVersion RoutingType_Mac RoutingType_Final @@ -71,12 +71,12 @@ const ( MaxRoutingLen = 96 ) -type NetworkType uint8 +type L4ProtoType uint8 const ( - NetworkType_TCP NetworkType = 1 - NetworkType_UDP NetworkType = 2 - NetworkType_TCP_UDP NetworkType = 3 + L4ProtoType_TCP L4ProtoType = 1 + L4ProtoType_UDP L4ProtoType = 2 + L4ProtoType_TCP_UDP L4ProtoType = 3 ) type IpVersion uint8 diff --git a/common/consts/routing.go b/common/consts/routing.go index 42cc16a..19a67eb 100644 --- a/common/consts/routing.go +++ b/common/consts/routing.go @@ -11,6 +11,12 @@ const ( RoutingDomain_Suffix = "suffix" RoutingDomain_Regex = "regex" - Function_Domain = "domain" - Function_Ip = "ip" + Function_Domain = "domain" + Function_Ip = "ip" + Function_SourceIp = "sip" + Function_Port = "port" + Function_SourcePort = "sport" + Function_Mac = "mac" + Function_L4Proto = "l4proto" + Function_IpVersion = "ipversion" ) diff --git a/common/utils.go b/common/utils.go index c864893..7d33c79 100644 --- a/common/utils.go +++ b/common/utils.go @@ -8,7 +8,10 @@ package common import ( "encoding/base64" "encoding/binary" + "encoding/hex" + "fmt" "net/url" + "strconv" "strings" ) @@ -78,3 +81,42 @@ func SetValue(values *url.Values, key string, value string) { } values.Set(key, value) } + +func ParseMac(mac string) (addr [6]byte, err error) { + fields := strings.SplitN(mac, ":", 6) + if len(fields) != 6 { + return addr, fmt.Errorf("invalid mac: %v", mac) + } + for i, field := range fields { + v, err := hex.DecodeString(field) + if err != nil { + return addr, fmt.Errorf("parse mac %v: %w", mac, err) + } + if len(v) != 1 { + return addr, fmt.Errorf("invalid mac: %v", mac) + } + addr[i] = v[0] + } + return addr, nil +} + +func ParsePortRange(pr string) (portRange [2]int, err error) { + fields := strings.SplitN(pr, "-", 2) + for i, field := range fields { + if field == "" { + return portRange, fmt.Errorf("bad port range: %v", pr) + } + port, err := strconv.Atoi(field) + if err != nil { + return portRange, err + } + if port < 0 || port > 0xffff { + return portRange, fmt.Errorf("port %v exceeds uint16 range", port) + } + portRange[i] = port + } + if len(fields) == 1 { + portRange[1] = portRange[0] + } + return portRange, nil +} diff --git a/component/control/kern/tproxy.c b/component/control/kern/tproxy.c index fd4c957..a5604a6 100644 --- a/component/control/kern/tproxy.c +++ b/component/control/kern/tproxy.c @@ -162,15 +162,15 @@ enum __attribute__((__packed__)) ROUTING_TYPE { ROUTING_TYPE_SOURCE_IP_SET, ROUTING_TYPE_PORT, ROUTING_TYPE_SOURCE_PORT, - ROUTING_TYPE_NETWORK, + ROUTING_TYPE_L4PROTO, ROUTING_TYPE_IPVERSION, ROUTING_TYPE_MAC, ROUTING_TYPE_FINAL, }; -enum __attribute__((__packed__)) NETWORK_TYPE { - NETWORK_TYPE_TCP = 1, - NETWORK_TYPE_UDP = 2, - NETWORK_TYPE_TCP_UDP = 3, +enum __attribute__((__packed__)) L4PROTO_TYPE { + L4PROTO_TYPE_TCP = 1, + L4PROTO_TYPE_UDP = 2, + L4PROTO_TYPE_TCP_UDP = 3, }; enum __attribute__((__packed__)) IP_VERSION { IPVERSION_4 = 1, @@ -187,7 +187,7 @@ struct routing { __u32 index; struct port_range port_range; - enum NETWORK_TYPE network_type; + enum L4PROTO_TYPE l4proto_type; enum IP_VERSION ip_version; }; enum ROUTING_TYPE type; @@ -740,7 +740,7 @@ static __always_inline int decap_after_udp_hdr(struct __sk_buff *skb, // Do not use __always_inline here because this function is too heavy. static long routing(__u8 flag[2], void *l4_hdr, __be32 saddr[4], __be32 daddr[4], __be32 mac[4]) { -#define _network flag[0] +#define _l4proto flag[0] #define _ipversion flag[1] // // Get len of routings and epoch from param_map. // __u32 *routings_len = bpf_map_lookup_elem(¶m_map, &routings_len_key); @@ -754,7 +754,7 @@ static long routing(__u8 flag[2], void *l4_hdr, __be32 saddr[4], // Define variables for further use. __u16 h_dport; __u16 h_sport; - if (_network == NETWORK_TYPE_TCP) { + if (_l4proto == L4PROTO_TYPE_TCP) { h_dport = bpf_ntohs(((struct tcphdr *)l4_hdr)->dest); h_sport = bpf_ntohs(((struct tcphdr *)l4_hdr)->source); } else { @@ -762,8 +762,9 @@ static long routing(__u8 flag[2], void *l4_hdr, __be32 saddr[4], h_sport = bpf_ntohs(((struct udphdr *)l4_hdr)->source); } // Modify DNS upstream for routing. - if (h_dport == 53 && _network == NETWORK_TYPE_UDP) { - struct ip_port* upstream = bpf_map_lookup_elem(&dns_upstream_map, &zero_key); + if (h_dport == 53 && _l4proto == L4PROTO_TYPE_UDP) { + struct ip_port *upstream = + bpf_map_lookup_elem(&dns_upstream_map, &zero_key); if (!upstream) { return -EFAULT; } @@ -777,6 +778,7 @@ static long routing(__u8 flag[2], void *l4_hdr, __be32 saddr[4], __builtin_memcpy(lpm_key_saddr.data, saddr, IPV6_BYTE_LENGTH); __builtin_memcpy(lpm_key_daddr.data, daddr, IPV6_BYTE_LENGTH); __builtin_memcpy(lpm_key_mac.data, mac, IPV6_BYTE_LENGTH); + bpf_printk("mac: %pI6", mac); struct map_lpm_type *lpm; struct routing *routing; @@ -842,8 +844,8 @@ static long routing(__u8 flag[2], void *l4_hdr, __be32 saddr[4], h_sport > routing->port_range.port_end) { bad_rule = true; } - } else if (routing->type == ROUTING_TYPE_NETWORK) { - if (!(_network & routing->network_type)) { + } else if (routing->type == ROUTING_TYPE_L4PROTO) { + if (!(_l4proto & routing->l4proto_type)) { bad_rule = true; } } else if (routing->type == ROUTING_TYPE_IPVERSION) { @@ -867,7 +869,7 @@ static long routing(__u8 flag[2], void *l4_hdr, __be32 saddr[4], // Decide whether to hit. if (!bad_rule) { if (routing->outbound == OUTBOUND_DIRECT && h_dport == 53 && - _network == NETWORK_TYPE_UDP) { + _l4proto == L4PROTO_TYPE_UDP) { // DNS packet should go through control plane. return OUTBOUND_CONTROL_PLANE_DIRECT; } @@ -879,7 +881,7 @@ static long routing(__u8 flag[2], void *l4_hdr, __be32 saddr[4], bpf_printk( "Did coder forget to sync common/consts/ebpf.go with enum ROUTING_TYPE?"); return -EPERM; -#undef _network +#undef _l4proto #undef _ip_version } @@ -918,12 +920,12 @@ int tproxy_ingress(struct __sk_buff *skb) { if (iph) { saddr[0] = 0; saddr[1] = 0; - saddr[2] = bpf_ntohl(0xffff); + saddr[2] = bpf_htonl(0x0000ffff); saddr[3] = iph->saddr; daddr[0] = 0; daddr[1] = 0; - daddr[2] = bpf_ntohl(0xffff); + daddr[2] = bpf_htonl(0x0000ffff); daddr[3] = iph->daddr; } else if (ipv6h) { __builtin_memcpy(daddr, &ipv6h->daddr, IPV6_BYTE_LENGTH); @@ -968,15 +970,19 @@ int tproxy_ingress(struct __sk_buff *skb) { if (unlikely(tcp_state_syn)) { // New TCP connection. // bpf_printk("[%X]New Connection", bpf_ntohl(tcph->seq)); - __u8 flag[2] = {NETWORK_TYPE_TCP}; // TCP + __u8 flag[2] = {L4PROTO_TYPE_TCP}; // TCP if (ipv6h) { flag[1] = IPVERSION_6; } else { flag[1] = IPVERSION_4; } - __be32 mac[4]; - __builtin_memset(mac, 0, IPV6_BYTE_LENGTH); - __builtin_memcpy(mac, ethh->h_source, sizeof(ethh->h_source)); + __be32 mac[4] = { + 0, + 0, + bpf_htonl((ethh->h_source[0] << 8) + (ethh->h_source[1])), + bpf_htonl((ethh->h_source[2] << 24) + (ethh->h_source[3] << 16) + + (ethh->h_source[4] << 8) + (ethh->h_source[5])), + }; if ((ret = routing(flag, tcph, saddr, daddr, mac)) < 0) { bpf_printk("shot routing: %ld", ret); return TC_ACT_SHOT; @@ -1031,15 +1037,19 @@ int tproxy_ingress(struct __sk_buff *skb) { new_hdr.port = udph->dest; // Routing. It decides if we redirect traffic to control plane. - __u8 flag[2] = {NETWORK_TYPE_UDP}; + __u8 flag[2] = {L4PROTO_TYPE_UDP}; if (ipv6h) { flag[1] = IPVERSION_6; } else { flag[1] = IPVERSION_4; } - __be32 mac[4]; - __builtin_memset(mac, 0, IPV6_BYTE_LENGTH); - __builtin_memcpy(mac, ethh->h_source, sizeof(ethh->h_source)); + __be32 mac[4] = { + 0, + 0, + bpf_htonl((ethh->h_source[0] << 8) + (ethh->h_source[1])), + bpf_htonl((ethh->h_source[2] << 24) + (ethh->h_source[3] << 16) + + (ethh->h_source[4] << 8) + (ethh->h_source[5])), + }; if ((ret = routing(flag, udph, saddr, daddr, mac)) < 0) { bpf_printk("shot routing: %ld", ret); return TC_ACT_SHOT; @@ -1131,12 +1141,12 @@ int tproxy_egress(struct __sk_buff *skb) { if (iph) { saddr[0] = 0; saddr[1] = 0; - saddr[2] = bpf_ntohl(0xffff); + saddr[2] = bpf_htonl(0x0000ffff); saddr[3] = iph->saddr; daddr[0] = 0; daddr[1] = 0; - daddr[2] = bpf_ntohl(0xffff); + daddr[2] = bpf_htonl(0x0000ffff); daddr[3] = iph->daddr; } else if (ipv6h) { __builtin_memcpy(daddr, ipv6h->daddr.in6_u.u6_addr32, IPV6_BYTE_LENGTH); diff --git a/component/control/routing_matcher_builder.go b/component/control/routing_matcher_builder.go index 1d78723..4228a36 100644 --- a/component/control/routing_matcher_builder.go +++ b/component/control/routing_matcher_builder.go @@ -74,6 +74,27 @@ func (b *RoutingMatcherBuilder) AddDomain(key string, values []string, outbound }) } +func (b *RoutingMatcherBuilder) AddMac(macAddrs [][6]byte, outbound string) { + if b.err != nil { + return + } + var addr16 [16]byte + values := make([]netip.Prefix, 0, len(macAddrs)) + for _, mac := range macAddrs { + copy(addr16[10:], mac[:]) + prefix := netip.PrefixFrom(netip.AddrFrom16(addr16), 128) + values = append(values, prefix) + } + lpmTrieIndex := len(b.SimulatedLpmTries) + b.SimulatedLpmTries = append(b.SimulatedLpmTries, values) + b.rules = append(b.rules, bpfRouting{ + Type: uint8(consts.RoutingType_Mac), + Value: uint32(lpmTrieIndex), + Outbound: b.OutboundToId(outbound), + }) + +} + func (b *RoutingMatcherBuilder) AddIp(values []netip.Prefix, outbound string) { if b.err != nil { return @@ -87,6 +108,41 @@ func (b *RoutingMatcherBuilder) AddIp(values []netip.Prefix, outbound string) { }) } +func (b *RoutingMatcherBuilder) AddSource(values []netip.Prefix, outbound string) { + if b.err != nil { + return + } + lpmTrieIndex := len(b.SimulatedLpmTries) + b.SimulatedLpmTries = append(b.SimulatedLpmTries, values) + b.rules = append(b.rules, bpfRouting{ + Type: uint8(consts.RoutingType_SourceIpSet), + Value: uint32(lpmTrieIndex), + Outbound: b.OutboundToId(outbound), + }) +} + +func (b *RoutingMatcherBuilder) AddL4Proto(values consts.L4ProtoType, outbound string) { + if b.err != nil { + return + } + b.rules = append(b.rules, bpfRouting{ + Type: uint8(consts.RoutingType_L4Proto), + Value: uint32(values), + Outbound: b.OutboundToId(outbound), + }) +} + +func (b *RoutingMatcherBuilder) AddIpVersion(values consts.IpVersion, outbound string) { + if b.err != nil { + return + } + b.rules = append(b.rules, bpfRouting{ + Type: uint8(consts.RoutingType_IpVersion), + Value: uint32(values), + Outbound: b.OutboundToId(outbound), + }) +} + func (b *RoutingMatcherBuilder) AddFinal(outbound string) { if b.err != nil { return diff --git a/component/routing/matcher_builder.go b/component/routing/matcher_builder.go index 47e5985..88b6b54 100644 --- a/component/routing/matcher_builder.go +++ b/component/routing/matcher_builder.go @@ -7,6 +7,7 @@ package routing import ( "fmt" + "github.com/v2rayA/dae/common" "github.com/v2rayA/dae/common/consts" "net/netip" "strings" @@ -20,7 +21,7 @@ type MatcherBuilder interface { AddPort(values [][2]int, outbound string) AddSource(values []netip.Prefix, outbound string) AddSourcePort(values [][2]int, outbound string) - AddNetwork(values consts.NetworkType, outbound string) + AddL4Proto(values consts.L4ProtoType, outbound string) AddIpVersion(values consts.IpVersion, outbound string) AddMac(values [][6]byte, outbound string) AddFinal(outbound string) @@ -66,14 +67,66 @@ func ApplyMatcherBuilder(builder MatcherBuilder, rules []RoutingRule, finalOutbo } builder.AddAnyBefore(key, paramValueGroup, outbound) switch f.Name { - case "domain": + case consts.Function_Domain: builder.AddDomain(key, paramValueGroup, outbound) - case "ip": + case consts.Function_Ip, consts.Function_SourceIp: cidrs, err := ParsePrefixes(paramValueGroup) if err != nil { return err } - builder.AddIp(cidrs, outbound) + if f.Name == consts.Function_Ip { + builder.AddIp(cidrs, outbound) + } else { + builder.AddSource(cidrs, outbound) + } + case consts.Function_Mac: + var macAddrs [][6]byte + for _, v := range paramValueGroup { + mac, err := common.ParseMac(v) + if err != nil { + return err + } + macAddrs = append(macAddrs, mac) + } + builder.AddMac(macAddrs, outbound) + case consts.Function_Port, consts.Function_SourcePort: + var portRanges [][2]int + for _, v := range paramValueGroup { + portRange, err := common.ParsePortRange(v) + if err != nil { + return err + } + portRanges = append(portRanges, portRange) + } + if f.Name == consts.Function_Port { + builder.AddPort(portRanges, outbound) + } else { + builder.AddSourcePort(portRanges, outbound) + } + case consts.Function_L4Proto: + var l4protoType consts.L4ProtoType + for _, v := range paramValueGroup { + switch v { + case "tcp": + l4protoType |= consts.L4ProtoType_TCP + case "udp": + l4protoType |= consts.L4ProtoType_UDP + } + } + builder.AddL4Proto(l4protoType, outbound) + case consts.Function_IpVersion: + var ipVersion consts.IpVersion + for _, v := range paramValueGroup { + switch v { + case "4": + ipVersion |= consts.IpVersion_4 + case "6": + ipVersion |= consts.IpVersion_6 + } + } + builder.AddIpVersion(ipVersion, outbound) + default: + return fmt.Errorf("unsupported function name: %v", f.Name) } builder.AddAnyAfter(key, paramValueGroup, outbound) } @@ -92,7 +145,7 @@ func (d *DefaultMatcherBuilder) AddIp(values []netip.Prefix, outbound string) func (d *DefaultMatcherBuilder) AddPort(values [][2]int, outbound string) {} func (d *DefaultMatcherBuilder) AddSource(values []netip.Prefix, outbound string) {} func (d *DefaultMatcherBuilder) AddSourcePort(values [][2]int, outbound string) {} -func (d *DefaultMatcherBuilder) AddNetwork(values consts.NetworkType, outbound string) {} +func (d *DefaultMatcherBuilder) AddL4Proto(values consts.L4ProtoType, outbound string) {} func (d *DefaultMatcherBuilder) AddIpVersion(values consts.IpVersion, outbound string) {} func (d *DefaultMatcherBuilder) AddMac(values [][6]byte, outbound string) {} func (d *DefaultMatcherBuilder) AddFinal(outbound string) {} diff --git a/component/routing/walker.go b/component/routing/walker.go index 9d9e0b2..c283c70 100644 --- a/component/routing/walker.go +++ b/component/routing/walker.go @@ -131,11 +131,10 @@ func (s *RoutingAWalker) parseFunctionPrototype(ctx *routingA.FunctionPrototypeC // Validate function name and param keys. for _, param := range params { switch funcName { - case "domain": + case consts.Function_Domain: switch param.Key { - case "", "domain", consts.RoutingDomain_Suffix, + case "", consts.RoutingDomain_Suffix, consts.RoutingDomain_Keyword, - "contains", consts.RoutingDomain_Full, consts.RoutingDomain_Regex, "geosite": @@ -143,14 +142,14 @@ func (s *RoutingAWalker) parseFunctionPrototype(ctx *routingA.FunctionPrototypeC s.reportKeyUnsupportedError(ctx, param.Key, funcName) return nil } - case "ip": + case consts.Function_Ip, consts.Function_SourceIp: switch param.Key { case "", "geoip": default: s.reportKeyUnsupportedError(ctx, param.Key, funcName) return nil } - case "port", "source", "sourcePort", "network", "ipVersion": + case consts.Function_Port, consts.Function_SourcePort, consts.Function_Mac, consts.Function_L4Proto, consts.Function_IpVersion: if param.Key != "" { s.reportKeyUnsupportedError(ctx, param.Key, funcName) return nil diff --git a/main.go b/main.go index 67a6c63..9ede86c 100644 --- a/main.go +++ b/main.go @@ -26,10 +26,14 @@ func main() { log.Println("Running") t, err := control.NewControlPlane(log, ` default:proxy +#sip(172.17.0.2)->proxy +#mac("02:42:ac:11:00:02")->proxy +#ipversion(4)->proxy +#l4proto(tcp)->proxy ip(119.29.29.29) -> proxy ip(223.5.5.5) -> direct ip(geoip:cn) -> direct -domain(geosite:cn, domain:"ip.sb") -> direct +domain(geosite:cn, suffix:"ip.sb") -> direct ip("91.105.192.0/23","91.108.4.0/22","91.108.8.0/21","91.108.16.0/21","91.108.56.0/22","95.161.64.0/20","149.154.160.0/20","185.76.151.0/24")->proxy domain(geosite:category-scholar-!cn, geosite:category-scholar-cn)->direct `)