summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--dhcpv6/server6/conn.go62
-rw-r--r--dhcpv6/server6/server.go32
-rw-r--r--dhcpv6/server6/server_test.go2
3 files changed, 85 insertions, 11 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)
+}
diff --git a/dhcpv6/server6/server.go b/dhcpv6/server6/server.go
index 4506195..ac25076 100644
--- a/dhcpv6/server6/server.go
+++ b/dhcpv6/server6/server.go
@@ -112,9 +112,14 @@ func WithConn(conn net.PacketConn) ServerOpt {
}
}
-// NewServer initializes and returns a new Server object, listening on `addr`,
-// and joining the multicast group ff02::1:2 . If `addr` is nil, IPv6 unspec is
-// used. If `WithConn` is used with a non-nil address, `addr` and `ifname` have
+// NewServer initializes and returns a new Server object, listening on `addr`.
+// * If `addr` is a multicast group, the group will be additionally joined
+// * If `addr` is the wildcard address on the DHCPv6 server port (`[::]:547), the
+// multicast groups All_DHCP_Relay_Agents_and_Servers(`[ff02::1:2]`) and
+// All_DHCP_Servers(`[ff05::1:3]:547`) will be joined.
+// * If `addr` is nil, IPv6 unspec on the DHCP server port is used and the above
+// behaviour applies
+// If `WithConn` is used with a non-nil address, `addr` and `ifname` have
// no effect. In such case, joining the multicast group is the caller's
// responsibility.
func NewServer(ifname string, addr *net.UDPAddr, handler Handler, opt ...ServerOpt) (*Server, error) {
@@ -125,19 +130,21 @@ func NewServer(ifname string, addr *net.UDPAddr, handler Handler, opt ...ServerO
for _, o := range opt {
o(s)
}
-
if s.conn != nil {
return s, nil
}
- var err error
- // no connection provided by the user, create a new one
- s.conn, err = net.ListenUDP("udp6", addr)
- if err != nil {
- return nil, err
+ if addr == nil {
+ addr = &net.UDPAddr{
+ IP: net.IPv6unspecified,
+ Port: dhcpv6.DefaultServerPort,
+ }
}
- var iface *net.Interface
+ var (
+ err error
+ iface *net.Interface
+ )
if ifname == "" {
iface = nil
} else {
@@ -146,6 +153,11 @@ func NewServer(ifname string, addr *net.UDPAddr, handler Handler, opt ...ServerO
return nil, err
}
}
+ // no connection provided by the user, create a new one
+ s.conn, err = NewIPv6UDPConn(ifname, addr)
+ if err != nil {
+ return nil, err
+ }
p := ipv6.NewPacketConn(s.conn)
if addr.IP.IsMulticast() {
diff --git a/dhcpv6/server6/server_test.go b/dhcpv6/server6/server_test.go
index cf5fbea..09cde4c 100644
--- a/dhcpv6/server6/server_test.go
+++ b/dhcpv6/server6/server_test.go
@@ -33,7 +33,7 @@ func setUpClientAndServer(handler Handler) (*nclient6.Client, *Server) {
IP: net.ParseIP("::1"),
Port: 0,
}
- s, err := NewServer("lo", laddr, handler)
+ s, err := NewServer("", laddr, handler)
if err != nil {
panic(err)
}