2018-06-28 13:39:48 +02:00
|
|
|
|
// 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.
|
|
|
|
|
|
2018-05-27 17:30:42 +02:00
|
|
|
|
// Package dhcp6 implements a DHCPv6 client.
|
|
|
|
|
package dhcp6
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"log"
|
|
|
|
|
"net"
|
2018-06-02 18:01:44 +02:00
|
|
|
|
"strconv"
|
2018-05-27 17:30:42 +02:00
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/insomniacslk/dhcp/dhcpv6"
|
2019-01-28 21:06:10 +00:00
|
|
|
|
"github.com/insomniacslk/dhcp/dhcpv6/client6"
|
2018-06-02 18:01:44 +02:00
|
|
|
|
"github.com/insomniacslk/dhcp/iana"
|
2018-05-27 17:30:42 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
2018-06-02 18:01:44 +02:00
|
|
|
|
// 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
|
|
|
|
|
|
2019-01-27 02:47:19 -08:00
|
|
|
|
Conn net.PacketConn // for testing
|
|
|
|
|
TransactionIDs []dhcpv6.TransactionID // for testing
|
2019-04-29 19:16:19 +02:00
|
|
|
|
|
|
|
|
|
// HardwareAddr allows overriding the hardware address in tests. If nil,
|
|
|
|
|
// defaults to the hardware address of the interface identified by
|
|
|
|
|
// InterfaceName.
|
|
|
|
|
HardwareAddr net.HardwareAddr
|
2018-05-27 17:30:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
2019-04-16 08:36:06 +02:00
|
|
|
|
hardwareAddr net.HardwareAddr
|
2018-05-27 17:30:42 +02:00
|
|
|
|
raddr *net.UDPAddr
|
|
|
|
|
timeNow func() time.Time
|
2018-06-02 18:01:44 +02:00
|
|
|
|
duid *dhcpv6.Duid
|
2019-03-01 09:48:56 -08:00
|
|
|
|
advertise *dhcpv6.Message
|
2018-05-27 17:30:42 +02:00
|
|
|
|
|
|
|
|
|
cfg Config
|
|
|
|
|
err error
|
|
|
|
|
|
|
|
|
|
Conn net.PacketConn // TODO: unexport
|
2019-01-27 02:47:19 -08:00
|
|
|
|
transactionIDs []dhcpv6.TransactionID
|
2018-05-27 17:30:42 +02:00
|
|
|
|
|
|
|
|
|
ReadTimeout time.Duration
|
|
|
|
|
WriteTimeout time.Duration
|
|
|
|
|
|
|
|
|
|
RemoteAddr net.Addr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewClient(cfg ClientConfig) (*Client, error) {
|
2018-06-02 17:33:01 +02:00
|
|
|
|
iface, err := net.InterfaceByName(cfg.InterfaceName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-27 17:30:42 +02:00
|
|
|
|
// 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{
|
2018-09-05 08:39:35 +02:00
|
|
|
|
IP: llAddr,
|
2018-05-27 17:30:42 +02:00
|
|
|
|
Port: dhcpv6.DefaultClientPort,
|
2018-06-02 17:33:01 +02:00
|
|
|
|
// HACK: Zone should ideally be cfg.InterfaceName, but Go’s
|
|
|
|
|
// 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),
|
2018-05-27 17:30:42 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if no RemoteAddr is specified, use AllDHCPRelayAgentsAndServers
|
|
|
|
|
raddr := cfg.RemoteAddr
|
|
|
|
|
if raddr == nil {
|
|
|
|
|
raddr = &net.UDPAddr{
|
2019-04-29 15:43:30 +01:00
|
|
|
|
IP: dhcpv6.AllDHCPRelayAgentsAndServers,
|
2018-05-27 17:30:42 +02:00
|
|
|
|
Port: dhcpv6.DefaultServerPort,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-04-29 19:16:19 +02:00
|
|
|
|
hardwareAddr := iface.HardwareAddr
|
|
|
|
|
if cfg.HardwareAddr != nil {
|
|
|
|
|
hardwareAddr = cfg.HardwareAddr
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-02 18:01:44 +02:00
|
|
|
|
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 {
|
|
|
|
|
duid = &dhcpv6.Duid{
|
|
|
|
|
Type: dhcpv6.DUID_LLT,
|
2019-01-14 23:02:56 -08:00
|
|
|
|
HwType: iana.HWTypeEthernet,
|
2018-06-02 18:01:44 +02:00
|
|
|
|
Time: dhcpv6.GetTime(),
|
2019-04-29 19:16:19 +02:00
|
|
|
|
LinkLayerAddr: hardwareAddr,
|
2018-06-02 18:01:44 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-27 17:30:42 +02:00
|
|
|
|
// 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,
|
2019-04-29 19:16:19 +02:00
|
|
|
|
hardwareAddr: hardwareAddr,
|
2018-05-27 17:30:42 +02:00
|
|
|
|
timeNow: time.Now,
|
|
|
|
|
raddr: raddr,
|
|
|
|
|
Conn: conn,
|
2018-06-02 18:01:44 +02:00
|
|
|
|
duid: duid,
|
2018-05-27 17:30:42 +02:00
|
|
|
|
transactionIDs: cfg.TransactionIDs,
|
2019-01-28 21:06:10 +00:00
|
|
|
|
ReadTimeout: client6.DefaultReadTimeout,
|
|
|
|
|
WriteTimeout: client6.DefaultWriteTimeout,
|
2018-05-27 17:30:42 +02:00
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) Close() error {
|
|
|
|
|
return c.Conn.Close()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const maxUDPReceivedPacketSize = 8192 // arbitrary size. Theoretically could be up to 65kb
|
|
|
|
|
|
2019-03-01 09:48:56 -08:00
|
|
|
|
func (c *Client) sendReceive(packet *dhcpv6.Message, expectedType dhcpv6.MessageType) (*dhcpv6.Message, error) {
|
2018-05-27 17:30:42 +02:00
|
|
|
|
if packet == nil {
|
|
|
|
|
return nil, fmt.Errorf("Packet to send cannot be nil")
|
|
|
|
|
}
|
2018-08-05 11:45:03 +02:00
|
|
|
|
if expectedType == dhcpv6.MessageTypeNone {
|
2018-05-27 17:30:42 +02:00
|
|
|
|
// infer the expected type from the packet being sent
|
2018-08-05 11:45:03 +02:00
|
|
|
|
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
|
2018-05-27 17:30:42 +02:00
|
|
|
|
} // 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 (
|
2019-03-01 09:48:56 -08:00
|
|
|
|
adv *dhcpv6.Message
|
2018-05-27 17:30:42 +02:00
|
|
|
|
)
|
|
|
|
|
for {
|
|
|
|
|
buf := make([]byte, maxUDPReceivedPacketSize)
|
|
|
|
|
n, _, err := c.Conn.ReadFrom(buf)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2019-03-01 09:48:56 -08:00
|
|
|
|
adv, err = dhcpv6.MessageFromBytes(buf[:n])
|
2018-05-27 17:30:42 +02:00
|
|
|
|
if err != nil {
|
|
|
|
|
log.Printf("non-DHCP: %v", err)
|
|
|
|
|
// skip non-DHCP packets
|
|
|
|
|
continue
|
|
|
|
|
}
|
2019-03-01 09:48:56 -08:00
|
|
|
|
if packet.TransactionID != adv.TransactionID {
|
|
|
|
|
log.Printf("different XID: got %v, want %v", adv.TransactionID, packet.TransactionID)
|
|
|
|
|
// different XID, we don't want this packet for sure
|
|
|
|
|
continue
|
2018-05-27 17:30:42 +02:00
|
|
|
|
}
|
2018-08-05 11:45:03 +02:00
|
|
|
|
if expectedType == dhcpv6.MessageTypeNone {
|
2018-05-27 17:30:42 +02:00
|
|
|
|
// just take whatever arrived
|
|
|
|
|
break
|
2019-03-01 09:48:56 -08:00
|
|
|
|
} else if adv.MessageType == expectedType {
|
2018-05-27 17:30:42 +02:00
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return adv, nil
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-01 09:48:56 -08:00
|
|
|
|
func (c *Client) solicit(solicit *dhcpv6.Message) (*dhcpv6.Message, *dhcpv6.Message, error) {
|
2018-05-27 17:30:42 +02:00
|
|
|
|
var err error
|
|
|
|
|
if solicit == nil {
|
2019-04-16 08:36:06 +02:00
|
|
|
|
solicit, err = dhcpv6.NewSolicit(c.hardwareAddr, dhcpv6.WithClientID(*c.duid))
|
2018-05-27 17:30:42 +02:00
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if len(c.transactionIDs) > 0 {
|
|
|
|
|
id := c.transactionIDs[0]
|
|
|
|
|
c.transactionIDs = c.transactionIDs[1:]
|
2019-03-01 09:48:56 -08:00
|
|
|
|
solicit.TransactionID = id
|
2018-05-27 17:30:42 +02:00
|
|
|
|
}
|
2020-03-13 00:47:38 -07:00
|
|
|
|
solicit.AddOption(&dhcpv6.OptIAPD{IaId: [4]byte{0, 0, 0, 1}})
|
2018-08-05 11:45:03 +02:00
|
|
|
|
advertise, err := c.sendReceive(solicit, dhcpv6.MessageTypeNone)
|
2018-05-27 17:30:42 +02:00
|
|
|
|
return solicit, advertise, err
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-01 09:48:56 -08:00
|
|
|
|
func (c *Client) request(advertise *dhcpv6.Message) (*dhcpv6.Message, *dhcpv6.Message, error) {
|
2018-06-03 20:03:07 +02:00
|
|
|
|
request, err := dhcpv6.NewRequestFromAdvertise(advertise, dhcpv6.WithClientID(*c.duid))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, err
|
2018-05-27 17:30:42 +02:00
|
|
|
|
}
|
2020-03-13 00:47:38 -07:00
|
|
|
|
if iapd := advertise.Options.OneIAPD(); iapd != nil {
|
2018-06-03 20:03:07 +02:00
|
|
|
|
request.AddOption(iapd)
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-27 17:30:42 +02:00
|
|
|
|
if len(c.transactionIDs) > 0 {
|
|
|
|
|
id := c.transactionIDs[0]
|
|
|
|
|
c.transactionIDs = c.transactionIDs[1:]
|
2019-03-01 09:48:56 -08:00
|
|
|
|
request.TransactionID = id
|
2018-05-27 17:30:42 +02:00
|
|
|
|
}
|
2018-08-05 11:45:03 +02:00
|
|
|
|
reply, err := c.sendReceive(request, dhcpv6.MessageTypeNone)
|
2018-05-27 17:30:42 +02:00
|
|
|
|
return request, reply, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) ObtainOrRenew() bool {
|
2018-06-29 12:02:16 +02:00
|
|
|
|
c.err = nil // clear previous error
|
2018-05-27 17:30:42 +02:00
|
|
|
|
_, advertise, err := c.solicit(nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
c.err = err
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-02 21:00:56 +02:00
|
|
|
|
c.advertise = advertise
|
|
|
|
|
_, reply, err := c.request(advertise)
|
2018-05-27 17:30:42 +02:00
|
|
|
|
if err != nil {
|
|
|
|
|
c.err = err
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
var newCfg Config
|
2020-03-13 00:47:38 -07:00
|
|
|
|
for _, iapd := range reply.Options.IAPD() {
|
|
|
|
|
t1 := c.timeNow().Add(iapd.T1)
|
2020-03-07 00:35:59 -08:00
|
|
|
|
if t1.Before(newCfg.RenewAfter) || newCfg.RenewAfter.IsZero() {
|
|
|
|
|
newCfg.RenewAfter = t1
|
2018-05-27 17:30:42 +02:00
|
|
|
|
}
|
2020-03-13 00:47:38 -07:00
|
|
|
|
for _, prefix := range iapd.Options.Prefixes() {
|
|
|
|
|
newCfg.Prefixes = append(newCfg.Prefixes, *prefix.Prefix)
|
2020-03-07 00:35:59 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, dns := range reply.Options.DNS() {
|
|
|
|
|
newCfg.DNS = append(newCfg.DNS, dns.String())
|
2018-05-27 17:30:42 +02:00
|
|
|
|
}
|
|
|
|
|
c.cfg = newCfg
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2019-03-01 09:48:56 -08:00
|
|
|
|
func (c *Client) Release() (release *dhcpv6.Message, reply *dhcpv6.Message, err error) {
|
2018-06-02 21:00:56 +02:00
|
|
|
|
release, err = dhcpv6.NewRequestFromAdvertise(c.advertise, dhcpv6.WithClientID(*c.duid))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
2019-03-01 09:48:56 -08:00
|
|
|
|
release.MessageType = dhcpv6.MessageTypeRelease
|
2018-06-02 21:00:56 +02:00
|
|
|
|
|
|
|
|
|
if len(c.transactionIDs) > 0 {
|
|
|
|
|
id := c.transactionIDs[0]
|
|
|
|
|
c.transactionIDs = c.transactionIDs[1:]
|
2019-03-01 09:48:56 -08:00
|
|
|
|
release.TransactionID = id
|
2018-06-02 21:00:56 +02:00
|
|
|
|
}
|
2018-08-05 11:45:03 +02:00
|
|
|
|
reply, err = c.sendReceive(release, dhcpv6.MessageTypeNone)
|
2018-06-02 21:00:56 +02:00
|
|
|
|
return release, reply, err
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-27 17:30:42 +02:00
|
|
|
|
func (c *Client) Err() error {
|
|
|
|
|
return c.err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Client) Config() Config {
|
|
|
|
|
return c.cfg
|
|
|
|
|
}
|