github.com/pawelgaczynski/gain@v0.4.0-alpha.0.20230821120126-41f1e60a18da/pkg/socket/sockopts_posix.go (about)

     1  // Copyright (c) 2023 Paweł Gaczyński
     2  // Copyright (c) 2021 Andy Pan
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package socket
    17  
    18  import (
    19  	"fmt"
    20  	"net"
    21  	"os"
    22  	"syscall"
    23  
    24  	"github.com/pawelgaczynski/gain/pkg/errors"
    25  	gainNet "github.com/pawelgaczynski/gain/pkg/net"
    26  	"golang.org/x/sys/unix"
    27  )
    28  
    29  // SetNoDelay controls whether the operating system should delay
    30  // packet transmission in hopes of sending fewer packets (Nagle's algorithm).
    31  //
    32  // The default is true (no delay), meaning that data is
    33  // sent as soon as possible after a Write.
    34  func SetNoDelay(fd, noDelay int) error {
    35  	return os.NewSyscallError("setsockopt", unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_NODELAY, noDelay))
    36  }
    37  
    38  // SetRecvBuffer sets the size of the operating system's
    39  // receive buffer associated with the connection.
    40  func SetRecvBuffer(fd, size int) error {
    41  	return os.NewSyscallError("setsockopt", unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_RCVBUF, size))
    42  }
    43  
    44  // SetSendBuffer sets the size of the operating system's
    45  // transmit buffer associated with the connection.
    46  func SetSendBuffer(fd, size int) error {
    47  	return os.NewSyscallError("setsockopt", unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_SNDBUF, size))
    48  }
    49  
    50  // SetReuseport enables SO_REUSEPORT option on socket.
    51  func SetReuseport(fd, reusePort int) error {
    52  	return os.NewSyscallError("setsockopt", unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_REUSEPORT, reusePort))
    53  }
    54  
    55  // SetReuseAddr enables SO_REUSEADDR option on socket.
    56  func SetReuseAddr(fd, reuseAddr int) error {
    57  	return os.NewSyscallError("setsockopt", unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_REUSEADDR, reuseAddr))
    58  }
    59  
    60  // SetIPv6Only restricts a IPv6 socket to only process IPv6 requests or both IPv4 and IPv6 requests.
    61  func SetIPv6Only(fd, ipv6only int) error {
    62  	return os.NewSyscallError("setsockopt", unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_V6ONLY, ipv6only))
    63  }
    64  
    65  // SetQuickAck controls quickack mode on socket.
    66  // If quickack mode, acks are sent immediately, rather than delayed if needed in accordance to normal TCP operation.
    67  func SetQuickAck(fd, enabled int) error {
    68  	return os.NewSyscallError("setsockopt", unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_QUICKACK, enabled))
    69  }
    70  
    71  // SetFastOpen enables TCP_FASTOPEN on socket which allow to send and accept data in the opening SYN packet.
    72  // https://sysctl-explorer.net/net/ipv4/tcp_fastopen/
    73  func SetFastOpen(fd, enabled int) error {
    74  	return os.NewSyscallError("setsockopt", unix.SetsockoptInt(fd, unix.IPPROTO_TCP, unix.TCP_FASTOPEN, enabled))
    75  }
    76  
    77  // SetLinger sets the behavior of Close on a connection which still
    78  // has data waiting to be sent or to be acknowledged.
    79  //
    80  // If sec < 0 (the default), the operating system finishes sending the
    81  // data in the background.
    82  //
    83  // If sec == 0, the operating system discards any unsent or
    84  // unacknowledged data.
    85  //
    86  // If sec > 0, the data is sent in the background as with sec < 0. On
    87  // some operating systems after sec seconds have elapsed any remaining
    88  // unsent data may be discarded.
    89  func SetLinger(fd, sec int) error {
    90  	var linger unix.Linger
    91  	if sec >= 0 {
    92  		linger.Onoff = 1
    93  		linger.Linger = int32(sec)
    94  	} else {
    95  		linger.Onoff = 0
    96  		linger.Linger = 0
    97  	}
    98  
    99  	return os.NewSyscallError("setsockopt", unix.SetsockoptLinger(fd, syscall.SOL_SOCKET, syscall.SO_LINGER, &linger))
   100  }
   101  
   102  // SetMulticastMembership returns with a socket option function based on the IP
   103  // version. Returns nil when multicast membership cannot be applied.
   104  func SetMulticastMembership(proto string, udpAddr *net.UDPAddr) func(int, int) error {
   105  	udpVersion, err := determineUDPProto(proto, udpAddr)
   106  	if err != nil {
   107  		return nil
   108  	}
   109  
   110  	switch udpVersion {
   111  	case gainNet.UDP4:
   112  		return func(fd int, ifIndex int) error {
   113  			return SetIPv4MulticastMembership(fd, udpAddr.IP, ifIndex)
   114  		}
   115  
   116  	case gainNet.UDP6:
   117  		return func(fd int, ifIndex int) error {
   118  			return SetIPv6MulticastMembership(fd, udpAddr.IP, ifIndex)
   119  		}
   120  
   121  	default:
   122  		return nil
   123  	}
   124  }
   125  
   126  // SetIPv4MulticastMembership joins fd to the specified multicast IPv4 address.
   127  // ifIndex is the index of the interface where the multicast datagrams will be
   128  // received. If ifIndex is 0 then the operating system will choose the default,
   129  // it is usually needed when the host has multiple network interfaces configured.
   130  func SetIPv4MulticastMembership(fd int, mcast net.IP, ifIndex int) error {
   131  	// Multicast interfaces are selected by IP address on IPv4 (and by index on IPv6)
   132  	ip, err := interfaceFirstIPv4Addr(ifIndex)
   133  	if err != nil {
   134  		return err
   135  	}
   136  
   137  	mreq := &unix.IPMreq{}
   138  	copy(mreq.Multiaddr[:], mcast.To4())
   139  	copy(mreq.Interface[:], ip.To4())
   140  
   141  	if ifIndex > 0 {
   142  		if err = os.NewSyscallError(
   143  			"setsockopt", unix.SetsockoptInet4Addr(fd, syscall.IPPROTO_IP, syscall.IP_MULTICAST_IF, mreq.Interface),
   144  		); err != nil {
   145  			return err
   146  		}
   147  	}
   148  
   149  	if err = os.NewSyscallError(
   150  		"setsockopt", unix.SetsockoptByte(fd, syscall.IPPROTO_IP, syscall.IP_MULTICAST_LOOP, 0),
   151  	); err != nil {
   152  		return err
   153  	}
   154  
   155  	return os.NewSyscallError("setsockopt", unix.SetsockoptIPMreq(fd, syscall.IPPROTO_IP, syscall.IP_ADD_MEMBERSHIP, mreq))
   156  }
   157  
   158  // SetIPv6MulticastMembership joins fd to the specified multicast IPv6 address.
   159  // ifIndex is the index of the interface where the multicast datagrams will be
   160  // received. If ifIndex is 0 then the operating system will choose the default,
   161  // it is usually needed when the host has multiple network interfaces configured.
   162  func SetIPv6MulticastMembership(fd int, mcast net.IP, ifIndex int) error {
   163  	mreq := &unix.IPv6Mreq{}
   164  	mreq.Interface = uint32(ifIndex)
   165  	copy(mreq.Multiaddr[:], mcast.To16())
   166  
   167  	if ifIndex > 0 {
   168  		if err := os.NewSyscallError(
   169  			"setsockopt", unix.SetsockoptInt(fd, syscall.IPPROTO_IPV6, syscall.IPV6_MULTICAST_IF, ifIndex),
   170  		); err != nil {
   171  			return err
   172  		}
   173  	}
   174  
   175  	if err := os.NewSyscallError(
   176  		"setsockopt", unix.SetsockoptInt(fd, syscall.IPPROTO_IPV6, syscall.IPV6_MULTICAST_LOOP, 0),
   177  	); err != nil {
   178  		return err
   179  	}
   180  
   181  	return os.NewSyscallError(
   182  		"setsockopt", unix.SetsockoptIPv6Mreq(fd, syscall.IPPROTO_IPV6, syscall.IPV6_JOIN_GROUP, mreq),
   183  	)
   184  }
   185  
   186  // interfaceFirstIPv4Addr returns the first IPv4 address of the interface.
   187  func interfaceFirstIPv4Addr(ifIndex int) (net.IP, error) {
   188  	if ifIndex == 0 {
   189  		return net.IP([]byte{0, 0, 0, 0}), nil
   190  	}
   191  
   192  	iface, err := net.InterfaceByIndex(ifIndex)
   193  	if err != nil {
   194  		return nil, fmt.Errorf("interface by index error: %w", err)
   195  	}
   196  
   197  	addrs, err := iface.Addrs()
   198  	if err != nil {
   199  		return nil, fmt.Errorf("get addrs error: %w", err)
   200  	}
   201  
   202  	for _, addr := range addrs {
   203  		var ip net.IP
   204  
   205  		ip, _, err = net.ParseCIDR(addr.String())
   206  		if err != nil {
   207  			return nil, fmt.Errorf("parse CIDR error: %w", err)
   208  		}
   209  
   210  		if ip.To4() != nil {
   211  			return ip, nil
   212  		}
   213  	}
   214  
   215  	return nil, errors.ErrNoIPv4AddressOnInterface
   216  }