wgengine/magicsock: improve don't fragment bit set/get support

Add an enable/disable argument to setDontFragment() in preparation for dynamic
enable/disable of peer path MTU discovery. Add getDontFragment() to get the
status of the don't fragment bit from a socket.

Updates #311

Co-authored-by: James Tucker <james@tailscale.com>
Signed-off-by: Val <valerie@tailscale.com>
pull/9506/head
Val 8 months ago committed by valscale
parent 4c793014af
commit a5ae21a832

@ -9,6 +9,7 @@ import (
"net/netip" "net/netip"
"sync" "sync"
"sync/atomic" "sync/atomic"
"syscall"
"time" "time"
"golang.org/x/net/ipv6" "golang.org/x/net/ipv6"
@ -192,3 +193,11 @@ retry:
} }
return err return err
} }
func (c *batchingUDPConn) SyscallConn() (syscall.RawConn, error) {
sc, ok := c.pc.(syscall.Conn)
if !ok {
return nil, errUnsupportedConnType
}
return sc.SyscallConn()
}

@ -8,6 +8,7 @@ import (
"net" "net"
"net/netip" "net/netip"
"sync" "sync"
"syscall"
"time" "time"
) )
@ -48,6 +49,7 @@ func (c *blockForeverConn) Close() error {
return nil return nil
} }
func (c *blockForeverConn) SetDeadline(t time.Time) error { return errors.New("unimplemented") } func (c *blockForeverConn) SetDeadline(t time.Time) error { return errors.New("unimplemented") }
func (c *blockForeverConn) SetReadDeadline(t time.Time) error { return errors.New("unimplemented") } func (c *blockForeverConn) SetReadDeadline(t time.Time) error { return errors.New("unimplemented") }
func (c *blockForeverConn) SetWriteDeadline(t time.Time) error { return errors.New("unimplemented") } func (c *blockForeverConn) SetWriteDeadline(t time.Time) error { return errors.New("unimplemented") }
func (c *blockForeverConn) SyscallConn() (syscall.RawConn, error) { return nil, errUnsupportedConnType }

@ -983,6 +983,8 @@ var errDropDerpPacket = errors.New("too many DERP packets queued; dropping")
var errNoUDP = errors.New("no UDP available on platform") var errNoUDP = errors.New("no UDP available on platform")
var errUnsupportedConnType = errors.New("unsupported connection type")
var ( var (
// This acts as a compile-time check for our usage of ipv6.Message in // This acts as a compile-time check for our usage of ipv6.Message in
// batchingUDPConn for both IPv6 and IPv4 operations. // batchingUDPConn for both IPv6 and IPv4 operations.
@ -2309,7 +2311,7 @@ func (c *Conn) bindSocket(ruc *RebindingUDPConn, network string, curPortFate cur
trySetSocketBuffer(pconn, c.logf) trySetSocketBuffer(pconn, c.logf)
if CanPMTUD() { if CanPMTUD() {
err = setDontFragment(pconn, network) err = c.setDontFragment(network, true)
if err != nil { if err != nil {
c.logf("magicsock: set dontfragment failed for %v port %d: %v", network, port, err) c.logf("magicsock: set dontfragment failed for %v port %d: %v", network, port, err)
// TODO disable PMTUD in this case. We don't expect the setsockopt to fail on // TODO disable PMTUD in this case. We don't expect the setsockopt to fail on

@ -6,30 +6,46 @@
package magicsock package magicsock
import ( import (
"net"
"syscall" "syscall"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"tailscale.com/types/nettype"
) )
func setDontFragment(pconn nettype.PacketConn, network string) (err error) { func getDontFragOpt(network string) int {
if c, ok := pconn.(*net.UDPConn); ok { if network == "udp4" {
rc, err := c.SyscallConn() return unix.IP_DONTFRAG
if err == nil { }
rc.Control(func(fd uintptr) { return unix.IPV6_DONTFRAG
if network == "udp4" { }
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, unix.IP_DONTFRAG, 1)
} func (c *Conn) setDontFragment(network string, enable bool) error {
if network == "udp6" { optArg := 1
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, unix.IPV6_DONTFRAG, 1) if enable == false {
} optArg = 0
}) }
} var err error
rcErr := c.connControl(network, func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), getIPProto(network), getDontFragOpt(network), optArg)
})
if rcErr != nil {
return rcErr
} }
return err return err
} }
func CanPMTUD() bool { func (c *Conn) getDontFragment(network string) (bool, error) {
return debugEnablePMTUD() // only if the envknob is for now. var v int
var err error
rcErr := c.connControl(network, func(fd uintptr) {
v, err = syscall.GetsockoptInt(int(fd), getIPProto(network), getDontFragOpt(network))
})
if rcErr != nil {
return false, rcErr
}
if v == 1 {
return true, err
}
return false, err
} }

@ -6,29 +6,44 @@
package magicsock package magicsock
import ( import (
"net"
"syscall" "syscall"
"tailscale.com/types/nettype"
) )
func setDontFragment(pconn nettype.PacketConn, network string) (err error) { func getDontFragOpt(network string) int {
if c, ok := pconn.(*net.UDPConn); ok { if network == "udp4" {
rc, err := c.SyscallConn() return syscall.IP_MTU_DISCOVER
if err == nil { }
rc.Control(func(fd uintptr) { return syscall.IPV6_MTU_DISCOVER
if network == "udp4" { }
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_MTU_DISCOVER, syscall.IP_PMTUDISC_DO)
} func (c *Conn) setDontFragment(network string, enable bool) error {
if network == "udp6" { optArg := syscall.IP_PMTUDISC_DO
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_MTU_DISCOVER, syscall.IP_PMTUDISC_DO) if enable == false {
} optArg = syscall.IP_PMTUDISC_DONT
}) }
} var err error
rcErr := c.connControl(network, func(fd uintptr) {
err = syscall.SetsockoptInt(int(fd), getIPProto(network), getDontFragOpt(network), optArg)
})
if rcErr != nil {
return rcErr
} }
return err return err
} }
func CanPMTUD() bool { func (c *Conn) getDontFragment(network string) (bool, error) {
return debugEnablePMTUD() // only if the envknob is enabled, for now. var v int
var err error
rcErr := c.connControl(network, func(fd uintptr) {
v, err = syscall.GetsockoptInt(int(fd), getIPProto(network), getDontFragOpt(network))
})
if rcErr != nil {
return false, rcErr
}
if v == syscall.IP_PMTUDISC_DO {
return true, err
}
return false, err
} }

@ -7,15 +7,27 @@ package magicsock
import ( import (
"errors" "errors"
"tailscale.com/types/nettype"
) )
// setDontFragment sets the dontfragment sockopt on pconn on the platforms that support it, // setDontFragment sets the don't fragment sockopt on the underlying connection
// for both IPv4 and IPv6. // specified by network, which must be "udp4" or "udp6". See
// (C.f. https://datatracker.ietf.org/doc/html/rfc3542#section-11.2 for IPv6 fragmentation) // https://datatracker.ietf.org/doc/html/rfc3542#section-11.2 for details on
func setDontFragment(pconn nettype.PacketConn, network string) (err error) { // IPv6 fragmentation.
return errors.New("setting don't fragment bit not supported on this OS") //
// Return values:
// - an error if peer MTU is not supported on this OS
// - errNoActiveUDP if the underlying connection is not UDP
// - otherwise, the result of setting the don't fragment bit
func (c *Conn) setDontFragment(network string, enable bool) error {
return errors.New("peer path MTU discovery not supported on this OS")
}
// getDontFragment gets the don't fragment setting on the underlying connection
// specified by network, which must be "udp4" or "udp6". Returns true if the
// underlying connection is UDP and the don't fragment bit is set, otherwise
// false.
func (c *Conn) getDontFragment(network string) (bool, error) {
return false, nil
} }
// CanPMTUD returns whether this platform supports performing peet path MTU discovery. // CanPMTUD returns whether this platform supports performing peet path MTU discovery.

@ -0,0 +1,46 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
//go:build (darwin && !ios) || (linux && !android)
package magicsock
import (
"syscall"
)
// getIPProto returns the value of the get/setsockopt proto argument necessary
// to set an IP sockopt that corresponds with the string network, which must be
// "udp4" or "udp6".
func getIPProto(network string) int {
if network == "udp4" {
return syscall.IPPROTO_IP
}
return syscall.IPPROTO_IPV6
}
// connControl allows the caller to run a system call on the socket underlying
// Conn specified by the string network, which must be "udp4" or "udp6". If the
// pconn type implements the syscall method, this function returns the value of
// of the system call fn called with the fd of the socket as its arg (or the
// error from rc.Control() if that fails). Otherwise it returns the error
// errUnsupportedConnType.
func (c *Conn) connControl(network string, fn func(fd uintptr)) error {
pconn := c.pconn4.pconn
if network == "udp6" {
pconn = c.pconn6.pconn
}
sc, ok := pconn.(syscall.Conn)
if !ok {
return errUnsupportedConnType
}
rc, err := sc.SyscallConn()
if err != nil {
return err
}
return rc.Control(fn)
}
func CanPMTUD() bool {
return debugEnablePMTUD()
}

@ -9,6 +9,7 @@ import (
"net/netip" "net/netip"
"sync" "sync"
"sync/atomic" "sync/atomic"
"syscall"
"golang.org/x/net/ipv6" "golang.org/x/net/ipv6"
"tailscale.com/net/netaddr" "tailscale.com/net/netaddr"
@ -166,3 +167,13 @@ func (c *RebindingUDPConn) writeToUDPAddrPortWithInitPconn(pconn nettype.PacketC
func (c *RebindingUDPConn) WriteToUDPAddrPort(b []byte, addr netip.AddrPort) (int, error) { func (c *RebindingUDPConn) WriteToUDPAddrPort(b []byte, addr netip.AddrPort) (int, error) {
return c.writeToUDPAddrPortWithInitPconn(*c.pconnAtomic.Load(), b, addr) return c.writeToUDPAddrPortWithInitPconn(*c.pconnAtomic.Load(), b, addr)
} }
func (c *RebindingUDPConn) SyscallConn() (syscall.RawConn, error) {
c.mu.Lock()
defer c.mu.Unlock()
sc, ok := c.pconn.(syscall.Conn)
if !ok {
return nil, errUnsupportedConnType
}
return sc.SyscallConn()
}

Loading…
Cancel
Save