diff options
author | Anatole Denis <natolumin@unverle.fr> | 2019-09-17 16:02:29 +0200 |
---|---|---|
committer | Anatole Denis <natolumin@unverle.fr> | 2019-09-18 11:13:40 +0200 |
commit | a8311dfaca587339aa1d905598ac0f2093ad7ea9 (patch) | |
tree | 3f1bf71af187d4a4f12042d44cb4514fae4a3d1c /dhcpv6/server6/conn.go | |
parent | eac77666371b0272d980e9ea739cc890957971a9 (diff) |
server6: Create UDP conn manually for more control
Similar to server4 where the UDP connection is manually created using
the socket interfaces, this creates a connection with adequate options:
* SO_BINDTODEVICE or equivalent if an interface is requested
* V6ONLY when supported by the operating system
* Allows binding to a multicast address specifically instead of
falling back to wildcard
Signed-off-by: Anatole Denis <natolumin@unverle.fr>
Diffstat (limited to 'dhcpv6/server6/conn.go')
-rw-r--r-- | dhcpv6/server6/conn.go | 62 |
1 files changed, 62 insertions, 0 deletions
diff --git a/dhcpv6/server6/conn.go b/dhcpv6/server6/conn.go new file mode 100644 index 0000000..08c54e8 --- /dev/null +++ b/dhcpv6/server6/conn.go @@ -0,0 +1,62 @@ +package server6 + +import ( + "errors" + "fmt" + "net" + "os" + + "github.com/insomniacslk/dhcp/interfaces" + "golang.org/x/sys/unix" +) + +// NewIPv6UDPConn returns a UDPv6-only connection bound to both the interface and port +// given based on a IPv6 DGRAM socket. +// As a bonus, you can actually listen on a multicast address instead of being punted to the wildcard +// +// The interface must already be configured. +func NewIPv6UDPConn(iface string, addr *net.UDPAddr) (net.PacketConn, error) { + fd, err := unix.Socket(unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP) + if err != nil { + return nil, fmt.Errorf("cannot get a UDP socket: %v", err) + } + f := os.NewFile(uintptr(fd), "") + // net.FilePacketConn dups the FD, so we have to close this in any case. + defer f.Close() + + // Allow broadcasting. + if err := unix.SetsockoptInt(fd, unix.IPPROTO_IPV6, unix.IPV6_V6ONLY, 1); err != nil { + if errno, ok := err.(unix.Errno); !ok { + return nil, fmt.Errorf("unexpected socket error: %v", err) + } else if errno != unix.ENOPROTOOPT { // Unsupported on some OSes (but in that case v6only is default), so we ignore ENOPROTOOPT + return nil, fmt.Errorf("cannot bind socket v6only %v", err) + } + } + // Allow reusing the addr to aid debugging. + if err := unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_REUSEADDR, 1); err != nil { + return nil, fmt.Errorf("cannot set reuseaddr on socket: %v", err) + } + if len(iface) != 0 { + // Bind directly to the interface. + if err := interfaces.BindToInterface(fd, iface); err != nil { + if errno, ok := err.(unix.Errno); ok && errno == unix.EACCES { + // Return a more helpful error message in this (fairly common) case + return nil, errors.New("Cannot bind to interface without CAP_NET_RAW or root permissions. " + + "Restart with elevated privilege, or run without specifying an interface to bind to all available interfaces.") + } + return nil, fmt.Errorf("cannot bind to interface %s: %v", iface, err) + } + } + + if addr == nil { + return nil, errors.New("An address to listen on needs to be specified") + } + // Bind to the port. + saddr := unix.SockaddrInet6{Port: addr.Port} + copy(saddr.Addr[:], addr.IP) + if err := unix.Bind(fd, &saddr); err != nil { + return nil, fmt.Errorf("cannot bind to address %v: %v", addr, err) + } + + return net.FilePacketConn(f) +} |