summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--dhcpv4/bindtodevice_darwin.go19
-rw-r--r--dhcpv4/bindtointerface.go10
-rw-r--r--dhcpv6/server6/conn.go62
-rw-r--r--dhcpv6/server6/server.go71
-rw-r--r--dhcpv6/server6/server_test.go2
-rw-r--r--interfaces/bindtodevice_bsd.go (renamed from dhcpv4/bindtodevice_bsd.go)4
-rw-r--r--interfaces/bindtodevice_linux.go (renamed from dhcpv4/bindtodevice_linux.go)2
7 files changed, 125 insertions, 45 deletions
diff --git a/dhcpv4/bindtodevice_darwin.go b/dhcpv4/bindtodevice_darwin.go
deleted file mode 100644
index 3a1406b..0000000
--- a/dhcpv4/bindtodevice_darwin.go
+++ /dev/null
@@ -1,19 +0,0 @@
-// +build darwin
-
-package dhcpv4
-
-import (
- "net"
-
- "golang.org/x/sys/unix"
-)
-
-// BindToInterface emulates linux's SO_BINDTODEVICE option for a socket by using
-// SO_IP_BOUND_IF.
-func BindToInterface(fd int, ifname string) error {
- iface, err := net.InterfaceByName(ifname)
- if err != nil {
- return err
- }
- return unix.SetsockoptInt(fd, unix.IPPROTO_IP, unix.IP_BOUND_IF, iface.Index)
-}
diff --git a/dhcpv4/bindtointerface.go b/dhcpv4/bindtointerface.go
new file mode 100644
index 0000000..dbe8fbc
--- /dev/null
+++ b/dhcpv4/bindtointerface.go
@@ -0,0 +1,10 @@
+package dhcpv4
+
+import (
+ "github.com/insomniacslk/dhcp/interfaces"
+)
+
+// BindToInterface (deprecated) redirects to interfaces.BindToInterface
+func BindToInterface(fd int, ifname string) error {
+ return interfaces.BindToInterface(fd, ifname)
+}
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 ab9cb32..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,32 +130,54 @@ func NewServer(ifname string, addr *net.UDPAddr, handler Handler, opt ...ServerO
for _, o := range opt {
o(s)
}
+ if s.conn != nil {
+ return s, nil
+ }
+
+ if addr == nil {
+ addr = &net.UDPAddr{
+ IP: net.IPv6unspecified,
+ Port: dhcpv6.DefaultServerPort,
+ }
+ }
- if s.conn == nil {
- // no connection provided by the user, create a new one
- conn, err := net.ListenUDP("udp6", addr)
+ var (
+ err error
+ iface *net.Interface
+ )
+ if ifname == "" {
+ iface = nil
+ } else {
+ iface, err = net.InterfaceByName(ifname)
if err != nil {
return nil, err
}
- // join multicast group on the specified interface
- var iface *net.Interface
- if ifname == "" {
- iface = nil
- } else {
- iface, err = net.InterfaceByName(ifname)
- if err != nil {
+ }
+ // 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() {
+ if err := p.JoinGroup(iface, addr); err != nil {
+ return nil, err
+ }
+ } else if addr.IP.IsUnspecified() && addr.Port == dhcpv6.DefaultServerPort {
+ // For wildcard addresses on the correct port, listen on both multicast
+ // addresses defined in the RFC as a "default" behaviour
+ for _, g := range []net.IP{dhcpv6.AllDHCPRelayAgentsAndServers, dhcpv6.AllDHCPServers} {
+ group := net.UDPAddr{
+ IP: g,
+ Port: dhcpv6.DefaultServerPort,
+ }
+ if err := p.JoinGroup(iface, &group); err != nil {
return nil, err
}
+
}
- group := net.UDPAddr{
- IP: dhcpv6.AllDHCPRelayAgentsAndServers,
- Port: dhcpv6.DefaultServerPort,
- }
- p := ipv6.NewPacketConn(conn)
- if err := p.JoinGroup(iface, &group); err != nil {
- return nil, err
- }
- s.conn = conn
}
+
return s, nil
}
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)
}
diff --git a/dhcpv4/bindtodevice_bsd.go b/interfaces/bindtodevice_bsd.go
index 0f1b581..180c3eb 100644
--- a/dhcpv4/bindtodevice_bsd.go
+++ b/interfaces/bindtodevice_bsd.go
@@ -1,6 +1,6 @@
-// +build freebsd openbsd netbsd
+// +build freebsd openbsd netbsd darwin
-package dhcpv4
+package interfaces
import (
"net"
diff --git a/dhcpv4/bindtodevice_linux.go b/interfaces/bindtodevice_linux.go
index c1ae025..52c7177 100644
--- a/dhcpv4/bindtodevice_linux.go
+++ b/interfaces/bindtodevice_linux.go
@@ -1,6 +1,6 @@
// +build linux
-package dhcpv4
+package interfaces
import "golang.org/x/sys/unix"