From fa2d34dd8875e6a09c257acfb9321c1230658b87 Mon Sep 17 00:00:00 2001 From: Dmitriy Matrenichev Date: Mon, 12 Feb 2024 18:58:34 +0300 Subject: [PATCH] chore: enable v6 support on the same port Replace `SO_REUSEPORT` with `SO_REUSEPORT`. Signed-off-by: Dmitriy Matrenichev --- .../controllers/network/dns_resolve_cache.go | 92 ++++++++++++------- .../network/dns_resolve_cache_test.go | 3 +- .../runtime/v1alpha2/v1alpha2_controller.go | 1 + internal/pkg/dns/dns.go | 60 +++++++++--- internal/pkg/dns/dns_test.go | 6 +- 5 files changed, 113 insertions(+), 49 deletions(-) diff --git a/internal/app/machined/pkg/controllers/network/dns_resolve_cache.go b/internal/app/machined/pkg/controllers/network/dns_resolve_cache.go index e6903e5460..d1d3eeec2d 100644 --- a/internal/app/machined/pkg/controllers/network/dns_resolve_cache.go +++ b/internal/app/machined/pkg/controllers/network/dns_resolve_cache.go @@ -6,7 +6,10 @@ package network import ( "context" + "errors" "fmt" + "io" + "net" "time" "github.com/coredns/coredns/plugin/pkg/proxy" @@ -24,6 +27,7 @@ import ( // DNSResolveCacheController starts dns server on both udp and tcp ports based on finalized network configuration. type DNSResolveCacheController struct { Addr string + AddrV6 string Logger *zap.Logger } @@ -100,54 +104,70 @@ func (ctrl *DNSResolveCacheController) runServer(originCtx context.Context, r co defer handler.Stop() cache := dns.NewCache(handler, ctrl.Logger) - addr := ctrl.Addr ctx := originCtx + serverOpts := map[string]dns.ServerOptins{} + for _, opt := range []struct { - net string - addr string - srvOpts dns.ServerOptins + net string + addr string }{ - { - net: "udp", - addr: addr, - srvOpts: dns.ServerOptins{ - Handler: cache, - }, - }, - { - net: "tcp", - addr: addr, - srvOpts: dns.ServerOptins{ - Handler: cache, - ReadTimeout: 3 * time.Second, - WriteTimeout: 5 * time.Second, - IdleTimeout: func() time.Duration { return 10 * time.Second }, - MaxTCPQueries: -1, - }, - }, + {net: "udp", addr: ctrl.Addr}, + {net: "udp6", addr: ctrl.AddrV6}, + {net: "tcp", addr: ctrl.Addr}, + {net: "tcp6", addr: ctrl.AddrV6}, } { - l := ctrl.Logger.With(zap.String("net", opt.net)) + l := ctrl.Logger.With(zap.String("net", opt.net), zap.String("addr", opt.addr)) - if opt.net == "tcp" { - listener, err := dns.NewTCPListener(opt.addr) + switch opt.net { + case "udp", "udp6": + packetConn, err := dns.NewUDPPacketConn(opt.net, opt.addr) if err != nil { - return fmt.Errorf("error creating tcp listener: %w", err) + if opt.net == "udp6" { + // If we can't bind to ipv6, we can continue with ipv4 + continue + } + + return fmt.Errorf("error creating udp packet conn: %w", err) + } + + defer closeListener(packetConn, l) + + serverOpts[opt.net] = dns.ServerOptins{ + PacketConn: packetConn, + Handler: cache, } - opt.srvOpts.Listener = listener - } else if opt.net == "udp" { - packetConn, err := dns.NewUDPPacketConn(opt.addr) + case "tcp", "tcp6": + listener, err := dns.NewTCPListener(opt.net, opt.addr) if err != nil { - return fmt.Errorf("error creating udp packet conn: %w", err) + if opt.net == "tcp6" { + // If we can't bind to ipv6, we can continue with ipv4 + continue + } + + return fmt.Errorf("error creating tcp listener: %w", err) } - opt.srvOpts.PacketConn = packetConn + defer closeListener(listener, l) + + serverOpts[opt.net] = dns.ServerOptins{ + Listener: listener, + Handler: cache, + ReadTimeout: 3 * time.Second, + WriteTimeout: 5 * time.Second, + IdleTimeout: func() time.Duration { return 10 * time.Second }, + MaxTCPQueries: -1, + } } + } + + for netwk, opt := range serverOpts { + l := ctrl.Logger.With(zap.String("net", netwk)) - runner := dns.NewRunner(dns.NewServer(opt.srvOpts), l) + runner := dns.NewRunner(dns.NewServer(opt), l) - err := ctrl.writeDNSStatus(ctx, r, opt.net) + err := ctrl.writeDNSStatus(ctx, r, netwk) if err != nil { return err } @@ -203,6 +223,12 @@ func (ctrl *DNSResolveCacheController) runServer(originCtx context.Context, r co } } +func closeListener(lis io.Closer, l *zap.Logger) { + if err := lis.Close(); err != nil && !errors.Is(err, net.ErrClosed) { + l.Error("error closing listener", zap.Error(err)) + } +} + func dropResolveResources(ctx context.Context, r controller.Runtime, nets ...resource.ID) error { for _, net := range nets { if err := r.Destroy(ctx, network.NewDNSResolveCache(net).Metadata()); err != nil { diff --git a/internal/app/machined/pkg/controllers/network/dns_resolve_cache_test.go b/internal/app/machined/pkg/controllers/network/dns_resolve_cache_test.go index 4d0a69d7e1..844fccaaa5 100644 --- a/internal/app/machined/pkg/controllers/network/dns_resolve_cache_test.go +++ b/internal/app/machined/pkg/controllers/network/dns_resolve_cache_test.go @@ -148,7 +148,8 @@ func TestDNSServer(t *testing.T) { AfterSetup: func(suite *ctest.DefaultSuite) { suite.Require().NoError(suite.Runtime().RegisterController(&netctrl.DNSUpstreamController{})) suite.Require().NoError(suite.Runtime().RegisterController(&netctrl.DNSResolveCacheController{ - Addr: ":10700", + Addr: "127.0.0.1:10700", + AddrV6: "[::1]:10700", Logger: zaptest.NewLogger(t), })) }, diff --git a/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go b/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go index 49568bf545..0f340d1adb 100644 --- a/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go +++ b/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go @@ -190,6 +190,7 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error &network.DeviceConfigController{}, &network.DNSResolveCacheController{ Addr: "127.0.0.1:53", + AddrV6: "[::1]:53", Logger: dnsCacheLogger, }, &network.DNSUpstreamController{}, diff --git a/internal/pkg/dns/dns.go b/internal/pkg/dns/dns.go index 8737b0b18e..88572fcec4 100644 --- a/internal/pkg/dns/dns.go +++ b/internal/pkg/dns/dns.go @@ -229,39 +229,77 @@ func NewServer(opts ServerOptins) Server { type server struct{ *dns.Server } // NewTCPListener creates a new TCP listener. -func NewTCPListener(addr string) (net.Listener, error) { - lc := net.ListenConfig{ - Control: makeControl(tcpOptions), +func NewTCPListener(network, addr string) (net.Listener, error) { + var opts []controlOptions + + switch network { + case "tcp", "tcp4": + network = "tcp4" + opts = tcpOptions + + case "tcp6": + opts = tcpOptionsV6 + + default: + return nil, fmt.Errorf("unsupported network: %s", network) } - return lc.Listen(context.Background(), "tcp", addr) + lc := net.ListenConfig{Control: makeControl(opts)} + + return lc.Listen(context.Background(), network, addr) } // NewUDPPacketConn creates a new UDP packet connection. -func NewUDPPacketConn(addr string) (net.PacketConn, error) { +func NewUDPPacketConn(network, addr string) (net.PacketConn, error) { + var opts []controlOptions + + switch network { + case "udp", "udp4": + network = "udp4" + opts = udpOptions + + case "udp6": + opts = udpOptionsV6 + + default: + return nil, fmt.Errorf("unsupported network: %s", network) + } + lc := net.ListenConfig{ - Control: makeControl(udpOptions), + Control: makeControl(opts), } - return lc.ListenPacket(context.Background(), "udp", addr) + return lc.ListenPacket(context.Background(), network, addr) } var ( tcpOptions = []controlOptions{ - // this isn't really necessary, because currently if the process dies, OS dies with it - {unix.SOL_SOCKET, unix.SO_REUSEADDR, 1, "failed to set SO_REUSEADDR"}, + {unix.SOL_SOCKET, unix.SO_REUSEPORT, 1, "failed to set SO_REUSEADDR"}, {unix.IPPROTO_IP, unix.IP_RECVTTL, 1, "failed to set IP_RECVTTL"}, {unix.IPPROTO_TCP, unix.TCP_FASTOPEN, 5, "failed to set TCP_FASTOPEN"}, // tcp specific stuff from systemd {unix.IPPROTO_TCP, unix.TCP_NODELAY, 1, "failed to set TCP_NODELAY"}, // tcp specific stuff from systemd {unix.IPPROTO_IP, unix.IP_TTL, 1, "failed to set IP_TTL"}, } + tcpOptionsV6 = []controlOptions{ + {unix.SOL_SOCKET, unix.SO_REUSEPORT, 1, "failed to set SO_REUSEADDR"}, + {unix.IPPROTO_IPV6, unix.IPV6_RECVHOPLIMIT, 1, "failed to set IPV6_RECVHOPLIMIT"}, + {unix.IPPROTO_TCP, unix.TCP_FASTOPEN, 5, "failed to set TCP_FASTOPEN"}, // tcp specific stuff from systemd + {unix.IPPROTO_TCP, unix.TCP_NODELAY, 1, "failed to set TCP_NODELAY"}, // tcp specific stuff from systemd + {unix.IPPROTO_IPV6, unix.IPV6_UNICAST_HOPS, 1, "failed to set IPV6_UNICAST_HOPS"}, + } + udpOptions = []controlOptions{ - // this isn't really necessary, because currently if the process dies, OS dies with it - {unix.SOL_SOCKET, unix.SO_REUSEADDR, 1, "failed to set SO_REUSEADDR"}, + {unix.SOL_SOCKET, unix.SO_REUSEPORT, 1, "failed to set SO_REUSEADDR"}, {unix.IPPROTO_IP, unix.IP_RECVTTL, 1, "failed to set IP_RECVTTL"}, {unix.IPPROTO_IP, unix.IP_TTL, 1, "failed to set IP_TTL"}, } + + udpOptionsV6 = []controlOptions{ + {unix.SOL_SOCKET, unix.SO_REUSEPORT, 1, "failed to set SO_REUSEADDR"}, + {unix.IPPROTO_IPV6, unix.IPV6_RECVHOPLIMIT, 1, "failed to set IPV6_RECVHOPLIMIT"}, + {unix.IPPROTO_IPV6, unix.IPV6_UNICAST_HOPS, 1, "failed to set IPV6_UNICAST_HOPS"}, + } ) type controlOptions struct { diff --git a/internal/pkg/dns/dns_test.go b/internal/pkg/dns/dns_test.go index 647c56787b..4342c36ce7 100644 --- a/internal/pkg/dns/dns_test.go +++ b/internal/pkg/dns/dns_test.go @@ -111,16 +111,14 @@ func newServer(t *testing.T, nameservers ...string) (context.Context, func()) { p := proxy.NewProxy(ns, net.JoinHostPort(ns, "53"), "dns") p.Start(500 * time.Millisecond) - t.Cleanup(func() { - p.Stop() - }) + t.Cleanup(p.Stop) return p }) handler.SetProxy(pxs) - pc, err := dns.NewUDPPacketConn(":10700") + pc, err := dns.NewUDPPacketConn("udp", "127.0.0.1:10700") require.NoError(t, err) runner := dns.NewRunner(dns.NewServer(dns.ServerOptins{