233 lines
5.2 KiB
Go
233 lines
5.2 KiB
Go
// Package radvd implements IPv6 router advertisments.
|
||
package radvd
|
||
|
||
import (
|
||
"encoding/binary"
|
||
"log"
|
||
"net"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/google/gopacket"
|
||
"github.com/google/gopacket/layers"
|
||
|
||
"golang.org/x/net/icmp"
|
||
"golang.org/x/net/ipv6"
|
||
)
|
||
|
||
type Server struct {
|
||
pc *ipv6.PacketConn
|
||
|
||
mu sync.Mutex
|
||
prefixes []net.IPNet
|
||
iface *net.Interface
|
||
}
|
||
|
||
func NewServer() (*Server, error) {
|
||
return &Server{}, nil
|
||
}
|
||
|
||
func (s *Server) SetPrefixes(prefixes []net.IPNet) {
|
||
s.mu.Lock()
|
||
s.prefixes = prefixes
|
||
s.mu.Unlock()
|
||
if s.iface != nil {
|
||
s.sendAdvertisement(nil)
|
||
}
|
||
}
|
||
|
||
func (s *Server) Serve(ifname string, conn net.PacketConn) error {
|
||
var err error
|
||
s.iface, err = net.InterfaceByName(ifname)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
defer conn.Close()
|
||
s.pc = ipv6.NewPacketConn(conn)
|
||
s.pc.SetHopLimit(255) // as per RFC 4861, section 4.1
|
||
s.pc.SetMulticastHopLimit(255) // as per RFC 4861, section 4.1
|
||
|
||
var filter ipv6.ICMPFilter
|
||
filter.SetAll(true)
|
||
filter.Accept(ipv6.ICMPTypeRouterSolicitation)
|
||
if err := s.pc.SetICMPFilter(&filter); err != nil {
|
||
return err
|
||
}
|
||
|
||
go func() {
|
||
for {
|
||
s.sendAdvertisement(nil) // TODO: handle error
|
||
time.Sleep(1 * time.Minute)
|
||
}
|
||
}()
|
||
|
||
// A 512 bytes buffer is sufficient for router solicitation packets, which
|
||
// are basically empty.
|
||
buf := make([]byte, 512)
|
||
for {
|
||
n, _, addr, err := s.pc.ReadFrom(buf)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
// TODO: isn’t this guaranteed by the filter above?
|
||
if n == 0 ||
|
||
ipv6.ICMPType(buf[0]) != ipv6.ICMPTypeRouterSolicitation {
|
||
continue
|
||
}
|
||
if err := s.sendAdvertisement(addr); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (s *Server) ListenAndServe(ifname string) error {
|
||
// TODO(correctness): would it be better to listen on
|
||
// net.IPv6linklocalallrouters? Just specifying that results in an error,
|
||
// though.
|
||
conn, err := net.ListenIP("ip6:ipv6-icmp", &net.IPAddr{net.IPv6unspecified, ""})
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return s.Serve(ifname, conn)
|
||
}
|
||
|
||
type sourceLinkLayerAddress struct {
|
||
address net.HardwareAddr
|
||
}
|
||
|
||
func (o sourceLinkLayerAddress) Marshal() layers.ICMPv6Option {
|
||
return layers.ICMPv6Option{
|
||
Type: layers.ICMPv6OptSourceAddress,
|
||
Data: o.address,
|
||
}
|
||
}
|
||
|
||
type mtu struct {
|
||
mtu uint32
|
||
}
|
||
|
||
func (o mtu) Marshal() layers.ICMPv6Option {
|
||
buf := make([]byte, 6)
|
||
// First 2 bytes are reserved
|
||
binary.BigEndian.PutUint32(buf[2:], o.mtu)
|
||
return layers.ICMPv6Option{
|
||
Type: layers.ICMPv6OptMTU,
|
||
Data: buf,
|
||
}
|
||
}
|
||
|
||
type prefixInfo struct {
|
||
prefixLength byte
|
||
flags byte // TODO: enum for values
|
||
validLifetime uint32 // seconds
|
||
preferredLifetime uint32 // seconds
|
||
prefix [16]byte
|
||
}
|
||
|
||
func (o prefixInfo) Marshal() layers.ICMPv6Option {
|
||
buf := make([]byte, 30)
|
||
buf[0] = o.prefixLength
|
||
buf[1] = o.flags
|
||
binary.BigEndian.PutUint32(buf[2:], o.validLifetime)
|
||
binary.BigEndian.PutUint32(buf[6:], o.preferredLifetime)
|
||
// 4 bytes reserved
|
||
copy(buf[14:], o.prefix[:])
|
||
return layers.ICMPv6Option{
|
||
Type: layers.ICMPv6OptPrefixInfo,
|
||
Data: buf,
|
||
}
|
||
}
|
||
|
||
type rdnss struct {
|
||
lifetime uint32 // seconds
|
||
server [16]byte
|
||
}
|
||
|
||
func (o rdnss) Marshal() layers.ICMPv6Option {
|
||
buf := make([]byte, 22)
|
||
// 2 bytes reserved
|
||
binary.BigEndian.PutUint32(buf[2:], o.lifetime)
|
||
copy(buf[6:], o.server[:])
|
||
return layers.ICMPv6Option{
|
||
Type: 25, // TODO: Recursive DNS Server
|
||
Data: buf,
|
||
}
|
||
}
|
||
|
||
func (s *Server) sendAdvertisement(addr net.Addr) error {
|
||
if s.prefixes == nil {
|
||
return nil // nothing to do
|
||
}
|
||
if addr == nil {
|
||
addr = &net.IPAddr{net.IPv6linklocalallnodes, s.iface.Name}
|
||
}
|
||
// TODO: cache the packet
|
||
msgbody := []byte{
|
||
0x40, // hop limit: 64
|
||
0x80, // flags: managed address configuration
|
||
0x07, 0x08, // router lifetime (s): 1800
|
||
0x00, 0x00, 0x00, 0x00, // reachable time (ms): 0
|
||
0x00, 0x00, 0x00, 0x00, // retrans time (ms): 0
|
||
}
|
||
|
||
options := layers.ICMPv6Options{
|
||
(sourceLinkLayerAddress{address: s.iface.HardwareAddr}).Marshal(),
|
||
(mtu{mtu: uint32(s.iface.MTU)}).Marshal(),
|
||
}
|
||
s.mu.Lock()
|
||
for _, prefix := range s.prefixes {
|
||
ones, _ := prefix.Mask.Size()
|
||
// Use the first /64 subnet within larger prefixes
|
||
if ones < 64 {
|
||
ones = 64
|
||
}
|
||
|
||
var net [16]byte
|
||
copy(net[:], prefix.IP)
|
||
options = append(options, (prefixInfo{
|
||
prefixLength: byte(ones),
|
||
flags: 0xc0, // TODO
|
||
validLifetime: 7200, // seconds
|
||
preferredLifetime: 1800, // seconds
|
||
prefix: net,
|
||
}).Marshal())
|
||
}
|
||
if len(s.prefixes) > 0 {
|
||
prefix := s.prefixes[0]
|
||
var server [16]byte
|
||
copy(server[:], prefix.IP)
|
||
// pick the first address of the prefix, e.g. address 2a02:168:4a00::1
|
||
// for prefix 2a02:168:4a00::/48
|
||
server[len(server)-1] = 1
|
||
options = append(options, (rdnss{
|
||
lifetime: 1800, // seconds
|
||
server: server,
|
||
}).Marshal())
|
||
}
|
||
s.mu.Unlock()
|
||
|
||
buf := gopacket.NewSerializeBuffer()
|
||
if err := options.SerializeTo(buf, gopacket.SerializeOptions{FixLengths: true}); err != nil {
|
||
return err
|
||
}
|
||
msgbody = append(msgbody, buf.Bytes()...)
|
||
|
||
msg := &icmp.Message{
|
||
Type: ipv6.ICMPTypeRouterAdvertisement,
|
||
Code: 0,
|
||
Checksum: 0, // calculated by the kernel
|
||
Body: &icmp.DefaultMessageBody{msgbody}}
|
||
mb, err := msg.Marshal(nil)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
log.Printf("sending to %s", addr)
|
||
if _, err := s.pc.WriteTo(mb, nil, addr); err != nil {
|
||
return err
|
||
}
|
||
return nil
|
||
}
|