mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 03:38:41 +00:00 
			
		
		
		
	wgengine/magicsock: set UDP socket buffer sizes to 7MB
- At high data rates more buffer space is required in order to avoid
  packet loss during any cause of delay.
- On slower machines more buffer space is required in order to avoid
  packet loss while decryption & tun writing is underway.
- On higher latency network paths more buffer space is required in order
  to overcome BDP.
- On Linux set with SO_*BUFFORCE to bypass net.core.{r,w}mem_max.
- 7MB is the current default maximum on macOS 12.6
- Windows test is omitted, as Windows does not support getsockopt for
  these options.
Signed-off-by: James Tucker <james@tailscale.com>
			
			
This commit is contained in:
		 James Tucker
					James Tucker
				
			
				
					committed by
					
						 James Tucker
						James Tucker
					
				
			
			
				
	
			
			
			 James Tucker
						James Tucker
					
				
			
						parent
						
							a315336287
						
					
				
				
					commit
					539c073cf0
				
			| @@ -69,6 +69,11 @@ const ( | ||||
| 	// _linux variant. | ||||
| 	discoMagic1 = 0x5453f09f | ||||
| 	discoMagic2 = 0x92ac | ||||
| 
 | ||||
| 	// UDP socket read/write buffer size (7MB). The value of 7MB is chosen as it | ||||
| 	// is the max supported by a default configuration of macOS. Some platforms | ||||
| 	// will silently clamp the value. | ||||
| 	socketBufferSize = 7 << 20 | ||||
| ) | ||||
| 
 | ||||
| // useDerpRoute reports whether magicsock should enable the DERP | ||||
| @@ -2893,6 +2898,7 @@ func (c *Conn) bindSocket(ruc *RebindingUDPConn, network string, curPortFate cur | ||||
| 			c.logf("magicsock: unable to bind %v port %d: %v", network, port, err) | ||||
| 			continue | ||||
| 		} | ||||
| 		trySetSocketBuffer(pconn, c.logf) | ||||
| 		// Success. | ||||
| 		ruc.setConnLocked(pconn) | ||||
| 		if network == "udp4" { | ||||
| @@ -3948,6 +3954,20 @@ func (de *endpoint) handlePongConnLocked(m *disco.Pong, di *discoInfo, src netip | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| // portableTrySetSocketBuffer sets SO_SNDBUF and SO_RECVBUF on pconn to socketBufferSize, | ||||
| // logging an error if it occurs. | ||||
| func portableTrySetSocketBuffer(pconn nettype.PacketConn, logf logger.Logf) { | ||||
| 	if c, ok := pconn.(*net.UDPConn); ok { | ||||
| 		// Attempt to increase the buffer size, and allow failures. | ||||
| 		if err := c.SetReadBuffer(socketBufferSize); err != nil { | ||||
| 			logf("magicsock: failed to set UDP read buffer size to %d: %v", socketBufferSize, err) | ||||
| 		} | ||||
| 		if err := c.SetWriteBuffer(socketBufferSize); err != nil { | ||||
| 			logf("magicsock: failed to set UDP write buffer size to %d: %v", socketBufferSize, err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // addrLatency is an IPPort with an associated latency. | ||||
| type addrLatency struct { | ||||
| 	netip.AddrPort | ||||
|   | ||||
| @@ -10,8 +10,15 @@ package magicsock | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"io" | ||||
| 
 | ||||
| 	"tailscale.com/types/logger" | ||||
| 	"tailscale.com/types/nettype" | ||||
| ) | ||||
| 
 | ||||
| func (c *Conn) listenRawDisco(family string) (io.Closer, error) { | ||||
| 	return nil, errors.New("raw disco listening not supported on this OS") | ||||
| } | ||||
| 
 | ||||
| func trySetSocketBuffer(pconn nettype.PacketConn, logf logger.Logf) { | ||||
| 	portableTrySetSocketBuffer(pconn, logf) | ||||
| } | ||||
|   | ||||
| @@ -12,6 +12,7 @@ import ( | ||||
| 	"io" | ||||
| 	"net" | ||||
| 	"net/netip" | ||||
| 	"syscall" | ||||
| 	"time" | ||||
| 	"unsafe" | ||||
| 
 | ||||
| @@ -20,6 +21,8 @@ import ( | ||||
| 	"tailscale.com/envknob" | ||||
| 	"tailscale.com/net/netns" | ||||
| 	"tailscale.com/types/key" | ||||
| 	"tailscale.com/types/logger" | ||||
| 	"tailscale.com/types/nettype" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| @@ -288,3 +291,30 @@ func setBPF(conn net.PacketConn, filter []bpf.RawInstruction) error { | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // trySetSocketBuffer attempts to set SO_SNDBUFFORCE and SO_RECVBUFFORCE which | ||||
| // can overcome the limit of net.core.{r,w}mem_max, but require CAP_NET_ADMIN. | ||||
| // It falls back to the portable implementation if that fails, which may be | ||||
| // silently capped to net.core.{r,w}mem_max. | ||||
| func trySetSocketBuffer(pconn nettype.PacketConn, logf logger.Logf) { | ||||
| 	if c, ok := pconn.(*net.UDPConn); ok { | ||||
| 		var errRcv, errSnd error | ||||
| 		rc, err := c.SyscallConn() | ||||
| 		if err == nil { | ||||
| 			rc.Control(func(fd uintptr) { | ||||
| 				errRcv = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_RCVBUFFORCE, socketBufferSize) | ||||
| 				if errRcv != nil { | ||||
| 					logf("magicsock: failed to force-set UDP read buffer size to %d: %v", socketBufferSize, errRcv) | ||||
| 				} | ||||
| 				errSnd = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_SNDBUFFORCE, socketBufferSize) | ||||
| 				if errSnd != nil { | ||||
| 					logf("magicsock: failed to force-set UDP write buffer size to %d: %v", socketBufferSize, errSnd) | ||||
| 				} | ||||
| 			}) | ||||
| 		} | ||||
| 
 | ||||
| 		if err != nil || errRcv != nil || errSnd != nil { | ||||
| 			portableTrySetSocketBuffer(pconn, logf) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										62
									
								
								wgengine/magicsock/magicsock_unix_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								wgengine/magicsock/magicsock_unix_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| // Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
| 
 | ||||
| //go:build unix | ||||
| // +build unix | ||||
| 
 | ||||
| package magicsock | ||||
| 
 | ||||
| import ( | ||||
| 	"net" | ||||
| 	"syscall" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"tailscale.com/types/nettype" | ||||
| ) | ||||
| 
 | ||||
| func TestTrySetSocketBuffer(t *testing.T) { | ||||
| 	c, err := net.ListenPacket("udp", ":0") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	defer c.Close() | ||||
| 
 | ||||
| 	rc, err := c.(*net.UDPConn).SyscallConn() | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 
 | ||||
| 	getBufs := func() (int, int) { | ||||
| 		var rcv, snd int | ||||
| 		rc.Control(func(fd uintptr) { | ||||
| 			rcv, err = syscall.GetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_RCVBUF) | ||||
| 			if err != nil { | ||||
| 				t.Errorf("getsockopt(SO_RCVBUF): %v", err) | ||||
| 			} | ||||
| 			snd, err = syscall.GetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_SNDBUF) | ||||
| 			if err != nil { | ||||
| 				t.Errorf("getsockopt(SO_SNDBUF): %v", err) | ||||
| 			} | ||||
| 		}) | ||||
| 		return rcv, snd | ||||
| 	} | ||||
| 
 | ||||
| 	curRcv, curSnd := getBufs() | ||||
| 
 | ||||
| 	trySetSocketBuffer(c.(nettype.PacketConn), t.Logf) | ||||
| 
 | ||||
| 	newRcv, newSnd := getBufs() | ||||
| 
 | ||||
| 	if curRcv > newRcv { | ||||
| 		t.Errorf("SO_RCVBUF decreased: %v -> %v", curRcv, newRcv) | ||||
| 	} | ||||
| 	if curSnd > newSnd { | ||||
| 		t.Errorf("SO_SNDBUF decreased: %v -> %v", curSnd, newSnd) | ||||
| 	} | ||||
| 
 | ||||
| 	// On many systems we may not increase the value, particularly running as a | ||||
| 	// regular user, so log the information for manual verification. | ||||
| 	t.Logf("SO_RCVBUF: %v -> %v", curRcv, newRcv) | ||||
| 	t.Logf("SO_SNDBUF: %v -> %v", curRcv, newRcv) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user