Skip to content

feat: allow mapping multiple domain names to single ip #763

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions control/control_plane.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,28 +428,29 @@ func NewControlPlane(
if plane.dnsController, err = NewDnsController(dnsUpstream, &DnsControllerOption{
Log: log,
CacheAccessCallback: func(cache *DnsCache) (err error) {
// Write mappings into eBPF map:
// IP record (from dns lookup) -> domain routing
if err = core.BatchUpdateDomainRouting(cache); err != nil {
return fmt.Errorf("BatchUpdateDomainRouting: %w", err)
}
return nil
},
CacheRemoveCallback: func(cache *DnsCache) (err error) {
// Write mappings into eBPF map:
// IP record (from dns lookup) -> domain routing
if err = core.BatchRemoveDomainRouting(cache); err != nil {
return fmt.Errorf("BatchUpdateDomainRouting: %w", err)
if err = core.BatchRemoveDomain(cache); err != nil {
return fmt.Errorf("BatchRemoveDomain: %w", err)
}
return nil
},
NewCache: func(fqdn string, answers []dnsmessage.RR, deadline time.Time, originalDeadline time.Time) (cache *DnsCache, err error) {
return &DnsCache{
cache = &DnsCache{
DomainBitmap: plane.routingMatcher.domainMatcher.MatchDomainBitmap(fqdn),
Answer: answers,
Deadline: deadline,
OriginalDeadline: originalDeadline,
}, nil
}
// Write mappings into eBPF map:
// IP record (from dns lookup) -> domain routing
if err = core.BatchNewDomain(cache); err != nil {
return cache, fmt.Errorf("BatchNewDomain: %w", err)
}
return cache, nil
},
BestDialerChooser: plane.chooseBestDnsDialer,
TimeoutExceedCallback: func(dialArgument *dialArgument, err error) {
Expand Down
147 changes: 122 additions & 25 deletions control/control_plane_core.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ type controlPlaneCore struct {
isReload bool
bpfEjected bool

domainBumpMap map[netip.Addr][]uint32
domainRoutingMap map[netip.Addr][]uint32
bumpMapMu sync.Mutex

closed context.Context
close context.CancelFunc
ifmgr *component.InterfaceManager
Expand All @@ -67,17 +71,19 @@ func newControlPlaneCore(log *logrus.Logger,
ifmgr := component.NewInterfaceManager(log)
deferFuncs = append(deferFuncs, ifmgr.Close)
return &controlPlaneCore{
log: log,
deferFuncs: deferFuncs,
bpf: bpf,
outboundId2Name: outboundId2Name,
kernelVersion: kernelVersion,
flip: coreFlip,
isReload: isReload,
bpfEjected: false,
ifmgr: ifmgr,
closed: closed,
close: toClose,
log: log,
deferFuncs: deferFuncs,
bpf: bpf,
outboundId2Name: outboundId2Name,
kernelVersion: kernelVersion,
flip: coreFlip,
isReload: isReload,
bpfEjected: false,
ifmgr: ifmgr,
domainBumpMap: make(map[netip.Addr][]uint32),
domainRoutingMap: make(map[netip.Addr][]uint32),
closed: closed,
close: toClose,
}
}

Expand Down Expand Up @@ -603,9 +609,8 @@ func (c *controlPlaneCore) bindDaens() (err error) {
return
}

// BatchUpdateDomainRouting update bpf map domain_routing. Since one IP may have multiple domains, this function should
// be invoked every A/AAAA-record lookup.
func (c *controlPlaneCore) BatchUpdateDomainRouting(cache *DnsCache) error {
// BatchNewDomain update bpf map domain_bump and domain_routing. This function should be invoked every new cache.
func (c *controlPlaneCore) BatchNewDomain(cache *DnsCache) error {
// Parse ips from DNS resp answers.
var ips []netip.Addr
for _, ans := range cache.Answer {
Expand All @@ -631,27 +636,70 @@ func (c *controlPlaneCore) BatchUpdateDomainRouting(cache *DnsCache) error {
// Update bpf map.
// Construct keys and vals, and BpfMapBatchUpdate.
var keys [][4]uint32
var vals []bpfDomainRouting
var vals_bump []bpfDomainRouting
var vals_routing []bpfDomainRouting

c.bumpMapMu.Lock()
defer c.bumpMapMu.Unlock()

for _, ip := range ips {
ip6 := ip.As16()
keys = append(keys, common.Ipv6ByteSliceToUint32Array(ip6[:]))

r := bpfDomainRouting{}
if len(cache.DomainBitmap) != len(r.Bitmap) {

if consts.MaxMatchSetLen/32 != len(r.Bitmap) || len(cache.DomainBitmap) != len(r.Bitmap) {
return fmt.Errorf("domain bitmap length not sync with kern program")
}
copy(r.Bitmap[:], cache.DomainBitmap)
vals = append(vals, r)

newBumpMap, exists := c.domainBumpMap[ip]
if !exists {
newBumpMap = make([]uint32, consts.MaxMatchSetLen)
}
for index := 0; index < consts.MaxMatchSetLen; index++ {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

似乎有点浪费 cpu 了,大部分用户的规则都不会超过 100 条吧,这里 MaxMatchSetLen 是 1024,可以做个短路 break

newBumpMap[index] += cache.DomainBitmap[index/32] >> (index % 32) & 1
}
for index, val := range newBumpMap {
if val > 0 {
r.Bitmap[index/32] |= 1 << (index % 32)
}
}
c.domainBumpMap[ip] = newBumpMap
vals_bump = append(vals_bump, r)

if !exists {
// New IP, init routingMap
c.domainRoutingMap[ip] = cache.DomainBitmap
} else {
// Old IP, Update routingMap
for index := 0; index < consts.MaxMatchSetLen; index++ {
if (cache.DomainBitmap[index/32]>>(index%32)&1) == 1 && (c.domainRoutingMap[ip][index/32]>>(index%32)&1) == 1 {
// If this domain matches the current rule, all previous domains also match the current rule, then it still matches
c.domainRoutingMap[ip][index/32] |= 1 << (index % 32)
} else {
// Otherwise, it does not match
c.domainRoutingMap[ip][index/32] &^= 1 << (index % 32)
}
}
}
copy(r.Bitmap[:], c.domainRoutingMap[ip])
vals_routing = append(vals_routing, r)
}
if _, err := BpfMapBatchUpdate(c.bpf.DomainBumpMap, keys, vals_bump, &ebpf.BatchOptions{
ElemFlags: uint64(ebpf.UpdateAny),
}); err != nil {
return err
}
if _, err := BpfMapBatchUpdate(c.bpf.DomainRoutingMap, keys, vals, &ebpf.BatchOptions{
if _, err := BpfMapBatchUpdate(c.bpf.DomainRoutingMap, keys, vals_routing, &ebpf.BatchOptions{
ElemFlags: uint64(ebpf.UpdateAny),
}); err != nil {
return err
}
return nil
}

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

// Update bpf map.
// Construct keys and vals, and BpfMapBatchUpdate.
var keys [][4]uint32
// Update and determine whether to delete
var keys_del [][4]uint32
var keys_modify [][4]uint32
var vals_modify_bump []bpfDomainRouting
var vals_modify_routing []bpfDomainRouting

c.bumpMapMu.Lock()
defer c.bumpMapMu.Unlock()

for _, ip := range ips {
ip6 := ip.As16()
keys = append(keys, common.Ipv6ByteSliceToUint32Array(ip6[:]))
newBumpMapVal := c.domainBumpMap[ip]
for index := 0; index < consts.MaxMatchSetLen; index++ {
newBumpMapVal[index] -= cache.DomainBitmap[index/32] >> (index % 32) & 1
}

bumpMap := bpfDomainRouting{}
routingMap := bpfDomainRouting{}
copy(routingMap.Bitmap[:], c.domainRoutingMap[ip])

del := true
for index, val := range newBumpMapVal {
if val > 0 {
del = false // This IP refers to some domain name that matches the domain_set, so there is no need to delete
bumpMap.Bitmap[index/32] |= 1 << (index % 32)
} else {
// This IP no longer refers to any domain name that matches the domain_set
routingMap.Bitmap[index/32] &^= 1 << (index % 32)
}
}
if del {
delete(c.domainBumpMap, ip)
delete(c.domainRoutingMap, ip)
keys_del = append(keys_del, common.Ipv6ByteSliceToUint32Array(ip6[:]))
} else {
c.domainBumpMap[ip] = newBumpMapVal
keys_modify = append(keys_modify, common.Ipv6ByteSliceToUint32Array(ip6[:]))
vals_modify_bump = append(vals_modify_bump, bumpMap)
vals_modify_routing = append(vals_modify_routing, routingMap)
}
}
if _, err := BpfMapBatchDelete(c.bpf.DomainBumpMap, keys_del); err != nil {
return err
}
if _, err := BpfMapBatchDelete(c.bpf.DomainRoutingMap, keys); err != nil {
if _, err := BpfMapBatchDelete(c.bpf.DomainRoutingMap, keys_del); err != nil {
return err
}
if _, err := BpfMapBatchUpdate(c.bpf.DomainBumpMap, keys_modify, vals_modify_bump, &ebpf.BatchOptions{
ElemFlags: uint64(ebpf.UpdateAny),
}); err != nil {
return err
}
if _, err := BpfMapBatchUpdate(c.bpf.DomainRoutingMap, keys_modify, vals_modify_routing, &ebpf.BatchOptions{
ElemFlags: uint64(ebpf.UpdateAny),
}); err != nil {
return err
}

return nil
}

Expand Down
28 changes: 26 additions & 2 deletions control/kern/tproxy.c
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,15 @@ struct {
// __uint(pinning, LIBBPF_PIN_BY_NAME);
} domain_routing_map SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_LRU_HASH);
__type(key, __be32[4]);
__type(value, struct domain_routing);
__uint(max_entries, MAX_DOMAIN_ROUTING_NUM);
/// NOTICE: No persistence.
// __uint(pinning, LIBBPF_PIN_BY_NAME);
} domain_bump_map SEC(".maps");

struct ip_port_proto {
__u32 ip[4];
__be16 port;
Expand Down Expand Up @@ -650,6 +659,8 @@ static int route_loop_cb(__u32 index, void *data)
// proxy Subrule is like: domain(suffix:baidu.com, suffix:google.com) Match
// set is like: suffix:baidu.com
struct domain_routing *domain_routing;
struct domain_routing *domain_bump;
bool need_control_plane_routing = false;

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

// We use key instead of k to pass checker.
if (domain_routing &&
(domain_routing->bitmap[index / 32] >> (index % 32)) & 1)
(domain_routing->bitmap[index / 32] >> (index % 32)) & 1) {
// All domains mapeed by the current IP address are matched.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: typo mapeed

ctx->goodsubrule = true;
} else {
// Get domain bump bitmap.
domain_bump = bpf_map_lookup_elem(&domain_bump_map,
ctx->params->daddr);
if (domain_bump &&
(domain_bump->bitmap[index / 32] >> (index % 32)) & 1) {
ctx->goodsubrule = true;
// The current IP has mapped domains that match this rule, but not all of them do.
// jump to control plane.
need_control_plane_routing = true;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这一段逻辑也应该更新到 routing_matcher_userspace.go:Match() ?

break;
case MatchType_ProcessName:
#ifdef __DEBUG_ROUTING
Expand Down Expand Up @@ -833,7 +857,7 @@ static int route_loop_cb(__u32 index, void *data)
} else {
bool must = ctx->must || match_set->must;

if (!must && ctx->isdns) {
if ((!must && ctx->isdns) || need_control_plane_routing) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

ctx->result =
(__s64)OUTBOUND_CONTROL_PLANE_ROUTING |
((__s64)match_set->mark << 8) |
Expand Down
Loading