Skip to content

Commit

Permalink
chore: enable v6 support on the same port
Browse files Browse the repository at this point in the history
Replace `SO_REUSEPORT` with `SO_REUSEPORT`.

Signed-off-by: Dmitriy Matrenichev <dmitry.matrenichev@siderolabs.com>
  • Loading branch information
DmitriyMV committed Feb 12, 2024
1 parent 83e0b0c commit fa2d34d
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 49 deletions.
92 changes: 59 additions & 33 deletions internal/app/machined/pkg/controllers/network/dns_resolve_cache.go
Expand Up @@ -6,7 +6,10 @@ package network

import (
"context"
"errors"
"fmt"
"io"
"net"
"time"

"github.com/coredns/coredns/plugin/pkg/proxy"
Expand All @@ -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
}

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down
Expand Up @@ -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),
}))
},
Expand Down
Expand Up @@ -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{},
Expand Down
60 changes: 49 additions & 11 deletions internal/pkg/dns/dns.go
Expand Up @@ -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 {
Expand Down
6 changes: 2 additions & 4 deletions internal/pkg/dns/dns_test.go
Expand Up @@ -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{
Expand Down

0 comments on commit fa2d34d

Please sign in to comment.