Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DHCPv6 Server multicast handling improvements #316

Merged
merged 3 commits into from
Sep 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions dhcpv4/bindtodevice_darwin.go

This file was deleted.

10 changes: 10 additions & 0 deletions dhcpv4/bindtointerface.go
Original file line number Diff line number Diff line change
@@ -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)
}
62 changes: 62 additions & 0 deletions dhcpv6/server6/conn.go
Original file line number Diff line number Diff line change
@@ -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)
}
71 changes: 49 additions & 22 deletions dhcpv6/server6/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
}
2 changes: 1 addition & 1 deletion dhcpv6/server6/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
4 changes: 2 additions & 2 deletions dhcpv4/bindtodevice_bsd.go → interfaces/bindtodevice_bsd.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// +build freebsd openbsd netbsd
// +build freebsd openbsd netbsd darwin

package dhcpv4
package interfaces

import (
"net"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// +build linux

package dhcpv4
package interfaces

import "golang.org/x/sys/unix"

Expand Down