From 68b575c53993e0d1807f7bc2818752f687b14c14 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sat, 2 Nov 2024 20:26:34 +0100 Subject: [PATCH 1/3] dhcp: fix scope moving when it shouldn't --- pkg/roles/dhcp/leases.go | 13 ++++++++++++- pkg/roles/dhcp/scopes.go | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/pkg/roles/dhcp/leases.go b/pkg/roles/dhcp/leases.go index 8ef08f567..d9a17fca2 100644 --- a/pkg/roles/dhcp/leases.go +++ b/pkg/roles/dhcp/leases.go @@ -49,7 +49,18 @@ func (r *Role) FindLease(req *Request4) *Lease { return nil } // Check if the leases's scope matches the expected scope to handle this request - expectedScope := r.findScopeForRequest(req) + expectedScope := r.findScopeForRequest(req, func(scope *Scope) int { + // Consider the existing lease for finding the scope + // Check how many bits of the leases address match the scope + sm := scope.match(net.ParseIP(lease.Address)) + // If the matching bits match how many bits are in the CIDR, and + // the scope of the lease matches the scope we're filtering, we've + // got a match + if sm == lease.scope.cidr.Bits() && lease.ScopeKey == scope.Name { + return 99 + } + return -1 + }) if expectedScope != nil && lease.scope != expectedScope { // We have a specific scope to handle this request but it doesn't match the lease lease.scope = expectedScope diff --git a/pkg/roles/dhcp/scopes.go b/pkg/roles/dhcp/scopes.go index 635324c90..843894662 100644 --- a/pkg/roles/dhcp/scopes.go +++ b/pkg/roles/dhcp/scopes.go @@ -96,7 +96,9 @@ func (s *Scope) ipamType(previous *Scope) (IPAM, error) { } } -func (r *Role) findScopeForRequest(req *Request4) *Scope { +type scopeSelector func(scope *Scope) int + +func (r *Role) findScopeForRequest(req *Request4, additionalSelectors ...scopeSelector) *Scope { var match *Scope longestBits := 0 r.scopesM.RLock() @@ -105,7 +107,15 @@ func (r *Role) findScopeForRequest(req *Request4) *Scope { // match a 1 bit more priority const dhcpRelayBias = 1 for _, scope := range r.scopes { - // Check based on gateway IP (highest priority) + // Check additional selectors (highest priority) + for _, sel := range additionalSelectors { + m := sel(scope) + if m > -1 && m > longestBits { + match = scope + longestBits = m + } + } + // Check based on gateway IP (next highest priority) gatewayMatchBits := scope.match(req.GatewayIPAddr) if gatewayMatchBits > -1 && gatewayMatchBits+dhcpRelayBias > longestBits { req.log.Debug("selected scope based on cidr match (gateway IP)", zap.String("scope", scope.Name)) From f9870b3f7c0bea5023c96e72a0cc9a3003fd725c Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sat, 2 Nov 2024 20:38:56 +0100 Subject: [PATCH 2/3] add auto config import on first startup --- docs/content/docs/install/_index.md | 9 ++++ pkg/extconfig/config.go | 9 ++-- pkg/instance/instance.go | 1 + pkg/instance/instance_auto_import.go | 71 ++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 pkg/instance/instance_auto_import.go diff --git a/docs/content/docs/install/_index.md b/docs/content/docs/install/_index.md index 1c1f7972e..830258eb0 100644 --- a/docs/content/docs/install/_index.md +++ b/docs/content/docs/install/_index.md @@ -76,6 +76,15 @@ The following environment variables can be set. - `ETCD_PEER_PORT`: Port used for etcd peer traffic. Defaults to `2380`. This may need to be changed when running in Kubernetes. - `ETCD_ENDPOINT`: etcd Client endpoint. Defaults to `localhost:2379` when using embedded etcd. - `ETCD_JOIN_CLUSTER`: Set to a join cluster token to join the node to a cluster. See [Clustering](./cluster). +- `IMPORT_CONFIGS`: A list of base64-encoded configs or file URIs to import configs from on first startup. For example: + + ```bash + IMPORT_CONFIGS=file:///config.json + # Multiple configs + IMPORT_CONFIGS=file:///configA.json|file:///configB.json + # Base64 import + IMPORT_CONFIGS=eyJlbnRyaWVzIjogW3sia2V5IjogIi9ncmF2aXR5L2ZvbyIsInZhbHVlIjogIlptOXYifV19 + ``` ### Changing Environment Variables diff --git a/pkg/extconfig/config.go b/pkg/extconfig/config.go index 2c394adf8..9ebcdc681 100644 --- a/pkg/extconfig/config.go +++ b/pkg/extconfig/config.go @@ -25,10 +25,11 @@ type ExtConfig struct { Interface string `env:"INSTANCE_INTERFACE"` Listen string `env:"INSTANCE_LISTEN"` } - LogLevel string `env:"LOG_LEVEL,default=info"` - DataPath string `env:"DATA_PATH,default=./data"` - BootstrapRoles string `env:"BOOTSTRAP_ROLES,default=dns;dhcp;api;etcd;discovery;backup;monitoring;tsdb;tftp"` - FallbackDNS string `env:"FALLBACK_DNS,default=1.1.1.1:53"` + LogLevel string `env:"LOG_LEVEL,default=info"` + DataPath string `env:"DATA_PATH,default=./data"` + BootstrapRoles string `env:"BOOTSTRAP_ROLES,default=dns;dhcp;api;etcd;discovery;backup;monitoring;tsdb;tftp"` + FallbackDNS string `env:"FALLBACK_DNS,default=1.1.1.1:53"` + ImportConfigs []string `env:"IMPORT_CONFIGS"` Sentry struct { DSN string `env:"SENTRY_DSN,default=https://731a93aa4a1a42a2960ac9eecee628c5@sentry.beryju.org/2"` diff --git a/pkg/instance/instance.go b/pkg/instance/instance.go index a524fd8c2..538ba554b 100644 --- a/pkg/instance/instance.go +++ b/pkg/instance/instance.go @@ -252,6 +252,7 @@ func (i *Instance) checkFirstStart(ctx context.Context) { return } i.log.Info("Initial startup") + i.autoImportConfig() inst.DispatchEvent( types.EventTopicInstanceFirstStart, roles.NewEvent(ctx, map[string]interface{}{}), diff --git a/pkg/instance/instance_auto_import.go b/pkg/instance/instance_auto_import.go new file mode 100644 index 000000000..9a7cb091a --- /dev/null +++ b/pkg/instance/instance_auto_import.go @@ -0,0 +1,71 @@ +package instance + +import ( + "context" + "encoding/base64" + "encoding/json" + "net/url" + "os" + "strings" + + "beryju.io/gravity/pkg/extconfig" + "beryju.io/gravity/pkg/roles/api" + "github.com/pkg/errors" + "go.uber.org/zap" +) + +func (i *Instance) autoImportConfig() { + for _, p := range extconfig.Get().ImportConfigs { + var c []byte + id := p + if strings.HasPrefix(p, "file://") { + u, err := url.Parse(p) + if err != nil { + i.log.Warn("failed to import config", zap.String("path", id), zap.Error(err)) + continue + } + _c, err := os.ReadFile(u.Path) + if err != nil { + i.log.Warn("failed to import config", zap.String("path", id), zap.Error(err)) + continue + } + c = _c + } else { + id := p[20:] + dec, err := base64.StdEncoding.DecodeString(p) + if err != nil { + i.log.Warn("failed to import config", zap.String("path", id), zap.Error(err)) + continue + } + c = dec + } + err := i.importSingleConfig(c) + if err != nil { + i.log.Warn("failed to import config", zap.String("path", id), zap.Error(err)) + } else { + i.log.Info("Successfully imported config", zap.String("path", id)) + } + } +} + +func (i *Instance) importSingleConfig(c []byte) error { + var input api.APIImportInput + err := json.Unmarshal(c, &input) + if err != nil { + return errors.Wrap(err, "failed to unmarshal config") + } + inst := i.ForRole("root", context.Background()) + for _, entry := range input.Entries { + val, err := base64.StdEncoding.DecodeString(entry.Value) + if err != nil { + i.log.Warn("failed to decode value", zap.Error(err), zap.String("key", entry.Key)) + continue + } + _, err = inst.KV().Put(context.Background(), entry.Key, string(val)) + if err != nil { + i.log.Warn("failed to put value", zap.Error(err), zap.String("key", entry.Key)) + continue + } + } + return nil +} From cd84a2cab1e7879577491ce68812bb3f4d0bd259 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sat, 2 Nov 2024 21:00:12 +0100 Subject: [PATCH 3/3] initial testing env --- hack/e2e/config.json | 20 ++++++++ hack/e2e/container/entrypoint.sh | 7 +++ hack/e2e/docker-compose.yaml | 81 ++++++++++++++++++++++++++++++++ hack/e2e/relay.Dockerfile | 11 +++++ hack/e2e/test.Dockerfile | 8 ++++ 5 files changed, 127 insertions(+) create mode 100644 hack/e2e/config.json create mode 100755 hack/e2e/container/entrypoint.sh create mode 100644 hack/e2e/docker-compose.yaml create mode 100644 hack/e2e/relay.Dockerfile create mode 100644 hack/e2e/test.Dockerfile diff --git a/hack/e2e/config.json b/hack/e2e/config.json new file mode 100644 index 000000000..1eb02144f --- /dev/null +++ b/hack/e2e/config.json @@ -0,0 +1,20 @@ +{ + "entries": [ + { + "key": "/dhcp/scopes/network-A", + "value": "eyJkbnMiOnsiem9uZSI6IiIsInNlYXJjaCI6bnVsbCwiYWRkWm9uZUluSG9zdG5hbWUiOmZhbHNlfSwiaXBhbSI6eyJyYW5nZV9lbmQiOiIxMC4xMDAuMC4yMDAiLCJyYW5nZV9zdGFydCI6IjEwLjEwMC4wLjEwMCIsInR5cGUiOiJpbnRlcm5hbCJ9LCJzdWJuZXRDaWRyIjoiMTAuMTAwLjAuMC8yNCIsIm9wdGlvbnMiOlt7InRhZyI6bnVsbCwidGFnTmFtZSI6InJvdXRlciIsInZhbHVlIjoiIiwidmFsdWU2NCI6bnVsbCwidmFsdWVIZXgiOm51bGx9XSwidHRsIjo4NjQwMCwiZGVmYXVsdCI6ZmFsc2UsImhvb2siOiIifQ==" + }, + { + "key": "/dhcp/scopes/network-B", + "value": "eyJkbnMiOnsiem9uZSI6IiIsInNlYXJjaCI6bnVsbCwiYWRkWm9uZUluSG9zdG5hbWUiOmZhbHNlfSwiaXBhbSI6eyJyYW5nZV9lbmQiOiIxMC4xMDEuMC4yMDAiLCJyYW5nZV9zdGFydCI6IjEwLjEwMS4wLjEwMCIsInR5cGUiOiJpbnRlcm5hbCJ9LCJzdWJuZXRDaWRyIjoiMTAuMTAxLjAuMC8yNCIsIm9wdGlvbnMiOlt7InRhZyI6bnVsbCwidGFnTmFtZSI6InJvdXRlciIsInZhbHVlIjoiIiwidmFsdWU2NCI6bnVsbCwidmFsdWVIZXgiOm51bGx9XSwidHRsIjo4NjQwMCwiZGVmYXVsdCI6ZmFsc2UsImhvb2siOiIifQ==" + }, + { + "key": "/dns/zones/.", + "value": "eyJoYW5kbGVyQ29uZmlncyI6W3sidHlwZSI6Im1lbW9yeSJ9LHsidHlwZSI6ImV0Y2QifSx7ImNhY2hlX3R0bCI6IjM2MDAiLCJ0byI6IjEuMS4xLjE6NTMiLCJ0eXBlIjoiZm9yd2FyZF9pcCJ9XSwiZGVmYXVsdFRUTCI6MzYwMCwiYXV0aG9yaXRhdGl2ZSI6ZmFsc2UsImhvb2siOiIifQ==" + }, + { + "key": "/role/cluster", + "value": "eyJzZXR1cCI6dHJ1ZX0=" + } + ] +} diff --git a/hack/e2e/container/entrypoint.sh b/hack/e2e/container/entrypoint.sh new file mode 100755 index 000000000..a10406fbc --- /dev/null +++ b/hack/e2e/container/entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/bash +echo "Removing IP Addresses" +ip addr flush eth0 + +trap "echo Shutting down; exit 0" SIGTERM SIGINT SIGKILL +/bin/sleep infinity & +wait diff --git a/hack/e2e/docker-compose.yaml b/hack/e2e/docker-compose.yaml new file mode 100644 index 000000000..ab5b9c368 --- /dev/null +++ b/hack/e2e/docker-compose.yaml @@ -0,0 +1,81 @@ +--- +networks: + network-A: + labels: + io.beryju.gravity/testing: "true" + # Required so we can set the IP for the gravity container directly here + # not used to assign IP addresses to test containers + ipam: + driver: default + config: + - subnet: 10.100.0.0/24 + network-B: + labels: + io.beryju.gravity/testing: "true" + # Required so we can set the IP for the gravity container directly here + # not used to assign IP addresses to test containers + ipam: + driver: default + config: + - subnet: 10.101.0.0/24 + +services: + gravity: + image: ghcr.io/beryju/gravity:latest + networks: + network-A: + ipv4_address: 10.100.0.10 + ports: + - 8008:8008 + hostname: gravity1 + volumes: + - ./config.json:/config.json + environment: + BOOTSTRAP_ROLES: dns;dhcp;api;etcd;discovery;backup;monitoring;tftp + IMPORT_CONFIGS: file:///config.json + LOG_LEVEL: debug + ADMIN_PASSWORD: test # testing password + relay: + build: + context: . + dockerfile: relay.Dockerfile + image: ghcr.io/beryju/gravity-dhcp-relay:latest + cap_add: + - NET_ADMIN + networks: + network-A: + ipv4_address: 10.100.0.5 + network-B: + ipv4_address: 10.101.0.5 + depends_on: + - gravity + command: -d -iu eth1 -id eth0 10.100.0.10 + + ubuntu-net-a: + build: + context: . + dockerfile: test.Dockerfile + image: ghcr.io/beryju/gravity-testing:latest + cap_add: + - NET_ADMIN + networks: + - network-A + depends_on: + - gravity + deploy: + mode: replicated + replicas: 6 + ubuntu-net-b: + build: + context: . + dockerfile: test.Dockerfile + image: ghcr.io/beryju/gravity-testing:latest + cap_add: + - NET_ADMIN + networks: + - network-B + depends_on: + - gravity + deploy: + mode: replicated + replicas: 6 diff --git a/hack/e2e/relay.Dockerfile b/hack/e2e/relay.Dockerfile new file mode 100644 index 000000000..8cf2a0fa2 --- /dev/null +++ b/hack/e2e/relay.Dockerfile @@ -0,0 +1,11 @@ +FROM library/ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && \ + apt-get install -y --no-install-recommends isc-dhcp-relay && \ + apt-get clean + +STOPSIGNAL SIGINT + +ENTRYPOINT [ "/usr/sbin/dhcrelay" ] diff --git a/hack/e2e/test.Dockerfile b/hack/e2e/test.Dockerfile new file mode 100644 index 000000000..bd99029e3 --- /dev/null +++ b/hack/e2e/test.Dockerfile @@ -0,0 +1,8 @@ +FROM docker.io/library/ubuntu:24.04 + +RUN apt-get update && \ + apt-get install -y iproute2 isc-dhcp-client tcpdump + +COPY ./container/entrypoint.sh /entrypoint.sh + +CMD ["/entrypoint.sh"]