From 7273be6a0689bcd46c19f76a2662e6a33aa83168 Mon Sep 17 00:00:00 2001 From: mzz <2017@duck.com> Date: Sun, 20 Aug 2023 01:55:42 +0800 Subject: [PATCH] feat: support ToS routing rule (#292) --- cmd/honk.go | 45 +++++++++---------- common/consts/ebpf.go | 1 + common/consts/routing.go | 1 + component/routing/function_parser.go | 23 +++++++++- control/dns_control.go | 1 + control/kern/tproxy.c | 23 +++++++++- control/routing_matcher_builder.go | 27 ++++++++++- control/routing_matcher_userspace.go | 7 ++- control/tcp.go | 1 + control/udp.go | 2 + control/utils.go | 1 + docs/en/configuration/routing.md | 3 ++ docs/zh/configuration/routing.md | 3 ++ .../{rawsock.go => rawsock_linux.go} | 0 14 files changed, 110 insertions(+), 28 deletions(-) rename pkg/ebpf_internal/{rawsock.go => rawsock_linux.go} (100%) diff --git a/cmd/honk.go b/cmd/honk.go index 52db754..0a5fdfd 100644 --- a/cmd/honk.go +++ b/cmd/honk.go @@ -3,27 +3,26 @@ * Copyright (c) 2022-2023, daeuniverse Organization */ - package cmd +package cmd - import ( - "fmt" - "os" - - "github.com/spf13/cobra" - ) - - var ( - honkCmd = &cobra.Command{ - Use: "honk", - Short: "Let dae call for you.", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("Honk! Honk! Honk! This is dae!") - os.Exit(0) - }, - } - ) - - func init() { - rootCmd.AddCommand(honkCmd) - } - +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +var ( + honkCmd = &cobra.Command{ + Use: "honk", + Short: "Let dae call for you.", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Honk! Honk! Honk! This is dae!") + os.Exit(0) + }, + } +) + +func init() { + rootCmd.AddCommand(honkCmd) +} diff --git a/common/consts/ebpf.go b/common/consts/ebpf.go index 1fbe002..d57e183 100644 --- a/common/consts/ebpf.go +++ b/common/consts/ebpf.go @@ -52,6 +52,7 @@ const ( MatchType_IpVersion MatchType_Mac MatchType_ProcessName + MatchType_Tos MatchType_Fallback MatchType_MustRules diff --git a/common/consts/routing.go b/common/consts/routing.go index f776b76..60395fd 100644 --- a/common/consts/routing.go +++ b/common/consts/routing.go @@ -22,6 +22,7 @@ const ( Function_IpVersion = "ipversion" Function_Mac = "mac" Function_ProcessName = "pname" + Function_Tos = "tos" Function_QName = "qname" Function_QType = "qtype" diff --git a/component/routing/function_parser.go b/component/routing/function_parser.go index c125f10..e6c8902 100644 --- a/component/routing/function_parser.go +++ b/component/routing/function_parser.go @@ -6,13 +6,17 @@ package routing import ( + "encoding/binary" "fmt" + "net/netip" + "strconv" + "strings" + "github.com/daeuniverse/dae/common" "github.com/daeuniverse/dae/common/consts" "github.com/daeuniverse/dae/pkg/config_parser" "github.com/sirupsen/logrus" - "net/netip" - "strings" + "golang.org/x/exp/constraints" ) type FunctionParser func(log *logrus.Logger, f *config_parser.Function, key string, paramValueGroup []string, overrideOutbound *Outbound) (err error) @@ -137,3 +141,18 @@ func toProcessName(processName string) (procName [consts.TaskCommLen]byte) { copy(procName[:], n) return procName } + +func UintParserFactory[T constraints.Unsigned](callback func(f *config_parser.Function, values []T, overrideOutbound *Outbound) (err error)) FunctionParser { + size := binary.Size(new(T)) + return func(log *logrus.Logger, f *config_parser.Function, key string, paramValueGroup []string, overrideOutbound *Outbound) (err error) { + var values []T + for _, v := range paramValueGroup { + val, err := strconv.ParseUint(v, 10, 8*size) + if err != nil { + return fmt.Errorf("cannot parse %v: %w", v, err) + } + values = append(values, T(val)) + } + return callback(f, values, overrideOutbound) + } +} diff --git a/control/dns_control.go b/control/dns_control.go index 36556b9..452dff0 100644 --- a/control/dns_control.go +++ b/control/dns_control.go @@ -729,6 +729,7 @@ func (c *DnsController) dialSend(invokingDepth int, req *udpRequest, data []byte "_qname": qname, "qtype": qtype, "pid": req.routingResult.Pid, + "tos": req.routingResult.Tos, "pname": ProcessName2String(req.routingResult.Pname[:]), "mac": Mac2String(req.routingResult.Mac[:]), } diff --git a/control/kern/tproxy.c b/control/kern/tproxy.c index 5620a1a..b6c933b 100644 --- a/control/kern/tproxy.c +++ b/control/kern/tproxy.c @@ -140,6 +140,7 @@ struct routing_result { __u8 outbound; __u8 pname[TASK_COMM_LEN]; __u32 pid; + __u8 tos; }; struct dst_routing_result { @@ -155,6 +156,7 @@ struct tuples { __u16 sport; __u16 dport; __u8 l4proto; + __u8 tos; }; struct { @@ -282,6 +284,7 @@ enum __attribute__((packed)) MatchType { MatchType_IpVersion, MatchType_Mac, MatchType_ProcessName, + MatchType_Tos, MatchType_Fallback, }; enum L4ProtoType { @@ -317,6 +320,7 @@ struct match_set { enum L4ProtoType l4proto_type; enum IpVersionType ip_version; __u32 pname[TASK_COMM_LEN / 4]; + __u8 tos; }; bool not ; // A subrule flag (this is not a match_set flag). enum MatchType type; @@ -380,9 +384,13 @@ get_tuples(const struct __sk_buff *skb, struct tuples *tuples, tuples->dip.u6_addr32[2] = bpf_htonl(0x0000ffff); tuples->dip.u6_addr32[3] = iph->daddr; + tuples->tos = iph->tos; + } else { __builtin_memcpy(&tuples->dip, &ipv6h->daddr, IPV6_BYTE_LENGTH); __builtin_memcpy(&tuples->sip, &ipv6h->saddr, IPV6_BYTE_LENGTH); + + tuples->tos = ipv6h->priority; } if (l4proto == IPPROTO_TCP) { tuples->sport = tcph->source; @@ -969,6 +977,7 @@ route(const __u32 flag[6], const void *l4hdr, const __be32 saddr[4], #define _ipversion_type flag[1] #define _pname &flag[2] #define _is_wan flag[2] +#define _tos flag[3] int ret; struct lpm_key lpm_key_instance, *lpm_key; @@ -1123,6 +1132,11 @@ route(const __u32 flag[6], const void *l4hdr, const __be32 saddr[4], isdns_must_goodsubrule_badrule |= 0b10; } break; + case MatchType_Tos: + if (_tos == match_set->tos) { + isdns_must_goodsubrule_badrule |= 0b10; + } + break; case MatchType_Fallback: #ifdef __DEBUG_ROUTING bpf_printk("CHECK: hit fallback"); @@ -1200,6 +1214,7 @@ route(const __u32 flag[6], const void *l4hdr, const __be32 saddr[4], #undef _ipversion_type #undef _pname #undef _is_wan +#undef _tos } static bool __always_inline is_not_to_lan(void *_ori_src) { @@ -1394,6 +1409,7 @@ new_connection: } else { flag[1] = IpVersionType_6; } + flag[3] = tuples.tos; __be32 mac[4] = { 0, 0, @@ -1411,6 +1427,7 @@ new_connection: routing_result.outbound = s64_ret; routing_result.mark = s64_ret >> 8; routing_result.must = (s64_ret >> 40) & 1; + routing_result.tos = tuples.tos; __builtin_memcpy(routing_result.mac, ethh.h_source, sizeof(routing_result.mac)); /// NOTICE: No pid pname info for LAN packet. @@ -1685,6 +1702,7 @@ int tproxy_wan_egress(struct __sk_buff *skb) { } else { flag[1] = IpVersionType_6; } + flag[3] = tuples.tos; if (pid_is_control_plane(skb, &pid_pname)) { // From control plane. Direct. return TC_ACT_OK; @@ -1706,7 +1724,7 @@ int tproxy_wan_egress(struct __sk_buff *skb) { return TC_ACT_SHOT; } - outbound = s64_ret; + outbound = s64_ret & 0xff; mark = s64_ret >> 8; must = (s64_ret >> 40) & 1; @@ -1763,6 +1781,7 @@ int tproxy_wan_egress(struct __sk_buff *skb) { routing_info.routing_result.outbound = outbound; routing_info.routing_result.mark = mark; routing_info.routing_result.must = must; + routing_info.routing_result.tos = tuples.tos; __builtin_memcpy(routing_info.routing_result.mac, ethh.h_source, sizeof(ethh.h_source)); if (pid_pname) { @@ -1796,6 +1815,7 @@ int tproxy_wan_egress(struct __sk_buff *skb) { } else { flag[1] = IpVersionType_6; } + flag[3] = tuples.tos; struct pid_pname *pid_pname; if (pid_is_control_plane(skb, &pid_pname)) { // From control plane. Direct. @@ -1826,6 +1846,7 @@ int tproxy_wan_egress(struct __sk_buff *skb) { new_hdr.routing_result.outbound = s64_ret; new_hdr.routing_result.mark = s64_ret >> 8; new_hdr.routing_result.must = (s64_ret >> 40) & 1; + new_hdr.routing_result.tos = tuples.tos; __builtin_memcpy(new_hdr.routing_result.mac, ethh.h_source, sizeof(ethh.h_source)); if (pid_pname) { diff --git a/control/routing_matcher_builder.go b/control/routing_matcher_builder.go index e0d138a..06a8da1 100644 --- a/control/routing_matcher_builder.go +++ b/control/routing_matcher_builder.go @@ -8,10 +8,11 @@ package control import ( "encoding/binary" "fmt" - "github.com/daeuniverse/dae/pkg/trie" "net/netip" "strconv" + "github.com/daeuniverse/dae/pkg/trie" + "github.com/cilium/ebpf" "github.com/daeuniverse/dae/common" "github.com/daeuniverse/dae/common/consts" @@ -43,6 +44,7 @@ func NewRoutingMatcherBuilder(log *logrus.Logger, rules []*config_parser.Routing rulesBuilder.RegisterFunctionParser(consts.Function_L4Proto, routing.L4ProtoParserFactory(b.addL4Proto)) rulesBuilder.RegisterFunctionParser(consts.Function_Mac, routing.MacParserFactory(b.addSourceMac)) rulesBuilder.RegisterFunctionParser(consts.Function_ProcessName, routing.ProcessNameParserFactory(b.addProcessName)) + rulesBuilder.RegisterFunctionParser(consts.Function_Tos, routing.UintParserFactory(b.addTos)) rulesBuilder.RegisterFunctionParser(consts.Function_IpVersion, routing.IpVersionParserFactory(b.addIpVersion)) if err = rulesBuilder.Apply(rules); err != nil { return nil, err @@ -274,6 +276,29 @@ func (b *RoutingMatcherBuilder) addProcessName(f *config_parser.Function, values return nil } +func (b *RoutingMatcherBuilder) addTos(f *config_parser.Function, values []uint8, outbound *routing.Outbound) (err error) { + for i, value := range values { + outboundName := consts.OutboundLogicalOr.String() + if i == len(values)-1 { + outboundName = outbound.Name + } + outboundId, err := b.outboundToId(outboundName) + if err != nil { + return err + } + matchSet := bpfMatchSet{ + Type: uint8(consts.MatchType_Tos), + Not: f.Not, + Outbound: outboundId, + Mark: outbound.Mark, + Must: outbound.Must, + } + matchSet.Value[0] = value + b.rules = append(b.rules, matchSet) + } + return nil +} + func (b *RoutingMatcherBuilder) addFallback(fallbackOutbound config.FunctionOrString) (err error) { outbound, err := routing.ParseOutbound(config.FunctionOrStringToFunction(fallbackOutbound)) if err != nil { diff --git a/control/routing_matcher_userspace.go b/control/routing_matcher_userspace.go index e917d11..a1f36c2 100644 --- a/control/routing_matcher_userspace.go +++ b/control/routing_matcher_userspace.go @@ -8,12 +8,12 @@ package control import ( "encoding/binary" "fmt" - "github.com/daeuniverse/dae/pkg/trie" "net" "net/netip" "github.com/daeuniverse/dae/common/consts" "github.com/daeuniverse/dae/component/routing" + "github.com/daeuniverse/dae/pkg/trie" ) type RoutingMatcher struct { @@ -33,6 +33,7 @@ func (m *RoutingMatcher) Match( l4proto consts.L4ProtoType, domain string, processName [16]uint8, + tos uint8, mac []byte, ) (outboundIndex consts.OutboundIndex, mark uint32, must bool, err error) { if len(sourceAddr) != net.IPv6len || len(destAddr) != net.IPv6len || len(mac) != net.IPv6len { @@ -92,6 +93,10 @@ func (m *RoutingMatcher) Match( if processName[0] != 0 && match.Value == processName { goodSubrule = true } + case consts.MatchType_Tos: + if tos == match.Value[0] { + goodSubrule = true + } case consts.MatchType_Fallback: goodSubrule = true default: diff --git a/control/tcp.go b/control/tcp.go index 0f100ba..c9f43c4 100644 --- a/control/tcp.go +++ b/control/tcp.go @@ -182,6 +182,7 @@ func (c *ControlPlane) RouteDialTcp(p *RouteDialParam) (conn netproxy.Conn, err "sniffed": domain, "ip": RefineAddrPortToShow(dst), "pid": routingResult.Pid, + "tos": routingResult.Tos, "pname": ProcessName2String(routingResult.Pname[:]), "mac": Mac2String(routingResult.Mac[:]), }).Infof("%v <-> %v", RefineSourceToShow(src, dst.Addr(), consts.LanWanFlag_NotApplicable), dialTarget) diff --git a/control/udp.go b/control/udp.go index 1a7d72a..a3a1f8e 100644 --- a/control/udp.go +++ b/control/udp.go @@ -274,6 +274,7 @@ getNew: "to": realDst.String(), "domain": domain, "pid": routingResult.Pid, + "tos": routingResult.Tos, "pname": ProcessName2String(routingResult.Pname[:]), "mac": Mac2String(routingResult.Mac[:]), "from": realSrc.String(), @@ -298,6 +299,7 @@ getNew: "domain": domain, "ip": RefineAddrPortToShow(realDst), "pid": routingResult.Pid, + "tos": routingResult.Tos, "pname": ProcessName2String(routingResult.Pname[:]), "mac": Mac2String(routingResult.Mac[:]), } diff --git a/control/utils.go b/control/utils.go index 60972f3..49c46eb 100644 --- a/control/utils.go +++ b/control/utils.go @@ -37,6 +37,7 @@ func (c *ControlPlane) Route(src, dst netip.AddrPort, domain string, l4proto con l4proto, domain, routingResult.Pname, + routingResult.Tos, append([]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, routingResult.Mac[:]...), ); err != nil { return 0, 0, false, err diff --git a/docs/en/configuration/routing.md b/docs/en/configuration/routing.md index 272dc0a..6286295 100644 --- a/docs/en/configuration/routing.md +++ b/docs/en/configuration/routing.md @@ -55,6 +55,9 @@ mac('02:42:ac:11:00:02') -> direct ### Process Name rule (only support localhost process when binding to WAN) pname(curl) -> direct +### ToS rule (match ToS/DSCP; is useful for BT bypass) +tos(4) -> direct + ### Multiple domains rule domain(keyword: google, suffix: www.twitter.com, suffix: v2raya.org) -> my_group ### Multiple IP rule diff --git a/docs/zh/configuration/routing.md b/docs/zh/configuration/routing.md index 377119c..0114f98 100644 --- a/docs/zh/configuration/routing.md +++ b/docs/zh/configuration/routing.md @@ -55,6 +55,9 @@ mac('02:42:ac:11:00:02') -> direct ### 进程名称规则(绑定WAN时仅支持本机进程) pname(curl) -> direct +### ToS规则(匹配 ToS 和 DSCP,可用于绕过 BT) +tos(4) -> direct + ### 多个域名规则 domain(keyword: google, suffix: www.twitter.com, suffix: v2raya.org) -> my_group ### 多个IP规则 diff --git a/pkg/ebpf_internal/rawsock.go b/pkg/ebpf_internal/rawsock_linux.go similarity index 100% rename from pkg/ebpf_internal/rawsock.go rename to pkg/ebpf_internal/rawsock_linux.go