chore: enable v6 support on the same port

Replace `SO_REUSEPORT` with `SO_REUSEPORT`.

Signed-off-by: Dmitriy Matrenichev <dmitry.matrenichev@siderolabs.com>
This commit is contained in:
Dmitriy Matrenichev 2024-02-12 18:58:34 +03:00
parent 83e0b0c19a
commit fa2d34dd88
No known key found for this signature in database
GPG Key ID: D3363CF894E68892
5 changed files with 116 additions and 52 deletions

View File

@ -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{
{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), zap.String("addr", opt.addr))
switch opt.net {
case "udp", "udp6":
packetConn, err := dns.NewUDPPacketConn(opt.net, opt.addr)
if err != nil {
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,
}
case "tcp", "tcp6":
listener, err := dns.NewTCPListener(opt.net, opt.addr)
if err != nil {
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)
}
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,
},
},
} {
l := ctrl.Logger.With(zap.String("net", opt.net))
if opt.net == "tcp" {
listener, err := dns.NewTCPListener(opt.addr)
if err != nil {
return fmt.Errorf("error creating tcp listener: %w", err)
}
opt.srvOpts.Listener = listener
} else if opt.net == "udp" {
packetConn, err := dns.NewUDPPacketConn(opt.addr)
if err != nil {
return fmt.Errorf("error creating udp packet conn: %w", err)
}
opt.srvOpts.PacketConn = packetConn
}
}
runner := dns.NewRunner(dns.NewServer(opt.srvOpts), l)
for netwk, opt := range serverOpts {
l := ctrl.Logger.With(zap.String("net", netwk))
err := ctrl.writeDNSStatus(ctx, r, opt.net)
runner := dns.NewRunner(dns.NewServer(opt), l)
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 {

View File

@ -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),
}))
},

View File

@ -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{},

View File

@ -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) {
lc := net.ListenConfig{
Control: makeControl(udpOptions),
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)
}
return lc.ListenPacket(context.Background(), "udp", addr)
lc := net.ListenConfig{
Control: makeControl(opts),
}
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 {

View File

@ -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{