feat: support must_xxx outbounds

This commit is contained in:
mzz2017 2023-04-02 11:07:53 +08:00
parent 3e55f85d91
commit 006b7fbfd2
18 changed files with 115 additions and 62 deletions

View File

@ -64,14 +64,13 @@ const (
OutboundDirect OutboundIndex = iota
OutboundBlock
OutboundMustDirect OutboundIndex = 0xFC
OutboundControlPlaneRouting OutboundIndex = 0xFD
OutboundLogicalOr OutboundIndex = 0xFE
OutboundLogicalAnd OutboundIndex = 0xFF
OutboundLogicalMask OutboundIndex = 0xFE
OutboundMax = OutboundLogicalAnd
OutboundUserDefinedMax = OutboundMustDirect - 1
OutboundUserDefinedMax = OutboundControlPlaneRouting - 1
)
func (i OutboundIndex) String() string {
@ -80,8 +79,6 @@ func (i OutboundIndex) String() string {
return "direct"
case OutboundBlock:
return "block"
case OutboundMustDirect:
return "must_direct"
case OutboundControlPlaneRouting:
return "<Control Plane Routing>"
case OutboundLogicalOr:

View File

@ -112,6 +112,12 @@ func (b *RequestMatcherBuilder) addFallback(fallbackOutbound config.FunctionOrSt
if err != nil {
return err
}
if upstream.Must {
return fmt.Errorf("unsupported param: must")
}
if upstream.Mark != 0 {
return fmt.Errorf("unsupported param: mark")
}
upstreamId, err := b.upstreamToId(upstream.Name)
if err != nil {
return err

View File

@ -195,6 +195,12 @@ func (b *ResponseMatcherBuilder) addFallback(fallbackOutbound config.FunctionOrS
if err != nil {
return err
}
if upstream.Must {
return fmt.Errorf("unsupported param: must")
}
if upstream.Mark != 0 {
return fmt.Errorf("unsupported param: mark")
}
upstreamId, err := b.upstreamToId(upstream.Name)
if err != nil {
return err

View File

@ -7,11 +7,11 @@ package domain_matcher
import (
"fmt"
"github.com/sirupsen/logrus"
"github.com/daeuniverse/dae/common/consts"
"github.com/daeuniverse/dae/component/routing"
"github.com/daeuniverse/dae/config"
"github.com/daeuniverse/dae/pkg/config_parser"
"github.com/sirupsen/logrus"
"hash/fnv"
"math/rand"
"reflect"
@ -132,7 +132,7 @@ func getDomain() (simulatedDomainSet []routing.DomainSet, err error) {
sections, err := config_parser.Parse(`
routing {
domain(geosite:bing)->us
domain(full:dns.google) -> direct
domain(full:dns.google.com) -> direct
domain(geosite:category-ads-all) -> block
domain(geosite:cn) -> direct
}`)

View File

@ -7,9 +7,9 @@ package routing
import (
"fmt"
"github.com/sirupsen/logrus"
"github.com/daeuniverse/dae/common/consts"
"github.com/daeuniverse/dae/pkg/config_parser"
"github.com/sirupsen/logrus"
"strconv"
)
@ -22,6 +22,7 @@ type DomainSet struct {
type Outbound struct {
Name string
Mark uint32
Must bool
}
type RulesBuilder struct {
@ -62,6 +63,7 @@ func (b *RulesBuilder) Apply(rules []*config_parser.RoutingRule) (err error) {
overrideOutbound := &Outbound{
Name: consts.OutboundLogicalOr.String(),
Mark: outbound.Mark,
Must: outbound.Must,
}
if jMatchSet == len(keyOrder)-1 {
overrideOutbound.Name = consts.OutboundLogicalAnd.String()
@ -103,6 +105,7 @@ func ParseOutbound(rawOutbound *config_parser.Function) (outbound *Outbound, err
outbound = &Outbound{
Name: rawOutbound.Name,
Mark: 0,
Must: false,
}
for _, p := range rawOutbound.Params {
switch p.Key {
@ -113,8 +116,14 @@ func ParseOutbound(rawOutbound *config_parser.Function) (outbound *Outbound, err
return nil, fmt.Errorf("failed to parse mark: %v", err)
}
outbound.Mark = uint32(_mark)
case "":
if p.Val == "must" {
outbound.Must = true
} else {
return nil, fmt.Errorf("unknown outbound param: %v", p.Val)
}
default:
return nil, fmt.Errorf("unknown outbound param: %v", p.Key)
return nil, fmt.Errorf("unknown outbound param key: %v", p.Key)
}
}
return outbound, nil

View File

@ -18,7 +18,7 @@ type Global struct {
// We use DirectTcpCheckUrl to check (tcp)*(ipv4/ipv6) connectivity for direct.
//DirectTcpCheckUrl string `mapstructure:"direct_tcp_check_url" default:"http://www.qualcomm.cn/generate_204"`
TcpCheckUrl string `mapstructure:"tcp_check_url" default:"http://keep-alv.google.com/generate_204"`
UdpCheckDns string `mapstructure:"udp_check_dns" default:"dns.google:53"`
UdpCheckDns string `mapstructure:"udp_check_dns" default:"dns.google.com:53"`
CheckInterval time.Duration `mapstructure:"check_interval" default:"30s"`
CheckTolerance time.Duration `mapstructure:"check_tolerance" default:"0"`
LanInterface []string `mapstructure:"lan_interface"`

View File

@ -7,12 +7,15 @@ package config
import (
"github.com/daeuniverse/dae/common/consts"
"github.com/daeuniverse/dae/pkg/config_parser"
"strings"
)
type patch func(params *Config) error
var patches = []patch{
patchEmptyDns,
patchMustOutbound,
}
func patchEmptyDns(params *Config) error {
@ -24,3 +27,22 @@ func patchEmptyDns(params *Config) error {
}
return nil
}
func patchMustOutbound(params *Config) error {
for i := range params.Routing.Rules {
if strings.HasPrefix(params.Routing.Rules[i].Outbound.Name, "must_") {
params.Routing.Rules[i].Outbound.Name = strings.TrimPrefix(params.Routing.Rules[i].Outbound.Name, "must_")
params.Routing.Rules[i].Outbound.Params = append(params.Routing.Rules[i].Outbound.Params, &config_parser.Param{
Val: "must",
})
}
}
if f := FunctionOrStringToFunction(params.Routing.Fallback); strings.HasPrefix(f.Name, "must_") {
f.Name = strings.TrimPrefix(f.Name, "must_")
f.Params = append(f.Params, &config_parser.Param{
Val: "must",
})
params.Routing.Fallback = f
}
return nil
}

View File

@ -757,10 +757,6 @@ func (c *ControlPlane) chooseBestDnsDialer(
if err != nil {
return nil, err
}
// Already "must direct".
if outboundIndex == consts.OutboundMustDirect {
outboundIndex = consts.OutboundDirect
}
if int(outboundIndex) >= len(c.outbounds) {
return nil, fmt.Errorf("bad outbound index: %v", outboundIndex)
}

View File

@ -60,7 +60,6 @@
#define OUTBOUND_DIRECT 0
#define OUTBOUND_BLOCK 1
#define OUTBOUND_MUST_DIRECT 0xFC
#define OUTBOUND_CONTROL_PLANE_ROUTING 0xFD
#define OUTBOUND_LOGICAL_OR 0xFE
#define OUTBOUND_LOGICAL_AND 0xFF
@ -135,6 +134,7 @@ struct ip_port {
struct routing_result {
__u32 mark;
__u8 must;
__u8 mac[6];
__u8 outbound;
__u8 pname[TASK_COMM_LEN];
@ -307,7 +307,7 @@ struct match_set {
bool not ; // A subrule flag (this is not a match_set flag).
enum MatchType type;
__u8 outbound; // User-defined value range is [0, 252].
__u8 unused;
bool must;
__u32 mark;
};
struct {
@ -1133,11 +1133,13 @@ routing(const __u32 flag[6], const void *l4hdr, const __be32 saddr[4],
// DNS requests should routed by control plane if outbound is not
// must_direct.
if (match_set->outbound != OUTBOUND_MUST_DIRECT && h_dport == 53 &&
if (!match_set->must && h_dport == 53 &&
_l4proto_type == L4ProtoType_UDP) {
return OUTBOUND_CONTROL_PLANE_ROUTING | (match_set->mark << 8);
return (__s64)OUTBOUND_CONTROL_PLANE_ROUTING |
((__s64)match_set->mark << 8) | ((__s64)match_set->must << 40);
}
return match_set->outbound | (match_set->mark << 8);
return (__s64)match_set->outbound | ((__s64)match_set->mark << 8) |
((__s64)match_set->must << 40);
}
bad_rule = false;
}
@ -1325,14 +1327,16 @@ new_connection:
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, l4hdr, tuples.sip.u6_addr32, tuples.dip.u6_addr32,
mac)) < 0) {
bpf_printk("shot routing: %d", ret);
__s64 s64_ret;
if ((s64_ret = routing(flag, l4hdr, tuples.sip.u6_addr32,
tuples.dip.u6_addr32, mac)) < 0) {
bpf_printk("shot routing: %d", s64_ret);
return TC_ACT_SHOT;
}
struct routing_result routing_result = {0};
routing_result.outbound = ret;
routing_result.mark = ret >> 8;
routing_result.outbound = s64_ret;
routing_result.mark = s64_ret >> 8;
routing_result.must = (s64_ret >> 40) & 1;
__builtin_memcpy(routing_result.mac, ethh.h_source,
sizeof(routing_result.mac));
/// NOTICE: No pid pname info for LAN packet.
@ -1361,8 +1365,7 @@ new_connection:
bpf_ntohs(tuples.dport));
}
#endif
if (routing_result.outbound == OUTBOUND_DIRECT ||
routing_result.outbound == OUTBOUND_MUST_DIRECT) {
if (routing_result.outbound == OUTBOUND_DIRECT) {
skb->mark = routing_result.mark;
goto direct;
} else if (unlikely(routing_result.outbound == OUTBOUND_BLOCK)) {
@ -1593,6 +1596,7 @@ int tproxy_wan_egress(struct __sk_buff *skb) {
__builtin_memcpy(&key_src.ip, &tuples.dip, IPV6_BYTE_LENGTH);
key_src.port = tcph.source;
__u8 outbound;
bool must;
__u32 mark;
struct pid_pname *pid_pname = NULL;
if (unlikely(tcp_state_syn)) {
@ -1618,14 +1622,16 @@ int tproxy_wan_egress(struct __sk_buff *skb) {
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, tuples.sip.u6_addr32,
tuples.dip.u6_addr32, mac)) < 0) {
bpf_printk("shot routing: %d", ret);
__s64 s64_ret;
if ((s64_ret = routing(flag, &tcph, tuples.sip.u6_addr32,
tuples.dip.u6_addr32, mac)) < 0) {
bpf_printk("shot routing: %d", s64_ret);
return TC_ACT_SHOT;
}
outbound = ret;
mark = ret >> 8;
outbound = s64_ret;
mark = s64_ret >> 8;
must = (s64_ret >> 40) & 1;
#if defined(__DEBUG_ROUTING) || defined(__PRINT_ROUTING_RESULT)
// Print only new connection.
@ -1646,9 +1652,10 @@ int tproxy_wan_egress(struct __sk_buff *skb) {
}
outbound = dst->routing_result.outbound;
mark = dst->routing_result.mark;
must = dst->routing_result.must;
}
if ((outbound == OUTBOUND_DIRECT || outbound == OUTBOUND_MUST_DIRECT) &&
if (outbound == OUTBOUND_DIRECT &&
mark == 0 // If mark is not zero, we should re-route it, so we send it
// to control plane in WAN.
) {
@ -1678,6 +1685,7 @@ int tproxy_wan_egress(struct __sk_buff *skb) {
routing_info.port = tcph.dest;
routing_info.routing_result.outbound = outbound;
routing_info.routing_result.mark = mark;
routing_info.routing_result.must = must;
__builtin_memcpy(routing_info.routing_result.mac, ethh.h_source,
sizeof(ethh.h_source));
if (pid_pname) {
@ -1726,9 +1734,10 @@ int tproxy_wan_egress(struct __sk_buff *skb) {
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, tuples.sip.u6_addr32,
tuples.dip.u6_addr32, mac)) < 0) {
bpf_printk("shot routing: %d", ret);
__s64 s64_ret;
if ((s64_ret = routing(flag, &udph, tuples.sip.u6_addr32,
tuples.dip.u6_addr32, mac)) < 0) {
bpf_printk("shot routing: %d", s64_ret);
return TC_ACT_SHOT;
}
// Construct new hdr to encap.
@ -1736,8 +1745,9 @@ int tproxy_wan_egress(struct __sk_buff *skb) {
__builtin_memset(&new_hdr, 0, sizeof(new_hdr));
__builtin_memcpy(new_hdr.ip, &tuples.dip, IPV6_BYTE_LENGTH);
new_hdr.port = udph.dest;
new_hdr.routing_result.outbound = ret;
new_hdr.routing_result.mark = ret >> 8;
new_hdr.routing_result.outbound = s64_ret;
new_hdr.routing_result.mark = s64_ret >> 8;
new_hdr.routing_result.must = (s64_ret >> 40) & 1;
__builtin_memcpy(new_hdr.routing_result.mac, ethh.h_source,
sizeof(ethh.h_source));
if (pid_pname) {
@ -1754,8 +1764,7 @@ int tproxy_wan_egress(struct __sk_buff *skb) {
bpf_ntohs(tuples.dport));
#endif
if ((new_hdr.routing_result.outbound == OUTBOUND_DIRECT ||
new_hdr.routing_result.outbound == OUTBOUND_MUST_DIRECT) &&
if (new_hdr.routing_result.outbound == OUTBOUND_DIRECT &&
new_hdr.routing_result.mark ==
0 // If mark is not zero, we should re-route it, so we
// send it to control plane in WAN.

View File

@ -56,8 +56,6 @@ func NewRoutingMatcherBuilder(log *logrus.Logger, rules []*config_parser.Routing
func (b *RoutingMatcherBuilder) outboundToId(outbound string) (uint8, error) {
var outboundId uint8
switch outbound {
case consts.OutboundMustDirect.String():
outboundId = uint8(consts.OutboundMustDirect)
case consts.OutboundLogicalOr.String():
outboundId = uint8(consts.OutboundLogicalOr)
case consts.OutboundLogicalAnd.String():
@ -95,6 +93,7 @@ func (b *RoutingMatcherBuilder) addDomain(f *config_parser.Function, key string,
Not: f.Not,
Outbound: outboundId,
Mark: outbound.Mark,
Must: outbound.Must,
})
return nil
}
@ -119,6 +118,7 @@ func (b *RoutingMatcherBuilder) addSourceMac(f *config_parser.Function, macAddrs
Not: f.Not,
Outbound: outboundId,
Mark: outbound.Mark,
Must: outbound.Must,
}
binary.LittleEndian.PutUint32(set.Value[:], uint32(lpmTrieIndex))
b.rules = append(b.rules, set)
@ -138,6 +138,7 @@ func (b *RoutingMatcherBuilder) addIp(f *config_parser.Function, values []netip.
Not: f.Not,
Outbound: outboundId,
Mark: outbound.Mark,
Must: outbound.Must,
}
binary.LittleEndian.PutUint32(set.Value[:], uint32(lpmTrieIndex))
b.rules = append(b.rules, set)
@ -163,6 +164,7 @@ func (b *RoutingMatcherBuilder) addPort(f *config_parser.Function, values [][2]u
Not: f.Not,
Outbound: outboundId,
Mark: outbound.Mark,
Must: outbound.Must,
})
}
return nil
@ -181,6 +183,7 @@ func (b *RoutingMatcherBuilder) addSourceIp(f *config_parser.Function, values []
Not: f.Not,
Outbound: outboundId,
Mark: outbound.Mark,
Must: outbound.Must,
}
binary.LittleEndian.PutUint32(set.Value[:], uint32(lpmTrieIndex))
b.rules = append(b.rules, set)
@ -206,6 +209,7 @@ func (b *RoutingMatcherBuilder) addSourcePort(f *config_parser.Function, values
Not: f.Not,
Outbound: outboundId,
Mark: outbound.Mark,
Must: outbound.Must,
})
}
return nil
@ -222,6 +226,7 @@ func (b *RoutingMatcherBuilder) addL4Proto(f *config_parser.Function, values con
Not: f.Not,
Outbound: outboundId,
Mark: outbound.Mark,
Must: outbound.Must,
})
return nil
}
@ -237,6 +242,7 @@ func (b *RoutingMatcherBuilder) addIpVersion(f *config_parser.Function, values c
Not: f.Not,
Outbound: outboundId,
Mark: outbound.Mark,
Must: outbound.Must,
})
return nil
}
@ -256,6 +262,7 @@ func (b *RoutingMatcherBuilder) addProcessName(f *config_parser.Function, values
Not: f.Not,
Outbound: outboundId,
Mark: outbound.Mark,
Must: outbound.Must,
}
copy(matchSet.Value[:], value[:])
b.rules = append(b.rules, matchSet)
@ -276,6 +283,7 @@ func (b *RoutingMatcherBuilder) addFallback(fallbackOutbound config.FunctionOrSt
Type: uint8(consts.MatchType_Fallback),
Outbound: outboundId,
Mark: outbound.Mark,
Must: outbound.Must,
})
return nil
}

View File

@ -70,8 +70,6 @@ func (c *ControlPlane) handleConn(lConn net.Conn) (err error) {
switch outboundIndex {
case consts.OutboundDirect:
case consts.OutboundMustDirect:
outboundIndex = consts.OutboundDirect
case consts.OutboundControlPlaneRouting:
if outboundIndex, routingResult.Mark, err = c.Route(src, dst, domain, consts.L4ProtoType_TCP, routingResult); err != nil {
return err

View File

@ -143,11 +143,11 @@ func (c *ControlPlane) handlePkt(lConn *net.UDPConn, data []byte, src, pktDst, r
outboundIndex = consts.OutboundControlPlaneRouting
}
if routingResult.Must > 0 {
isDns = false // Regard as plain traffic.
}
switch outboundIndex {
case consts.OutboundDirect:
case consts.OutboundMustDirect:
outboundIndex = consts.OutboundDirect
isDns = false // Regard as plain traffic.
case consts.OutboundControlPlaneRouting:
if isDns {
// Routing of DNS packets are managed by DNS controller.

View File

@ -15,7 +15,7 @@ dns {
# If dial_mode is "ip", the upstream DNS answer SHOULD NOT be polluted, so domestic public DNS is not recommended.
alidns: 'udp://dns.alidns.com:53'
googledns: 'tcp+udp://dns.google:53'
googledns: 'tcp+udp://dns.google.com:53'
}
# The routing format of 'request' and 'response' is similar with section 'routing'.
# See https://github.com/daeuniverse/dae/blob/main/docs/routing.md
@ -64,7 +64,7 @@ dns {
# Use alidns for China mainland domains and googledns for others.
dns {
upstream {
googledns: 'tcp+udp://dns.google:53'
googledns: 'tcp+udp://dns.google.com:53'
alidns: 'udp://dns.alidns.com:53'
}
routing {
@ -84,7 +84,7 @@ dns {
# Use alidns for all DNS queries and fallback to googledns if pollution result detected.
dns {
upstream {
googledns: 'tcp+udp://dns.google:53'
googledns: 'tcp+udp://dns.google.com:53'
alidns: 'udp://dns.alidns.com:53'
}
routing {

View File

@ -137,7 +137,7 @@ subscription {
# See https://github.com/daeuniverse/dae/blob/main/docs/dns.md for full examples.
dns {
upstream {
googledns: 'tcp+udp://dns.google:53'
googledns: 'tcp+udp://dns.google.com:53'
alidns: 'udp://dns.alidns.com:53'
}
routing {

View File

@ -133,7 +133,7 @@ subscription {
# 更多的 DNS 样例见 https://github.com/daeuniverse/dae/blob/main/docs/dns.md
dns {
upstream {
googledns: 'tcp+udp://dns.google:53'
googledns: 'tcp+udp://dns.google.com:53'
alidns: 'udp://dns.alidns.com:53'
}
routing {
@ -174,7 +174,7 @@ routing {
```shell
dns {
upstream {
googledns: 'tcp+udp://dns.google:53'
googledns: 'tcp+udp://dns.google.com:53'
alidns: 'udp://dns.alidns.com:53'
}
routing {

View File

@ -7,13 +7,13 @@ If you use a external DNS like AdguardHome, you could refer to the following gui
## External DNS on localhost
If you set up a external DNS on localhost, you may want to let the DNS queries to dns.google proxied. For example, if you have following configuration in AdguardHome:
If you set up a external DNS on localhost, you may want to let the DNS queries to dns.google.com proxied. For example, if you have following configuration in AdguardHome:
```
Listen on: the same machine with dae, port 53.
China mainland: udp://223.5.5.5:53
Others: https://dns.google/dns-query
Others: https://dns.google.com/dns-query
```
You should configure dae as follows:
@ -26,7 +26,7 @@ You should configure dae as follows:
pname(AdGuardHome) && l4proto(udp) && dport(53) -> must_direct
```
And make sure domain `dns.google` will be proxied in routing rules.
And make sure domain `dns.google.com` will be proxied in routing rules.
3. Add upstream and request to section "dns".
@ -51,13 +51,13 @@ You should configure dae as follows:
## External DNS on another machine in LAN
If you set up a external DNS on another machine in LAN, you may want to let the DNS queries to dns.google proxied. For example, if you have following configuration in AdguardHome:
If you set up a external DNS on another machine in LAN, you may want to let the DNS queries to dns.google.com proxied. For example, if you have following configuration in AdguardHome:
```
Listen on: 192.168.30.3:53 (mac address: 8c:16:45:36:1c:5a)
China mainland: udp://223.5.5.5:53
Others: https://dns.google/dns-query
Others: https://dns.google.com/dns-query
```
You should configure dae as follows:
@ -72,7 +72,7 @@ You should configure dae as follows:
# mac(8c:16:45:36:1c:5a) && l4proto(udp) && dport(53) -> must_direct
```
And make sure domain `dns.google` will be proxied in routing rules.
And make sure domain `dns.google.com` will be proxied in routing rules.
3. Add upstream and request to section "dns".

View File

@ -3,9 +3,11 @@
## Examples:
```shell
### Built-in outbounds: block, direct, must_direct
# The difference between "direct" and "must_direct" is that "direct" will intercept and process DNS request (for traffic
### Built-in outbounds: block, direct
# The difference between "direct" and "must_direct" is that "direct" will hijack and process DNS request (for traffic
# split use), but "must_direct" will not. "must_direct" is useful when there are traffic loops of DNS requests.
# "must_direct" can be written as "direct(must)".
# Similarly, "must_groupname" is also supported to NOT hijack and process DNS traffic, which equals to "groupname(must)".
### fallback outbound
# If no rule matches, traffic will go through the outbound defined by fallback.
@ -14,7 +16,7 @@ fallback: my_group
### Domain rule
domain(suffix: v2raya.org) -> my_group
# equals to domain(v2raya.org) -> my_group
domain(full: dns.google) -> my_group
domain(full: dns.google.com) -> my_group
domain(keyword: facebook) -> my_group
domain(regexp: '\.goo.*\.com$') -> my_group
domain(geosite:category-ads) -> block

View File

@ -15,7 +15,7 @@ global {
# This DNS will be used to check UDP connectivity of nodes. And if dns_upstream below contains tcp, it also be used to check
# TCP DNS connectivity of nodes.
# This DNS should have both IPv4 and IPv6 if you have double stack in local.
udp_check_dns: 'dns.google:53'
udp_check_dns: 'dns.google.com:53'
check_interval: 30s
@ -87,7 +87,7 @@ dns {
# If dial_mode is "ip", the upstream DNS answer SHOULD NOT be polluted, so domestic public DNS is not recommended.
alidns: 'udp://dns.alidns.com:53'
googledns: 'tcp+udp://dns.google:53'
googledns: 'tcp+udp://dns.google.com:53'
}
routing {
# According to the request of dns query, decide to use which DNS upstream.