Skip to content
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

Avoid overlapping split excludes in Split Routing #66

Merged
merged 1 commit into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
146 changes: 92 additions & 54 deletions internal/splitrt/excludes.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package splitrt
import (
"context"
"net"
"net/netip"
"sync"
"time"

Expand All @@ -14,72 +15,80 @@ const (
excludesTimer = 300
)

// exclude is a split excludes entry.
type exclude struct {
net *net.IPNet
static bool
// dynExclude is a dynamic split excludes entry.
type dynExclude struct {
ttl uint32
updated bool
}

// Excludes contains split Excludes.
type Excludes struct {
sync.Mutex
m map[string]*exclude
s map[string]*netip.Prefix
d map[netip.Addr]*dynExclude
done chan struct{}
closed chan struct{}
}

// addFilter adds the exclude to netfilter.
func (e *Excludes) addFilter(ctx context.Context, exclude *exclude) {
log.WithField("address", exclude.net).Debug("SplitRouting adding exclude to netfilter")
addExclude(ctx, exclude.net)
}

// setFilter resets the excludes in netfilter.
func (e *Excludes) setFilter(ctx context.Context) {
log.Debug("SplitRouting resetting excludes in netfilter")

addresses := []*net.IPNet{}
for _, v := range e.m {
addresses = append(addresses, v.net)
addresses := []*netip.Prefix{}
for _, v := range e.s {
addresses = append(addresses, v)
}
for k := range e.d {
prefix := netip.PrefixFrom(k, k.BitLen())
addresses = append(addresses, &prefix)
}
setExcludes(ctx, addresses)
}

// add adds the exclude entry for ip to the split excludes.
func (e *Excludes) add(ctx context.Context, ip *net.IPNet, exclude *exclude) {
// AddStatic adds a static entry to the split excludes.
func (e *Excludes) AddStatic(ctx context.Context, address *net.IPNet) {
log.WithField("address", address).Debug("SplitRouting adding static exclude")

a, err := netip.ParsePrefix(address.String())
if err != nil {
log.WithError(err).Error("SplitRouting could not parse static exclude")
return
}

e.Lock()
defer e.Unlock()

key := ip.String()
old := e.m[key]
// make sure new prefix in address does not overlap with existing
// prefixes in static excludes
removed := false
for k, v := range e.s {
if !v.Overlaps(a) {
// no overlap
continue
}
if v.Bits() <= a.Bits() {
// new prefix is already in existing prefix,
// do not add it
return
}

// new entry, just add it
if old == nil {
e.m[key] = exclude
e.addFilter(ctx, exclude)
return
// new prefix contains old prefix, remove old prefix
delete(e.s, k)
removed = true
}

// old entry exists, update values
if old.static {
// static entry is not updated
// add new prefix to static excludes
key := address.String()
e.s[key] = &a

// add to netfilter
if removed {
// existing entries removed, we need to reset all excludes
e.setFilter(ctx)
return
}
// update entry
old.static = exclude.static
old.ttl = exclude.ttl
old.updated = true
}

// AddStatic adds a static entry to the split excludes.
func (e *Excludes) AddStatic(ctx context.Context, address *net.IPNet) {
log.WithField("address", address).Debug("SplitRouting adding static exclude")
e.add(ctx, address, &exclude{
net: address,
static: true,
})
// single new entry, add it
addExclude(ctx, &a)
}

// AddDynamic adds a dynamic entry to the split excludes.
Expand All @@ -88,34 +97,62 @@ func (e *Excludes) AddDynamic(ctx context.Context, address *net.IPNet, ttl uint3
"address": address,
"ttl": ttl,
}).Debug("SplitRouting adding dynamic exclude")
e.add(ctx, address, &exclude{
net: address,

prefix, err := netip.ParsePrefix(address.String())
if err != nil {
log.WithError(err).Error("SplitRouting could not parse dynamic exclude")
return
}
if !prefix.IsSingleIP() {
log.WithError(err).Error("SplitRouting error adding dynamic exclude with multiple IPs")
return
}
a := prefix.Addr()

e.Lock()
defer e.Unlock()

// make sure new ip address is not in existing static excludes
for _, v := range e.s {
if v.Contains(a) {
return
}
}

// update existing entry in dynamic excludes
old := e.d[a]
if old != nil {
old.ttl = ttl
old.updated = true
return
}

// create new entry in dynamic excludes
e.d[a] = &dynExclude{
ttl: ttl,
updated: true,
})
}

// add to netfilter
addExclude(ctx, &prefix)
}

// Remove removes an entry from the split excludes.
func (e *Excludes) Remove(ctx context.Context, address *net.IPNet) {
// RemoveStatic removes a static entry from the split excludes.
func (e *Excludes) RemoveStatic(ctx context.Context, address *net.IPNet) {
e.Lock()
defer e.Unlock()

delete(e.m, address.String())
delete(e.s, address.String())
e.setFilter(ctx)
}

// cleanup cleans up the split excludes.
// cleanup cleans up the dynamic split excludes.
func (e *Excludes) cleanup(ctx context.Context) {
e.Lock()
defer e.Unlock()

changed := false
for k, v := range e.m {
// skip static entries
if v.static {
continue
}

for k, v := range e.d {
// skip recently updated entries
if v.updated {
v.updated = false
Expand All @@ -124,7 +161,7 @@ func (e *Excludes) cleanup(ctx context.Context) {

// exclude expired entries
if v.ttl < excludesTimer {
delete(e.m, k)
delete(e.d, k)
changed = true
continue
}
Expand Down Expand Up @@ -176,7 +213,8 @@ func (e *Excludes) Stop() {
// NewExcludes returns new split excludes.
func NewExcludes() *Excludes {
return &Excludes{
m: make(map[string]*exclude),
s: make(map[string]*netip.Prefix),
d: make(map[netip.Addr]*dynExclude),
done: make(chan struct{}),
closed: make(chan struct{}),
}
Expand Down
Loading
Loading