From 7f6c824122798e1605af6616f9d6e7df91d703fe Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 16 Dec 2025 18:33:56 -0500 Subject: [PATCH 1/2] Pull 21820 from config Former-commit-id: 56f46148996e01cae90c04da83862bde429962e9 --- go.mod | 47 +++++++++++++++++++- go.sum | 96 ++++++++++++++++++++++++++++++++++++++++ olm/olm.go | 15 ++++++- peers/manager.go | 8 +++- peers/monitor/monitor.go | 2 +- peers/types.go | 1 + websocket/client.go | 1 + 7 files changed, 165 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 2a36eb5..5e3ca07 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.25 require ( github.com/Microsoft/go-winio v0.6.2 - github.com/fosrl/newt v0.0.0-20251208171729-6d7985689552 + github.com/fosrl/newt v0.0.0-20251216233525-ff7fe1275b26 github.com/godbus/dbus/v5 v5.2.0 github.com/gorilla/websocket v1.5.3 github.com/miekg/dns v1.1.68 @@ -16,17 +16,62 @@ require ( ) require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/containerd/errdefs v0.3.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v28.5.2+incompatible // indirect + github.com/docker/go-connections v0.6.0 // indirect + github.com/docker/go-units v0.4.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/go-cmp v0.7.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/otlptranslator v0.0.2 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/vishvananda/netlink v1.3.1 // indirect github.com/vishvananda/netns v0.0.5 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.60.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/crypto v0.45.0 // indirect golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect golang.org/x/mod v0.30.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sync v0.18.0 // indirect + golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.12.0 // indirect golang.org/x/tools v0.39.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v0.5.3 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/grpc v1.76.0 // indirect + google.golang.org/protobuf v1.36.8 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index d963db9..f37df33 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,103 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= +github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= +github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fosrl/newt v0.0.0-20251208171729-6d7985689552 h1:51pHUtoqQhYPS9OiBDHLgYV44X/CBzR5J7GuWO3izhU= github.com/fosrl/newt v0.0.0-20251208171729-6d7985689552/go.mod h1:pol958CEs0nQmo/35Ltv0CGksheIKCS2hoNvdTVLEcI= +github.com/fosrl/newt v0.0.0-20251216233525-ff7fe1275b26 h1:ocuDvo6/bgoVByu8yhCnBVEhaQGwkilN9HUIPw00yYI= +github.com/fosrl/newt v0.0.0-20251216233525-ff7fe1275b26/go.mod h1:pol958CEs0nQmo/35Ltv0CGksheIKCS2hoNvdTVLEcI= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/godbus/dbus/v5 v5.2.0 h1:3WexO+U+yg9T70v9FdHr9kCxYlazaAXUhx2VMkbfax8= github.com/godbus/dbus/v5 v5.2.0/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA= github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/otlptranslator v0.0.2 h1:+1CdeLVrRQ6Psmhnobldo0kTp96Rj80DRXRd5OSnMEQ= +github.com/prometheus/otlptranslator v0.0.2/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4= github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0 h1:PeBoRj6af6xMI7qCupwFvTbbnd49V7n5YpG6pg8iDYQ= +go.opentelemetry.io/contrib/instrumentation/runtime v0.63.0/go.mod h1:ingqBCtMCe8I4vpz/UVzCW6sxoqgZB37nao91mLQ3Bw= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0 h1:cGtQxGvZbnrWdC2GyjZi0PDKVSLWP/Jocix3QWfXtbo= +go.opentelemetry.io/otel/exporters/prometheus v0.60.0/go.mod h1:hkd1EekxNo69PTV4OWFGZcKQiIqg0RfuWExcPKFvepk= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0= @@ -30,6 +112,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= @@ -42,6 +126,18 @@ golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdI golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ= golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE= golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI= +google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13 h1:vlzZttNJGVqTsRFU9AmdnrcO1Znh8Ew9kCD//yjigk0= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c h1:m/r7OM+Y2Ty1sgBQ7Qb27VgIMBW8ZZhT4gLnUyDIhzI= gvisor.dev/gvisor v0.0.0-20250503011706-39ed1f5ac29c/go.mod h1:3r5CMtNQMKIvBlrmM9xWUNamjKBYPOWyXOjmg5Kts3g= software.sslmate.com/src/go-pkcs12 v0.6.0 h1:f3sQittAeF+pao32Vb+mkli+ZyT+VwKaD014qFGq6oU= diff --git a/olm/olm.go b/olm/olm.go index 494ac02..2d9b42a 100644 --- a/olm/olm.go +++ b/olm/olm.go @@ -727,7 +727,7 @@ func StartTunnel(config TunnelConfig) { // Update HTTP server to mark this peer as using relay apiServer.UpdatePeerRelayStatus(relayData.SiteId, relayData.RelayEndpoint, true) - peerManager.RelayPeer(relayData.SiteId, primaryRelay) + peerManager.RelayPeer(relayData.SiteId, primaryRelay, relayData.RelayPort) }) olm.RegisterHandler("olm/wg/peer/unrelay", func(msg websocket.WSMessage) { @@ -777,6 +777,7 @@ func StartTunnel(config TunnelConfig) { ExitNode struct { PublicKey string `json:"publicKey"` Endpoint string `json:"endpoint"` + RelayPort uint16 `json:"relayPort"` } `json:"exitNode"` } @@ -792,8 +793,14 @@ func StartTunnel(config TunnelConfig) { return } + relayPort := handshakeData.ExitNode.RelayPort + if relayPort == 0 { + relayPort = 21820 // default relay port + } + exitNode := holepunch.ExitNode{ Endpoint: handshakeData.ExitNode.Endpoint, + RelayPort: relayPort, PublicKey: handshakeData.ExitNode.PublicKey, } @@ -878,8 +885,14 @@ func StartTunnel(config TunnelConfig) { // Convert websocket.ExitNode to holepunch.ExitNode hpExitNodes := make([]holepunch.ExitNode, len(exitNodes)) for i, node := range exitNodes { + relayPort := node.RelayPort + if relayPort == 0 { + relayPort = 21820 // default relay port + } + hpExitNodes[i] = holepunch.ExitNode{ Endpoint: node.Endpoint, + RelayPort: relayPort, PublicKey: node.PublicKey, } } diff --git a/peers/manager.go b/peers/manager.go index 59af2ce..af781e5 100644 --- a/peers/manager.go +++ b/peers/manager.go @@ -743,7 +743,7 @@ func (pm *PeerManager) RemoveAlias(siteId int, aliasName string) error { } // RelayPeer handles failover to the relay server when a peer is disconnected -func (pm *PeerManager) RelayPeer(siteId int, relayEndpoint string) { +func (pm *PeerManager) RelayPeer(siteId int, relayEndpoint string, relayPort uint16) { pm.mu.Lock() peer, exists := pm.peers[siteId] if exists { @@ -764,10 +764,14 @@ func (pm *PeerManager) RelayPeer(siteId int, relayEndpoint string) { formattedEndpoint = fmt.Sprintf("[%s]", relayEndpoint) } + if relayPort == 0 { + relayPort = 21820 // fall back to 21820 for backward compatibility + } + // Update only the endpoint for this peer (update_only preserves other settings) wgConfig := fmt.Sprintf(`public_key=%s update_only=true -endpoint=%s:21820`, util.FixKey(peer.PublicKey), formattedEndpoint) +endpoint=%s:%d`, util.FixKey(peer.PublicKey), formattedEndpoint, relayPort) err := pm.device.IpcSet(wgConfig) if err != nil { diff --git a/peers/monitor/monitor.go b/peers/monitor/monitor.go index 6c2e77b..27bc408 100644 --- a/peers/monitor/monitor.go +++ b/peers/monitor/monitor.go @@ -505,7 +505,7 @@ func (pm *PeerMonitor) checkHolepunchEndpoints() { pm.mutex.Unlock() for siteID, endpoint := range endpoints { - logger.Debug("Testing holepunch endpoint for site %d: %s", siteID, endpoint) + // logger.Debug("Testing holepunch endpoint for site %d: %s", siteID, endpoint) result := pm.holepunchTester.TestEndpoint(endpoint, timeout) pm.mutex.Lock() diff --git a/peers/types.go b/peers/types.go index dab49e1..9ef1462 100644 --- a/peers/types.go +++ b/peers/types.go @@ -33,6 +33,7 @@ type PeerRemove struct { type RelayPeerData struct { SiteId int `json:"siteId"` RelayEndpoint string `json:"relayEndpoint"` + RelayPort uint16 `json:"relayPort"` } type UnRelayPeerData struct { diff --git a/websocket/client.go b/websocket/client.go index ec25337..faede03 100644 --- a/websocket/client.go +++ b/websocket/client.go @@ -48,6 +48,7 @@ type TokenResponse struct { type ExitNode struct { Endpoint string `json:"endpoint"` + RelayPort uint16 `json:"relayPort"` PublicKey string `json:"publicKey"` } From 78dc6508a4ef03d814d6c918f17dad3478887d14 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 16 Dec 2025 21:33:41 -0500 Subject: [PATCH 2/2] Support wildcard alias records Former-commit-id: cec79bf0147e2f824d38a20306e63b58d8479a1c --- dns/dns_records.go | 203 ++++++++++++++++++++--- dns/dns_records_test.go | 350 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 531 insertions(+), 22 deletions(-) create mode 100644 dns/dns_records_test.go diff --git a/dns/dns_records.go b/dns/dns_records.go index 8d57d68..ed57b77 100644 --- a/dns/dns_records.go +++ b/dns/dns_records.go @@ -2,6 +2,7 @@ package dns import ( "net" + "strings" "sync" "github.com/miekg/dns" @@ -17,21 +18,26 @@ const ( // DNSRecordStore manages local DNS records for A and AAAA queries type DNSRecordStore struct { - mu sync.RWMutex - aRecords map[string][]net.IP // domain -> list of IPv4 addresses - aaaaRecords map[string][]net.IP // domain -> list of IPv6 addresses + mu sync.RWMutex + aRecords map[string][]net.IP // domain -> list of IPv4 addresses + aaaaRecords map[string][]net.IP // domain -> list of IPv6 addresses + aWildcards map[string][]net.IP // wildcard pattern -> list of IPv4 addresses + aaaaWildcards map[string][]net.IP // wildcard pattern -> list of IPv6 addresses } // NewDNSRecordStore creates a new DNS record store func NewDNSRecordStore() *DNSRecordStore { return &DNSRecordStore{ - aRecords: make(map[string][]net.IP), - aaaaRecords: make(map[string][]net.IP), + aRecords: make(map[string][]net.IP), + aaaaRecords: make(map[string][]net.IP), + aWildcards: make(map[string][]net.IP), + aaaaWildcards: make(map[string][]net.IP), } } // AddRecord adds a DNS record mapping (A or AAAA) // domain should be in FQDN format (e.g., "example.com.") +// domain can contain wildcards: * (0+ chars) and ? (exactly 1 char) // ip should be a valid IPv4 or IPv6 address func (s *DNSRecordStore) AddRecord(domain string, ip net.IP) error { s.mu.Lock() @@ -45,12 +51,23 @@ func (s *DNSRecordStore) AddRecord(domain string, ip net.IP) error { // Normalize domain to lowercase domain = dns.Fqdn(domain) + // Check if domain contains wildcards + isWildcard := strings.ContainsAny(domain, "*?") + if ip.To4() != nil { // IPv4 address - s.aRecords[domain] = append(s.aRecords[domain], ip) + if isWildcard { + s.aWildcards[domain] = append(s.aWildcards[domain], ip) + } else { + s.aRecords[domain] = append(s.aRecords[domain], ip) + } } else if ip.To16() != nil { // IPv6 address - s.aaaaRecords[domain] = append(s.aaaaRecords[domain], ip) + if isWildcard { + s.aaaaWildcards[domain] = append(s.aaaaWildcards[domain], ip) + } else { + s.aaaaRecords[domain] = append(s.aaaaRecords[domain], ip) + } } else { return &net.ParseError{Type: "IP address", Text: ip.String()} } @@ -59,7 +76,7 @@ func (s *DNSRecordStore) AddRecord(domain string, ip net.IP) error { } // RemoveRecord removes a specific DNS record mapping -// If ip is nil, removes all records for the domain +// If ip is nil, removes all records for the domain (including wildcards) func (s *DNSRecordStore) RemoveRecord(domain string, ip net.IP) { s.mu.Lock() defer s.mu.Unlock() @@ -72,33 +89,60 @@ func (s *DNSRecordStore) RemoveRecord(domain string, ip net.IP) { // Normalize domain to lowercase domain = dns.Fqdn(domain) + // Check if domain contains wildcards + isWildcard := strings.ContainsAny(domain, "*?") + if ip == nil { // Remove all records for this domain - delete(s.aRecords, domain) - delete(s.aaaaRecords, domain) + if isWildcard { + delete(s.aWildcards, domain) + delete(s.aaaaWildcards, domain) + } else { + delete(s.aRecords, domain) + delete(s.aaaaRecords, domain) + } return } if ip.To4() != nil { // Remove specific IPv4 address - if ips, ok := s.aRecords[domain]; ok { - s.aRecords[domain] = removeIP(ips, ip) - if len(s.aRecords[domain]) == 0 { - delete(s.aRecords, domain) + if isWildcard { + if ips, ok := s.aWildcards[domain]; ok { + s.aWildcards[domain] = removeIP(ips, ip) + if len(s.aWildcards[domain]) == 0 { + delete(s.aWildcards, domain) + } + } + } else { + if ips, ok := s.aRecords[domain]; ok { + s.aRecords[domain] = removeIP(ips, ip) + if len(s.aRecords[domain]) == 0 { + delete(s.aRecords, domain) + } } } } else if ip.To16() != nil { // Remove specific IPv6 address - if ips, ok := s.aaaaRecords[domain]; ok { - s.aaaaRecords[domain] = removeIP(ips, ip) - if len(s.aaaaRecords[domain]) == 0 { - delete(s.aaaaRecords, domain) + if isWildcard { + if ips, ok := s.aaaaWildcards[domain]; ok { + s.aaaaWildcards[domain] = removeIP(ips, ip) + if len(s.aaaaWildcards[domain]) == 0 { + delete(s.aaaaWildcards, domain) + } + } + } else { + if ips, ok := s.aaaaRecords[domain]; ok { + s.aaaaRecords[domain] = removeIP(ips, ip) + if len(s.aaaaRecords[domain]) == 0 { + delete(s.aaaaRecords, domain) + } } } } } // GetRecords returns all IP addresses for a domain and record type +// First checks for exact matches, then checks wildcard patterns func (s *DNSRecordStore) GetRecords(domain string, recordType RecordType) []net.IP { s.mu.RLock() defer s.mu.RUnlock() @@ -109,16 +153,45 @@ func (s *DNSRecordStore) GetRecords(domain string, recordType RecordType) []net. var records []net.IP switch recordType { case RecordTypeA: + // Check exact match first if ips, ok := s.aRecords[domain]; ok { // Return a copy to prevent external modifications records = make([]net.IP, len(ips)) copy(records, ips) + return records } + // Check wildcard patterns + for pattern, ips := range s.aWildcards { + if matchWildcard(pattern, domain) { + records = append(records, ips...) + } + } + if len(records) > 0 { + // Return a copy + result := make([]net.IP, len(records)) + copy(result, records) + return result + } + case RecordTypeAAAA: + // Check exact match first if ips, ok := s.aaaaRecords[domain]; ok { // Return a copy to prevent external modifications records = make([]net.IP, len(ips)) copy(records, ips) + return records + } + // Check wildcard patterns + for pattern, ips := range s.aaaaWildcards { + if matchWildcard(pattern, domain) { + records = append(records, ips...) + } + } + if len(records) > 0 { + // Return a copy + result := make([]net.IP, len(records)) + copy(result, records) + return result } } @@ -126,6 +199,7 @@ func (s *DNSRecordStore) GetRecords(domain string, recordType RecordType) []net. } // HasRecord checks if a domain has any records of the specified type +// Checks both exact matches and wildcard patterns func (s *DNSRecordStore) HasRecord(domain string, recordType RecordType) bool { s.mu.RLock() defer s.mu.RUnlock() @@ -135,11 +209,27 @@ func (s *DNSRecordStore) HasRecord(domain string, recordType RecordType) bool { switch recordType { case RecordTypeA: - _, ok := s.aRecords[domain] - return ok + // Check exact match + if _, ok := s.aRecords[domain]; ok { + return true + } + // Check wildcard patterns + for pattern := range s.aWildcards { + if matchWildcard(pattern, domain) { + return true + } + } case RecordTypeAAAA: - _, ok := s.aaaaRecords[domain] - return ok + // Check exact match + if _, ok := s.aaaaRecords[domain]; ok { + return true + } + // Check wildcard patterns + for pattern := range s.aaaaWildcards { + if matchWildcard(pattern, domain) { + return true + } + } } return false @@ -152,6 +242,8 @@ func (s *DNSRecordStore) Clear() { s.aRecords = make(map[string][]net.IP) s.aaaaRecords = make(map[string][]net.IP) + s.aWildcards = make(map[string][]net.IP) + s.aaaaWildcards = make(map[string][]net.IP) } // removeIP is a helper function to remove a specific IP from a slice @@ -164,3 +256,70 @@ func removeIP(ips []net.IP, toRemove net.IP) []net.IP { } return result } + +// matchWildcard checks if a domain matches a wildcard pattern +// Pattern supports * (0+ chars) and ? (exactly 1 char) +// Special case: *.domain.com does not match domain.com itself +func matchWildcard(pattern, domain string) bool { + return matchWildcardInternal(pattern, domain, 0, 0) +} + +// matchWildcardInternal performs the actual wildcard matching recursively +func matchWildcardInternal(pattern, domain string, pi, di int) bool { + plen := len(pattern) + dlen := len(domain) + + // Base cases + if pi == plen && di == dlen { + return true + } + if pi == plen { + return false + } + + // Handle wildcard characters + if pattern[pi] == '*' { + // Special case: if pattern starts with "*." and we're at the beginning, + // ensure we don't match the domain without a prefix + // e.g., *.autoco.internal should not match autoco.internal + if pi == 0 && pi+1 < plen && pattern[pi+1] == '.' { + // The * must match at least one character + if di == dlen { + return false + } + // Try matching 1 or more characters before the dot + for i := di + 1; i <= dlen; i++ { + if matchWildcardInternal(pattern, domain, pi+1, i) { + return true + } + } + return false + } + + // Normal * matching (0 or more characters) + // Try matching 0 characters (skip the *) + if matchWildcardInternal(pattern, domain, pi+1, di) { + return true + } + // Try matching 1+ characters + if di < dlen { + return matchWildcardInternal(pattern, domain, pi, di+1) + } + return false + } + + if pattern[pi] == '?' { + // ? matches exactly one character + if di >= dlen { + return false + } + return matchWildcardInternal(pattern, domain, pi+1, di+1) + } + + // Regular character - must match exactly + if di >= dlen || pattern[pi] != domain[di] { + return false + } + + return matchWildcardInternal(pattern, domain, pi+1, di+1) +} \ No newline at end of file diff --git a/dns/dns_records_test.go b/dns/dns_records_test.go new file mode 100644 index 0000000..0bb18a1 --- /dev/null +++ b/dns/dns_records_test.go @@ -0,0 +1,350 @@ +package dns + +import ( + "net" + "testing" +) + +func TestWildcardMatching(t *testing.T) { + tests := []struct { + name string + pattern string + domain string + expected bool + }{ + // Basic wildcard tests + { + name: "*.autoco.internal matches host.autoco.internal", + pattern: "*.autoco.internal.", + domain: "host.autoco.internal.", + expected: true, + }, + { + name: "*.autoco.internal matches longerhost.autoco.internal", + pattern: "*.autoco.internal.", + domain: "longerhost.autoco.internal.", + expected: true, + }, + { + name: "*.autoco.internal matches sub.host.autoco.internal", + pattern: "*.autoco.internal.", + domain: "sub.host.autoco.internal.", + expected: true, + }, + { + name: "*.autoco.internal does NOT match autoco.internal", + pattern: "*.autoco.internal.", + domain: "autoco.internal.", + expected: false, + }, + + // Question mark wildcard tests + { + name: "host-0?.autoco.internal matches host-01.autoco.internal", + pattern: "host-0?.autoco.internal.", + domain: "host-01.autoco.internal.", + expected: true, + }, + { + name: "host-0?.autoco.internal matches host-0a.autoco.internal", + pattern: "host-0?.autoco.internal.", + domain: "host-0a.autoco.internal.", + expected: true, + }, + { + name: "host-0?.autoco.internal does NOT match host-0.autoco.internal", + pattern: "host-0?.autoco.internal.", + domain: "host-0.autoco.internal.", + expected: false, + }, + { + name: "host-0?.autoco.internal does NOT match host-012.autoco.internal", + pattern: "host-0?.autoco.internal.", + domain: "host-012.autoco.internal.", + expected: false, + }, + + // Combined wildcard tests + { + name: "*.host-0?.autoco.internal matches sub.host-01.autoco.internal", + pattern: "*.host-0?.autoco.internal.", + domain: "sub.host-01.autoco.internal.", + expected: true, + }, + { + name: "*.host-0?.autoco.internal matches prefix.host-0a.autoco.internal", + pattern: "*.host-0?.autoco.internal.", + domain: "prefix.host-0a.autoco.internal.", + expected: true, + }, + { + name: "*.host-0?.autoco.internal does NOT match host-01.autoco.internal", + pattern: "*.host-0?.autoco.internal.", + domain: "host-01.autoco.internal.", + expected: false, + }, + + // Multiple asterisks + { + name: "*.*. autoco.internal matches any.thing.autoco.internal", + pattern: "*.*.autoco.internal.", + domain: "any.thing.autoco.internal.", + expected: true, + }, + { + name: "*.*.autoco.internal does NOT match single.autoco.internal", + pattern: "*.*.autoco.internal.", + domain: "single.autoco.internal.", + expected: false, + }, + + // Asterisk in middle + { + name: "host-*.autoco.internal matches host-anything.autoco.internal", + pattern: "host-*.autoco.internal.", + domain: "host-anything.autoco.internal.", + expected: true, + }, + { + name: "host-*.autoco.internal matches host-.autoco.internal (empty match)", + pattern: "host-*.autoco.internal.", + domain: "host-.autoco.internal.", + expected: true, + }, + + // Multiple question marks + { + name: "host-??.autoco.internal matches host-01.autoco.internal", + pattern: "host-??.autoco.internal.", + domain: "host-01.autoco.internal.", + expected: true, + }, + { + name: "host-??.autoco.internal does NOT match host-1.autoco.internal", + pattern: "host-??.autoco.internal.", + domain: "host-1.autoco.internal.", + expected: false, + }, + + // Exact match (no wildcards) + { + name: "exact.autoco.internal matches exact.autoco.internal", + pattern: "exact.autoco.internal.", + domain: "exact.autoco.internal.", + expected: true, + }, + { + name: "exact.autoco.internal does NOT match other.autoco.internal", + pattern: "exact.autoco.internal.", + domain: "other.autoco.internal.", + expected: false, + }, + + // Edge cases + { + name: "* matches anything", + pattern: "*", + domain: "anything.at.all.", + expected: true, + }, + { + name: "*.* matches multi.level.", + pattern: "*.*", + domain: "multi.level.", + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := matchWildcard(tt.pattern, tt.domain) + if result != tt.expected { + t.Errorf("matchWildcard(%q, %q) = %v, want %v", tt.pattern, tt.domain, result, tt.expected) + } + }) + } +} + +func TestDNSRecordStoreWildcard(t *testing.T) { + store := NewDNSRecordStore() + + // Add wildcard records + wildcardIP := net.ParseIP("10.0.0.1") + err := store.AddRecord("*.autoco.internal", wildcardIP) + if err != nil { + t.Fatalf("Failed to add wildcard record: %v", err) + } + + // Add exact record + exactIP := net.ParseIP("10.0.0.2") + err = store.AddRecord("exact.autoco.internal", exactIP) + if err != nil { + t.Fatalf("Failed to add exact record: %v", err) + } + + // Test exact match takes precedence + ips := store.GetRecords("exact.autoco.internal.", RecordTypeA) + if len(ips) != 1 { + t.Errorf("Expected 1 IP for exact match, got %d", len(ips)) + } + if !ips[0].Equal(exactIP) { + t.Errorf("Expected exact IP %v, got %v", exactIP, ips[0]) + } + + // Test wildcard match + ips = store.GetRecords("host.autoco.internal.", RecordTypeA) + if len(ips) != 1 { + t.Errorf("Expected 1 IP for wildcard match, got %d", len(ips)) + } + if !ips[0].Equal(wildcardIP) { + t.Errorf("Expected wildcard IP %v, got %v", wildcardIP, ips[0]) + } + + // Test non-match (base domain) + ips = store.GetRecords("autoco.internal.", RecordTypeA) + if len(ips) != 0 { + t.Errorf("Expected 0 IPs for base domain, got %d", len(ips)) + } +} + +func TestDNSRecordStoreComplexWildcard(t *testing.T) { + store := NewDNSRecordStore() + + // Add complex wildcard pattern + ip1 := net.ParseIP("10.0.0.1") + err := store.AddRecord("*.host-0?.autoco.internal", ip1) + if err != nil { + t.Fatalf("Failed to add wildcard record: %v", err) + } + + // Test matching domain + ips := store.GetRecords("sub.host-01.autoco.internal.", RecordTypeA) + if len(ips) != 1 { + t.Errorf("Expected 1 IP for complex wildcard match, got %d", len(ips)) + } + if len(ips) > 0 && !ips[0].Equal(ip1) { + t.Errorf("Expected IP %v, got %v", ip1, ips[0]) + } + + // Test non-matching domain (missing prefix) + ips = store.GetRecords("host-01.autoco.internal.", RecordTypeA) + if len(ips) != 0 { + t.Errorf("Expected 0 IPs for domain without prefix, got %d", len(ips)) + } + + // Test non-matching domain (wrong ? position) + ips = store.GetRecords("sub.host-012.autoco.internal.", RecordTypeA) + if len(ips) != 0 { + t.Errorf("Expected 0 IPs for domain with wrong ? match, got %d", len(ips)) + } +} + +func TestDNSRecordStoreRemoveWildcard(t *testing.T) { + store := NewDNSRecordStore() + + // Add wildcard record + ip := net.ParseIP("10.0.0.1") + err := store.AddRecord("*.autoco.internal", ip) + if err != nil { + t.Fatalf("Failed to add wildcard record: %v", err) + } + + // Verify it exists + ips := store.GetRecords("host.autoco.internal.", RecordTypeA) + if len(ips) != 1 { + t.Errorf("Expected 1 IP before removal, got %d", len(ips)) + } + + // Remove wildcard record + store.RemoveRecord("*.autoco.internal", nil) + + // Verify it's gone + ips = store.GetRecords("host.autoco.internal.", RecordTypeA) + if len(ips) != 0 { + t.Errorf("Expected 0 IPs after removal, got %d", len(ips)) + } +} + +func TestDNSRecordStoreMultipleWildcards(t *testing.T) { + store := NewDNSRecordStore() + + // Add multiple wildcard patterns that don't overlap + ip1 := net.ParseIP("10.0.0.1") + ip2 := net.ParseIP("10.0.0.2") + ip3 := net.ParseIP("10.0.0.3") + + err := store.AddRecord("*.prod.autoco.internal", ip1) + if err != nil { + t.Fatalf("Failed to add first wildcard: %v", err) + } + + err = store.AddRecord("*.dev.autoco.internal", ip2) + if err != nil { + t.Fatalf("Failed to add second wildcard: %v", err) + } + + // Add a broader wildcard that matches both + err = store.AddRecord("*.autoco.internal", ip3) + if err != nil { + t.Fatalf("Failed to add third wildcard: %v", err) + } + + // Test domain matching only the prod pattern and the broad pattern + ips := store.GetRecords("host.prod.autoco.internal.", RecordTypeA) + if len(ips) != 2 { + t.Errorf("Expected 2 IPs (prod + broad), got %d", len(ips)) + } + + // Test domain matching only the dev pattern and the broad pattern + ips = store.GetRecords("service.dev.autoco.internal.", RecordTypeA) + if len(ips) != 2 { + t.Errorf("Expected 2 IPs (dev + broad), got %d", len(ips)) + } + + // Test domain matching only the broad pattern + ips = store.GetRecords("host.test.autoco.internal.", RecordTypeA) + if len(ips) != 1 { + t.Errorf("Expected 1 IP (broad only), got %d", len(ips)) + } +} + +func TestDNSRecordStoreIPv6Wildcard(t *testing.T) { + store := NewDNSRecordStore() + + // Add IPv6 wildcard record + ip := net.ParseIP("2001:db8::1") + err := store.AddRecord("*.autoco.internal", ip) + if err != nil { + t.Fatalf("Failed to add IPv6 wildcard record: %v", err) + } + + // Test wildcard match for IPv6 + ips := store.GetRecords("host.autoco.internal.", RecordTypeAAAA) + if len(ips) != 1 { + t.Errorf("Expected 1 IPv6 for wildcard match, got %d", len(ips)) + } + if len(ips) > 0 && !ips[0].Equal(ip) { + t.Errorf("Expected IPv6 %v, got %v", ip, ips[0]) + } +} + +func TestHasRecordWildcard(t *testing.T) { + store := NewDNSRecordStore() + + // Add wildcard record + ip := net.ParseIP("10.0.0.1") + err := store.AddRecord("*.autoco.internal", ip) + if err != nil { + t.Fatalf("Failed to add wildcard record: %v", err) + } + + // Test HasRecord with wildcard match + if !store.HasRecord("host.autoco.internal.", RecordTypeA) { + t.Error("Expected HasRecord to return true for wildcard match") + } + + // Test HasRecord with non-match + if store.HasRecord("autoco.internal.", RecordTypeA) { + t.Error("Expected HasRecord to return false for base domain") + } +} \ No newline at end of file