Skip to content

Commit 4dd0103

Browse files
committed
feat: allow mapping multiple domain names to single ip
1 parent 89a7ae2 commit 4dd0103

File tree

3 files changed

+158
-36
lines changed

3 files changed

+158
-36
lines changed

control/control_plane.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -428,28 +428,29 @@ func NewControlPlane(
428428
if plane.dnsController, err = NewDnsController(dnsUpstream, &DnsControllerOption{
429429
Log: log,
430430
CacheAccessCallback: func(cache *DnsCache) (err error) {
431-
// Write mappings into eBPF map:
432-
// IP record (from dns lookup) -> domain routing
433-
if err = core.BatchUpdateDomainRouting(cache); err != nil {
434-
return fmt.Errorf("BatchUpdateDomainRouting: %w", err)
435-
}
436431
return nil
437432
},
438433
CacheRemoveCallback: func(cache *DnsCache) (err error) {
439434
// Write mappings into eBPF map:
440435
// IP record (from dns lookup) -> domain routing
441-
if err = core.BatchRemoveDomainRouting(cache); err != nil {
442-
return fmt.Errorf("BatchUpdateDomainRouting: %w", err)
436+
if err = core.BatchRemoveDomain(cache); err != nil {
437+
return fmt.Errorf("BatchRemoveDomain: %w", err)
443438
}
444439
return nil
445440
},
446441
NewCache: func(fqdn string, answers []dnsmessage.RR, deadline time.Time, originalDeadline time.Time) (cache *DnsCache, err error) {
447-
return &DnsCache{
442+
cache = &DnsCache{
448443
DomainBitmap: plane.routingMatcher.domainMatcher.MatchDomainBitmap(fqdn),
449444
Answer: answers,
450445
Deadline: deadline,
451446
OriginalDeadline: originalDeadline,
452-
}, nil
447+
}
448+
// Write mappings into eBPF map:
449+
// IP record (from dns lookup) -> domain routing
450+
if err = core.BatchNewDomain(cache); err != nil {
451+
return cache, fmt.Errorf("BatchNewDomain: %w", err)
452+
}
453+
return cache, nil
453454
},
454455
BestDialerChooser: plane.chooseBestDnsDialer,
455456
TimeoutExceedCallback: func(dialArgument *dialArgument, err error) {

control/control_plane_core.go

Lines changed: 122 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ type controlPlaneCore struct {
4545
isReload bool
4646
bpfEjected bool
4747

48+
domainBumpMap map[netip.Addr][]uint32
49+
domainRoutingMap map[netip.Addr][]uint32
50+
bumpMapMu sync.Mutex
51+
4852
closed context.Context
4953
close context.CancelFunc
5054
ifmgr *component.InterfaceManager
@@ -67,17 +71,19 @@ func newControlPlaneCore(log *logrus.Logger,
6771
ifmgr := component.NewInterfaceManager(log)
6872
deferFuncs = append(deferFuncs, ifmgr.Close)
6973
return &controlPlaneCore{
70-
log: log,
71-
deferFuncs: deferFuncs,
72-
bpf: bpf,
73-
outboundId2Name: outboundId2Name,
74-
kernelVersion: kernelVersion,
75-
flip: coreFlip,
76-
isReload: isReload,
77-
bpfEjected: false,
78-
ifmgr: ifmgr,
79-
closed: closed,
80-
close: toClose,
74+
log: log,
75+
deferFuncs: deferFuncs,
76+
bpf: bpf,
77+
outboundId2Name: outboundId2Name,
78+
kernelVersion: kernelVersion,
79+
flip: coreFlip,
80+
isReload: isReload,
81+
bpfEjected: false,
82+
ifmgr: ifmgr,
83+
domainBumpMap: make(map[netip.Addr][]uint32),
84+
domainRoutingMap: make(map[netip.Addr][]uint32),
85+
closed: closed,
86+
close: toClose,
8187
}
8288
}
8389

@@ -603,9 +609,8 @@ func (c *controlPlaneCore) bindDaens() (err error) {
603609
return
604610
}
605611

606-
// BatchUpdateDomainRouting update bpf map domain_routing. Since one IP may have multiple domains, this function should
607-
// be invoked every A/AAAA-record lookup.
608-
func (c *controlPlaneCore) BatchUpdateDomainRouting(cache *DnsCache) error {
612+
// BatchNewDomain update bpf map domain_bump and domain_routing. This function should be invoked every new cache.
613+
func (c *controlPlaneCore) BatchNewDomain(cache *DnsCache) error {
609614
// Parse ips from DNS resp answers.
610615
var ips []netip.Addr
611616
for _, ans := range cache.Answer {
@@ -631,27 +636,70 @@ func (c *controlPlaneCore) BatchUpdateDomainRouting(cache *DnsCache) error {
631636
// Update bpf map.
632637
// Construct keys and vals, and BpfMapBatchUpdate.
633638
var keys [][4]uint32
634-
var vals []bpfDomainRouting
639+
var vals_bump []bpfDomainRouting
640+
var vals_routing []bpfDomainRouting
641+
642+
c.bumpMapMu.Lock()
643+
defer c.bumpMapMu.Unlock()
644+
635645
for _, ip := range ips {
636646
ip6 := ip.As16()
637647
keys = append(keys, common.Ipv6ByteSliceToUint32Array(ip6[:]))
648+
638649
r := bpfDomainRouting{}
639-
if len(cache.DomainBitmap) != len(r.Bitmap) {
650+
651+
if consts.MaxMatchSetLen/32 != len(r.Bitmap) || len(cache.DomainBitmap) != len(r.Bitmap) {
640652
return fmt.Errorf("domain bitmap length not sync with kern program")
641653
}
642-
copy(r.Bitmap[:], cache.DomainBitmap)
643-
vals = append(vals, r)
654+
655+
newBumpMap, exists := c.domainBumpMap[ip]
656+
if !exists {
657+
newBumpMap = make([]uint32, consts.MaxMatchSetLen)
658+
}
659+
for index := 0; index < consts.MaxMatchSetLen; index++ {
660+
newBumpMap[index] += cache.DomainBitmap[index/32] >> (index % 32) & 1
661+
}
662+
for index, val := range newBumpMap {
663+
if val > 0 {
664+
r.Bitmap[index/32] |= 1 << (index % 32)
665+
}
666+
}
667+
c.domainBumpMap[ip] = newBumpMap
668+
vals_bump = append(vals_bump, r)
669+
670+
if !exists {
671+
// New IP, init routingMap
672+
c.domainRoutingMap[ip] = cache.DomainBitmap
673+
} else {
674+
// Old IP, Update routingMap
675+
for index := 0; index < consts.MaxMatchSetLen; index++ {
676+
if (cache.DomainBitmap[index/32]>>(index%32)&1) == 1 && (c.domainRoutingMap[ip][index/32]>>(index%32)&1) == 1 {
677+
// If this domain matches the current rule, all previous domains also match the current rule, then it still matches
678+
c.domainRoutingMap[ip][index/32] |= 1 << (index % 32)
679+
} else {
680+
// Otherwise, it does not match
681+
c.domainRoutingMap[ip][index/32] &^= 1 << (index % 32)
682+
}
683+
}
684+
}
685+
copy(r.Bitmap[:], c.domainRoutingMap[ip])
686+
vals_routing = append(vals_routing, r)
687+
}
688+
if _, err := BpfMapBatchUpdate(c.bpf.DomainBumpMap, keys, vals_bump, &ebpf.BatchOptions{
689+
ElemFlags: uint64(ebpf.UpdateAny),
690+
}); err != nil {
691+
return err
644692
}
645-
if _, err := BpfMapBatchUpdate(c.bpf.DomainRoutingMap, keys, vals, &ebpf.BatchOptions{
693+
if _, err := BpfMapBatchUpdate(c.bpf.DomainRoutingMap, keys, vals_routing, &ebpf.BatchOptions{
646694
ElemFlags: uint64(ebpf.UpdateAny),
647695
}); err != nil {
648696
return err
649697
}
650698
return nil
651699
}
652700

653-
// BatchRemoveDomainRouting remove bpf map domain_routing.
654-
func (c *controlPlaneCore) BatchRemoveDomainRouting(cache *DnsCache) error {
701+
// BatchRemoveDomainBump update or remove bpf map domain_bump and domain_routing.
702+
func (c *controlPlaneCore) BatchRemoveDomain(cache *DnsCache) error {
655703
// Parse ips from DNS resp answers.
656704
var ips []netip.Addr
657705
for _, ans := range cache.Answer {
@@ -675,15 +723,64 @@ func (c *controlPlaneCore) BatchRemoveDomainRouting(cache *DnsCache) error {
675723
}
676724

677725
// Update bpf map.
678-
// Construct keys and vals, and BpfMapBatchUpdate.
679-
var keys [][4]uint32
726+
// Update and determine whether to delete
727+
var keys_del [][4]uint32
728+
var keys_modify [][4]uint32
729+
var vals_modify_bump []bpfDomainRouting
730+
var vals_modify_routing []bpfDomainRouting
731+
732+
c.bumpMapMu.Lock()
733+
defer c.bumpMapMu.Unlock()
734+
680735
for _, ip := range ips {
681736
ip6 := ip.As16()
682-
keys = append(keys, common.Ipv6ByteSliceToUint32Array(ip6[:]))
737+
newBumpMapVal := c.domainBumpMap[ip]
738+
for index := 0; index < consts.MaxMatchSetLen; index++ {
739+
newBumpMapVal[index] -= cache.DomainBitmap[index/32] >> (index % 32) & 1
740+
}
741+
742+
bumpMap := bpfDomainRouting{}
743+
routingMap := bpfDomainRouting{}
744+
copy(routingMap.Bitmap[:], c.domainRoutingMap[ip])
745+
746+
del := true
747+
for index, val := range newBumpMapVal {
748+
if val > 0 {
749+
del = false // This IP refers to some domain name that matches the domain_set, so there is no need to delete
750+
bumpMap.Bitmap[index/32] |= 1 << (index % 32)
751+
} else {
752+
// This IP no longer refers to any domain name that matches the domain_set
753+
routingMap.Bitmap[index/32] &^= 1 << (index % 32)
754+
}
755+
}
756+
if del {
757+
delete(c.domainBumpMap, ip)
758+
delete(c.domainRoutingMap, ip)
759+
keys_del = append(keys_del, common.Ipv6ByteSliceToUint32Array(ip6[:]))
760+
} else {
761+
c.domainBumpMap[ip] = newBumpMapVal
762+
keys_modify = append(keys_modify, common.Ipv6ByteSliceToUint32Array(ip6[:]))
763+
vals_modify_bump = append(vals_modify_bump, bumpMap)
764+
vals_modify_routing = append(vals_modify_routing, routingMap)
765+
}
766+
}
767+
if _, err := BpfMapBatchDelete(c.bpf.DomainBumpMap, keys_del); err != nil {
768+
return err
683769
}
684-
if _, err := BpfMapBatchDelete(c.bpf.DomainRoutingMap, keys); err != nil {
770+
if _, err := BpfMapBatchDelete(c.bpf.DomainRoutingMap, keys_del); err != nil {
685771
return err
686772
}
773+
if _, err := BpfMapBatchUpdate(c.bpf.DomainBumpMap, keys_modify, vals_modify_bump, &ebpf.BatchOptions{
774+
ElemFlags: uint64(ebpf.UpdateAny),
775+
}); err != nil {
776+
return err
777+
}
778+
if _, err := BpfMapBatchUpdate(c.bpf.DomainRoutingMap, keys_modify, vals_modify_routing, &ebpf.BatchOptions{
779+
ElemFlags: uint64(ebpf.UpdateAny),
780+
}); err != nil {
781+
return err
782+
}
783+
687784
return nil
688785
}
689786

control/kern/tproxy.c

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,15 @@ struct {
342342
// __uint(pinning, LIBBPF_PIN_BY_NAME);
343343
} domain_routing_map SEC(".maps");
344344

345+
struct {
346+
__uint(type, BPF_MAP_TYPE_LRU_HASH);
347+
__type(key, __be32[4]);
348+
__type(value, struct domain_routing);
349+
__uint(max_entries, MAX_DOMAIN_ROUTING_NUM);
350+
/// NOTICE: No persistence.
351+
// __uint(pinning, LIBBPF_PIN_BY_NAME);
352+
} domain_bump_map SEC(".maps");
353+
345354
struct ip_port_proto {
346355
__u32 ip[4];
347356
__be16 port;
@@ -650,6 +659,8 @@ static int route_loop_cb(__u32 index, void *data)
650659
// proxy Subrule is like: domain(suffix:baidu.com, suffix:google.com) Match
651660
// set is like: suffix:baidu.com
652661
struct domain_routing *domain_routing;
662+
struct domain_routing *domain_bump;
663+
bool need_control_plane_routing = false;
653664

654665
if (unlikely(index / 32 >= MAX_MATCH_SET_LEN / 32)) {
655666
ctx->result = -EFAULT;
@@ -756,8 +767,21 @@ static int route_loop_cb(__u32 index, void *data)
756767

757768
// We use key instead of k to pass checker.
758769
if (domain_routing &&
759-
(domain_routing->bitmap[index / 32] >> (index % 32)) & 1)
770+
(domain_routing->bitmap[index / 32] >> (index % 32)) & 1) {
771+
// All domains mapeed by the current IP address are matched.
760772
ctx->goodsubrule = true;
773+
} else {
774+
// Get domain bump bitmap.
775+
domain_bump = bpf_map_lookup_elem(&domain_bump_map,
776+
ctx->params->daddr);
777+
if (domain_bump &&
778+
(domain_bump->bitmap[index / 32] >> (index % 32)) & 1) {
779+
ctx->goodsubrule = true;
780+
// The current IP has mapped domains that match this rule, but not all of them do.
781+
// jump to control plane.
782+
need_control_plane_routing = true;
783+
}
784+
}
761785
break;
762786
case MatchType_ProcessName:
763787
#ifdef __DEBUG_ROUTING
@@ -833,7 +857,7 @@ static int route_loop_cb(__u32 index, void *data)
833857
} else {
834858
bool must = ctx->must || match_set->must;
835859

836-
if (!must && ctx->isdns) {
860+
if ((!must && ctx->isdns) || need_control_plane_routing) {
837861
ctx->result =
838862
(__s64)OUTBOUND_CONTROL_PLANE_ROUTING |
839863
((__s64)match_set->mark << 8) |

0 commit comments

Comments
 (0)