controlfns_linux.go raw

   1  /* SPDX-License-Identifier: MIT
   2   *
   3   * Copyright (C) 2017-2025 WireGuard LLC. All Rights Reserved.
   4   */
   5  
   6  package conn
   7  
   8  import (
   9  	"fmt"
  10  	"runtime"
  11  	"syscall"
  12  
  13  	"golang.org/x/sys/unix"
  14  )
  15  
  16  // Taken from go/src/internal/syscall/unix/kernel_version_linux.go
  17  func kernelVersion() (major, minor int) {
  18  	var uname unix.Utsname
  19  	if err := unix.Uname(&uname); err != nil {
  20  		return
  21  	}
  22  
  23  	var (
  24  		values    [2]int
  25  		value, vi int
  26  	)
  27  	for _, c := range uname.Release {
  28  		if '0' <= c && c <= '9' {
  29  			value = (value * 10) + int(c-'0')
  30  		} else {
  31  			// Note that we're assuming N.N.N here.
  32  			// If we see anything else, we are likely to mis-parse it.
  33  			values[vi] = value
  34  			vi++
  35  			if vi >= len(values) {
  36  				break
  37  			}
  38  			value = 0
  39  		}
  40  	}
  41  
  42  	return values[0], values[1]
  43  }
  44  
  45  func init() {
  46  	controlFns = append(controlFns,
  47  
  48  		// Attempt to set the socket buffer size beyond net.core.{r,w}mem_max by
  49  		// using SO_*BUFFORCE. This requires CAP_NET_ADMIN, and is allowed here to
  50  		// fail silently - the result of failure is lower performance on very fast
  51  		// links or high latency links.
  52  		func(network, address string, c syscall.RawConn) error {
  53  			return c.Control(func(fd uintptr) {
  54  				// Set up to *mem_max
  55  				_ = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_RCVBUF, socketBufferSize)
  56  				_ = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_SNDBUF, socketBufferSize)
  57  				// Set beyond *mem_max if CAP_NET_ADMIN
  58  				_ = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_RCVBUFFORCE, socketBufferSize)
  59  				_ = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_SNDBUFFORCE, socketBufferSize)
  60  			})
  61  		},
  62  
  63  		// Enable receiving of the packet information (IP_PKTINFO for IPv4,
  64  		// IPV6_PKTINFO for IPv6) that is used to implement sticky socket support.
  65  		func(network, address string, c syscall.RawConn) error {
  66  			var err error
  67  			switch network {
  68  			case "udp4":
  69  				if runtime.GOOS != "android" {
  70  					c.Control(func(fd uintptr) {
  71  						err = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_PKTINFO, 1)
  72  					})
  73  				}
  74  			case "udp6":
  75  				c.Control(func(fd uintptr) {
  76  					if runtime.GOOS != "android" {
  77  						err = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_RECVPKTINFO, 1)
  78  						if err != nil {
  79  							return
  80  						}
  81  					}
  82  					err = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_V6ONLY, 1)
  83  				})
  84  			default:
  85  				err = fmt.Errorf("unhandled network: %s: %w", network, unix.EINVAL)
  86  			}
  87  			return err
  88  		},
  89  
  90  		// Attempt to enable UDP_GRO
  91  		func(network, address string, c syscall.RawConn) error {
  92  			// Kernels below 5.12 are missing 98184612aca0 ("net:
  93  			// udp: Add support for getsockopt(..., ..., UDP_GRO,
  94  			// ..., ...);"), which means we can't read this back
  95  			// later. We could pipe the return value through to
  96  			// the rest of the code, but UDP_GRO is kind of buggy
  97  			// anyway, so just gate this here.
  98  			major, minor := kernelVersion()
  99  			if major < 5 || (major == 5 && minor < 12) {
 100  				return nil
 101  			}
 102  
 103  			c.Control(func(fd uintptr) {
 104  				_ = unix.SetsockoptInt(int(fd), unix.IPPROTO_UDP, unix.UDP_GRO, 1)
 105  			})
 106  			return nil
 107  		},
 108  	)
 109  }
 110