2019-01-15 08:02:56 +01:00

331 lines
9.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package dhcp6 implements a DHCPv6 client.
package dhcp6
import (
"fmt"
"log"
"net"
"strconv"
"time"
"github.com/insomniacslk/dhcp/dhcpv6"
"github.com/insomniacslk/dhcp/iana"
)
type ClientConfig struct {
InterfaceName string // e.g. eth0
// LocalAddr allows overwriting the source address used for sending DHCPv6
// packets. It defaults to the first link-local address of InterfaceName.
LocalAddr *net.UDPAddr
// RemoteAddr allows addressing a specific DHCPv6 server. It defaults to
// the dhcpv6.AllDHCPRelayAgentsAndServers multicast address.
RemoteAddr *net.UDPAddr
// DUID contains all bytes (including the prefixing uint16 type field) for a
// DHCP Unique Identifier (e.g. []byte{0x00, 0x0a, 0x00, 0x03, 0x00, 0x01,
// 0x4c, 0x5e, 0xc, 0x41, 0xbf, 0x39}).
//
// Fiber7 assigns static IPv6 /48 networks to DUIDs, so it is important to
// be able to carry it around between devices.
DUID []byte
Conn net.PacketConn // for testing
TransactionIDs []uint32 // for testing
}
// Config contains the obtained network configuration.
type Config struct {
RenewAfter time.Time `json:"valid_until"`
Prefixes []net.IPNet `json:"prefixes"` // e.g. 2a02:168:4a00::/48
DNS []string `json:"dns"` // e.g. 2001:1620:2777:1::10, 2001:1620:2777:2::20
}
type Client struct {
interfaceName string
raddr *net.UDPAddr
timeNow func() time.Time
duid *dhcpv6.Duid
advertise dhcpv6.DHCPv6
cfg Config
err error
Conn net.PacketConn // TODO: unexport
transactionIDs []uint32
ReadTimeout time.Duration
WriteTimeout time.Duration
RemoteAddr net.Addr
}
func NewClient(cfg ClientConfig) (*Client, error) {
iface, err := net.InterfaceByName(cfg.InterfaceName)
if err != nil {
return nil, err
}
// if no LocalAddr is specified, get the interface's link-local address
laddr := cfg.LocalAddr
if laddr == nil {
llAddr, err := dhcpv6.GetLinkLocalAddr(cfg.InterfaceName)
if err != nil {
return nil, err
}
laddr = &net.UDPAddr{
IP: llAddr,
Port: dhcpv6.DefaultClientPort,
// HACK: Zone should ideally be cfg.InterfaceName, but Gos
// ipv6ZoneCache is only updated every 60s, so the addition of the
// veth interface will not be picked up for all tests after the
// first test.
Zone: strconv.Itoa(iface.Index),
}
}
// if no RemoteAddr is specified, use AllDHCPRelayAgentsAndServers
raddr := cfg.RemoteAddr
if raddr == nil {
raddr = &net.UDPAddr{
IP: dhcpv6.AllDHCPRelayAgentsAndServers,
Port: dhcpv6.DefaultServerPort,
}
}
var duid *dhcpv6.Duid
if cfg.DUID != nil {
var err error
duid, err = dhcpv6.DuidFromBytes(cfg.DUID)
if err != nil {
return nil, err
}
fmt.Printf("duid: %T, %v, %#v", duid, duid, duid)
} else {
iface, err := net.InterfaceByName(cfg.InterfaceName)
if err != nil {
return nil, err
}
duid = &dhcpv6.Duid{
Type: dhcpv6.DUID_LLT,
HwType: iana.HWTypeEthernet,
Time: dhcpv6.GetTime(),
LinkLayerAddr: iface.HardwareAddr,
}
}
// prepare the socket to listen on for replies
conn := cfg.Conn
if conn == nil {
udpConn, err := net.ListenUDP("udp6", laddr)
if err != nil {
return nil, err
}
conn = udpConn
}
return &Client{
interfaceName: cfg.InterfaceName,
timeNow: time.Now,
raddr: raddr,
Conn: conn,
duid: duid,
transactionIDs: cfg.TransactionIDs,
ReadTimeout: dhcpv6.DefaultReadTimeout,
WriteTimeout: dhcpv6.DefaultWriteTimeout,
}, nil
}
func (c *Client) Close() error {
return c.Conn.Close()
}
const maxUDPReceivedPacketSize = 8192 // arbitrary size. Theoretically could be up to 65kb
func (c *Client) sendReceive(packet dhcpv6.DHCPv6, expectedType dhcpv6.MessageType) (dhcpv6.DHCPv6, error) {
if packet == nil {
return nil, fmt.Errorf("Packet to send cannot be nil")
}
if expectedType == dhcpv6.MessageTypeNone {
// infer the expected type from the packet being sent
if packet.Type() == dhcpv6.MessageTypeSolicit {
expectedType = dhcpv6.MessageTypeAdvertise
} else if packet.Type() == dhcpv6.MessageTypeRequest {
expectedType = dhcpv6.MessageTypeReply
} else if packet.Type() == dhcpv6.MessageTypeRelayForward {
expectedType = dhcpv6.MessageTypeRelayReply
} else if packet.Type() == dhcpv6.MessageTypeLeaseQuery {
expectedType = dhcpv6.MessageTypeLeaseQueryReply
} // and probably more
}
// send the packet out
c.Conn.SetWriteDeadline(time.Now().Add(c.WriteTimeout))
if _, err := c.Conn.WriteTo(packet.ToBytes(), c.raddr); err != nil {
return nil, err
}
// wait for a reply
c.Conn.SetReadDeadline(time.Now().Add(c.ReadTimeout))
var (
adv dhcpv6.DHCPv6
isMessage bool
)
msg, ok := packet.(*dhcpv6.DHCPv6Message)
if ok {
isMessage = true
}
for {
buf := make([]byte, maxUDPReceivedPacketSize)
n, _, err := c.Conn.ReadFrom(buf)
if err != nil {
return nil, err
}
adv, err = dhcpv6.FromBytes(buf[:n])
if err != nil {
log.Printf("non-DHCP: %v", err)
// skip non-DHCP packets
continue
}
if recvMsg, ok := adv.(*dhcpv6.DHCPv6Message); ok && isMessage {
// if a regular message, check the transaction ID first
// XXX should this unpack relay messages and check the XID of the
// inner packet too?
if msg.TransactionID() != recvMsg.TransactionID() {
log.Printf("different XID: got %v, want %v", recvMsg.TransactionID(), msg.TransactionID())
// different XID, we don't want this packet for sure
continue
}
}
if expectedType == dhcpv6.MessageTypeNone {
// just take whatever arrived
break
} else if adv.Type() == expectedType {
break
}
}
return adv, nil
}
func (c *Client) solicit(solicit dhcpv6.DHCPv6) (dhcpv6.DHCPv6, dhcpv6.DHCPv6, error) {
var err error
if solicit == nil {
solicit, err = dhcpv6.NewSolicitForInterface(c.interfaceName, dhcpv6.WithClientID(*c.duid))
if err != nil {
return nil, nil, err
}
}
if len(c.transactionIDs) > 0 {
id := c.transactionIDs[0]
c.transactionIDs = c.transactionIDs[1:]
solicit.(*dhcpv6.DHCPv6Message).SetTransactionID(id)
}
iapd := []byte{0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
opt, err := dhcpv6.ParseOptIAForPrefixDelegation(iapd)
if err != nil {
return nil, nil, err
}
solicit.AddOption(opt)
advertise, err := c.sendReceive(solicit, dhcpv6.MessageTypeNone)
return solicit, advertise, err
}
func (c *Client) request(advertise dhcpv6.DHCPv6) (dhcpv6.DHCPv6, dhcpv6.DHCPv6, error) {
request, err := dhcpv6.NewRequestFromAdvertise(advertise, dhcpv6.WithClientID(*c.duid))
if err != nil {
return nil, nil, err
}
if iapd := advertise.GetOneOption(dhcpv6.OptionIAPD); iapd != nil {
request.AddOption(iapd)
}
if len(c.transactionIDs) > 0 {
id := c.transactionIDs[0]
c.transactionIDs = c.transactionIDs[1:]
request.(*dhcpv6.DHCPv6Message).SetTransactionID(id)
}
reply, err := c.sendReceive(request, dhcpv6.MessageTypeNone)
return request, reply, err
}
func (c *Client) ObtainOrRenew() bool {
c.err = nil // clear previous error
_, advertise, err := c.solicit(nil)
if err != nil {
c.err = err
return true
}
c.advertise = advertise
_, reply, err := c.request(advertise)
if err != nil {
c.err = err
return true
}
var newCfg Config
for _, opt := range reply.Options() {
switch o := opt.(type) {
case *dhcpv6.OptIAForPrefixDelegation:
t1 := c.timeNow().Add(time.Duration(o.T1) * time.Second)
if t1.Before(newCfg.RenewAfter) || newCfg.RenewAfter.IsZero() {
newCfg.RenewAfter = t1
}
if sopt := o.GetOneOption(dhcpv6.OptionIAPrefix); sopt != nil {
prefix := sopt.(*dhcpv6.OptIAPrefix)
newCfg.Prefixes = append(newCfg.Prefixes, net.IPNet{
IP: prefix.IPv6Prefix(),
Mask: net.CIDRMask(int(prefix.PrefixLength()), 128),
})
}
case *dhcpv6.OptDNSRecursiveNameServer:
for _, ns := range o.NameServers {
newCfg.DNS = append(newCfg.DNS, ns.String())
}
}
}
c.cfg = newCfg
return true
}
func (c *Client) Release() (release dhcpv6.DHCPv6, reply dhcpv6.DHCPv6, err error) {
release, err = dhcpv6.NewRequestFromAdvertise(c.advertise, dhcpv6.WithClientID(*c.duid))
if err != nil {
return nil, nil, err
}
release.(*dhcpv6.DHCPv6Message).SetMessage(dhcpv6.MessageTypeRelease)
if len(c.transactionIDs) > 0 {
id := c.transactionIDs[0]
c.transactionIDs = c.transactionIDs[1:]
release.(*dhcpv6.DHCPv6Message).SetTransactionID(id)
}
reply, err = c.sendReceive(release, dhcpv6.MessageTypeNone)
return release, reply, err
}
func (c *Client) Err() error {
return c.err
}
func (c *Client) Config() Config {
return c.cfg
}