fix(dhcp): can not renew an ip address

The dhcp server is systemd-networkd, and the dhcp
plugin can request an ip but can not renew it.
The systemd-networkd just ignore the renew request.

```
2024/09/14 21:46:00 no DHCP packet received within 10s
2024/09/14 21:46:00 retrying in 31.529038 seconds
2024/09/14 21:46:42 no DHCP packet received within 10s
2024/09/14 21:46:42 retrying in 63.150490 seconds
2024/09/14 21:47:45 98184616c91f15419f5cacd012697f85afaa2daeb5d3233e28b0ec21589fb45a/iot/eth1: no more tries
2024/09/14 21:47:45 98184616c91f15419f5cacd012697f85afaa2daeb5d3233e28b0ec21589fb45a/iot/eth1: renewal time expired, rebinding
2024/09/14 21:47:45 Link "eth1" down. Attempting to set up
2024/09/14 21:47:45 98184616c91f15419f5cacd012697f85afaa2daeb5d3233e28b0ec21589fb45a/iot/eth1: lease rebound, expiration is 2024-09-14 22:47:45.309270751 +0800 CST m=+11730.048516519
```

Follow the https://datatracker.ietf.org/doc/html/rfc2131#section-4.3.6,
following options must not be sent in renew

- Requested IP Address
- Server Identifier

Since the upstream code has been inactive for 6 years,
we should switch to another dhcpv4 library.
The new selected one is https://github.com/insomniacslk/dhcp.

Signed-off-by: Songmin Li <lisongmin@protonmail.com>
This commit is contained in:
Songmin Li 2024-09-14 22:00:20 +08:00 committed by Casey Callendrello
parent e4950728ce
commit d61e7e5e1f
310 changed files with 12785 additions and 11182 deletions

View File

@ -67,6 +67,10 @@ jobs:
sudo apt-get install linux-modules-extra-$(uname -r)
- name: Install nftables
run: sudo apt-get install nftables
- name: Install dnsmasq(dhcp server)
run: |
sudo apt-get install dnsmasq
sudo systemctl disable --now dnsmasq
- uses: actions/checkout@v4
- name: setup go
uses: actions/setup-go@v5

11
go.mod
View File

@ -9,10 +9,8 @@ require (
github.com/containernetworking/cni v1.2.3
github.com/coreos/go-iptables v0.8.0
github.com/coreos/go-systemd/v22 v22.5.0
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c
github.com/d2g/dhcp4client v1.0.0
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5
github.com/godbus/dbus/v5 v5.1.0
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475
github.com/mattn/go-shellwords v1.0.12
github.com/networkplumbing/go-nft v0.4.0
github.com/onsi/ginkgo/v2 v2.20.2
@ -28,17 +26,22 @@ require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/containerd/cgroups/v3 v3.0.3 // indirect
github.com/containerd/errdefs v0.1.0 // indirect
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/mdlayher/packet v1.1.2 // indirect
github.com/mdlayher/socket v0.5.1 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/tools v0.24.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect

24
go.sum
View File

@ -21,14 +21,6 @@ github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNs
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c h1:Xo2rK1pzOm0jO6abTPIQwbAmqBIOj132otexc1mmzFc=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
github.com/d2g/dhcp4client v1.0.0 h1:suYBsYZIkSlUMEz4TAYCczKf62IA2UWC+O8+KtdOhCo=
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5 h1:+CpLbZIeUn94m02LdEKPcgErLJ347NUwxPKs5u8ieiY=
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4 h1:itqmmf1PFpC4n5JW+j4BU7X4MTfVurhYRTjODoPb2Y8=
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -68,10 +60,20 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA=
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475 h1:hxST5pwMBEOWmxpkX20w9oZG+hXdhKmAIPQ3NGGAxas=
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY=
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY=
github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/networkplumbing/go-nft v0.4.0 h1:kExVMwXW48DOAukkBwyI16h4uhE5lN9iMvQd52lpTyU=
github.com/networkplumbing/go-nft v0.4.0/go.mod h1:HnnM+tYvlGAsMU7yoYwXEVLLiDW9gdMmb5HoGcwpuQs=
github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4=
@ -80,6 +82,8 @@ github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@ -99,6 +103,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
@ -129,6 +135,8 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -1,135 +0,0 @@
// Copyright 2021 CNI authors
//
// 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 main
import (
"github.com/d2g/dhcp4"
"github.com/d2g/dhcp4client"
)
const (
MaxDHCPLen = 576
)
// Send the Discovery Packet to the Broadcast Channel
func DhcpSendDiscoverPacket(c *dhcp4client.Client, options dhcp4.Options) (dhcp4.Packet, error) {
discoveryPacket := c.DiscoverPacket()
for opt, data := range options {
discoveryPacket.AddOption(opt, data)
}
discoveryPacket.PadToMinSize()
return discoveryPacket, c.SendPacket(discoveryPacket)
}
// Send Request Based On the offer Received.
func DhcpSendRequest(c *dhcp4client.Client, options dhcp4.Options, offerPacket *dhcp4.Packet) (dhcp4.Packet, error) {
requestPacket := c.RequestPacket(offerPacket)
for opt, data := range options {
requestPacket.AddOption(opt, data)
}
requestPacket.PadToMinSize()
return requestPacket, c.SendPacket(requestPacket)
}
// Send Decline to the received acknowledgement.
func DhcpSendDecline(c *dhcp4client.Client, acknowledgementPacket *dhcp4.Packet, options dhcp4.Options) (dhcp4.Packet, error) {
declinePacket := c.DeclinePacket(acknowledgementPacket)
for opt, data := range options {
declinePacket.AddOption(opt, data)
}
declinePacket.PadToMinSize()
return declinePacket, c.SendPacket(declinePacket)
}
// Lets do a Full DHCP Request.
func DhcpRequest(c *dhcp4client.Client, options dhcp4.Options) (bool, dhcp4.Packet, error) {
discoveryPacket, err := DhcpSendDiscoverPacket(c, options)
if err != nil {
return false, discoveryPacket, err
}
offerPacket, err := c.GetOffer(&discoveryPacket)
if err != nil {
return false, offerPacket, err
}
requestPacket, err := DhcpSendRequest(c, options, &offerPacket)
if err != nil {
return false, requestPacket, err
}
acknowledgement, err := c.GetAcknowledgement(&requestPacket)
if err != nil {
return false, acknowledgement, err
}
acknowledgementOptions := acknowledgement.ParseOptions()
if dhcp4.MessageType(acknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK {
return false, acknowledgement, nil
}
return true, acknowledgement, nil
}
// Renew a lease backed on the Acknowledgement Packet.
// Returns Successful, The AcknoledgementPacket, Any Errors
func DhcpRenew(c *dhcp4client.Client, acknowledgement dhcp4.Packet, options dhcp4.Options) (bool, dhcp4.Packet, error) {
renewRequest := c.RenewalRequestPacket(&acknowledgement)
for opt, data := range options {
renewRequest.AddOption(opt, data)
}
renewRequest.PadToMinSize()
err := c.SendPacket(renewRequest)
if err != nil {
return false, renewRequest, err
}
newAcknowledgement, err := c.GetAcknowledgement(&renewRequest)
if err != nil {
return false, newAcknowledgement, err
}
newAcknowledgementOptions := newAcknowledgement.ParseOptions()
if dhcp4.MessageType(newAcknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK {
return false, newAcknowledgement, nil
}
return true, newAcknowledgement, nil
}
// Release a lease backed on the Acknowledgement Packet.
// Returns Any Errors
func DhcpRelease(c *dhcp4client.Client, acknowledgement dhcp4.Packet, options dhcp4.Options) error {
release := c.ReleasePacket(&acknowledgement)
for opt, data := range options {
release.AddOption(opt, data)
}
release.PadToMinSize()
return c.SendPacket(release)
}

View File

@ -74,7 +74,7 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error {
return fmt.Errorf("error parsing netconf: %v", err)
}
optsRequesting, optsProviding, err := prepareOptions(args.Args, conf.IPAM.ProvideOptions, conf.IPAM.RequestOptions)
opts, err := prepareOptions(args.Args, conf.IPAM.ProvideOptions, conf.IPAM.RequestOptions)
if err != nil {
return err
}
@ -89,7 +89,7 @@ func (d *DHCP) Allocate(args *skel.CmdArgs, result *current.Result) error {
} else {
hostNetns := d.hostNetnsPrefix + args.Netns
l, err = AcquireLease(clientID, hostNetns, args.IfName,
optsRequesting, optsProviding,
opts,
d.clientTimeout, d.clientResendMax, d.broadcast)
if err != nil {
return err

View File

@ -61,8 +61,7 @@ var _ = Describe("DHCP Multiple Lease Operations", func() {
})
// Start the DHCP server
dhcpServerDone, err = dhcpServerStart(originalNS, 2, dhcpServerStopCh)
Expect(err).NotTo(HaveOccurred())
dhcpServerDone = dhcpServerStart(originalNS, 2, dhcpServerStopCh)
// Start the DHCP client daemon
dhcpPluginPath, err := exec.LookPath("dhcp")

View File

@ -25,10 +25,6 @@ import (
"sync"
"time"
"github.com/d2g/dhcp4"
"github.com/d2g/dhcp4server"
"github.com/d2g/dhcp4server/leasepool"
"github.com/d2g/dhcp4server/leasepool/memorypool"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/vishvananda/netlink"
@ -48,31 +44,43 @@ func getTmpDir() (string, error) {
return tmpDir, err
}
func dhcpServerStart(netns ns.NetNS, numLeases int, stopCh <-chan bool) (*sync.WaitGroup, error) {
// Add the expected IP to the pool
lp := memorypool.MemoryPool{}
type DhcpServer struct {
cmd *exec.Cmd
Expect(numLeases).To(BeNumerically(">", 0))
// Currently tests only need at most 2
Expect(numLeases).To(BeNumerically("<=", 2))
startAddr net.IP
endAddr net.IP
leaseTime time.Duration
}
// tests expect first lease to be at address 192.168.1.5
for i := 5; i < numLeases+5; i++ {
err := lp.AddLease(leasepool.Lease{IP: dhcp4.IPAdd(net.IPv4(192, 168, 1, byte(i)), 0)})
if err != nil {
return nil, fmt.Errorf("error adding IP to DHCP pool: %v", err)
}
}
dhcpServer, err := dhcp4server.New(
net.IPv4(192, 168, 1, 1),
&lp,
dhcp4server.SetLocalAddr(net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 67}),
dhcp4server.SetRemoteAddr(net.UDPAddr{IP: net.IPv4bcast, Port: 68}),
dhcp4server.LeaseDuration(time.Minute*15),
func (s *DhcpServer) Serve() error {
s.cmd = exec.Command(
"dnsmasq",
"--no-daemon",
"--dhcp-sequential-ip", // allocate IPs sequentially
"--port=0", // disable DNS
"--conf-file=-", // Do not read /etc/dnsmasq.conf
fmt.Sprintf("--dhcp-range=%s,%s,%d", s.startAddr, s.endAddr, int(s.leaseTime.Seconds())),
)
if err != nil {
return nil, fmt.Errorf("failed to create DHCP server: %v", err)
s.cmd.Stdin = bytes.NewBufferString("")
s.cmd.Stdout = os.Stdout
s.cmd.Stderr = os.Stderr
return s.cmd.Start()
}
func (s *DhcpServer) Stop() error {
if err := s.cmd.Process.Kill(); err != nil {
return err
}
_, err := s.cmd.Process.Wait()
return err
}
func dhcpServerStart(netns ns.NetNS, numLeases int, stopCh <-chan bool) *sync.WaitGroup {
dhcpServer := &DhcpServer{
startAddr: net.IPv4(192, 168, 1, 5),
endAddr: net.IPv4(192, 168, 1, 5+uint8(numLeases)-1),
leaseTime: 5 * time.Minute,
}
stopWg := sync.WaitGroup{}
@ -84,9 +92,10 @@ func dhcpServerStart(netns ns.NetNS, numLeases int, stopCh <-chan bool) (*sync.W
go func() {
defer GinkgoRecover()
err = netns.Do(func(ns.NetNS) error {
err := netns.Do(func(ns.NetNS) error {
startWg.Done()
if err := dhcpServer.ListenAndServe(); err != nil {
if err := dhcpServer.Serve(); err != nil {
// Log, but don't trap errors; the server will
// always report an error when stopped
GinkgoT().Logf("DHCP server finished with error: %v", err)
@ -103,12 +112,12 @@ func dhcpServerStart(netns ns.NetNS, numLeases int, stopCh <-chan bool) (*sync.W
go func() {
startWg.Done()
<-stopCh
dhcpServer.Shutdown()
dhcpServer.Stop()
stopWg.Done()
}()
startWg.Wait()
return &stopWg, nil
return &stopWg
}
const (
@ -200,8 +209,7 @@ var _ = Describe("DHCP Operations", func() {
})
// Start the DHCP server
dhcpServerDone, err = dhcpServerStart(originalNS, 1, dhcpServerStopCh)
Expect(err).NotTo(HaveOccurred())
dhcpServerDone = dhcpServerStart(originalNS, 1, dhcpServerStopCh)
// Start the DHCP client daemon
dhcpPluginPath, err := exec.LookPath("dhcp")
@ -517,8 +525,7 @@ var _ = Describe("DHCP Lease Unavailable Operations", func() {
})
// Start the DHCP server
dhcpServerDone, err = dhcpServerStart(originalNS, 1, dhcpServerStopCh)
Expect(err).NotTo(HaveOccurred())
dhcpServerDone = dhcpServerStart(originalNS, 1, dhcpServerStopCh)
// Start the DHCP client daemon
dhcpPluginPath, err := exec.LookPath("dhcp")

View File

@ -15,6 +15,7 @@
package main
import (
"context"
"fmt"
"log"
"math/rand"
@ -24,8 +25,8 @@ import (
"sync/atomic"
"time"
"github.com/d2g/dhcp4"
"github.com/d2g/dhcp4client"
dhcp4 "github.com/insomniacslk/dhcp/dhcpv4"
"github.com/insomniacslk/dhcp/dhcpv4/nclient4"
"github.com/vishvananda/netlink"
"github.com/containernetworking/cni/pkg/types"
@ -35,8 +36,9 @@ import (
// RFC 2131 suggests using exponential backoff, starting with 4sec
// and randomized to +/- 1sec
const (
resendDelay0 = 4 * time.Second
resendDelayMax = 62 * time.Second
resendDelay0 = 4 * time.Second
resendDelayMax = 62 * time.Second
defaultLeaseTime = 60 * time.Minute
)
// To speed up the retry for first few failures, we retry without
@ -60,8 +62,7 @@ const (
type DHCPLease struct {
clientID string
ack *dhcp4.Packet
opts dhcp4.Options
latestLease *nclient4.Lease
link netlink.Link
renewalTime time.Time
rebindingTime time.Time
@ -73,21 +74,22 @@ type DHCPLease struct {
stop chan struct{}
check chan struct{}
wg sync.WaitGroup
cancelFunc context.CancelFunc
ctx context.Context
// list of requesting and providing options and if they are necessary / their value
optsRequesting map[dhcp4.OptionCode]bool
optsProviding map[dhcp4.OptionCode][]byte
opts []dhcp4.Option
}
var requestOptionsDefault = map[dhcp4.OptionCode]bool{
dhcp4.OptionRouter: true,
dhcp4.OptionSubnetMask: true,
var requestOptionsDefault = []dhcp4.OptionCode{
dhcp4.OptionRouter,
dhcp4.OptionSubnetMask,
}
func prepareOptions(cniArgs string, provideOptions []ProvideOption, requestOptions []RequestOption) (
map[dhcp4.OptionCode]bool, map[dhcp4.OptionCode][]byte, error,
[]dhcp4.Option, error,
) {
var optsRequesting map[dhcp4.OptionCode]bool
var optsProviding map[dhcp4.OptionCode][]byte
var opts []dhcp4.Option
var err error
// parse CNI args
cniArgsParsed := map[string]string{}
@ -100,46 +102,51 @@ func prepareOptions(cniArgs string, provideOptions []ProvideOption, requestOptio
// parse providing options map
var optParsed dhcp4.OptionCode
optsProviding = make(map[dhcp4.OptionCode][]byte)
for _, opt := range provideOptions {
optParsed, err = parseOptionName(string(opt.Option))
if err != nil {
return nil, nil, fmt.Errorf("Can not parse option %q: %w", opt.Option, err)
return nil, fmt.Errorf("Can not parse option %q: %w", opt.Option, err)
}
if len(opt.Value) > 0 {
if len(opt.Value) > 255 {
return nil, nil, fmt.Errorf("value too long for option %q: %q", opt.Option, opt.Value)
return nil, fmt.Errorf("value too long for option %q: %q", opt.Option, opt.Value)
}
optsProviding[optParsed] = []byte(opt.Value)
opts = append(opts, dhcp4.Option{Code: optParsed, Value: dhcp4.String(opt.Value)})
}
if value, ok := cniArgsParsed[opt.ValueFromCNIArg]; ok {
if len(value) > 255 {
return nil, nil, fmt.Errorf("value too long for option %q from CNI_ARGS %q: %q", opt.Option, opt.ValueFromCNIArg, opt.Value)
return nil, fmt.Errorf("value too long for option %q from CNI_ARGS %q: %q", opt.Option, opt.ValueFromCNIArg, opt.Value)
}
optsProviding[optParsed] = []byte(value)
opts = append(opts, dhcp4.Option{Code: optParsed, Value: dhcp4.String(value)})
}
}
// parse necessary options map
optsRequesting = make(map[dhcp4.OptionCode]bool)
var optsRequesting dhcp4.OptionCodeList
skipRequireDefault := false
for _, opt := range requestOptions {
if opt.SkipDefault {
skipRequireDefault = true
}
if opt.Option == "" {
continue
}
optParsed, err = parseOptionName(string(opt.Option))
if err != nil {
return nil, nil, fmt.Errorf("Can not parse option %q: %w", opt.Option, err)
return nil, fmt.Errorf("Can not parse option %q: %w", opt.Option, err)
}
optsRequesting[optParsed] = true
optsRequesting.Add(optParsed)
}
for k, v := range requestOptionsDefault {
// only set if not skipping default and this value does not exists
if _, ok := optsRequesting[k]; !ok && !skipRequireDefault {
optsRequesting[k] = v
if !skipRequireDefault {
for _, opt := range requestOptionsDefault {
optsRequesting.Add(opt)
}
}
return optsRequesting, optsProviding, err
if len(optsRequesting) > 0 {
opts = append(opts, dhcp4.Option{Code: dhcp4.OptionParameterRequestList, Value: optsRequesting})
}
return opts, err
}
// AcquireLease gets an DHCP lease and then maintains it in the background
@ -147,19 +154,24 @@ func prepareOptions(cniArgs string, provideOptions []ProvideOption, requestOptio
// calling DHCPLease.Stop()
func AcquireLease(
clientID, netns, ifName string,
optsRequesting map[dhcp4.OptionCode]bool, optsProviding map[dhcp4.OptionCode][]byte,
opts []dhcp4.Option,
timeout, resendMax time.Duration, broadcast bool,
) (*DHCPLease, error) {
errCh := make(chan error, 1)
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
l := &DHCPLease{
clientID: clientID,
stop: make(chan struct{}),
check: make(chan struct{}),
timeout: timeout,
resendMax: resendMax,
broadcast: broadcast,
optsRequesting: optsRequesting,
optsProviding: optsProviding,
clientID: clientID,
stop: make(chan struct{}),
check: make(chan struct{}),
timeout: timeout,
resendMax: resendMax,
broadcast: broadcast,
opts: opts,
cancelFunc: cancel,
ctx: ctx,
}
log.Printf("%v: acquiring lease", clientID)
@ -209,28 +221,20 @@ func (l *DHCPLease) Check() {
l.check <- struct{}{}
}
func (l *DHCPLease) getOptionsWithClientID() dhcp4.Options {
opts := make(dhcp4.Options)
opts[dhcp4.OptionClientIdentifier] = []byte(l.clientID)
// client identifier's first byte is "type"
newClientID := []byte{0}
newClientID = append(newClientID, opts[dhcp4.OptionClientIdentifier]...)
opts[dhcp4.OptionClientIdentifier] = newClientID
return opts
func withClientID(clientID string) dhcp4.Modifier {
return func(d *dhcp4.DHCPv4) {
optClientID := []byte{0}
optClientID = append(optClientID, []byte(clientID)...)
d.Options.Update(dhcp4.OptClientIdentifier(optClientID))
}
}
func (l *DHCPLease) getAllOptions() dhcp4.Options {
opts := l.getOptionsWithClientID()
for k, v := range l.optsProviding {
opts[k] = v
func withAllOptions(l *DHCPLease) dhcp4.Modifier {
return func(d *dhcp4.DHCPv4) {
for _, opt := range l.opts {
d.Options.Update(opt)
}
}
opts[dhcp4.OptionParameterRequestList] = []byte{}
for k := range l.optsRequesting {
opts[dhcp4.OptionParameterRequestList] = append(opts[dhcp4.OptionParameterRequestList], byte(k))
}
return opts
}
func (l *DHCPLease) acquire() error {
@ -241,60 +245,39 @@ func (l *DHCPLease) acquire() error {
}
}
c, err := newDHCPClient(l.link, l.timeout, l.broadcast)
c, err := newDHCPClient(l.link, l.timeout)
if err != nil {
return err
}
defer c.Close()
opts := l.getAllOptions()
pkt, err := backoffRetry(l.resendMax, func() (*dhcp4.Packet, error) {
ok, ack, err := DhcpRequest(c, opts)
switch {
case err != nil:
return nil, err
case !ok:
return nil, fmt.Errorf("DHCP server NACK'd own offer")
default:
return &ack, nil
}
pkt, err := backoffRetry(l.resendMax, func() (*nclient4.Lease, error) {
return c.Request(
l.ctx,
withClientID(l.clientID),
withAllOptions(l),
)
})
if err != nil {
return err
}
return l.commit(pkt)
l.commit(pkt)
return nil
}
func (l *DHCPLease) commit(ack *dhcp4.Packet) error {
opts := ack.ParseOptions()
func (l *DHCPLease) commit(lease *nclient4.Lease) {
l.latestLease = lease
ack := lease.ACK
leaseTime, err := parseLeaseTime(opts)
if err != nil {
return err
}
rebindingTime, err := parseRebindingTime(opts)
if err != nil || rebindingTime > leaseTime {
// Per RFC 2131 Section 4.4.5, it should default to 85% of lease time
rebindingTime = leaseTime * 85 / 100
}
renewalTime, err := parseRenewalTime(opts)
if err != nil || renewalTime > rebindingTime {
// Per RFC 2131 Section 4.4.5, it should default to 50% of lease time
renewalTime = leaseTime / 2
}
leaseTime := ack.IPAddressLeaseTime(defaultLeaseTime)
rebindingTime := ack.IPAddressRebindingTime(leaseTime * 85 / 100)
renewalTime := ack.IPAddressRenewalTime(leaseTime / 2)
now := time.Now()
l.expireTime = now.Add(leaseTime)
l.renewalTime = now.Add(renewalTime)
l.rebindingTime = now.Add(rebindingTime)
l.ack = ack
l.opts = opts
return nil
}
func (l *DHCPLease) maintain() {
@ -362,44 +345,38 @@ func (l *DHCPLease) downIface() {
}
func (l *DHCPLease) renew() error {
c, err := newDHCPClient(l.link, l.timeout, l.broadcast)
c, err := newDHCPClient(l.link, l.timeout)
if err != nil {
return err
}
defer c.Close()
opts := l.getAllOptions()
pkt, err := backoffRetry(l.resendMax, func() (*dhcp4.Packet, error) {
ok, ack, err := DhcpRenew(c, *l.ack, opts)
switch {
case err != nil:
return nil, err
case !ok:
return nil, fmt.Errorf("DHCP server did not renew lease")
default:
return &ack, nil
}
lease, err := backoffRetry(l.resendMax, func() (*nclient4.Lease, error) {
return c.Renew(
l.ctx,
l.latestLease,
withClientID(l.clientID),
withAllOptions(l),
)
})
if err != nil {
return err
}
l.commit(pkt)
l.commit(lease)
return nil
}
func (l *DHCPLease) release() error {
log.Printf("%v: releasing lease", l.clientID)
c, err := newDHCPClient(l.link, l.timeout, l.broadcast)
c, err := newDHCPClient(l.link, l.timeout)
if err != nil {
return err
}
defer c.Close()
opts := l.getOptionsWithClientID()
if err = DhcpRelease(c, *l.ack, opts); err != nil {
if err = c.Release(l.latestLease, withClientID(l.clientID)); err != nil {
return fmt.Errorf("failed to send DHCPRELEASE")
}
@ -407,33 +384,47 @@ func (l *DHCPLease) release() error {
}
func (l *DHCPLease) IPNet() (*net.IPNet, error) {
mask := parseSubnetMask(l.opts)
ack := l.latestLease.ACK
mask := ack.SubnetMask()
if mask == nil {
return nil, fmt.Errorf("DHCP option Subnet Mask not found in DHCPACK")
}
return &net.IPNet{
IP: l.ack.YIAddr(),
IP: ack.YourIPAddr,
Mask: mask,
}, nil
}
func (l *DHCPLease) Gateway() net.IP {
return parseRouter(l.opts)
ack := l.latestLease.ACK
gws := ack.Router()
if len(gws) > 0 {
return gws[0]
}
return nil
}
func (l *DHCPLease) Routes() []*types.Route {
routes := []*types.Route{}
ack := l.latestLease.ACK
// RFC 3442 states that if Classless Static Routes (option 121)
// exist, we ignore Static Routes (option 33) and the Router/Gateway.
opt121Routes := parseCIDRRoutes(l.opts)
opt121Routes := ack.ClasslessStaticRoute()
if len(opt121Routes) > 0 {
return append(routes, opt121Routes...)
for _, r := range opt121Routes {
routes = append(routes, &types.Route{Dst: *r.Dest, GW: r.Router})
}
return routes
}
// Append Static Routes
routes = append(routes, parseRoutes(l.opts)...)
if ack.Options.Has(dhcp4.OptionStaticRoutingTable) {
routes = append(routes, parseRoutes(ack.Options.Get(dhcp4.OptionStaticRoutingTable))...)
}
// The CNI spec says even if there is a gateway specified, we must
// add a default route in the routes section.
@ -450,7 +441,7 @@ func jitter(span time.Duration) time.Duration {
return time.Duration(float64(span) * (2.0*rand.Float64() - 1.0))
}
func backoffRetry(resendMax time.Duration, f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) {
func backoffRetry(resendMax time.Duration, f func() (*nclient4.Lease, error)) (*nclient4.Lease, error) {
baseDelay := resendDelay0
var sleepTime time.Duration
fastRetryLimit := resendFastMax
@ -487,17 +478,8 @@ func backoffRetry(resendMax time.Duration, f func() (*dhcp4.Packet, error)) (*dh
func newDHCPClient(
link netlink.Link,
timeout time.Duration,
broadcast bool,
) (*dhcp4client.Client, error) {
pktsock, err := dhcp4client.NewPacketSock(link.Attrs().Index)
if err != nil {
return nil, err
}
return dhcp4client.New(
dhcp4client.HardwareAddr(link.Attrs().HardwareAddr),
dhcp4client.Timeout(timeout),
dhcp4client.Broadcast(broadcast),
dhcp4client.Connection(pktsock),
)
clientOpts ...nclient4.ClientOpt,
) (*nclient4.Client, error) {
clientOpts = append(clientOpts, nclient4.WithTimeout(timeout))
return nclient4.New(link.Attrs().Name, clientOpts...)
}

View File

@ -15,13 +15,11 @@
package main
import (
"encoding/binary"
"fmt"
"net"
"strconv"
"time"
"github.com/d2g/dhcp4"
dhcp4 "github.com/insomniacslk/dhcp/dhcpv4"
"github.com/containernetworking/cni/pkg/types"
)
@ -31,8 +29,8 @@ var optionNameToID = map[string]dhcp4.OptionCode{
"subnet-mask": dhcp4.OptionSubnetMask,
"routers": dhcp4.OptionRouter,
"host-name": dhcp4.OptionHostName,
"user-class": dhcp4.OptionUserClass,
"vendor-class-identifier": dhcp4.OptionVendorClassIdentifier,
"user-class": dhcp4.OptionUserClassInformation,
"vendor-class-identifier": dhcp4.OptionClassIdentifier,
}
func parseOptionName(option string) (dhcp4.OptionCode, error) {
@ -41,18 +39,9 @@ func parseOptionName(option string) (dhcp4.OptionCode, error) {
}
i, err := strconv.ParseUint(option, 10, 8)
if err != nil {
return 0, fmt.Errorf("Can not parse option: %w", err)
return dhcp4.OptionPad, fmt.Errorf("Can not parse option: %w", err)
}
return dhcp4.OptionCode(i), nil
}
func parseRouter(opts dhcp4.Options) net.IP {
if opts, ok := opts[dhcp4.OptionRouter]; ok {
if len(opts) == 4 {
return net.IP(opts)
}
}
return nil
return dhcp4.GenericOptionCode(i), nil
}
func classfulSubnet(sn net.IP) net.IPNet {
@ -62,100 +51,22 @@ func classfulSubnet(sn net.IP) net.IPNet {
}
}
func parseRoutes(opts dhcp4.Options) []*types.Route {
func parseRoutes(opt []byte) []*types.Route {
// StaticRoutes format: pairs of:
// Dest = 4 bytes; Classful IP subnet
// Router = 4 bytes; IP address of router
routes := []*types.Route{}
if opt, ok := opts[dhcp4.OptionStaticRoute]; ok {
for len(opt) >= 8 {
sn := opt[0:4]
r := opt[4:8]
rt := &types.Route{
Dst: classfulSubnet(sn),
GW: r,
}
routes = append(routes, rt)
opt = opt[8:]
for len(opt) >= 8 {
sn := opt[0:4]
r := opt[4:8]
rt := &types.Route{
Dst: classfulSubnet(sn),
GW: r,
}
routes = append(routes, rt)
opt = opt[8:]
}
return routes
}
func parseCIDRRoutes(opts dhcp4.Options) []*types.Route {
// See RFC4332 for format (http://tools.ietf.org/html/rfc3442)
routes := []*types.Route{}
if opt, ok := opts[dhcp4.OptionClasslessRouteFormat]; ok {
for len(opt) >= 5 {
width := int(opt[0])
if width > 32 {
// error: can't have more than /32
return nil
}
// network bits are compacted to avoid zeros
octets := 0
if width > 0 {
octets = (width-1)/8 + 1
}
if len(opt) < 1+octets+4 {
// error: too short
return nil
}
sn := make([]byte, 4)
copy(sn, opt[1:octets+1])
gw := net.IP(opt[octets+1 : octets+5])
rt := &types.Route{
Dst: net.IPNet{
IP: net.IP(sn),
Mask: net.CIDRMask(width, 32),
},
GW: gw,
}
routes = append(routes, rt)
opt = opt[octets+5:]
}
}
return routes
}
func parseSubnetMask(opts dhcp4.Options) net.IPMask {
mask, ok := opts[dhcp4.OptionSubnetMask]
if !ok {
return nil
}
return net.IPMask(mask)
}
func parseDuration(opts dhcp4.Options, code dhcp4.OptionCode, optName string) (time.Duration, error) {
val, ok := opts[code]
if !ok {
return 0, fmt.Errorf("option %v not found", optName)
}
if len(val) != 4 {
return 0, fmt.Errorf("option %v is not 4 bytes", optName)
}
secs := binary.BigEndian.Uint32(val)
return time.Duration(secs) * time.Second, nil
}
func parseLeaseTime(opts dhcp4.Options) (time.Duration, error) {
return parseDuration(opts, dhcp4.OptionIPAddressLeaseTime, "LeaseTime")
}
func parseRenewalTime(opts dhcp4.Options) (time.Duration, error) {
return parseDuration(opts, dhcp4.OptionRenewalTimeValue, "RenewalTime")
}
func parseRebindingTime(opts dhcp4.Options) (time.Duration, error) {
return parseDuration(opts, dhcp4.OptionRebindingTimeValue, "RebindingTime")
}

View File

@ -19,7 +19,7 @@ import (
"reflect"
"testing"
"github.com/d2g/dhcp4"
dhcp4 "github.com/insomniacslk/dhcp/dhcpv4"
"github.com/containernetworking/cni/pkg/types"
)
@ -61,17 +61,8 @@ func validateRoutes(t *testing.T, routes []*types.Route) {
}
func TestParseRoutes(t *testing.T) {
opts := make(dhcp4.Options)
opts[dhcp4.OptionStaticRoute] = []byte{10, 0, 0, 0, 10, 1, 2, 3, 192, 168, 1, 0, 192, 168, 2, 3}
routes := parseRoutes(opts)
validateRoutes(t, routes)
}
func TestParseCIDRRoutes(t *testing.T) {
opts := make(dhcp4.Options)
opts[dhcp4.OptionClasslessRouteFormat] = []byte{8, 10, 10, 1, 2, 3, 24, 192, 168, 1, 192, 168, 2, 3}
routes := parseCIDRRoutes(opts)
data := []byte{10, 0, 0, 0, 10, 1, 2, 3, 192, 168, 1, 0, 192, 168, 2, 3}
routes := parseRoutes(data)
validateRoutes(t, routes)
}
@ -87,10 +78,10 @@ func TestParseOptionName(t *testing.T) {
"hostname", "host-name", dhcp4.OptionHostName, false,
},
{
"hostname in number", "12", dhcp4.OptionHostName, false,
"hostname in number", "12", dhcp4.GenericOptionCode(12), false,
},
{
"random string", "doNotparseMe", 0, true,
"random string", "doNotparseMe", dhcp4.OptionPad, true,
},
}
for _, tt := range tests {

View File

@ -1,5 +0,0 @@
# DHCP4 - A DHCP library written in Go.
Warning: This library is still being developed. Function calls will change.
I've removed Server Functionality, for me this project supports the underlying DHCP format not the implementation.

View File

@ -1,121 +0,0 @@
package dhcp4
// OpCodes
const (
BootRequest OpCode = 1 // From Client
BootReply OpCode = 2 // From Server
)
// DHCP Message Type 53
const (
Discover MessageType = 1 // Broadcast Packet From Client - Can I have an IP?
Offer MessageType = 2 // Broadcast From Server - Here's an IP
Request MessageType = 3 // Broadcast From Client - I'll take that IP (Also start for renewals)
Decline MessageType = 4 // Broadcast From Client - Sorry I can't use that IP
ACK MessageType = 5 // From Server, Yes you can have that IP
NAK MessageType = 6 // From Server, No you cannot have that IP
Release MessageType = 7 // From Client, I don't need that IP anymore
Inform MessageType = 8 // From Client, I have this IP and there's nothing you can do about it
)
// DHCP Options
const (
End OptionCode = 255
Pad OptionCode = 0
OptionSubnetMask OptionCode = 1
OptionTimeOffset OptionCode = 2
OptionRouter OptionCode = 3
OptionTimeServer OptionCode = 4
OptionNameServer OptionCode = 5
OptionDomainNameServer OptionCode = 6
OptionLogServer OptionCode = 7
OptionCookieServer OptionCode = 8
OptionLPRServer OptionCode = 9
OptionImpressServer OptionCode = 10
OptionResourceLocationServer OptionCode = 11
OptionHostName OptionCode = 12
OptionBootFileSize OptionCode = 13
OptionMeritDumpFile OptionCode = 14
OptionDomainName OptionCode = 15
OptionSwapServer OptionCode = 16
OptionRootPath OptionCode = 17
OptionExtensionsPath OptionCode = 18
// IP Layer Parameters per Host
OptionIPForwardingEnableDisable OptionCode = 19
OptionNonLocalSourceRoutingEnableDisable OptionCode = 20
OptionPolicyFilter OptionCode = 21
OptionMaximumDatagramReassemblySize OptionCode = 22
OptionDefaultIPTimeToLive OptionCode = 23
OptionPathMTUAgingTimeout OptionCode = 24
OptionPathMTUPlateauTable OptionCode = 25
// IP Layer Parameters per Interface
OptionInterfaceMTU OptionCode = 26
OptionAllSubnetsAreLocal OptionCode = 27
OptionBroadcastAddress OptionCode = 28
OptionPerformMaskDiscovery OptionCode = 29
OptionMaskSupplier OptionCode = 30
OptionPerformRouterDiscovery OptionCode = 31
OptionRouterSolicitationAddress OptionCode = 32
OptionStaticRoute OptionCode = 33
// Link Layer Parameters per Interface
OptionTrailerEncapsulation OptionCode = 34
OptionARPCacheTimeout OptionCode = 35
OptionEthernetEncapsulation OptionCode = 36
// TCP Parameters
OptionTCPDefaultTTL OptionCode = 37
OptionTCPKeepaliveInterval OptionCode = 38
OptionTCPKeepaliveGarbage OptionCode = 39
// Application and Service Parameters
OptionNetworkInformationServiceDomain OptionCode = 40
OptionNetworkInformationServers OptionCode = 41
OptionNetworkTimeProtocolServers OptionCode = 42
OptionVendorSpecificInformation OptionCode = 43
OptionNetBIOSOverTCPIPNameServer OptionCode = 44
OptionNetBIOSOverTCPIPDatagramDistributionServer OptionCode = 45
OptionNetBIOSOverTCPIPNodeType OptionCode = 46
OptionNetBIOSOverTCPIPScope OptionCode = 47
OptionXWindowSystemFontServer OptionCode = 48
OptionXWindowSystemDisplayManager OptionCode = 49
OptionNetworkInformationServicePlusDomain OptionCode = 64
OptionNetworkInformationServicePlusServers OptionCode = 65
OptionMobileIPHomeAgent OptionCode = 68
OptionSimpleMailTransportProtocol OptionCode = 69
OptionPostOfficeProtocolServer OptionCode = 70
OptionNetworkNewsTransportProtocol OptionCode = 71
OptionDefaultWorldWideWebServer OptionCode = 72
OptionDefaultFingerServer OptionCode = 73
OptionDefaultInternetRelayChatServer OptionCode = 74
OptionStreetTalkServer OptionCode = 75
OptionStreetTalkDirectoryAssistance OptionCode = 76
// DHCP Extensions
OptionRequestedIPAddress OptionCode = 50
OptionIPAddressLeaseTime OptionCode = 51
OptionOverload OptionCode = 52
OptionDHCPMessageType OptionCode = 53
OptionServerIdentifier OptionCode = 54
OptionParameterRequestList OptionCode = 55
OptionMessage OptionCode = 56
OptionMaximumDHCPMessageSize OptionCode = 57
OptionRenewalTimeValue OptionCode = 58
OptionRebindingTimeValue OptionCode = 59
OptionVendorClassIdentifier OptionCode = 60
OptionClientIdentifier OptionCode = 61
OptionTFTPServerName OptionCode = 66
OptionBootFileName OptionCode = 67
OptionUserClass OptionCode = 77
OptionClientArchitecture OptionCode = 93
OptionTZPOSIXString OptionCode = 100
OptionTZDatabaseString OptionCode = 101
OptionClasslessRouteFormat OptionCode = 121
)

View File

@ -1,58 +0,0 @@
package dhcp4
import (
"encoding/binary"
"net"
"time"
)
// IPRange returns how many ips in the ip range from start to stop (inclusive)
func IPRange(start, stop net.IP) int {
//return int(Uint([]byte(stop))-Uint([]byte(start))) + 1
return int(binary.BigEndian.Uint32(stop.To4())) - int(binary.BigEndian.Uint32(start.To4())) + 1
}
// IPAdd returns a copy of start + add.
// IPAdd(net.IP{192,168,1,1},30) returns net.IP{192.168.1.31}
func IPAdd(start net.IP, add int) net.IP { // IPv4 only
start = start.To4()
//v := Uvarint([]byte(start))
result := make(net.IP, 4)
binary.BigEndian.PutUint32(result, binary.BigEndian.Uint32(start)+uint32(add))
//PutUint([]byte(result), v+uint64(add))
return result
}
// IPLess returns where IP a is less than IP b.
func IPLess(a, b net.IP) bool {
b = b.To4()
for i, ai := range a.To4() {
if ai != b[i] {
return ai < b[i]
}
}
return false
}
// IPInRange returns true if ip is between (inclusive) start and stop.
func IPInRange(start, stop, ip net.IP) bool {
return !(IPLess(ip, start) || IPLess(stop, ip))
}
// OptionsLeaseTime - converts a time.Duration to a 4 byte slice, compatible
// with OptionIPAddressLeaseTime.
func OptionsLeaseTime(d time.Duration) []byte {
leaseBytes := make([]byte, 4)
binary.BigEndian.PutUint32(leaseBytes, uint32(d/time.Second))
//PutUvarint(leaseBytes, uint64(d/time.Second))
return leaseBytes
}
// JoinIPs returns a byte slice of IP addresses, one immediately after the other
// This may be useful for creating multiple IP options such as OptionRouter.
func JoinIPs(ips []net.IP) (b []byte) {
for _, v := range ips {
b = append(b, v.To4()...)
}
return
}

View File

@ -1,40 +0,0 @@
package dhcp4
type OptionCode byte
type Option struct {
Code OptionCode
Value []byte
}
// Map of DHCP options
type Options map[OptionCode][]byte
// SelectOrderOrAll has same functionality as SelectOrder, except if the order
// param is nil, whereby all options are added (in arbitrary order).
func (o Options) SelectOrderOrAll(order []byte) []Option {
if order == nil {
opts := make([]Option, 0, len(o))
for i, v := range o {
opts = append(opts, Option{Code: i, Value: v})
}
return opts
}
return o.SelectOrder(order)
}
// SelectOrder returns a slice of options ordered and selected by a byte array
// usually defined by OptionParameterRequestList. This result is expected to be
// used in ReplyPacket()'s []Option parameter.
func (o Options) SelectOrder(order []byte) []Option {
opts := make([]Option, 0, len(order))
for _, v := range order {
if data, ok := o[OptionCode(v)]; ok {
opts = append(opts, Option{Code: OptionCode(v), Value: data})
}
}
return opts
}
type OpCode byte
type MessageType byte // Option 53

150
vendor/github.com/d2g/dhcp4/packet.go generated vendored
View File

@ -1,150 +0,0 @@
package dhcp4
import (
"net"
"time"
)
// A DHCP packet
type Packet []byte
func (p Packet) OpCode() OpCode { return OpCode(p[0]) }
func (p Packet) HType() byte { return p[1] }
func (p Packet) HLen() byte { return p[2] }
func (p Packet) Hops() byte { return p[3] }
func (p Packet) XId() []byte { return p[4:8] }
func (p Packet) Secs() []byte { return p[8:10] } // Never Used?
func (p Packet) Flags() []byte { return p[10:12] }
func (p Packet) CIAddr() net.IP { return net.IP(p[12:16]) }
func (p Packet) YIAddr() net.IP { return net.IP(p[16:20]) }
func (p Packet) SIAddr() net.IP { return net.IP(p[20:24]) }
func (p Packet) GIAddr() net.IP { return net.IP(p[24:28]) }
func (p Packet) CHAddr() net.HardwareAddr {
hLen := p.HLen()
if hLen > 16 { // Prevent chaddr exceeding p boundary
hLen = 16
}
return net.HardwareAddr(p[28 : 28+hLen]) // max endPos 44
}
// 192 bytes of zeros BOOTP legacy
func (p Packet) Cookie() []byte { return p[236:240] }
func (p Packet) Options() []byte {
if len(p) > 240 {
return p[240:]
}
return nil
}
func (p Packet) Broadcast() bool { return p.Flags()[0] > 127 }
func (p Packet) SetBroadcast(broadcast bool) {
if p.Broadcast() != broadcast {
p.Flags()[0] ^= 128
}
}
func (p Packet) SetOpCode(c OpCode) { p[0] = byte(c) }
func (p Packet) SetCHAddr(a net.HardwareAddr) {
copy(p[28:44], a)
p[2] = byte(len(a))
}
func (p Packet) SetHType(hType byte) { p[1] = hType }
func (p Packet) SetCookie(cookie []byte) { copy(p.Cookie(), cookie) }
func (p Packet) SetHops(hops byte) { p[3] = hops }
func (p Packet) SetXId(xId []byte) { copy(p.XId(), xId) }
func (p Packet) SetSecs(secs []byte) { copy(p.Secs(), secs) }
func (p Packet) SetFlags(flags []byte) { copy(p.Flags(), flags) }
func (p Packet) SetCIAddr(ip net.IP) { copy(p.CIAddr(), ip.To4()) }
func (p Packet) SetYIAddr(ip net.IP) { copy(p.YIAddr(), ip.To4()) }
func (p Packet) SetSIAddr(ip net.IP) { copy(p.SIAddr(), ip.To4()) }
func (p Packet) SetGIAddr(ip net.IP) { copy(p.GIAddr(), ip.To4()) }
// Parses the packet's options into an Options map
func (p Packet) ParseOptions() Options {
opts := p.Options()
options := make(Options, 10)
for len(opts) >= 2 && OptionCode(opts[0]) != End {
if OptionCode(opts[0]) == Pad {
opts = opts[1:]
continue
}
size := int(opts[1])
if len(opts) < 2+size {
break
}
options[OptionCode(opts[0])] = opts[2 : 2+size]
opts = opts[2+size:]
}
return options
}
func NewPacket(opCode OpCode) Packet {
p := make(Packet, 241)
p.SetOpCode(opCode)
p.SetHType(1) // Ethernet
p.SetCookie([]byte{99, 130, 83, 99})
p[240] = byte(End)
return p
}
// Appends a DHCP option to the end of a packet
func (p *Packet) AddOption(o OptionCode, value []byte) {
*p = append((*p)[:len(*p)-1], []byte{byte(o), byte(len(value))}...) // Strip off End, Add OptionCode and Length
*p = append(*p, value...) // Add Option Value
*p = append(*p, byte(End)) // Add on new End
}
// Removes all options from packet.
func (p *Packet) StripOptions() {
*p = append((*p)[:240], byte(End))
}
// Creates a request packet that a Client would send to a server.
func RequestPacket(mt MessageType, chAddr net.HardwareAddr, cIAddr net.IP, xId []byte, broadcast bool, options []Option) Packet {
p := NewPacket(BootRequest)
p.SetCHAddr(chAddr)
p.SetXId(xId)
if cIAddr != nil {
p.SetCIAddr(cIAddr)
}
p.SetBroadcast(broadcast)
p.AddOption(OptionDHCPMessageType, []byte{byte(mt)})
for _, o := range options {
p.AddOption(o.Code, o.Value)
}
p.PadToMinSize()
return p
}
// ReplyPacket creates a reply packet that a Server would send to a client.
// It uses the req Packet param to copy across common/necessary fields to
// associate the reply the request.
func ReplyPacket(req Packet, mt MessageType, serverId, yIAddr net.IP, leaseDuration time.Duration, options []Option) Packet {
p := NewPacket(BootReply)
p.SetXId(req.XId())
p.SetFlags(req.Flags())
p.SetYIAddr(yIAddr)
p.SetGIAddr(req.GIAddr())
p.SetCHAddr(req.CHAddr())
p.AddOption(OptionDHCPMessageType, []byte{byte(mt)})
p.AddOption(OptionServerIdentifier, []byte(serverId))
if leaseDuration > 0 {
p.AddOption(OptionIPAddressLeaseTime, OptionsLeaseTime(leaseDuration))
}
for _, o := range options {
p.AddOption(o.Code, o.Value)
}
p.PadToMinSize()
return p
}
// PadToMinSize pads a packet so that when sent over UDP, the entire packet,
// is 300 bytes (BOOTP min), to be compatible with really old devices.
var padder [272]byte
func (p *Packet) PadToMinSize() {
if n := len(*p); n < 272 {
*p = append(*p, padder[:272-n]...)
}
}

View File

@ -1,354 +0,0 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. “Contributor”
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. “Contributor Version”
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributors Contribution.
1.3. “Contribution”
means Covered Software of a particular Contributor.
1.4. “Covered Software”
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. “Incompatible With Secondary Licenses”
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of version
1.1 or earlier of the License, but not also under the terms of a
Secondary License.
1.6. “Executable Form”
means any form of the work other than Source Code Form.
1.7. “Larger Work”
means a work that combines Covered Software with other material, in a separate
file or files, that is not Covered Software.
1.8. “License”
means this document.
1.9. “Licensable”
means having the right to grant, to the maximum extent possible, whether at the
time of the initial grant or subsequently, any and all of the rights conveyed by
this License.
1.10. “Modifications”
means any of the following:
a. any file in Source Code Form that results from an addition to, deletion
from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. “Patent Claims” of a Contributor
means any patent claim(s), including without limitation, method, process,
and apparatus claims, in any patent Licensable by such Contributor that
would be infringed, but for the grant of the License, by the making,
using, selling, offering for sale, having made, import, or transfer of
either its Contributions or its Contributor Version.
1.12. “Secondary License”
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. “Source Code Form”
means the form of the work preferred for making modifications.
1.14. “You” (or “Your”)
means an individual or a legal entity exercising rights under this
License. For legal entities, “You” includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, “control” means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or as
part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its Contributions
or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution become
effective for each Contribution on the date the Contributor first distributes
such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under this
License. No additional rights or licenses will be implied from the distribution
or licensing of Covered Software under this License. Notwithstanding Section
2.1(b) above, no patent license is granted by a Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third partys
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of its
Contributions.
This License does not grant any rights in the trademarks, service marks, or
logos of any Contributor (except as may be necessary to comply with the
notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this License
(see Section 10.2) or under the terms of a Secondary License (if permitted
under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its Contributions
are its original creation(s) or it has sufficient rights to grant the
rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under applicable
copyright doctrines of fair use, fair dealing, or other equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under the
terms of this License. You must inform recipients that the Source Code Form
of the Covered Software is governed by the terms of this License, and how
they can obtain a copy of this License. You may not attempt to alter or
restrict the recipients rights in the Source Code Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this License,
or sublicense it under different terms, provided that the license for
the Executable Form does not attempt to limit or alter the recipients
rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for the
Covered Software. If the Larger Work is a combination of Covered Software
with a work governed by one or more Secondary Licenses, and the Covered
Software is not Incompatible With Secondary Licenses, this License permits
You to additionally distribute such Covered Software under the terms of
such Secondary License(s), so that the recipient of the Larger Work may, at
their option, further distribute the Covered Software under the terms of
either this License or such Secondary License(s).
3.4. Notices
You may not remove or alter the substance of any license notices (including
copyright notices, patent notices, disclaimers of warranty, or limitations
of liability) contained within the Source Code Form of the Covered
Software, except that You may alter any license notices to the extent
required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on behalf
of any Contributor. You must make it absolutely clear that any such
warranty, support, indemnity, or liability obligation is offered by You
alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute, judicial
order, or regulation then You must: (a) comply with the terms of this License
to the maximum extent possible; and (b) describe the limitations and the code
they affect. Such description must be placed in a text file included with all
distributions of the Covered Software under this License. Except to the
extent prohibited by statute or regulation, such description must be
sufficiently detailed for a recipient of ordinary skill to be able to
understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
if such Contributor fails to notify You of the non-compliance by some
reasonable means prior to 60 days after You have come back into compliance.
Moreover, Your grants from a particular Contributor are reinstated on an
ongoing basis if such Contributor notifies You of the non-compliance by
some reasonable means, this is the first time You have received notice of
non-compliance with this License from such Contributor, and You become
compliant prior to 30 days after Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions, counter-claims,
and cross-claims) alleging that a Contributor Version directly or
indirectly infringes any patent, then the rights granted to You by any and
all Contributors for the Covered Software under Section 2.1 of this License
shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an “as is” basis, without
warranty of any kind, either expressed, implied, or statutory, including,
without limitation, warranties that the Covered Software is free of defects,
merchantable, fit for a particular purpose or non-infringing. The entire
risk as to the quality and performance of the Covered Software is with You.
Should any Covered Software prove defective in any respect, You (not any
Contributor) assume the cost of any necessary servicing, repair, or
correction. This disclaimer of warranty constitutes an essential part of this
License. No use of any Covered Software is authorized under this License
except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from such
partys negligence to the extent applicable law prohibits such limitation.
Some jurisdictions do not allow the exclusion or limitation of incidental or
consequential damages, so this exclusion and limitation may not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts of
a jurisdiction where the defendant maintains its principal place of business
and such litigation shall be governed by laws of that jurisdiction, without
reference to its conflict-of-law provisions. Nothing in this Section shall
prevent a partys ability to bring cross-claims or counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject matter
hereof. If any provision of this License is held to be unenforceable, such
provision shall be reformed only to the extent necessary to make it
enforceable. Any law or regulation which provides that the language of a
contract shall be construed against the drafter shall not be used to construe
this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version of
the License under which You originally received the Covered Software, or
under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a modified
version of this License if you rename the license and remove any
references to the name of the license steward (except to note that such
modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file, then
You may include the notice in a location (such as a LICENSE file in a relevant
directory) where a recipient would be likely to look for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - “Incompatible With Secondary Licenses” Notice
This Source Code Form is “Incompatible
With Secondary Licenses”, as defined by
the Mozilla Public License, v. 2.0.

View File

@ -1,8 +0,0 @@
dhcp4client [![GoDoc](https://godoc.org/github.com/d2g/dhcp4client?status.svg)](http://godoc.org/github.com/d2g/dhcp4client) [![Coverage Status](https://coveralls.io/repos/d2g/dhcp4client/badge.svg?branch=HEAD)](https://coveralls.io/r/d2g/dhcp4client?branch=HEAD) [![Codeship Status for d2g/dhcp4client](https://codeship.com/projects/d75d9860-b364-0132-bc79-7e1d8cf367b9/status?branch=master)](https://codeship.com/projects/70187)
===========
DHCP Client
###### Thanks to:
@eyakubovich For AF_PACKET support.

View File

@ -1,416 +0,0 @@
package dhcp4client
import (
"bytes"
"fmt"
"hash/fnv"
"math/rand"
"net"
"sync"
"syscall"
"time"
"github.com/d2g/dhcp4"
)
const (
MaxDHCPLen = 576
)
type Client struct {
hardwareAddr net.HardwareAddr //The HardwareAddr to send in the request.
ignoreServers []net.IP //List of Servers to Ignore requests from.
timeout time.Duration //Time before we timeout.
broadcast bool //Set the Bcast flag in BOOTP Flags
connection ConnectionInt //The Connection Method to use
generateXID func([]byte) //Function Used to Generate a XID
}
//Abstracts the type of underlying socket used
type ConnectionInt interface {
Close() error
Write(packet []byte) error
ReadFrom() ([]byte, net.IP, error)
SetReadTimeout(t time.Duration) error
}
func New(options ...func(*Client) error) (*Client, error) {
c := Client{
timeout: time.Second * 10,
broadcast: true,
}
err := c.SetOption(options...)
if err != nil {
return nil, err
}
if c.generateXID == nil {
// https://tools.ietf.org/html/rfc2131#section-4.1 explains:
//
// A DHCP client MUST choose 'xid's in such a way as to minimize the chance
// of using an 'xid' identical to one used by another client.
//
// Hence, seed a random number generator with the current time and hardware
// address.
h := fnv.New64()
h.Write(c.hardwareAddr)
seed := int64(h.Sum64()) + time.Now().Unix()
rnd := rand.New(rand.NewSource(seed))
var rndMu sync.Mutex
c.generateXID = func(b []byte) {
rndMu.Lock()
defer rndMu.Unlock()
rnd.Read(b)
}
}
//if connection hasn't been set as an option create the default.
if c.connection == nil {
conn, err := NewInetSock()
if err != nil {
return nil, err
}
c.connection = conn
}
return &c, nil
}
func (c *Client) SetOption(options ...func(*Client) error) error {
for _, opt := range options {
if err := opt(c); err != nil {
return err
}
}
return nil
}
func Timeout(t time.Duration) func(*Client) error {
return func(c *Client) error {
c.timeout = t
return nil
}
}
func IgnoreServers(s []net.IP) func(*Client) error {
return func(c *Client) error {
c.ignoreServers = s
return nil
}
}
func HardwareAddr(h net.HardwareAddr) func(*Client) error {
return func(c *Client) error {
c.hardwareAddr = h
return nil
}
}
func Broadcast(b bool) func(*Client) error {
return func(c *Client) error {
c.broadcast = b
return nil
}
}
func Connection(conn ConnectionInt) func(*Client) error {
return func(c *Client) error {
c.connection = conn
return nil
}
}
func GenerateXID(g func([]byte)) func(*Client) error {
return func(c *Client) error {
c.generateXID = g
return nil
}
}
//Close Connections
func (c *Client) Close() error {
if c.connection != nil {
return c.connection.Close()
}
return nil
}
//Send the Discovery Packet to the Broadcast Channel
func (c *Client) SendDiscoverPacket() (dhcp4.Packet, error) {
discoveryPacket := c.DiscoverPacket()
discoveryPacket.PadToMinSize()
return discoveryPacket, c.SendPacket(discoveryPacket)
}
// TimeoutError records a timeout when waiting for a DHCP packet.
type TimeoutError struct {
Timeout time.Duration
}
func (te *TimeoutError) Error() string {
return fmt.Sprintf("no DHCP packet received within %v", te.Timeout)
}
//Retreive Offer...
//Wait for the offer for a specific Discovery Packet.
func (c *Client) GetOffer(discoverPacket *dhcp4.Packet) (dhcp4.Packet, error) {
start := time.Now()
for {
timeout := c.timeout - time.Since(start)
if timeout < 0 {
return dhcp4.Packet{}, &TimeoutError{Timeout: c.timeout}
}
c.connection.SetReadTimeout(timeout)
readBuffer, source, err := c.connection.ReadFrom()
if err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == syscall.EAGAIN {
return dhcp4.Packet{}, &TimeoutError{Timeout: c.timeout}
}
return dhcp4.Packet{}, err
}
offerPacket := dhcp4.Packet(readBuffer)
offerPacketOptions := offerPacket.ParseOptions()
// Ignore Servers in my Ignore list
for _, ignoreServer := range c.ignoreServers {
if source.Equal(ignoreServer) {
continue
}
if offerPacket.SIAddr().Equal(ignoreServer) {
continue
}
}
if len(offerPacketOptions[dhcp4.OptionDHCPMessageType]) < 1 || dhcp4.MessageType(offerPacketOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.Offer || !bytes.Equal(discoverPacket.XId(), offerPacket.XId()) {
continue
}
return offerPacket, nil
}
}
//Send Request Based On the offer Received.
func (c *Client) SendRequest(offerPacket *dhcp4.Packet) (dhcp4.Packet, error) {
requestPacket := c.RequestPacket(offerPacket)
requestPacket.PadToMinSize()
return requestPacket, c.SendPacket(requestPacket)
}
//Retreive Acknowledgement
//Wait for the offer for a specific Request Packet.
func (c *Client) GetAcknowledgement(requestPacket *dhcp4.Packet) (dhcp4.Packet, error) {
start := time.Now()
for {
timeout := c.timeout - time.Since(start)
if timeout < 0 {
return dhcp4.Packet{}, &TimeoutError{Timeout: c.timeout}
}
c.connection.SetReadTimeout(timeout)
readBuffer, source, err := c.connection.ReadFrom()
if err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == syscall.EAGAIN {
return dhcp4.Packet{}, &TimeoutError{Timeout: c.timeout}
}
return dhcp4.Packet{}, err
}
acknowledgementPacket := dhcp4.Packet(readBuffer)
acknowledgementPacketOptions := acknowledgementPacket.ParseOptions()
// Ignore Servers in my Ignore list
for _, ignoreServer := range c.ignoreServers {
if source.Equal(ignoreServer) {
continue
}
if acknowledgementPacket.SIAddr().Equal(ignoreServer) {
continue
}
}
if !bytes.Equal(requestPacket.XId(), acknowledgementPacket.XId()) || len(acknowledgementPacketOptions[dhcp4.OptionDHCPMessageType]) < 1 || (dhcp4.MessageType(acknowledgementPacketOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK && dhcp4.MessageType(acknowledgementPacketOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.NAK) {
continue
}
return acknowledgementPacket, nil
}
}
//Send Decline to the received acknowledgement.
func (c *Client) SendDecline(acknowledgementPacket *dhcp4.Packet) (dhcp4.Packet, error) {
declinePacket := c.DeclinePacket(acknowledgementPacket)
declinePacket.PadToMinSize()
return declinePacket, c.SendPacket(declinePacket)
}
//Send a DHCP Packet.
func (c *Client) SendPacket(packet dhcp4.Packet) error {
return c.connection.Write(packet)
}
//Create Discover Packet
func (c *Client) DiscoverPacket() dhcp4.Packet {
messageid := make([]byte, 4)
c.generateXID(messageid)
packet := dhcp4.NewPacket(dhcp4.BootRequest)
packet.SetCHAddr(c.hardwareAddr)
packet.SetXId(messageid)
packet.SetBroadcast(c.broadcast)
packet.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Discover)})
//packet.PadToMinSize()
return packet
}
//Create Request Packet
func (c *Client) RequestPacket(offerPacket *dhcp4.Packet) dhcp4.Packet {
offerOptions := offerPacket.ParseOptions()
packet := dhcp4.NewPacket(dhcp4.BootRequest)
packet.SetCHAddr(c.hardwareAddr)
packet.SetXId(offerPacket.XId())
packet.SetCIAddr(offerPacket.CIAddr())
packet.SetSIAddr(offerPacket.SIAddr())
packet.SetBroadcast(c.broadcast)
packet.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Request)})
packet.AddOption(dhcp4.OptionRequestedIPAddress, (offerPacket.YIAddr()).To4())
packet.AddOption(dhcp4.OptionServerIdentifier, offerOptions[dhcp4.OptionServerIdentifier])
return packet
}
//Create Request Packet For a Renew
func (c *Client) RenewalRequestPacket(acknowledgement *dhcp4.Packet) dhcp4.Packet {
messageid := make([]byte, 4)
c.generateXID(messageid)
acknowledgementOptions := acknowledgement.ParseOptions()
packet := dhcp4.NewPacket(dhcp4.BootRequest)
packet.SetCHAddr(acknowledgement.CHAddr())
packet.SetXId(messageid)
packet.SetCIAddr(acknowledgement.YIAddr())
packet.SetSIAddr(acknowledgement.SIAddr())
packet.SetBroadcast(c.broadcast)
packet.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Request)})
packet.AddOption(dhcp4.OptionRequestedIPAddress, (acknowledgement.YIAddr()).To4())
packet.AddOption(dhcp4.OptionServerIdentifier, acknowledgementOptions[dhcp4.OptionServerIdentifier])
return packet
}
//Create Release Packet For a Release
func (c *Client) ReleasePacket(acknowledgement *dhcp4.Packet) dhcp4.Packet {
messageid := make([]byte, 4)
c.generateXID(messageid)
acknowledgementOptions := acknowledgement.ParseOptions()
packet := dhcp4.NewPacket(dhcp4.BootRequest)
packet.SetCHAddr(acknowledgement.CHAddr())
packet.SetXId(messageid)
packet.SetCIAddr(acknowledgement.YIAddr())
packet.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Release)})
packet.AddOption(dhcp4.OptionServerIdentifier, acknowledgementOptions[dhcp4.OptionServerIdentifier])
return packet
}
//Create Decline Packet
func (c *Client) DeclinePacket(acknowledgement *dhcp4.Packet) dhcp4.Packet {
messageid := make([]byte, 4)
c.generateXID(messageid)
acknowledgementOptions := acknowledgement.ParseOptions()
packet := dhcp4.NewPacket(dhcp4.BootRequest)
packet.SetCHAddr(acknowledgement.CHAddr())
packet.SetXId(messageid)
packet.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Decline)})
packet.AddOption(dhcp4.OptionRequestedIPAddress, (acknowledgement.YIAddr()).To4())
packet.AddOption(dhcp4.OptionServerIdentifier, acknowledgementOptions[dhcp4.OptionServerIdentifier])
return packet
}
//Lets do a Full DHCP Request.
func (c *Client) Request() (bool, dhcp4.Packet, error) {
discoveryPacket, err := c.SendDiscoverPacket()
if err != nil {
return false, discoveryPacket, err
}
offerPacket, err := c.GetOffer(&discoveryPacket)
if err != nil {
return false, offerPacket, err
}
requestPacket, err := c.SendRequest(&offerPacket)
if err != nil {
return false, requestPacket, err
}
acknowledgement, err := c.GetAcknowledgement(&requestPacket)
if err != nil {
return false, acknowledgement, err
}
acknowledgementOptions := acknowledgement.ParseOptions()
if dhcp4.MessageType(acknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK {
return false, acknowledgement, nil
}
return true, acknowledgement, nil
}
//Renew a lease backed on the Acknowledgement Packet.
//Returns Sucessfull, The AcknoledgementPacket, Any Errors
func (c *Client) Renew(acknowledgement dhcp4.Packet) (bool, dhcp4.Packet, error) {
renewRequest := c.RenewalRequestPacket(&acknowledgement)
renewRequest.PadToMinSize()
err := c.SendPacket(renewRequest)
if err != nil {
return false, renewRequest, err
}
newAcknowledgement, err := c.GetAcknowledgement(&renewRequest)
if err != nil {
return false, newAcknowledgement, err
}
newAcknowledgementOptions := newAcknowledgement.ParseOptions()
if dhcp4.MessageType(newAcknowledgementOptions[dhcp4.OptionDHCPMessageType][0]) != dhcp4.ACK {
return false, newAcknowledgement, nil
}
return true, newAcknowledgement, nil
}
//Release a lease backed on the Acknowledgement Packet.
//Returns Any Errors
func (c *Client) Release(acknowledgement dhcp4.Packet) error {
release := c.ReleasePacket(&acknowledgement)
release.PadToMinSize()
return c.SendPacket(release)
}

View File

@ -1,18 +0,0 @@
package dhcp4client
import (
cryptorand "crypto/rand"
mathrand "math/rand"
)
func CryptoGenerateXID(b []byte) {
if _, err := cryptorand.Read(b); err != nil {
panic(err)
}
}
func MathGenerateXID(b []byte) {
if _, err := mathrand.Read(b); err != nil {
panic(err)
}
}

View File

@ -1,75 +0,0 @@
package dhcp4client
import (
"net"
"time"
)
type inetSock struct {
*net.UDPConn
laddr net.UDPAddr
raddr net.UDPAddr
}
func NewInetSock(options ...func(*inetSock) error) (*inetSock, error) {
c := &inetSock{
laddr: net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 68},
raddr: net.UDPAddr{IP: net.IPv4bcast, Port: 67},
}
err := c.setOption(options...)
if err != nil {
return nil, err
}
conn, err := net.ListenUDP("udp4", &c.laddr)
if err != nil {
return nil, err
}
c.UDPConn = conn
return c, err
}
func (c *inetSock) setOption(options ...func(*inetSock) error) error {
for _, opt := range options {
if err := opt(c); err != nil {
return err
}
}
return nil
}
func SetLocalAddr(l net.UDPAddr) func(*inetSock) error {
return func(c *inetSock) error {
c.laddr = l
return nil
}
}
func SetRemoteAddr(r net.UDPAddr) func(*inetSock) error {
return func(c *inetSock) error {
c.raddr = r
return nil
}
}
func (c *inetSock) Write(packet []byte) error {
_, err := c.WriteToUDP(packet, &c.raddr)
return err
}
func (c *inetSock) ReadFrom() ([]byte, net.IP, error) {
readBuffer := make([]byte, MaxDHCPLen)
n, source, err := c.ReadFromUDP(readBuffer)
if source != nil {
return readBuffer[:n], source.IP, err
} else {
return readBuffer[:n], net.IP{}, err
}
}
func (c *inetSock) SetReadTimeout(t time.Duration) error {
return c.SetReadDeadline(time.Now().Add(t))
}

View File

@ -1,147 +0,0 @@
package dhcp4client
import (
"encoding/binary"
"math/rand"
"net"
"time"
"golang.org/x/sys/unix"
)
const (
minIPHdrLen = 20
maxIPHdrLen = 60
udpHdrLen = 8
ip4Ver = 0x40
ttl = 16
srcPort = 68
dstPort = 67
)
var (
bcastMAC = []byte{255, 255, 255, 255, 255, 255}
)
// abstracts AF_PACKET
type packetSock struct {
fd int
ifindex int
}
func NewPacketSock(ifindex int) (*packetSock, error) {
fd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_DGRAM, int(swap16(unix.ETH_P_IP)))
if err != nil {
return nil, err
}
addr := unix.SockaddrLinklayer{
Ifindex: ifindex,
Protocol: swap16(unix.ETH_P_IP),
}
if err = unix.Bind(fd, &addr); err != nil {
return nil, err
}
return &packetSock{
fd: fd,
ifindex: ifindex,
}, nil
}
func (pc *packetSock) Close() error {
return unix.Close(pc.fd)
}
func (pc *packetSock) Write(packet []byte) error {
lladdr := unix.SockaddrLinklayer{
Ifindex: pc.ifindex,
Protocol: swap16(unix.ETH_P_IP),
Halen: uint8(len(bcastMAC)),
}
copy(lladdr.Addr[:], bcastMAC)
pkt := make([]byte, minIPHdrLen+udpHdrLen+len(packet))
fillIPHdr(pkt[0:minIPHdrLen], udpHdrLen+uint16(len(packet)))
fillUDPHdr(pkt[minIPHdrLen:minIPHdrLen+udpHdrLen], uint16(len(packet)))
// payload
copy(pkt[minIPHdrLen+udpHdrLen:len(pkt)], packet)
return unix.Sendto(pc.fd, pkt, 0, &lladdr)
}
func (pc *packetSock) ReadFrom() ([]byte, net.IP, error) {
pkt := make([]byte, maxIPHdrLen+udpHdrLen+MaxDHCPLen)
n, _, err := unix.Recvfrom(pc.fd, pkt, 0)
if err != nil {
return nil, nil, err
}
// IP hdr len
ihl := int(pkt[0]&0x0F) * 4
// Source IP address
src := net.IP(pkt[12:16])
return pkt[ihl+udpHdrLen : n], src, nil
}
func (pc *packetSock) SetReadTimeout(t time.Duration) error {
tv := unix.NsecToTimeval(t.Nanoseconds())
return unix.SetsockoptTimeval(pc.fd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv)
}
// compute's 1's complement checksum
func chksum(p []byte, csum []byte) {
cklen := len(p)
s := uint32(0)
for i := 0; i < (cklen - 1); i += 2 {
s += uint32(p[i+1])<<8 | uint32(p[i])
}
if cklen&1 == 1 {
s += uint32(p[cklen-1])
}
s = (s >> 16) + (s & 0xffff)
s = s + (s >> 16)
s = ^s
csum[0] = uint8(s & 0xff)
csum[1] = uint8(s >> 8)
}
func fillIPHdr(hdr []byte, payloadLen uint16) {
// version + IHL
hdr[0] = ip4Ver | (minIPHdrLen / 4)
// total length
binary.BigEndian.PutUint16(hdr[2:4], uint16(len(hdr))+payloadLen)
// identification
if _, err := rand.Read(hdr[4:5]); err != nil {
panic(err)
}
// TTL
hdr[8] = 16
// Protocol
hdr[9] = unix.IPPROTO_UDP
// dst IP
copy(hdr[16:20], net.IPv4bcast.To4())
// compute IP hdr checksum
chksum(hdr[0:len(hdr)], hdr[10:12])
}
func fillUDPHdr(hdr []byte, payloadLen uint16) {
// src port
binary.BigEndian.PutUint16(hdr[0:2], srcPort)
// dest port
binary.BigEndian.PutUint16(hdr[2:4], dstPort)
// length
binary.BigEndian.PutUint16(hdr[4:6], udpHdrLen+payloadLen)
}
func swap16(x uint16) uint16 {
var b [2]byte
binary.BigEndian.PutUint16(b[:], x)
return binary.LittleEndian.Uint16(b[:])
}

View File

@ -1,354 +0,0 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. “Contributor”
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. “Contributor Version”
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributors Contribution.
1.3. “Contribution”
means Covered Software of a particular Contributor.
1.4. “Covered Software”
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. “Incompatible With Secondary Licenses”
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of version
1.1 or earlier of the License, but not also under the terms of a
Secondary License.
1.6. “Executable Form”
means any form of the work other than Source Code Form.
1.7. “Larger Work”
means a work that combines Covered Software with other material, in a separate
file or files, that is not Covered Software.
1.8. “License”
means this document.
1.9. “Licensable”
means having the right to grant, to the maximum extent possible, whether at the
time of the initial grant or subsequently, any and all of the rights conveyed by
this License.
1.10. “Modifications”
means any of the following:
a. any file in Source Code Form that results from an addition to, deletion
from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. “Patent Claims” of a Contributor
means any patent claim(s), including without limitation, method, process,
and apparatus claims, in any patent Licensable by such Contributor that
would be infringed, but for the grant of the License, by the making,
using, selling, offering for sale, having made, import, or transfer of
either its Contributions or its Contributor Version.
1.12. “Secondary License”
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. “Source Code Form”
means the form of the work preferred for making modifications.
1.14. “You” (or “Your”)
means an individual or a legal entity exercising rights under this
License. For legal entities, “You” includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, “control” means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or as
part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its Contributions
or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution become
effective for each Contribution on the date the Contributor first distributes
such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under this
License. No additional rights or licenses will be implied from the distribution
or licensing of Covered Software under this License. Notwithstanding Section
2.1(b) above, no patent license is granted by a Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third partys
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of its
Contributions.
This License does not grant any rights in the trademarks, service marks, or
logos of any Contributor (except as may be necessary to comply with the
notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this License
(see Section 10.2) or under the terms of a Secondary License (if permitted
under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its Contributions
are its original creation(s) or it has sufficient rights to grant the
rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under applicable
copyright doctrines of fair use, fair dealing, or other equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under the
terms of this License. You must inform recipients that the Source Code Form
of the Covered Software is governed by the terms of this License, and how
they can obtain a copy of this License. You may not attempt to alter or
restrict the recipients rights in the Source Code Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this License,
or sublicense it under different terms, provided that the license for
the Executable Form does not attempt to limit or alter the recipients
rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for the
Covered Software. If the Larger Work is a combination of Covered Software
with a work governed by one or more Secondary Licenses, and the Covered
Software is not Incompatible With Secondary Licenses, this License permits
You to additionally distribute such Covered Software under the terms of
such Secondary License(s), so that the recipient of the Larger Work may, at
their option, further distribute the Covered Software under the terms of
either this License or such Secondary License(s).
3.4. Notices
You may not remove or alter the substance of any license notices (including
copyright notices, patent notices, disclaimers of warranty, or limitations
of liability) contained within the Source Code Form of the Covered
Software, except that You may alter any license notices to the extent
required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on behalf
of any Contributor. You must make it absolutely clear that any such
warranty, support, indemnity, or liability obligation is offered by You
alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute, judicial
order, or regulation then You must: (a) comply with the terms of this License
to the maximum extent possible; and (b) describe the limitations and the code
they affect. Such description must be placed in a text file included with all
distributions of the Covered Software under this License. Except to the
extent prohibited by statute or regulation, such description must be
sufficiently detailed for a recipient of ordinary skill to be able to
understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing basis,
if such Contributor fails to notify You of the non-compliance by some
reasonable means prior to 60 days after You have come back into compliance.
Moreover, Your grants from a particular Contributor are reinstated on an
ongoing basis if such Contributor notifies You of the non-compliance by
some reasonable means, this is the first time You have received notice of
non-compliance with this License from such Contributor, and You become
compliant prior to 30 days after Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions, counter-claims,
and cross-claims) alleging that a Contributor Version directly or
indirectly infringes any patent, then the rights granted to You by any and
all Contributors for the Covered Software under Section 2.1 of this License
shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an “as is” basis, without
warranty of any kind, either expressed, implied, or statutory, including,
without limitation, warranties that the Covered Software is free of defects,
merchantable, fit for a particular purpose or non-infringing. The entire
risk as to the quality and performance of the Covered Software is with You.
Should any Covered Software prove defective in any respect, You (not any
Contributor) assume the cost of any necessary servicing, repair, or
correction. This disclaimer of warranty constitutes an essential part of this
License. No use of any Covered Software is authorized under this License
except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from such
partys negligence to the extent applicable law prohibits such limitation.
Some jurisdictions do not allow the exclusion or limitation of incidental or
consequential damages, so this exclusion and limitation may not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts of
a jurisdiction where the defendant maintains its principal place of business
and such litigation shall be governed by laws of that jurisdiction, without
reference to its conflict-of-law provisions. Nothing in this Section shall
prevent a partys ability to bring cross-claims or counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject matter
hereof. If any provision of this License is held to be unenforceable, such
provision shall be reformed only to the extent necessary to make it
enforceable. Any law or regulation which provides that the language of a
contract shall be construed against the drafter shall not be used to construe
this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version of
the License under which You originally received the Covered Software, or
under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a modified
version of this License if you rename the license and remove any
references to the name of the license steward (except to note that such
modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file, then
You may include the notice in a location (such as a LICENSE file in a relevant
directory) where a recipient would be likely to look for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - “Incompatible With Secondary Licenses” Notice
This Source Code Form is “Incompatible
With Secondary Licenses”, as defined by
the Mozilla Public License, v. 2.0.

View File

@ -1,4 +0,0 @@
dhcp4server [![GoDoc](https://godoc.org/github.com/d2g/dhcp4server?status.svg)](http://godoc.org/github.com/d2g/dhcp4server) [![Coverage Status](https://coveralls.io/repos/d2g/dhcp4server/badge.svg)](https://coveralls.io/r/d2g/dhcp4server) [![Codeship Status for d2g/dhcp4server](https://codeship.com/projects/ff96ded0-89cb-0132-8209-4635861fb902/status?branch=master)](https://codeship.com/projects/59804)
===========
DHCP Server

View File

@ -1,102 +0,0 @@
package leasepool
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"net"
"time"
)
type LeaseStatus int
const (
Free LeaseStatus = 0
Reserved LeaseStatus = 1
Active LeaseStatus = 2
)
type Lease struct {
IP net.IP //The IP of the Lease
Status LeaseStatus //Are Reserved, Active or Free
MACAddress net.HardwareAddr //Mac Address of the Device
ClientID []byte //ClientID of the request
Hostname string //Hostname From option 12
Expiry time.Time //Expiry Time
}
//leaseMarshal is a mirror of Lease used for marshalling, since
//net.HardwareAddr has no native marshalling capability.
type leaseMarshal struct {
IP string
Status int
MACAddress string
ClientID string
Hostname string
Expiry time.Time
}
func (this Lease) MarshalJSON() ([]byte, error) {
return json.Marshal(leaseMarshal{
IP: this.IP.String(),
Status: int(this.Status),
MACAddress: this.MACAddress.String(),
ClientID: hex.EncodeToString(this.ClientID),
Hostname: this.Hostname,
Expiry: this.Expiry,
})
}
func (this *Lease) UnmarshalJSON(data []byte) error {
stringUnMarshal := leaseMarshal{}
err := json.Unmarshal(data, &stringUnMarshal)
if err != nil {
return err
}
this.IP = net.ParseIP(stringUnMarshal.IP)
this.Status = LeaseStatus(stringUnMarshal.Status)
if stringUnMarshal.MACAddress != "" {
this.MACAddress, err = net.ParseMAC(stringUnMarshal.MACAddress)
if err != nil {
return fmt.Errorf("error parsing MAC address: %v", err)
}
}
this.ClientID, err = hex.DecodeString(stringUnMarshal.ClientID)
if err != nil {
return fmt.Errorf("error decoding clientID: %v", err)
}
this.Hostname = stringUnMarshal.Hostname
this.Expiry = stringUnMarshal.Expiry
return nil
}
func (this Lease) Equal(other Lease) bool {
if !this.IP.Equal(other.IP) {
return false
}
if int(this.Status) != int(other.Status) {
return false
}
if this.MACAddress.String() != other.MACAddress.String() {
return false
}
if !bytes.Equal(this.ClientID, other.ClientID) {
return false
}
if this.Hostname != other.Hostname {
return false
}
if !this.Expiry.Equal(other.Expiry) {
return false
}
return true
}

View File

@ -1,49 +0,0 @@
package leasepool
import (
"net"
)
/*
* Lease.IP is the Key.
*/
type LeasePool interface {
//Add A Lease To The Pool
AddLease(Lease) error
//Remove
RemoveLease(net.IP) error
//Remove All Leases from the Pool (Required for Persistant LeaseManagers)
PurgeLeases() error
/*
* Get the Lease
* -Found
* -Copy Of the Lease
* -Any Error
*/
GetLease(net.IP) (bool, Lease, error)
//Get the lease already in use by that hardware address and/or client identifier.
GetLeaseForClient(net.HardwareAddr, []byte) (bool, Lease, error)
/*
* -Lease Available
* -Lease
* -Error
*/
GetNextFreeLease() (bool, Lease, error)
/*
* Return All Leases
*/
GetLeases() ([]Lease, error)
/*
* Update Lease
* - Has Updated
* - Error
*/
UpdateLease(Lease) (bool, error)
}

View File

@ -1,161 +0,0 @@
package memorypool
import (
"bytes"
"errors"
"github.com/d2g/dhcp4server/leasepool"
"net"
"sync"
)
type MemoryPool struct {
pool []leasepool.Lease
poolLock sync.Mutex
}
//Add A Lease To The Pool
func (t *MemoryPool) AddLease(newLease leasepool.Lease) error {
t.poolLock.Lock()
defer t.poolLock.Unlock()
if t.pool == nil {
t.pool = make([]leasepool.Lease, 0)
}
for i := range t.pool {
if t.pool[i].IP.Equal(newLease.IP) {
//Lease Already Exists In Pool
return errors.New("Error: Lease IP \"" + newLease.IP.String() + "\" alreay exists in Pool")
}
}
t.pool = append([]leasepool.Lease{newLease}, t.pool...)
return nil
}
//Remove a Lease From The Pool
func (t *MemoryPool) RemoveLease(leaseIP net.IP) error {
t.poolLock.Lock()
defer t.poolLock.Unlock()
for i := range t.pool {
if t.pool[i].IP.Equal(leaseIP) {
//Move the Last Element to This Position.
t.pool[i] = t.pool[len(t.pool)-1]
//Shortern the Pool By One.
t.pool = t.pool[0:(len(t.pool) - 1)]
return nil
}
}
return errors.New("Error: Lease IP \"" + leaseIP.String() + "\" Is Not In The Pool")
}
//Remove All Leases from the Pool (Required for Persistant LeaseManagers)
func (t *MemoryPool) PurgeLeases() error {
t.poolLock.Lock()
defer t.poolLock.Unlock()
t.pool = nil
t.pool = make([]leasepool.Lease, 0)
return nil
}
/*
* Get the Lease
* -Found
* -Copy Of the Lease
* -Any Error
*/
func (t *MemoryPool) GetLease(leaseIP net.IP) (bool, leasepool.Lease, error) {
t.poolLock.Lock()
defer t.poolLock.Unlock()
for i := range t.pool {
if t.pool[i].IP.Equal(leaseIP) {
return true, t.pool[i], nil
}
}
return false, leasepool.Lease{}, nil
}
func makeKey(macAddress net.HardwareAddr, clientID []byte) []byte {
key := []byte(macAddress)
if len(clientID) > 0 {
key = append(key, clientID...)
}
return key
}
//Get the lease already in use by that hardware address and/or client identifier.
func (t *MemoryPool) GetLeaseForClient(macAddress net.HardwareAddr, clientID []byte) (bool, leasepool.Lease, error) {
t.poolLock.Lock()
defer t.poolLock.Unlock()
needleKey := makeKey(macAddress, clientID)
for i := range t.pool {
haystackKey := makeKey(t.pool[i].MACAddress, t.pool[i].ClientID)
if bytes.Equal(needleKey, haystackKey) {
return true, t.pool[i], nil
}
}
return false, leasepool.Lease{}, nil
}
/*
* -Lease Available
* -Lease
* -Error
*/
func (t *MemoryPool) GetNextFreeLease() (bool, leasepool.Lease, error) {
t.poolLock.Lock()
defer t.poolLock.Unlock()
//Loop Through the elements backwards.
for i := (len(t.pool) - 1); i >= 0; i-- {
//If the Lease Is Free
if t.pool[i].Status == leasepool.Free {
//Take the Element
iLease := t.pool[i]
//Shrink the Pool By 1
t.pool = t.pool[:(len(t.pool) - 1)]
//Place the Lease At the Begining (This saves us having some sort of counter...)
t.pool = append([]leasepool.Lease{iLease}, t.pool...)
return true, iLease, nil
}
}
return false, leasepool.Lease{}, nil
}
/*
* Return All Leases
*/
func (t *MemoryPool) GetLeases() ([]leasepool.Lease, error) {
return t.pool, nil
}
/*
* Update Lease
* - Has Updated
* - Error
*/
func (t *MemoryPool) UpdateLease(lease leasepool.Lease) (bool, error) {
t.poolLock.Lock()
defer t.poolLock.Unlock()
for i := range t.pool {
if t.pool[i].IP.Equal(lease.IP) {
t.pool[i].MACAddress = lease.MACAddress
t.pool[i].ClientID = lease.ClientID
t.pool[i].Hostname = lease.Hostname
t.pool[i].Expiry = lease.Expiry
t.pool[i].Status = lease.Status
return true, nil
}
}
return false, nil
}

View File

@ -1,590 +0,0 @@
package dhcp4server
import (
"bytes"
"errors"
"log"
"net"
"sync/atomic"
"time"
"github.com/d2g/dhcp4"
"github.com/d2g/dhcp4server/leasepool"
"golang.org/x/net/ipv4"
)
/*
* The DHCP Server Structure
*/
type Server struct {
//Configuration Options
ip net.IP //The IP Address We Tell Clients The Server Is On.
defaultGateway net.IP //The Default Gateway Address
dnsServers []net.IP //DNS Servers
subnetMask net.IP //ie. 255.255.255.0
leaseDuration time.Duration //Number of Seconds
ignoreIPs []net.IP //Slice of IP's that should be ignored by the Server.
ignoreHardwareAddress []net.HardwareAddr //Slice of Hardware Addresses we should ignore.
//Local Address
laddr net.UDPAddr
//Remote address
raddr net.UDPAddr
//LeasePool
leasePool leasepool.LeasePool //Lease Pool Manager
//Used to Gracefully Close the Server
shutdown uint32
//Listeners & Response Connection.
connection *ipv4.PacketConn
}
// Create A New Server
func New(ip net.IP, l leasepool.LeasePool, options ...func(*Server) error) (*Server, error) {
s := Server{
ip: ip,
defaultGateway: ip,
dnsServers: []net.IP{net.IPv4(208, 67, 222, 222), net.IPv4(208, 67, 220, 220)}, //OPENDNS
subnetMask: net.IPv4(255, 255, 255, 0),
leaseDuration: 24 * time.Hour,
leasePool: l,
laddr: net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 67},
raddr: net.UDPAddr{IP: net.IPv4bcast, Port: 68},
}
err := s.setOptions(options...)
if err != nil {
return &s, err
}
return &s, err
}
func (s *Server) setOptions(options ...func(*Server) error) error {
for _, opt := range options {
if err := opt(s); err != nil {
return err
}
}
return nil
}
// Set the Server IP
func IP(i net.IP) func(*Server) error {
return func(s *Server) error {
s.ip = i
return nil
}
return nil
}
// Set the Default Gateway Address.
func DefaultGateway(r net.IP) func(*Server) error {
return func(s *Server) error {
s.defaultGateway = r
return nil
}
}
// Set the DNS servers.
func DNSServers(dnss []net.IP) func(*Server) error {
return func(s *Server) error {
s.dnsServers = dnss
return nil
}
}
// Set the Subnet Mask
func SubnetMask(m net.IP) func(*Server) error {
return func(s *Server) error {
s.subnetMask = m
return nil
}
}
// Set Lease Duration
func LeaseDuration(d time.Duration) func(*Server) error {
return func(s *Server) error {
s.leaseDuration = d
return nil
}
}
// Set Ignore IPs
func IgnoreIPs(ips []net.IP) func(*Server) error {
return func(s *Server) error {
s.ignoreIPs = ips
return nil
}
}
// Set Ignore Hardware Addresses
func IgnoreHardwareAddresses(h []net.HardwareAddr) func(*Server) error {
return func(s *Server) error {
s.ignoreHardwareAddress = h
return nil
}
}
// Set LeasePool
func LeasePool(p leasepool.LeasePool) func(*Server) error {
return func(s *Server) error {
s.leasePool = p
return nil
}
}
// Set The Local Address
func SetLocalAddr(a net.UDPAddr) func(*Server) error {
return func(s *Server) error {
s.laddr = a
return nil
}
}
// Set The Remote Address
func SetRemoteAddr(a net.UDPAddr) func(*Server) error {
return func(s *Server) error {
s.raddr = a
return nil
}
}
/*
* Start The DHCP Server
*/
func (s *Server) ListenAndServe() error {
var err error
connection, err := net.ListenPacket("udp4", s.laddr.String())
if err != nil {
log.Printf("Debug: Error Returned From ListenPacket On \"%s\" Because of \"%s\"\n", s.laddr.String(), err.Error())
return err
}
s.connection = ipv4.NewPacketConn(connection)
defer s.connection.Close()
//We Currently Don't Use this Feature Which is the only bit that is Linux Only.
//if err := s.connection.SetControlMessage(ipv4.FlagInterface, true); err != nil {
// return err
//}
log.Println("Trace: DHCP Server Listening.")
for {
ListenForDHCPPackets:
if s.shouldShutdown() {
return nil
}
//Make Our Buffer (Max Buffer is 574) "I believe this 576 size comes from RFC 791" - Random Mailing list quote of the day.
buffer := make([]byte, 576)
//Set Read Deadline
s.connection.SetReadDeadline(time.Now().Add(time.Second))
// Read Packet
n, control_message, source, err := s.connection.ReadFrom(buffer)
if err != nil {
switch v := err.(type) {
case *net.OpError:
// If we've been signaled to shut down, ignore
// the "use of closed network connection" error
// since the connection was closed by the
// shutdown request
if s.shouldShutdown() {
return nil
}
if v.Timeout() {
goto ListenForDHCPPackets
}
case *net.AddrError:
if v.Timeout() {
goto ListenForDHCPPackets
}
case *net.UnknownNetworkError:
if v.Timeout() {
goto ListenForDHCPPackets
}
}
log.Printf("Debug: Unexpect Error from Connection Read From: %v\n", err)
return err
}
//We seem to have an issue with undersized packets?
if n < 240 {
log.Printf("Error: Invalid Packet Size \"%d\" Received:%v\n", n, buffer[:n])
continue
}
//We should ignore some requests
//It shouldn't be possible to ignore IP's because they shouldn't have them as we're the DHCP server.
//However, they can have i.e. if you're the client & server :S.
for _, ipToIgnore := range s.ignoreIPs {
if ipToIgnore.Equal(source.(*net.UDPAddr).IP) {
log.Println("Debug: Ignoring DHCP Request From IP:" + ipToIgnore.String())
continue
}
}
packet := dhcp4.Packet(buffer[:n])
//We can ignore hardware addresses.
//Usefull for ignoring a range of hardware addresses
for _, hardwareAddressToIgnore := range s.ignoreHardwareAddress {
if bytes.Equal(hardwareAddressToIgnore, packet.CHAddr()) {
log.Println("Debug: Ignoring DHCP Request From Hardware Address:" + hardwareAddressToIgnore.String())
continue
}
}
log.Printf("Trace: Packet Received ID:%v\n", packet.XId())
log.Printf("Trace: Packet Options:%v\n", packet.ParseOptions())
log.Printf("Trace: Packet Client IP : %v\n", packet.CIAddr().String())
log.Printf("Trace: Packet Your IP : %v\n", packet.YIAddr().String())
log.Printf("Trace: Packet Server IP : %v\n", packet.SIAddr().String())
log.Printf("Trace: Packet Gateway IP: %v\n", packet.GIAddr().String())
log.Printf("Trace: Packet Client Mac: %v\n", packet.CHAddr().String())
//We need to stop butting in with other servers.
if packet.SIAddr().Equal(net.IPv4(0, 0, 0, 0)) || packet.SIAddr().Equal(net.IP{}) || packet.SIAddr().Equal(s.ip) {
returnPacket, err := s.ServeDHCP(packet)
if err != nil {
log.Println("Debug: Error Serving DHCP:" + err.Error())
return err
}
if len(returnPacket) > 0 {
log.Printf("Trace: Packet Returned ID:%v\n", returnPacket.XId())
log.Printf("Trace: Packet Options:%v\n", returnPacket.ParseOptions())
log.Printf("Trace: Packet Client IP : %v\n", returnPacket.CIAddr().String())
log.Printf("Trace: Packet Your IP : %v\n", returnPacket.YIAddr().String())
log.Printf("Trace: Packet Server IP : %v\n", returnPacket.SIAddr().String())
log.Printf("Trace: Packet Gateway IP: %v\n", returnPacket.GIAddr().String())
log.Printf("Trace: Packet Client Mac: %v\n", returnPacket.CHAddr().String())
_, err = s.connection.WriteTo(returnPacket, control_message, &s.raddr)
if err != nil {
log.Println("Debug: Error Writing:" + err.Error())
return err
}
}
}
}
}
func getClientID(packetOptions dhcp4.Options) []byte {
if clientID, ok := packetOptions[dhcp4.OptionClientIdentifier]; ok {
return clientID
}
return nil
}
func (s *Server) ServeDHCP(packet dhcp4.Packet) (dhcp4.Packet, error) {
packetOptions := packet.ParseOptions()
switch dhcp4.MessageType(packetOptions[dhcp4.OptionDHCPMessageType][0]) {
case dhcp4.Discover:
//Discover Received from client
//Lets get the lease we're going to send them
found, lease, err := s.GetLease(packet)
if err != nil {
return dhcp4.Packet{}, err
}
if !found {
log.Println("Warning: It Looks Like Our Leases Are Depleted...")
return dhcp4.Packet{}, nil
}
offerPacket := s.OfferPacket(packet)
offerPacket.SetYIAddr(lease.IP)
//Sort out the packet options
offerPacket.PadToMinSize()
lease.Status = leasepool.Reserved
lease.MACAddress = packet.CHAddr()
lease.ClientID = getClientID(packetOptions)
//If the lease expires within the next 5 Mins increase the lease expiary (Giving the Client 5 mins to complete)
if lease.Expiry.Before(time.Now().Add(time.Minute * 5)) {
lease.Expiry = time.Now().Add(time.Minute * 5)
}
if packetOptions[dhcp4.OptionHostName] != nil && string(packetOptions[dhcp4.OptionHostName]) != "" {
lease.Hostname = string(packetOptions[dhcp4.OptionHostName])
}
updated, err := s.leasePool.UpdateLease(lease)
if err != nil {
return dhcp4.Packet{}, err
}
if !updated {
//Unable to reserve lease (It's now active else where maybe?)
return dhcp4.Packet{}, errors.New("Unable to Reserve Lease:" + lease.IP.String())
}
return offerPacket, nil
case dhcp4.Request:
//Request Received from client
//Lets get the lease we're going to send them
found, lease, err := s.GetLease(packet)
if err != nil {
return dhcp4.Packet{}, err
}
if !found {
log.Println("Warning: It Looks Like Our Leases Are Depleted...")
return dhcp4.Packet{}, nil
}
//If the lease is not the one requested We should send a NAK..
if len(packetOptions) > 0 && !net.IP(packetOptions[dhcp4.OptionRequestedIPAddress]).Equal(lease.IP) {
//NAK
declinePacket := s.DeclinePacket(packet)
declinePacket.PadToMinSize()
return declinePacket, nil
} else {
lease.Status = leasepool.Active
lease.MACAddress = packet.CHAddr()
lease.ClientID = getClientID(packetOptions)
lease.Expiry = time.Now().Add(s.leaseDuration)
if packetOptions[dhcp4.OptionHostName] != nil && string(packetOptions[dhcp4.OptionHostName]) != "" {
lease.Hostname = string(packetOptions[dhcp4.OptionHostName])
}
updated, err := s.leasePool.UpdateLease(lease)
if err != nil {
return dhcp4.Packet{}, err
}
if updated {
//ACK
acknowledgementPacket := s.AcknowledgementPacket(packet)
acknowledgementPacket.SetYIAddr(lease.IP)
//Lease time.
acknowledgementPacket.AddOption(dhcp4.OptionIPAddressLeaseTime, dhcp4.OptionsLeaseTime(lease.Expiry.Sub(time.Now())))
acknowledgementPacket.PadToMinSize()
return acknowledgementPacket, nil
} else {
//NAK
declinePacket := s.DeclinePacket(packet)
declinePacket.PadToMinSize()
return declinePacket, nil
}
}
case dhcp4.Decline:
//Decline from the client:
log.Printf("Debug: Decline Message:%v\n", packet)
case dhcp4.Release:
//Decline from the client:
log.Printf("Debug: Release Message:%v\n", packet)
default:
log.Printf("Debug: Unexpected Packet Type:%v\n", dhcp4.MessageType(packetOptions[dhcp4.OptionDHCPMessageType][0]))
}
return dhcp4.Packet{}, nil
}
/*
* Create DHCP Offer Packet
*/
func (s *Server) OfferPacket(discoverPacket dhcp4.Packet) dhcp4.Packet {
offerPacket := dhcp4.NewPacket(dhcp4.BootReply)
offerPacket.SetXId(discoverPacket.XId())
offerPacket.SetFlags(discoverPacket.Flags())
offerPacket.SetCHAddr(discoverPacket.CHAddr())
offerPacket.SetGIAddr(discoverPacket.GIAddr())
offerPacket.SetSecs(discoverPacket.Secs())
//53
offerPacket.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.Offer)})
//54
offerPacket.AddOption(dhcp4.OptionServerIdentifier, s.ip.To4())
//51
offerPacket.AddOption(dhcp4.OptionIPAddressLeaseTime, dhcp4.OptionsLeaseTime(s.leaseDuration))
//Other options go in requested order...
discoverPacketOptions := discoverPacket.ParseOptions()
ourOptions := make(dhcp4.Options)
//1
ourOptions[dhcp4.OptionSubnetMask] = s.subnetMask.To4()
//3
ourOptions[dhcp4.OptionRouter] = s.defaultGateway.To4()
//6
ourOptions[dhcp4.OptionDomainNameServer] = dhcp4.JoinIPs(s.dnsServers)
if discoverPacketOptions[dhcp4.OptionParameterRequestList] != nil {
//Loop through the requested options and if we have them add them.
for _, optionCode := range discoverPacketOptions[dhcp4.OptionParameterRequestList] {
if !bytes.Equal(ourOptions[dhcp4.OptionCode(optionCode)], []byte{}) {
offerPacket.AddOption(dhcp4.OptionCode(optionCode), ourOptions[dhcp4.OptionCode(optionCode)])
delete(ourOptions, dhcp4.OptionCode(optionCode))
}
}
}
//Add all the options not requested.
for optionCode, optionValue := range ourOptions {
offerPacket.AddOption(optionCode, optionValue)
}
return offerPacket
}
/*
* Create DHCP Acknowledgement
*/
func (s *Server) AcknowledgementPacket(requestPacket dhcp4.Packet) dhcp4.Packet {
acknowledgementPacket := dhcp4.NewPacket(dhcp4.BootReply)
acknowledgementPacket.SetXId(requestPacket.XId())
acknowledgementPacket.SetFlags(requestPacket.Flags())
acknowledgementPacket.SetGIAddr(requestPacket.GIAddr())
acknowledgementPacket.SetCHAddr(requestPacket.CHAddr())
acknowledgementPacket.SetSecs(requestPacket.Secs())
acknowledgementPacket.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.ACK)})
acknowledgementPacket.AddOption(dhcp4.OptionSubnetMask, s.subnetMask.To4())
acknowledgementPacket.AddOption(dhcp4.OptionRouter, s.defaultGateway.To4())
acknowledgementPacket.AddOption(dhcp4.OptionDomainNameServer, dhcp4.JoinIPs(s.dnsServers))
acknowledgementPacket.AddOption(dhcp4.OptionServerIdentifier, s.ip.To4())
return acknowledgementPacket
}
/*
* Create DHCP Decline
*/
func (s *Server) DeclinePacket(requestPacket dhcp4.Packet) dhcp4.Packet {
declinePacket := dhcp4.NewPacket(dhcp4.BootReply)
declinePacket.SetXId(requestPacket.XId())
declinePacket.SetFlags(requestPacket.Flags())
declinePacket.SetGIAddr(requestPacket.GIAddr())
declinePacket.SetCHAddr(requestPacket.CHAddr())
declinePacket.SetSecs(requestPacket.Secs())
declinePacket.AddOption(dhcp4.OptionDHCPMessageType, []byte{byte(dhcp4.NAK)})
declinePacket.AddOption(dhcp4.OptionSubnetMask, s.subnetMask.To4())
declinePacket.AddOption(dhcp4.OptionRouter, s.defaultGateway.To4())
declinePacket.AddOption(dhcp4.OptionDomainNameServer, dhcp4.JoinIPs(s.dnsServers))
declinePacket.AddOption(dhcp4.OptionServerIdentifier, s.ip.To4())
return declinePacket
}
/*
* Get Lease tries to work out the best lease for the packet supplied.
* Taking into account all Requested IP, Exisitng MACAddresses and Free leases.
*/
func (s *Server) GetLease(packet dhcp4.Packet) (found bool, lease leasepool.Lease, err error) {
packetOptions := packet.ParseOptions()
clientID := getClientID(packetOptions)
//Requested an IP
if (len(packetOptions) > 0) &&
packetOptions[dhcp4.OptionRequestedIPAddress] != nil &&
!net.IP(packetOptions[dhcp4.OptionRequestedIPAddress]).Equal(net.IP{}) {
//An IP Has Been Requested Let's Try and Get that One.
found, lease, err = s.leasePool.GetLease(net.IP(packetOptions[dhcp4.OptionRequestedIPAddress]))
if err != nil {
return
}
if found {
//If lease is free, return it to client. If it is not
//free match against the MAC address and client
//identifier.
if lease.Status == leasepool.Free {
//Lease Is Free you Can Have it.
return
}
if bytes.Equal(lease.MACAddress, packet.CHAddr()) &&
bytes.Equal(lease.ClientID, clientID) {
//Lease isn't free but it's yours
return
}
}
}
//Ok Even if you requested an IP you can't have it.
found, lease, err = s.leasePool.GetLeaseForClient(packet.CHAddr(), clientID)
if found || err != nil {
return
}
//Just get the next free lease if you can.
found, lease, err = s.leasePool.GetNextFreeLease()
return
}
/*
* Shutdown The Server Gracefully
*/
func (s *Server) Shutdown() {
atomic.StoreUint32(&s.shutdown, 1)
s.connection.Close()
}
func (s *Server) shouldShutdown() bool {
return atomic.LoadUint32(&s.shutdown) == 1
}
/*
* Garbage Collection
* Run Garbage Collection On Your Leases To Free Expired Leases.
*/
func (s *Server) GC() error {
leases, err := s.leasePool.GetLeases()
if err != nil {
return err
}
for i := range leases {
if leases[i].Status != leasepool.Free {
//Lease Is Not Free
if time.Now().After(leases[i].Expiry) {
//Lease has expired.
leases[i].Status = leasepool.Free
updated, err := s.leasePool.UpdateLease(leases[i])
if err != nil {
log.Printf("Warning: Error trying to Free Lease %s \"%v\"\n", leases[i].IP.To4().String(), err)
}
if !updated {
log.Printf("Warning: Unable to Free Lease %s\n", leases[i].IP.To4().String())
}
continue
}
}
}
return nil
}

10
vendor/github.com/insomniacslk/dhcp/CONTRIBUTORS.md generated vendored Normal file
View File

@ -0,0 +1,10 @@
## Contributors
* Andrea Barberio (main author)
* Pablo Mazzini (tons of fixes and new options)
* Sean Karlage (BSDP package, and of tons of improvements to the DHCPv4 package)
* Owen Mooney (several option fixes and modifiers)
* Mikolaj Walczak (asynchronous DHCPv6 client)
* Chris Koch (tons of improvements in DHCPv4 and DHCPv6 internals and interface)
* Akshay Navale, Brandon Bennett and Chris Gorham (ZTPv6 and ZTPv4 packages)
* Anatole Denis (tons of fixes and new options)

29
vendor/github.com/insomniacslk/dhcp/LICENSE generated vendored Normal file
View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2018, Andrea Barberio
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -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)
}

View File

@ -0,0 +1,6 @@
package dhcpv4
const (
ServerPort = 67
ClientPort = 68
)

856
vendor/github.com/insomniacslk/dhcp/dhcpv4/dhcpv4.go generated vendored Normal file
View File

@ -0,0 +1,856 @@
// Package dhcpv4 provides encoding and decoding of DHCPv4 packets and options.
//
// Example Usage:
//
// p, err := dhcpv4.New(
// dhcpv4.WithClientIP(net.IP{192, 168, 0, 1}),
// dhcpv4.WithMessageType(dhcpv4.MessageTypeInform),
// )
// p.UpdateOption(dhcpv4.OptServerIdentifier(net.IP{192, 110, 110, 110}))
//
// // Retrieve the DHCP Message Type option.
// m := p.MessageType()
//
// bytesOnTheWire := p.ToBytes()
// longSummary := p.Summary()
package dhcpv4
import (
"bytes"
"context"
"errors"
"fmt"
"net"
"strings"
"time"
"github.com/insomniacslk/dhcp/iana"
"github.com/insomniacslk/dhcp/rfc1035label"
"github.com/u-root/uio/rand"
"github.com/u-root/uio/uio"
)
const (
// minPacketLen is the minimum DHCP header length.
minPacketLen = 236
// MaxHWAddrLen is the maximum hardware address length of the ClientHWAddr
// (client hardware address) according to RFC 2131, Section 2. This is the
// link-layer destination a server must send responses to.
MaxHWAddrLen = 16
// MaxMessageSize is the maximum size in bytes that a DHCPv4 packet can hold.
MaxMessageSize = 576
// Per RFC 951, the minimum length of a packet is 300 bytes.
bootpMinLen = 300
)
// RandomTimeout is the amount of time to wait until random number generation
// is canceled.
var RandomTimeout = 2 * time.Minute
// magicCookie is the magic 4-byte value at the beginning of the list of options
// in a DHCPv4 packet.
var magicCookie = [4]byte{99, 130, 83, 99}
// DHCPv4 represents a DHCPv4 packet header and options. See the New* functions
// to build DHCPv4 packets.
type DHCPv4 struct {
OpCode OpcodeType
HWType iana.HWType
HopCount uint8
TransactionID TransactionID
NumSeconds uint16
Flags uint16
ClientIPAddr net.IP
YourIPAddr net.IP
ServerIPAddr net.IP
GatewayIPAddr net.IP
ClientHWAddr net.HardwareAddr
ServerHostName string
BootFileName string
Options Options
}
// Modifier defines the signature for functions that can modify DHCPv4
// structures. This is used to simplify packet manipulation
type Modifier func(d *DHCPv4)
// IPv4AddrsForInterface obtains the currently-configured, non-loopback IPv4
// addresses for iface.
func IPv4AddrsForInterface(iface *net.Interface) ([]net.IP, error) {
if iface == nil {
return nil, errors.New("IPv4AddrsForInterface: iface cannot be nil")
}
addrs, err := iface.Addrs()
if err != nil {
return nil, err
}
return GetExternalIPv4Addrs(addrs)
}
// GetExternalIPv4Addrs obtains the currently-configured, non-loopback IPv4
// addresses from `addrs` coming from a particular interface (e.g.
// net.Interface.Addrs).
func GetExternalIPv4Addrs(addrs []net.Addr) ([]net.IP, error) {
var v4addrs []net.IP
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPAddr:
ip = v.IP
case *net.IPNet:
ip = v.IP
}
if ip == nil || ip.IsLoopback() {
continue
}
ip = ip.To4()
if ip == nil {
continue
}
v4addrs = append(v4addrs, ip)
}
return v4addrs, nil
}
// GenerateTransactionID generates a random 32-bits number suitable for use as
// TransactionID
func GenerateTransactionID() (TransactionID, error) {
var xid TransactionID
ctx, cancel := context.WithTimeout(context.Background(), RandomTimeout)
defer cancel()
n, err := rand.ReadContext(ctx, xid[:])
if err != nil {
return xid, fmt.Errorf("could not get random number: %v", err)
}
if n != 4 {
return xid, errors.New("invalid random sequence for transaction ID: smaller than 32 bits")
}
return xid, err
}
// New creates a new DHCPv4 structure and fill it up with default values. It
// won't be a valid DHCPv4 message so you will need to adjust its fields.
// See also NewDiscovery, NewRequest, NewAcknowledge, NewInform and NewRelease.
func New(modifiers ...Modifier) (*DHCPv4, error) {
xid, err := GenerateTransactionID()
if err != nil {
return nil, err
}
d := DHCPv4{
OpCode: OpcodeBootRequest,
HWType: iana.HWTypeEthernet,
ClientHWAddr: make(net.HardwareAddr, 6),
HopCount: 0,
TransactionID: xid,
NumSeconds: 0,
Flags: 0,
ClientIPAddr: net.IPv4zero,
YourIPAddr: net.IPv4zero,
ServerIPAddr: net.IPv4zero,
GatewayIPAddr: net.IPv4zero,
Options: make(Options),
}
for _, mod := range modifiers {
mod(&d)
}
return &d, nil
}
// NewDiscoveryForInterface builds a new DHCPv4 Discovery message, with a default
// Ethernet HW type and the hardware address obtained from the specified
// interface.
func NewDiscoveryForInterface(ifname string, modifiers ...Modifier) (*DHCPv4, error) {
iface, err := net.InterfaceByName(ifname)
if err != nil {
return nil, err
}
return NewDiscovery(iface.HardwareAddr, modifiers...)
}
// NewDiscovery builds a new DHCPv4 Discovery message, with a default Ethernet
// HW type and specified hardware address.
func NewDiscovery(hwaddr net.HardwareAddr, modifiers ...Modifier) (*DHCPv4, error) {
return New(PrependModifiers(modifiers,
WithHwAddr(hwaddr),
WithRequestedOptions(
OptionSubnetMask,
OptionRouter,
OptionDomainName,
OptionDomainNameServer,
),
WithMessageType(MessageTypeDiscover),
)...)
}
// NewInformForInterface builds a new DHCPv4 Informational message with default
// Ethernet HW type and the hardware address obtained from the specified
// interface.
func NewInformForInterface(ifname string, needsBroadcast bool) (*DHCPv4, error) {
// get hw addr
iface, err := net.InterfaceByName(ifname)
if err != nil {
return nil, err
}
// Set Client IP as iface's currently-configured IP.
localIPs, err := IPv4AddrsForInterface(iface)
if err != nil || len(localIPs) == 0 {
return nil, fmt.Errorf("could not get local IPs for iface %s", ifname)
}
pkt, err := NewInform(iface.HardwareAddr, localIPs[0])
if err != nil {
return nil, err
}
if needsBroadcast {
pkt.SetBroadcast()
} else {
pkt.SetUnicast()
}
return pkt, nil
}
// PrependModifiers prepends other to m.
func PrependModifiers(m []Modifier, other ...Modifier) []Modifier {
return append(other, m...)
}
// NewInform builds a new DHCPv4 Informational message with the specified
// hardware address.
func NewInform(hwaddr net.HardwareAddr, localIP net.IP, modifiers ...Modifier) (*DHCPv4, error) {
return New(PrependModifiers(modifiers,
WithHwAddr(hwaddr),
WithMessageType(MessageTypeInform),
WithClientIP(localIP),
)...)
}
// NewRequestFromOffer builds a DHCPv4 request from an offer.
// It assumes the SELECTING state by default, see Section 4.3.2 in RFC 2131 for more details.
func NewRequestFromOffer(offer *DHCPv4, modifiers ...Modifier) (*DHCPv4, error) {
return New(PrependModifiers(modifiers,
WithReply(offer),
WithMessageType(MessageTypeRequest),
WithClientIP(offer.ClientIPAddr),
WithOption(OptRequestedIPAddress(offer.YourIPAddr)),
// This is usually the server IP.
WithOptionCopied(offer, OptionServerIdentifier),
WithRequestedOptions(
OptionSubnetMask,
OptionRouter,
OptionDomainName,
OptionDomainNameServer,
),
)...)
}
// NewRenewFromAck builds a DHCPv4 RENEW-style request from the ACK of a lease. RENEW requests have
// minor changes to their options compared to SELECT requests as specified by RFC 2131, section 4.3.2.
func NewRenewFromAck(ack *DHCPv4, modifiers ...Modifier) (*DHCPv4, error) {
return New(PrependModifiers(modifiers,
WithReply(ack),
WithMessageType(MessageTypeRequest),
// The client IP must be filled in with the IP offered to the client
WithClientIP(ack.YourIPAddr),
// The renewal request must use unicast
WithBroadcast(false),
WithRequestedOptions(
OptionSubnetMask,
OptionRouter,
OptionDomainName,
OptionDomainNameServer,
),
)...)
}
// NewReplyFromRequest builds a DHCPv4 reply from a request.
func NewReplyFromRequest(request *DHCPv4, modifiers ...Modifier) (*DHCPv4, error) {
return New(PrependModifiers(modifiers,
WithReply(request),
WithGatewayIP(request.GatewayIPAddr),
WithOptionCopied(request, OptionRelayAgentInformation),
// RFC 6842 states the Client Identifier option must be copied
// from the request if a client specified it.
WithOptionCopied(request, OptionClientIdentifier),
)...)
}
// NewReleaseFromACK creates a DHCPv4 Release message from ACK.
// default Release message without any Modifer is created as following:
// - option Message Type is Release
// - ClientIP is set to ack.YourIPAddr
// - ClientHWAddr is set to ack.ClientHWAddr
// - Unicast
// - option Server Identifier is set to ack's ServerIdentifier
func NewReleaseFromACK(ack *DHCPv4, modifiers ...Modifier) (*DHCPv4, error) {
return New(PrependModifiers(modifiers,
WithMessageType(MessageTypeRelease),
WithClientIP(ack.YourIPAddr),
WithHwAddr(ack.ClientHWAddr),
WithBroadcast(false),
WithOptionCopied(ack, OptionServerIdentifier),
)...)
}
// FromBytes decodes a DHCPv4 packet from a sequence of bytes, and returns an
// error if the packet is not valid.
func FromBytes(q []byte) (*DHCPv4, error) {
var p DHCPv4
buf := uio.NewBigEndianBuffer(q)
p.OpCode = OpcodeType(buf.Read8())
p.HWType = iana.HWType(buf.Read8())
hwAddrLen := buf.Read8()
p.HopCount = buf.Read8()
buf.ReadBytes(p.TransactionID[:])
p.NumSeconds = buf.Read16()
p.Flags = buf.Read16()
p.ClientIPAddr = net.IP(buf.CopyN(net.IPv4len))
p.YourIPAddr = net.IP(buf.CopyN(net.IPv4len))
p.ServerIPAddr = net.IP(buf.CopyN(net.IPv4len))
p.GatewayIPAddr = net.IP(buf.CopyN(net.IPv4len))
if hwAddrLen > 16 {
hwAddrLen = 16
}
// Always read 16 bytes, but only use hwaddrlen of them.
p.ClientHWAddr = make(net.HardwareAddr, 16)
buf.ReadBytes(p.ClientHWAddr)
p.ClientHWAddr = p.ClientHWAddr[:hwAddrLen]
var sname [64]byte
buf.ReadBytes(sname[:])
length := strings.Index(string(sname[:]), "\x00")
if length == -1 {
length = 64
}
p.ServerHostName = string(sname[:length])
var file [128]byte
buf.ReadBytes(file[:])
length = strings.Index(string(file[:]), "\x00")
if length == -1 {
length = 128
}
p.BootFileName = string(file[:length])
var cookie [4]byte
buf.ReadBytes(cookie[:])
if err := buf.Error(); err != nil {
return nil, err
}
if cookie != magicCookie {
return nil, fmt.Errorf("malformed DHCP packet: got magic cookie %v, want %v", cookie[:], magicCookie[:])
}
p.Options = make(Options)
if err := p.Options.fromBytesCheckEnd(buf.Data(), true); err != nil {
return nil, err
}
return &p, nil
}
// FlagsToString returns a human-readable representation of the flags field.
func (d *DHCPv4) FlagsToString() string {
flags := ""
if d.IsBroadcast() {
flags += "Broadcast"
} else {
flags += "Unicast"
}
if d.Flags&0xfe != 0 {
flags += " (reserved bits not zeroed)"
}
return flags
}
// IsBroadcast indicates whether the packet is a broadcast packet.
func (d *DHCPv4) IsBroadcast() bool {
return d.Flags&0x8000 == 0x8000
}
// SetBroadcast sets the packet to be a broadcast packet.
func (d *DHCPv4) SetBroadcast() {
d.Flags |= 0x8000
}
// IsUnicast indicates whether the packet is a unicast packet.
func (d *DHCPv4) IsUnicast() bool {
return d.Flags&0x8000 == 0
}
// SetUnicast sets the packet to be a unicast packet.
func (d *DHCPv4) SetUnicast() {
d.Flags &= ^uint16(0x8000)
}
// GetOneOption returns the option that matches the given option code.
//
// According to RFC 3396, options that are specified more than once are
// concatenated, and hence this should always just return one option.
func (d *DHCPv4) GetOneOption(code OptionCode) []byte {
return d.Options.Get(code)
}
// DeleteOption deletes an existing option with the given option code.
func (d *DHCPv4) DeleteOption(code OptionCode) {
if d.Options != nil {
d.Options.Del(code)
}
}
// UpdateOption replaces an existing option with the same option code with the
// given one, adding it if not already present.
func (d *DHCPv4) UpdateOption(opt Option) {
if d.Options == nil {
d.Options = make(Options)
}
d.Options.Update(opt)
}
// String implements fmt.Stringer.
func (d *DHCPv4) String() string {
return fmt.Sprintf("DHCPv4(xid=%s hwaddr=%s msg_type=%s, your_ip=%s, server_ip=%s)",
d.TransactionID, d.ClientHWAddr, d.MessageType(), d.YourIPAddr, d.ServerIPAddr)
}
// SummaryWithVendor prints a summary of the packet, interpreting the
// vendor-specific info option using the given parser (can be nil).
func (d *DHCPv4) SummaryWithVendor(vendorDecoder OptionDecoder) string {
ret := fmt.Sprintf(
"DHCPv4 Message\n"+
" opcode: %s\n"+
" hwtype: %s\n"+
" hopcount: %v\n"+
" transaction ID: %s\n"+
" num seconds: %v\n"+
" flags: %v (0x%02x)\n"+
" client IP: %s\n"+
" your IP: %s\n"+
" server IP: %s\n"+
" gateway IP: %s\n"+
" client MAC: %s\n"+
" server hostname: %s\n"+
" bootfile name: %s\n",
d.OpCode,
d.HWType,
d.HopCount,
d.TransactionID,
d.NumSeconds,
d.FlagsToString(),
d.Flags,
d.ClientIPAddr,
d.YourIPAddr,
d.ServerIPAddr,
d.GatewayIPAddr,
d.ClientHWAddr,
d.ServerHostName,
d.BootFileName,
)
ret += " options:\n"
ret += d.Options.Summary(vendorDecoder)
return ret
}
// Summary prints detailed information about the packet.
func (d *DHCPv4) Summary() string {
return d.SummaryWithVendor(nil)
}
// IsOptionRequested returns true if that option is within the requested
// options of the DHCPv4 message.
func (d *DHCPv4) IsOptionRequested(requested OptionCode) bool {
rq := d.ParameterRequestList()
if rq == nil {
// RFC2131§3.5
// Not all clients require initialization of all parameters [...]
// Two techniques are used to reduce the number of parameters transmitted from
// the server to the client. [...] Second, in its initial DHCPDISCOVER or
// DHCPREQUEST message, a client may provide the server with a list of specific
// parameters the client is interested in.
// We interpret this to say that all available parameters should be sent if
// the parameter request list is not sent at all.
return true
}
for _, o := range rq {
if o.Code() == requested.Code() {
return true
}
}
return false
}
// In case somebody forgets to set an IP, just write 0s as default values.
func writeIP(b *uio.Lexer, ip net.IP) {
var zeros [net.IPv4len]byte
if ip == nil {
b.WriteBytes(zeros[:])
} else {
// Converting IP to 4 byte format
ip = ip.To4()
b.WriteBytes(ip[:net.IPv4len])
}
}
// ToBytes writes the packet to binary.
func (d *DHCPv4) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(make([]byte, 0, minPacketLen))
buf.Write8(uint8(d.OpCode))
buf.Write8(uint8(d.HWType))
// HwAddrLen
hlen := uint8(len(d.ClientHWAddr))
buf.Write8(hlen)
buf.Write8(d.HopCount)
buf.WriteBytes(d.TransactionID[:])
buf.Write16(d.NumSeconds)
buf.Write16(d.Flags)
writeIP(buf, d.ClientIPAddr)
writeIP(buf, d.YourIPAddr)
writeIP(buf, d.ServerIPAddr)
writeIP(buf, d.GatewayIPAddr)
copy(buf.WriteN(16), d.ClientHWAddr)
var sname [64]byte
copy(sname[:63], []byte(d.ServerHostName))
buf.WriteBytes(sname[:])
var file [128]byte
copy(file[:127], []byte(d.BootFileName))
buf.WriteBytes(file[:])
// The magic cookie.
buf.WriteBytes(magicCookie[:])
// Write all options.
d.Options.Marshal(buf)
// Finish the options.
buf.Write8(OptionEnd.Code())
// DHCP is based on BOOTP, and BOOTP messages have a minimum length of
// 300 bytes per RFC 951. This not stated explicitly, but if you sum up
// all the bytes in the message layout, you'll get 300 bytes.
//
// Some DHCP servers and relay agents care about this BOOTP legacy B.S.
// and "conveniently" drop messages that are less than 300 bytes long.
if buf.Len() < bootpMinLen {
buf.WriteBytes(bytes.Repeat([]byte{OptionPad.Code()}, bootpMinLen-buf.Len()))
}
return buf.Data()
}
// GetBroadcastAddress returns the DHCPv4 Broadcast Address value in d.
//
// The broadcast address option is described in RFC 2132, Section 5.3.
func (d *DHCPv4) BroadcastAddress() net.IP {
return GetIP(OptionBroadcastAddress, d.Options)
}
// RequestedIPAddress returns the DHCPv4 Requested IP Address value in d.
//
// The requested IP address option is described by RFC 2132, Section 9.1.
func (d *DHCPv4) RequestedIPAddress() net.IP {
return GetIP(OptionRequestedIPAddress, d.Options)
}
// ServerIdentifier returns the DHCPv4 Server Identifier value in d.
//
// The server identifier option is described by RFC 2132, Section 9.7.
func (d *DHCPv4) ServerIdentifier() net.IP {
return GetIP(OptionServerIdentifier, d.Options)
}
// Router parses the DHCPv4 Router option if present.
//
// The Router option is described by RFC 2132, Section 3.5.
func (d *DHCPv4) Router() []net.IP {
return GetIPs(OptionRouter, d.Options)
}
// ClasslessStaticRoute parses the DHCPv4 Classless Static Route option if present.
//
// The Classless Static Route option is described by RFC 3442.
func (d *DHCPv4) ClasslessStaticRoute() []*Route {
v := d.Options.Get(OptionClasslessStaticRoute)
if v == nil {
return nil
}
var routes Routes
if err := routes.FromBytes(v); err != nil {
return nil
}
return routes
}
// NTPServers parses the DHCPv4 NTP Servers option if present.
//
// The NTP servers option is described by RFC 2132, Section 8.3.
func (d *DHCPv4) NTPServers() []net.IP {
return GetIPs(OptionNTPServers, d.Options)
}
// DNS parses the DHCPv4 Domain Name Server option if present.
//
// The DNS server option is described by RFC 2132, Section 3.8.
func (d *DHCPv4) DNS() []net.IP {
return GetIPs(OptionDomainNameServer, d.Options)
}
// DomainName parses the DHCPv4 Domain Name option if present.
//
// The Domain Name option is described by RFC 2132, Section 3.17.
func (d *DHCPv4) DomainName() string {
return GetString(OptionDomainName, d.Options)
}
// HostName parses the DHCPv4 Host Name option if present.
//
// The Host Name option is described by RFC 2132, Section 3.14.
func (d *DHCPv4) HostName() string {
name := GetString(OptionHostName, d.Options)
return strings.TrimRight(name, "\x00")
}
// RootPath parses the DHCPv4 Root Path option if present.
//
// The Root Path option is described by RFC 2132, Section 3.19.
func (d *DHCPv4) RootPath() string {
return GetString(OptionRootPath, d.Options)
}
// BootFileNameOption parses the DHCPv4 Bootfile Name option if present.
//
// The Bootfile Name option is described by RFC 2132, Section 9.5.
func (d *DHCPv4) BootFileNameOption() string {
name := GetString(OptionBootfileName, d.Options)
return strings.TrimRight(name, "\x00")
}
// TFTPServerName parses the DHCPv4 TFTP Server Name option if present.
//
// The TFTP Server Name option is described by RFC 2132, Section 9.4.
func (d *DHCPv4) TFTPServerName() string {
name := GetString(OptionTFTPServerName, d.Options)
return strings.TrimRight(name, "\x00")
}
// ClassIdentifier parses the DHCPv4 Class Identifier option if present.
//
// The Vendor Class Identifier option is described by RFC 2132, Section 9.13.
func (d *DHCPv4) ClassIdentifier() string {
return GetString(OptionClassIdentifier, d.Options)
}
// ClientArch returns the Client System Architecture Type option.
func (d *DHCPv4) ClientArch() []iana.Arch {
v := d.Options.Get(OptionClientSystemArchitectureType)
if v == nil {
return nil
}
var archs iana.Archs
if err := archs.FromBytes(v); err != nil {
return nil
}
return archs
}
// DomainSearch returns the domain search list if present.
//
// The domain search option is described by RFC 3397, Section 2.
func (d *DHCPv4) DomainSearch() *rfc1035label.Labels {
v := d.Options.Get(OptionDNSDomainSearchList)
if v == nil {
return nil
}
labels, err := rfc1035label.FromBytes(v)
if err != nil {
return nil
}
return labels
}
// IPAddressLeaseTime returns the IP address lease time or the given
// default duration if not present.
//
// The IP address lease time option is described by RFC 2132, Section 9.2.
func (d *DHCPv4) IPAddressLeaseTime(def time.Duration) time.Duration {
v := d.Options.Get(OptionIPAddressLeaseTime)
if v == nil {
return def
}
var dur Duration
if err := dur.FromBytes(v); err != nil {
return def
}
return time.Duration(dur)
}
// IPAddressRenewalTime returns the IP address renewal time or the given
// default duration if not present.
//
// The IP address renewal time option is described by RFC 2132, Section 9.11.
func (d *DHCPv4) IPAddressRenewalTime(def time.Duration) time.Duration {
v := d.Options.Get(OptionRenewTimeValue)
if v == nil {
return def
}
var dur Duration
if err := dur.FromBytes(v); err != nil {
return def
}
return time.Duration(dur)
}
// IPAddressRebindingTime returns the IP address rebinding time or the given
// default duration if not present.
//
// The IP address rebinding time option is described by RFC 2132, Section 9.12.
func (d *DHCPv4) IPAddressRebindingTime(def time.Duration) time.Duration {
v := d.Options.Get(OptionRebindingTimeValue)
if v == nil {
return def
}
var dur Duration
if err := dur.FromBytes(v); err != nil {
return def
}
return time.Duration(dur)
}
// IPv6OnlyPreferred returns the V6ONLY_WAIT duration, and a boolean
// indicating whether this option was present.
//
// The IPv6-Only Preferred option is described by RFC 8925, Section 3.1.
func (d *DHCPv4) IPv6OnlyPreferred() (time.Duration, bool) {
v := d.Options.Get(OptionIPv6OnlyPreferred)
if v == nil {
return 0, false
}
var dur Duration
if err := dur.FromBytes(v); err != nil {
return 0, false
}
return time.Duration(dur), true
}
// MaxMessageSize returns the DHCP Maximum Message Size if present.
//
// The Maximum DHCP Message Size option is described by RFC 2132, Section 9.10.
func (d *DHCPv4) MaxMessageSize() (uint16, error) {
return GetUint16(OptionMaximumDHCPMessageSize, d.Options)
}
// AutoConfigure returns the value of the AutoConfigure option, and a
// boolean indicating if it was present.
//
// The AutoConfigure option is described by RFC 2563, Section 2.
func (d *DHCPv4) AutoConfigure() (AutoConfiguration, bool) {
v, err := GetByte(OptionAutoConfigure, d.Options)
return AutoConfiguration(v), err == nil
}
// MessageType returns the DHCPv4 Message Type option.
func (d *DHCPv4) MessageType() MessageType {
v := d.Options.Get(OptionDHCPMessageType)
if v == nil {
return MessageTypeNone
}
var m MessageType
if err := m.FromBytes(v); err != nil {
return MessageTypeNone
}
return m
}
// Message returns the DHCPv4 (Error) Message option.
//
// The message options is described in RFC 2132, Section 9.9.
func (d *DHCPv4) Message() string {
return GetString(OptionMessage, d.Options)
}
// ParameterRequestList returns the DHCPv4 Parameter Request List.
//
// The parameter request list option is described by RFC 2132, Section 9.8.
func (d *DHCPv4) ParameterRequestList() OptionCodeList {
v := d.Options.Get(OptionParameterRequestList)
if v == nil {
return nil
}
var codes OptionCodeList
if err := codes.FromBytes(v); err != nil {
return nil
}
return codes
}
// RelayAgentInfo returns options embedded by the relay agent.
//
// The relay agent info option is described by RFC 3046.
func (d *DHCPv4) RelayAgentInfo() *RelayOptions {
v := d.Options.Get(OptionRelayAgentInformation)
if v == nil {
return nil
}
var relayOptions RelayOptions
if err := relayOptions.FromBytes(v); err != nil {
return nil
}
return &relayOptions
}
// SubnetMask returns a subnet mask option contained if present.
//
// The subnet mask option is described by RFC 2132, Section 3.3.
func (d *DHCPv4) SubnetMask() net.IPMask {
v := d.Options.Get(OptionSubnetMask)
if v == nil {
return nil
}
var im IPMask
if err := im.FromBytes(v); err != nil {
return nil
}
return net.IPMask(im)
}
// UserClass returns the user class if present.
//
// The user class information option is defined by RFC 3004.
func (d *DHCPv4) UserClass() []string {
v := d.Options.Get(OptionUserClassInformation)
if v == nil {
return nil
}
var uc Strings
if err := uc.FromBytes(v); err != nil {
return []string{GetString(OptionUserClassInformation, d.Options)}
}
return uc
}
// VIVC returns the vendor-identifying vendor class option if present.
func (d *DHCPv4) VIVC() VIVCIdentifiers {
v := d.Options.Get(OptionVendorIdentifyingVendorClass)
if v == nil {
return nil
}
var ids VIVCIdentifiers
if err := ids.FromBytes(v); err != nil {
return nil
}
return ids
}

176
vendor/github.com/insomniacslk/dhcp/dhcpv4/modifiers.go generated vendored Normal file
View File

@ -0,0 +1,176 @@
package dhcpv4
import (
"net"
"time"
"github.com/insomniacslk/dhcp/iana"
"github.com/insomniacslk/dhcp/rfc1035label"
)
// WithTransactionID sets the Transaction ID for the DHCPv4 packet
func WithTransactionID(xid TransactionID) Modifier {
return func(d *DHCPv4) {
d.TransactionID = xid
}
}
// WithClientIP sets the Client IP for a DHCPv4 packet.
func WithClientIP(ip net.IP) Modifier {
return func(d *DHCPv4) {
d.ClientIPAddr = ip
}
}
// WithYourIP sets the Your IP for a DHCPv4 packet.
func WithYourIP(ip net.IP) Modifier {
return func(d *DHCPv4) {
d.YourIPAddr = ip
}
}
// WithServerIP sets the Server IP for a DHCPv4 packet.
func WithServerIP(ip net.IP) Modifier {
return func(d *DHCPv4) {
d.ServerIPAddr = ip
}
}
// WithGatewayIP sets the Gateway IP for the DHCPv4 packet.
func WithGatewayIP(ip net.IP) Modifier {
return func(d *DHCPv4) {
d.GatewayIPAddr = ip
}
}
// WithOptionCopied copies the value of option opt from request.
func WithOptionCopied(request *DHCPv4, opt OptionCode) Modifier {
return func(d *DHCPv4) {
if val := request.Options.Get(opt); val != nil {
d.UpdateOption(OptGeneric(opt, val))
}
}
}
// WithReply fills in opcode, hwtype, xid, clienthwaddr, and flags from the given packet.
func WithReply(request *DHCPv4) Modifier {
return func(d *DHCPv4) {
if request.OpCode == OpcodeBootRequest {
d.OpCode = OpcodeBootReply
} else {
d.OpCode = OpcodeBootRequest
}
d.HWType = request.HWType
d.TransactionID = request.TransactionID
d.ClientHWAddr = request.ClientHWAddr
d.Flags = request.Flags
}
}
// WithHWType sets the Hardware Type for a DHCPv4 packet.
func WithHWType(hwt iana.HWType) Modifier {
return func(d *DHCPv4) {
d.HWType = hwt
}
}
// WithBroadcast sets the packet to be broadcast or unicast
func WithBroadcast(broadcast bool) Modifier {
return func(d *DHCPv4) {
if broadcast {
d.SetBroadcast()
} else {
d.SetUnicast()
}
}
}
// WithHwAddr sets the hardware address for a packet
func WithHwAddr(hwaddr net.HardwareAddr) Modifier {
return func(d *DHCPv4) {
d.ClientHWAddr = hwaddr
}
}
// WithOption appends a DHCPv4 option provided by the user
func WithOption(opt Option) Modifier {
return func(d *DHCPv4) {
d.UpdateOption(opt)
}
}
// WithoutOption removes the DHCPv4 option with the given code
func WithoutOption(code OptionCode) Modifier {
return func(d *DHCPv4) {
d.DeleteOption(code)
}
}
// WithUserClass adds a user class option to the packet.
// The rfc parameter allows you to specify if the userclass should be
// rfc compliant or not. More details in issue #113
func WithUserClass(uc string, rfc bool) Modifier {
// TODO let the user specify multiple user classes
return func(d *DHCPv4) {
if rfc {
d.UpdateOption(OptRFC3004UserClass([]string{uc}))
} else {
d.UpdateOption(OptUserClass(uc))
}
}
}
// WithNetboot adds bootfile URL and bootfile param options to a DHCPv4 packet.
func WithNetboot(d *DHCPv4) {
WithRequestedOptions(OptionTFTPServerName, OptionBootfileName)(d)
}
// WithMessageType adds the DHCPv4 message type m to a packet.
func WithMessageType(m MessageType) Modifier {
return WithOption(OptMessageType(m))
}
// WithRequestedOptions adds requested options to the packet.
func WithRequestedOptions(optionCodes ...OptionCode) Modifier {
return func(d *DHCPv4) {
cl := d.ParameterRequestList()
cl.Add(optionCodes...)
d.UpdateOption(OptParameterRequestList(cl...))
}
}
// WithRelay adds parameters required for DHCPv4 to be relayed by the relay
// server with given ip
func WithRelay(ip net.IP) Modifier {
return func(d *DHCPv4) {
d.SetUnicast()
d.GatewayIPAddr = ip
d.HopCount++
}
}
// WithNetmask adds or updates an OptSubnetMask
func WithNetmask(mask net.IPMask) Modifier {
return WithOption(OptSubnetMask(mask))
}
// WithLeaseTime adds or updates an OptIPAddressLeaseTime
func WithLeaseTime(leaseTime uint32) Modifier {
return WithOption(OptIPAddressLeaseTime(time.Duration(leaseTime) * time.Second))
}
// WithIPv6OnlyPreferred adds or updates an OptIPv6OnlyPreferred
func WithIPv6OnlyPreferred(v6OnlyWait uint32) Modifier {
return WithOption(OptIPv6OnlyPreferred(time.Duration(v6OnlyWait) * time.Second))
}
// WithDomainSearchList adds or updates an OptionDomainSearch
func WithDomainSearchList(searchList ...string) Modifier {
return WithOption(OptDomainSearch(&rfc1035label.Labels{
Labels: searchList,
}))
}
func WithGeneric(code OptionCode, value []byte) Modifier {
return WithOption(OptGeneric(code, value))
}

View File

@ -0,0 +1,669 @@
// Copyright 2018 the u-root Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.12
// Package nclient4 is a small, minimum-functionality client for DHCPv4.
//
// It only supports the 4-way DHCPv4 Discover-Offer-Request-Ack handshake as
// well as the Request-Ack renewal process.
package nclient4
import (
"bytes"
"context"
"errors"
"fmt"
"log"
"net"
"os"
"sync"
"sync/atomic"
"time"
"github.com/insomniacslk/dhcp/dhcpv4"
)
const (
defaultBufferCap = 5
// DefaultTimeout is the default value for read-timeout if option WithTimeout is not set
DefaultTimeout = 5 * time.Second
// DefaultRetries is amount of retries will be done if no answer was received within read-timeout amount of time
DefaultRetries = 3
// MaxMessageSize is the value to be used for DHCP option "MaxMessageSize".
MaxMessageSize = 1500
// ClientPort is the port that DHCP clients listen on.
ClientPort = 68
// ServerPort is the port that DHCP servers and relay agents listen on.
ServerPort = 67
)
var (
// DefaultServers is the address of all link-local DHCP servers and
// relay agents.
DefaultServers = &net.UDPAddr{
IP: net.IPv4bcast,
Port: ServerPort,
}
)
var (
// ErrNoResponse is returned when no response packet is received.
ErrNoResponse = errors.New("no matching response packet received")
// ErrNoConn is returned when NewWithConn is called with nil-value as conn.
ErrNoConn = errors.New("conn is nil")
// ErrNoIfaceHWAddr is returned when NewWithConn is called with nil-value as ifaceHWAddr
ErrNoIfaceHWAddr = errors.New("ifaceHWAddr is nil")
)
// pendingCh is a channel associated with a pending TransactionID.
type pendingCh struct {
// SendAndRead closes done to indicate that it wishes for no more
// messages for this particular XID.
done <-chan struct{}
// ch is used by the receive loop to distribute DHCP messages.
ch chan<- *dhcpv4.DHCPv4
}
// Logger is a handler which will be used to output logging messages
type Logger interface {
// PrintMessage print _all_ DHCP messages
PrintMessage(prefix string, message *dhcpv4.DHCPv4)
// Printf is use to print the rest debugging information
Printf(format string, v ...interface{})
}
// EmptyLogger prints nothing
type EmptyLogger struct{}
// Printf is just a dummy function that does nothing
func (e EmptyLogger) Printf(format string, v ...interface{}) {}
// PrintMessage is just a dummy function that does nothing
func (e EmptyLogger) PrintMessage(prefix string, message *dhcpv4.DHCPv4) {}
// Printfer is used for actual output of the logger. For example *log.Logger is a Printfer.
type Printfer interface {
// Printf is the function for logging output. Arguments are handled in the manner of fmt.Printf.
Printf(format string, v ...interface{})
}
// ShortSummaryLogger is a wrapper for Printfer to implement interface Logger.
// DHCP messages are printed in the short format.
type ShortSummaryLogger struct {
// Printfer is used for actual output of the logger
Printfer
}
// Printf prints a log message as-is via predefined Printfer
func (s ShortSummaryLogger) Printf(format string, v ...interface{}) {
s.Printfer.Printf(format, v...)
}
// PrintMessage prints a DHCP message in the short format via predefined Printfer
func (s ShortSummaryLogger) PrintMessage(prefix string, message *dhcpv4.DHCPv4) {
s.Printf("%s: %s", prefix, message)
}
// DebugLogger is a wrapper for Printfer to implement interface Logger.
// DHCP messages are printed in the long format.
type DebugLogger struct {
// Printfer is used for actual output of the logger
Printfer
}
// Printf prints a log message as-is via predefined Printfer
func (d DebugLogger) Printf(format string, v ...interface{}) {
d.Printfer.Printf(format, v...)
}
// PrintMessage prints a DHCP message in the long format via predefined Printfer
func (d DebugLogger) PrintMessage(prefix string, message *dhcpv4.DHCPv4) {
d.Printf("%s: %s", prefix, message.Summary())
}
// Client is an IPv4 DHCP client.
type Client struct {
ifaceHWAddr net.HardwareAddr
conn net.PacketConn
timeout time.Duration
retry int
logger Logger
// bufferCap is the channel capacity for each TransactionID.
bufferCap int
// serverAddr is the UDP address to send all packets to.
//
// This may be an actual broadcast address, or a unicast address.
serverAddr *net.UDPAddr
// closed is an atomic bool set to 1 when done is closed.
closed uint32
// done is closed to unblock the receive loop.
done chan struct{}
// wg protects any spawned goroutines, namely the receiveLoop.
wg sync.WaitGroup
pendingMu sync.Mutex
// pending stores the distribution channels for each pending
// TransactionID. receiveLoop uses this map to determine which channel
// to send a new DHCP message to.
pending map[dhcpv4.TransactionID]*pendingCh
}
// New returns a client usable with an unconfigured interface.
func New(iface string, opts ...ClientOpt) (*Client, error) {
return new(iface, nil, nil, opts...)
}
// NewWithConn creates a new DHCP client that sends and receives packets on the
// given interface.
func NewWithConn(conn net.PacketConn, ifaceHWAddr net.HardwareAddr, opts ...ClientOpt) (*Client, error) {
return new(``, conn, ifaceHWAddr, opts...)
}
func new(iface string, conn net.PacketConn, ifaceHWAddr net.HardwareAddr, opts ...ClientOpt) (*Client, error) {
c := &Client{
ifaceHWAddr: ifaceHWAddr,
timeout: DefaultTimeout,
retry: DefaultRetries,
serverAddr: DefaultServers,
bufferCap: defaultBufferCap,
conn: conn,
logger: EmptyLogger{},
done: make(chan struct{}),
pending: make(map[dhcpv4.TransactionID]*pendingCh),
}
for _, opt := range opts {
err := opt(c)
if err != nil {
return nil, fmt.Errorf("unable to apply option: %w", err)
}
}
if c.ifaceHWAddr == nil {
if iface == `` {
return nil, ErrNoIfaceHWAddr
}
i, err := net.InterfaceByName(iface)
if err != nil {
return nil, fmt.Errorf("unable to get interface information: %w", err)
}
c.ifaceHWAddr = i.HardwareAddr
}
if c.conn == nil {
var err error
if iface == `` {
return nil, ErrNoConn
}
c.conn, err = NewRawUDPConn(iface, ClientPort) // broadcast
if err != nil {
return nil, fmt.Errorf("unable to open a broadcasting socket: %w", err)
}
}
c.wg.Add(1)
go c.receiveLoop()
return c, nil
}
// Close closes the underlying connection.
func (c *Client) Close() error {
// Make sure not to close done twice.
if !atomic.CompareAndSwapUint32(&c.closed, 0, 1) {
return nil
}
err := c.conn.Close()
// Closing c.done sets off a chain reaction:
//
// Any SendAndRead unblocks trying to receive more messages, which
// means rem() gets called.
//
// rem() should be unblocking receiveLoop if it is blocked.
//
// receiveLoop should then exit gracefully.
close(c.done)
// Wait for receiveLoop to stop.
c.wg.Wait()
return err
}
func (c *Client) isClosed() bool {
return atomic.LoadUint32(&c.closed) != 0
}
func (c *Client) receiveLoop() {
defer c.wg.Done()
for {
// TODO: Clients can send a "max packet size" option in their
// packets, IIRC. Choose a reasonable size and set it.
b := make([]byte, MaxMessageSize)
n, _, err := c.conn.ReadFrom(b)
if err != nil {
if !c.isClosed() {
c.logger.Printf("error reading from UDP connection: %v", err)
}
return
}
msg, err := dhcpv4.FromBytes(b[:n])
if err != nil {
// Not a valid DHCP packet; keep listening.
continue
}
if msg.OpCode != dhcpv4.OpcodeBootReply {
// Not a response message.
continue
}
// This is a somewhat non-standard check, by the looks
// of RFC 2131. It should work as long as the DHCP
// server is spec-compliant for the HWAddr field.
if c.ifaceHWAddr != nil && !bytes.Equal(c.ifaceHWAddr, msg.ClientHWAddr) {
// Not for us.
continue
}
c.pendingMu.Lock()
p, ok := c.pending[msg.TransactionID]
if ok {
select {
case <-p.done:
close(p.ch)
delete(c.pending, msg.TransactionID)
// This send may block.
case p.ch <- msg:
}
}
c.pendingMu.Unlock()
}
}
// ClientOpt is a function that configures the Client.
type ClientOpt func(c *Client) error
// WithTimeout configures the retransmission timeout.
//
// Default is 5 seconds.
func WithTimeout(d time.Duration) ClientOpt {
return func(c *Client) (err error) {
c.timeout = d
return
}
}
// WithSummaryLogger logs one-line DHCPv4 message summaries when sent & received.
func WithSummaryLogger() ClientOpt {
return func(c *Client) (err error) {
c.logger = ShortSummaryLogger{
Printfer: log.New(os.Stderr, "[dhcpv4] ", log.LstdFlags),
}
return
}
}
// WithDebugLogger logs multi-line full DHCPv4 messages when sent & received.
func WithDebugLogger() ClientOpt {
return func(c *Client) (err error) {
c.logger = DebugLogger{
Printfer: log.New(os.Stderr, "[dhcpv4] ", log.LstdFlags),
}
return
}
}
// WithLogger set the logger (see interface Logger).
func WithLogger(newLogger Logger) ClientOpt {
return func(c *Client) (err error) {
c.logger = newLogger
return
}
}
// WithUnicast forces client to send messages as unicast frames.
// By default client sends messages as broadcast frames even if server address is defined.
//
// srcAddr is both:
// * The source address of outgoing frames.
// * The address to be listened for incoming frames.
func WithUnicast(srcAddr *net.UDPAddr) ClientOpt {
return func(c *Client) (err error) {
if srcAddr == nil {
srcAddr = &net.UDPAddr{Port: ClientPort}
}
c.conn, err = net.ListenUDP("udp4", srcAddr)
if err != nil {
err = fmt.Errorf("unable to start listening UDP port: %w", err)
}
return
}
}
// WithHWAddr tells to the Client to receive messages destinated to selected
// hardware address
func WithHWAddr(hwAddr net.HardwareAddr) ClientOpt {
return func(c *Client) (err error) {
c.ifaceHWAddr = hwAddr
return
}
}
func withBufferCap(n int) ClientOpt {
return func(c *Client) (err error) {
c.bufferCap = n
return
}
}
// WithRetry configures the number of retransmissions to attempt.
//
// Default is 3.
func WithRetry(r int) ClientOpt {
return func(c *Client) (err error) {
c.retry = r
return
}
}
// WithServerAddr configures the address to send messages to.
func WithServerAddr(n *net.UDPAddr) ClientOpt {
return func(c *Client) (err error) {
c.serverAddr = n
return
}
}
// Matcher matches DHCP packets.
type Matcher func(*dhcpv4.DHCPv4) bool
// IsMessageType returns a matcher that checks for the message types.
func IsMessageType(t dhcpv4.MessageType, tt ...dhcpv4.MessageType) Matcher {
return func(p *dhcpv4.DHCPv4) bool {
if p.MessageType() == t {
return true
}
for _, mt := range tt {
if p.MessageType() == mt {
return true
}
}
return false
}
}
// IsCorrectServer returns a matcher that checks for the correct ServerAddress.
func IsCorrectServer(s net.IP) Matcher {
return func(p *dhcpv4.DHCPv4) bool {
return p.ServerIdentifier().Equal(s)
}
}
// IsAll returns a matcher that checks for all given matchers to be true.
func IsAll(ms ...Matcher) Matcher {
return func(p *dhcpv4.DHCPv4) bool {
for _, m := range ms {
if !m(p) {
return false
}
}
return true
}
}
// RemoteAddr is the default DHCP server address this client sends messages to.
func (c *Client) RemoteAddr() *net.UDPAddr {
// Make a copy so the caller cannot modify the address once the client
// is running.
cop := *c.serverAddr
return &cop
}
// InterfaceAddr returns the MAC address of the client's interface.
func (c *Client) InterfaceAddr() net.HardwareAddr {
b := make(net.HardwareAddr, len(c.ifaceHWAddr))
copy(b, c.ifaceHWAddr)
return b
}
// DiscoverOffer sends a DHCPDiscover message and returns the first valid offer
// received.
func (c *Client) DiscoverOffer(ctx context.Context, modifiers ...dhcpv4.Modifier) (offer *dhcpv4.DHCPv4, err error) {
// RFC 2131, Section 4.4.1, Table 5 details what a DISCOVER packet should
// contain.
discover, err := dhcpv4.NewDiscovery(c.ifaceHWAddr, dhcpv4.PrependModifiers(modifiers,
dhcpv4.WithOption(dhcpv4.OptMaxMessageSize(MaxMessageSize)))...)
if err != nil {
return nil, fmt.Errorf("unable to create a discovery request: %w", err)
}
offer, err = c.SendAndRead(ctx, c.serverAddr, discover, IsMessageType(dhcpv4.MessageTypeOffer))
if err != nil {
return nil, fmt.Errorf("got an error while the discovery request: %w", err)
}
return offer, nil
}
// Request completes the 4-way Discover-Offer-Request-Ack handshake.
//
// Note that modifiers will be applied *both* to Discover and Request packets.
func (c *Client) Request(ctx context.Context, modifiers ...dhcpv4.Modifier) (lease *Lease, err error) {
offer, err := c.DiscoverOffer(ctx, modifiers...)
if err != nil {
err = fmt.Errorf("unable to receive an offer: %w", err)
return
}
return c.RequestFromOffer(ctx, offer, modifiers...)
}
// Inform sends an INFORM request using the given local IP.
// Returns the ACK response from the server on success.
func (c *Client) Inform(ctx context.Context, localIP net.IP, modifiers ...dhcpv4.Modifier) (*dhcpv4.DHCPv4, error) {
request, err := dhcpv4.NewInform(c.ifaceHWAddr, localIP, modifiers...)
if err != nil {
return nil, err
}
// DHCP clients must not fill in the server identifier in an INFORM request as per RFC 2131 Section 4.4.1 Table 5,
// however, they may still unicast the request to the target server if the address is known (c.serverAddr), as per
// Section 4.4.3. The server must then respond with an ACK, as per Section 4.3.5.
response, err := c.SendAndRead(ctx, c.serverAddr, request, IsMessageType(dhcpv4.MessageTypeAck))
if err != nil {
return nil, fmt.Errorf("got an error while processing the request: %w", err)
}
return response, nil
}
// ErrNak is returned if a DHCP server rejected our Request.
type ErrNak struct {
Offer *dhcpv4.DHCPv4
Nak *dhcpv4.DHCPv4
}
// Error implements error.Error.
func (e *ErrNak) Error() string {
if msg := e.Nak.Message(); len(msg) > 0 {
return fmt.Sprintf("server rejected request with Nak (msg: %s)", msg)
}
return "server rejected request with Nak"
}
// RequestFromOffer sends a Request message and waits for an response.
// It assumes the SELECTING state by default, see Section 4.3.2 in RFC 2131 for more details.
func (c *Client) RequestFromOffer(ctx context.Context, offer *dhcpv4.DHCPv4, modifiers ...dhcpv4.Modifier) (*Lease, error) {
// TODO(chrisko): should this be unicast to the server?
request, err := dhcpv4.NewRequestFromOffer(offer, dhcpv4.PrependModifiers(modifiers,
dhcpv4.WithOption(dhcpv4.OptMaxMessageSize(MaxMessageSize)))...)
if err != nil {
return nil, fmt.Errorf("unable to create a request: %w", err)
}
// Servers are supposed to only respond to Requests containing their server identifier,
// but sometimes non-compliant servers respond anyway.
// Clients are not required to validate this field, but servers are required to
// include the server identifier in their Offer per RFC 2131 Section 4.3.1 Table 3.
response, err := c.SendAndRead(ctx, c.serverAddr, request, IsAll(
IsCorrectServer(offer.ServerIdentifier()),
IsMessageType(dhcpv4.MessageTypeAck, dhcpv4.MessageTypeNak)))
if err != nil {
return nil, fmt.Errorf("got an error while processing the request: %w", err)
}
if response.MessageType() == dhcpv4.MessageTypeNak {
return nil, &ErrNak{
Offer: offer,
Nak: response,
}
}
lease := &Lease{}
lease.ACK = response
lease.Offer = offer
lease.CreationTime = time.Now()
return lease, nil
}
// ErrTransactionIDInUse is returned if there were an attempt to send a message
// with the same TransactionID as we are already waiting an answer for.
type ErrTransactionIDInUse struct {
// TransactionID is the transaction ID of the message which the error is related to.
TransactionID dhcpv4.TransactionID
}
// Error is just the method to comply interface "error".
func (err *ErrTransactionIDInUse) Error() string {
return fmt.Sprintf("transaction ID %s already in use", err.TransactionID)
}
// send sends p to destination and returns a response channel.
//
// Responses will be matched by transaction ID and ClientHWAddr.
//
// The returned lambda function must be called after all desired responses have
// been received in order to return the Transaction ID to the usable pool.
func (c *Client) send(dest *net.UDPAddr, msg *dhcpv4.DHCPv4) (resp <-chan *dhcpv4.DHCPv4, cancel func(), err error) {
c.pendingMu.Lock()
if _, ok := c.pending[msg.TransactionID]; ok {
c.pendingMu.Unlock()
return nil, nil, &ErrTransactionIDInUse{msg.TransactionID}
}
ch := make(chan *dhcpv4.DHCPv4, c.bufferCap)
done := make(chan struct{})
c.pending[msg.TransactionID] = &pendingCh{done: done, ch: ch}
c.pendingMu.Unlock()
cancel = func() {
// Why can't we just close ch here?
//
// Because receiveLoop may potentially be blocked trying to
// send on ch. We gotta unblock it first, and then we can take
// the lock and remove the XID from the pending transaction
// map.
close(done)
c.pendingMu.Lock()
if p, ok := c.pending[msg.TransactionID]; ok {
close(p.ch)
delete(c.pending, msg.TransactionID)
}
c.pendingMu.Unlock()
}
if _, err := c.conn.WriteTo(msg.ToBytes(), dest); err != nil {
cancel()
return nil, nil, fmt.Errorf("error writing packet to connection: %w", err)
}
return ch, cancel, nil
}
// This error should never be visible to users.
// It is used only to increase the timeout in retryFn.
var errDeadlineExceeded = errors.New("INTERNAL ERROR: deadline exceeded")
// SendAndRead sends a packet p to a destination dest and waits for the first
// response matching `match` as well as its Transaction ID and ClientHWAddr.
//
// If match is nil, the first packet matching the Transaction ID and
// ClientHWAddr is returned.
func (c *Client) SendAndRead(ctx context.Context, dest *net.UDPAddr, p *dhcpv4.DHCPv4, match Matcher) (*dhcpv4.DHCPv4, error) {
var response *dhcpv4.DHCPv4
err := c.retryFn(func(timeout time.Duration) error {
ch, rem, err := c.send(dest, p)
if err != nil {
return err
}
c.logger.PrintMessage("sent message", p)
defer rem()
for {
select {
case <-c.done:
return ErrNoResponse
case <-time.After(timeout):
return errDeadlineExceeded
case <-ctx.Done():
return ctx.Err()
case packet := <-ch:
if match == nil || match(packet) {
c.logger.PrintMessage("received message", packet)
response = packet
return nil
}
}
}
})
if err == errDeadlineExceeded {
return nil, ErrNoResponse
}
if err != nil {
return nil, err
}
return response, nil
}
func (c *Client) retryFn(fn func(timeout time.Duration) error) error {
timeout := c.timeout
// Each retry takes the amount of timeout at worst.
for i := 0; i < c.retry || c.retry < 0; i++ { // TODO: why is this called "retry" if this is "tries" ("retries"+1)?
switch err := fn(timeout); err {
case nil:
// Got it!
return nil
case errDeadlineExceeded:
// Double timeout, then retry.
timeout *= 2
default:
return err
}
}
return errDeadlineExceeded
}

View File

@ -0,0 +1,154 @@
// Copyright 2018 the u-root Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.12 && (darwin || freebsd || linux || netbsd || openbsd || dragonfly)
// +build go1.12
// +build darwin freebsd linux netbsd openbsd dragonfly
package nclient4
import (
"errors"
"io"
"net"
"github.com/mdlayher/packet"
"github.com/u-root/uio/uio"
"golang.org/x/sys/unix"
)
var (
// BroadcastMac is the broadcast MAC address.
//
// Any UDP packet sent to this address is broadcast on the subnet.
BroadcastMac = net.HardwareAddr([]byte{255, 255, 255, 255, 255, 255})
)
var (
// ErrUDPAddrIsRequired is an error used when a passed argument is not of type "*net.UDPAddr".
ErrUDPAddrIsRequired = errors.New("must supply UDPAddr")
)
// NewRawUDPConn returns a UDP connection bound to the interface and port
// given based on a raw packet socket. All packets are broadcasted.
//
// The interface can be completely unconfigured.
func NewRawUDPConn(iface string, port int) (net.PacketConn, error) {
ifc, err := net.InterfaceByName(iface)
if err != nil {
return nil, err
}
rawConn, err := packet.Listen(ifc, packet.Datagram, unix.ETH_P_IP, nil)
if err != nil {
return nil, err
}
return NewBroadcastUDPConn(rawConn, &net.UDPAddr{Port: port}), nil
}
// BroadcastRawUDPConn uses a raw socket to send UDP packets to the broadcast
// MAC address.
type BroadcastRawUDPConn struct {
// PacketConn is a raw DGRAM socket.
net.PacketConn
// boundAddr is the address this RawUDPConn is "bound" to.
//
// Calls to ReadFrom will only return packets destined to this address.
boundAddr *net.UDPAddr
}
// NewBroadcastUDPConn returns a PacketConn that marshals and unmarshals UDP
// packets, sending them to the broadcast MAC at on rawPacketConn.
//
// Calls to ReadFrom will only return packets destined to boundAddr.
func NewBroadcastUDPConn(rawPacketConn net.PacketConn, boundAddr *net.UDPAddr) net.PacketConn {
return &BroadcastRawUDPConn{
PacketConn: rawPacketConn,
boundAddr: boundAddr,
}
}
func udpMatch(addr *net.UDPAddr, bound *net.UDPAddr) bool {
if bound == nil {
return true
}
if bound.IP != nil && !bound.IP.Equal(addr.IP) {
return false
}
return bound.Port == addr.Port
}
// ReadFrom implements net.PacketConn.ReadFrom.
//
// ReadFrom reads raw IP packets and will try to match them against
// upc.boundAddr. Any matching packets are returned via the given buffer.
func (upc *BroadcastRawUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
ipHdrMaxLen := ipv4MaximumHeaderSize
udpHdrLen := udpMinimumSize
for {
pkt := make([]byte, ipHdrMaxLen+udpHdrLen+len(b))
n, _, err := upc.PacketConn.ReadFrom(pkt)
if err != nil {
return 0, nil, err
}
if n == 0 {
return 0, nil, io.EOF
}
pkt = pkt[:n]
buf := uio.NewBigEndianBuffer(pkt)
ipHdr := ipv4(buf.Data())
if !ipHdr.isValid(n) {
continue
}
ipHdr = ipv4(buf.Consume(int(ipHdr.headerLength())))
if ipHdr.transportProtocol() != udpProtocolNumber {
continue
}
if !buf.Has(udpHdrLen) {
continue
}
udpHdr := udp(buf.Consume(udpHdrLen))
addr := &net.UDPAddr{
IP: ipHdr.destinationAddress(),
Port: int(udpHdr.destinationPort()),
}
if !udpMatch(addr, upc.boundAddr) {
continue
}
srcAddr := &net.UDPAddr{
IP: ipHdr.sourceAddress(),
Port: int(udpHdr.sourcePort()),
}
// Extra padding after end of IP packet should be ignored,
// if not dhcp option parsing will fail.
dhcpLen := int(ipHdr.payloadLength()) - udpHdrLen
return copy(b, buf.Consume(dhcpLen)), srcAddr, nil
}
}
// WriteTo implements net.PacketConn.WriteTo and broadcasts all packets at the
// raw socket level.
//
// WriteTo wraps the given packet in the appropriate UDP and IP header before
// sending it on the packet conn.
func (upc *BroadcastRawUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
udpAddr, ok := addr.(*net.UDPAddr)
if !ok {
return 0, ErrUDPAddrIsRequired
}
// Using the boundAddr is not quite right here, but it works.
pkt := udp4pkt(b, udpAddr, upc.boundAddr)
// Broadcasting is not always right, but hell, what the ARP do I know.
return upc.PacketConn.WriteTo(pkt, &packet.Addr{HardwareAddr: BroadcastMac})
}

View File

@ -0,0 +1,360 @@
// Copyright 2018 Google LLC
//
// 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.
//
// This file contains code taken from gVisor.
//go:build go1.12
// +build go1.12
package nclient4
import (
"encoding/binary"
"net"
"github.com/u-root/uio/uio"
)
const (
versIHL = 0
tos = 1
totalLen = 2
id = 4
flagsFO = 6
ttl = 8
protocol = 9
checksumOff = 10
srcAddr = 12
dstAddr = 16
)
// transportProtocolNumber is the number of a transport protocol.
type transportProtocolNumber uint32
// ipv4Fields contains the fields of an IPv4 packet. It is used to describe the
// fields of a packet that needs to be encoded.
type ipv4Fields struct {
// IHL is the "internet header length" field of an IPv4 packet.
IHL uint8
// TOS is the "type of service" field of an IPv4 packet.
TOS uint8
// TotalLength is the "total length" field of an IPv4 packet.
TotalLength uint16
// ID is the "identification" field of an IPv4 packet.
ID uint16
// Flags is the "flags" field of an IPv4 packet.
Flags uint8
// FragmentOffset is the "fragment offset" field of an IPv4 packet.
FragmentOffset uint16
// TTL is the "time to live" field of an IPv4 packet.
TTL uint8
// Protocol is the "protocol" field of an IPv4 packet.
Protocol uint8
// checksum is the "checksum" field of an IPv4 packet.
checksum uint16
// SrcAddr is the "source ip address" of an IPv4 packet.
SrcAddr net.IP
// DstAddr is the "destination ip address" of an IPv4 packet.
DstAddr net.IP
}
// ipv4 represents an ipv4 header stored in a byte array.
// Most of the methods of IPv4 access to the underlying slice without
// checking the boundaries and could panic because of 'index out of range'.
// Always call IsValid() to validate an instance of IPv4 before using other methods.
type ipv4 []byte
const (
// ipv4MinimumSize is the minimum size of a valid IPv4 packet.
ipv4MinimumSize = 20
// ipv4MaximumHeaderSize is the maximum size of an IPv4 header. Given
// that there are only 4 bits to represents the header length in 32-bit
// units, the header cannot exceed 15*4 = 60 bytes.
ipv4MaximumHeaderSize = 60
// ipv4AddressSize is the size, in bytes, of an IPv4 address.
ipv4AddressSize = 4
// IPv4Version is the version of the IPv4 protocol.
ipv4Version = 4
)
// ipVersion returns the version of IP used in the given packet. It returns -1
// if the packet is not large enough to contain the version field.
func ipVersion(b []byte) int {
// Length must be at least offset+length of version field.
if len(b) < versIHL+1 {
return -1
}
return int(b[versIHL] >> ipVersionShift)
}
const (
ipVersionShift = 4
)
// headerLength returns the value of the "header length" field of the ipv4
// header.
func (b ipv4) headerLength() uint8 {
return (b[versIHL] & 0xf) * 4
}
// protocol returns the value of the protocol field of the ipv4 header.
func (b ipv4) protocol() uint8 {
return b[protocol]
}
// sourceAddress returns the "source address" field of the ipv4 header.
func (b ipv4) sourceAddress() net.IP {
return net.IP(b[srcAddr : srcAddr+ipv4AddressSize])
}
// destinationAddress returns the "destination address" field of the ipv4
// header.
func (b ipv4) destinationAddress() net.IP {
return net.IP(b[dstAddr : dstAddr+ipv4AddressSize])
}
// transportProtocol implements Network.transportProtocol.
func (b ipv4) transportProtocol() transportProtocolNumber {
return transportProtocolNumber(b.protocol())
}
// payloadLength returns the length of the payload portion of the ipv4 packet.
func (b ipv4) payloadLength() uint16 {
return b.totalLength() - uint16(b.headerLength())
}
// totalLength returns the "total length" field of the ipv4 header.
func (b ipv4) totalLength() uint16 {
return binary.BigEndian.Uint16(b[totalLen:])
}
// setTotalLength sets the "total length" field of the ipv4 header.
func (b ipv4) setTotalLength(totalLength uint16) {
binary.BigEndian.PutUint16(b[totalLen:], totalLength)
}
// setChecksum sets the checksum field of the ipv4 header.
func (b ipv4) setChecksum(v uint16) {
binary.BigEndian.PutUint16(b[checksumOff:], v)
}
// setFlagsFragmentOffset sets the "flags" and "fragment offset" fields of the
// ipv4 header.
func (b ipv4) setFlagsFragmentOffset(flags uint8, offset uint16) {
v := (uint16(flags) << 13) | (offset >> 3)
binary.BigEndian.PutUint16(b[flagsFO:], v)
}
// calculateChecksum calculates the checksum of the ipv4 header.
func (b ipv4) calculateChecksum() uint16 {
return checksum(b[:b.headerLength()], 0)
}
// encode encodes all the fields of the ipv4 header.
func (b ipv4) encode(i *ipv4Fields) {
b[versIHL] = (4 << 4) | ((i.IHL / 4) & 0xf)
b[tos] = i.TOS
b.setTotalLength(i.TotalLength)
binary.BigEndian.PutUint16(b[id:], i.ID)
b.setFlagsFragmentOffset(i.Flags, i.FragmentOffset)
b[ttl] = i.TTL
b[protocol] = i.Protocol
b.setChecksum(i.checksum)
copy(b[srcAddr:srcAddr+ipv4AddressSize], i.SrcAddr)
copy(b[dstAddr:dstAddr+ipv4AddressSize], i.DstAddr)
}
// isValid performs basic validation on the packet.
func (b ipv4) isValid(pktSize int) bool {
if len(b) < ipv4MinimumSize {
return false
}
hlen := int(b.headerLength())
tlen := int(b.totalLength())
if hlen < ipv4MinimumSize || hlen > tlen || tlen > pktSize {
return false
}
if ipVersion(b) != ipv4Version {
return false
}
return true
}
const (
udpSrcPort = 0
udpDstPort = 2
udpLength = 4
udpchecksum = 6
)
// udpFields contains the fields of a udp packet. It is used to describe the
// fields of a packet that needs to be encoded.
type udpFields struct {
// SrcPort is the "source port" field of a udp packet.
SrcPort uint16
// DstPort is the "destination port" field of a UDP packet.
DstPort uint16
// Length is the "length" field of a UDP packet.
Length uint16
// checksum is the "checksum" field of a UDP packet.
checksum uint16
}
// udp represents a udp header stored in a byte array.
type udp []byte
const (
// udpMinimumSize is the minimum size of a valid udp packet.
udpMinimumSize = 8
// udpProtocolNumber is udp's transport protocol number.
udpProtocolNumber transportProtocolNumber = 17
)
// sourcePort returns the "source port" field of the udp header.
func (b udp) sourcePort() uint16 {
return binary.BigEndian.Uint16(b[udpSrcPort:])
}
// DestinationPort returns the "destination port" field of the udp header.
func (b udp) destinationPort() uint16 {
return binary.BigEndian.Uint16(b[udpDstPort:])
}
// Length returns the "length" field of the udp header.
func (b udp) length() uint16 {
return binary.BigEndian.Uint16(b[udpLength:])
}
// setChecksum sets the "checksum" field of the udp header.
func (b udp) setChecksum(checksum uint16) {
binary.BigEndian.PutUint16(b[udpchecksum:], checksum)
}
// calculateChecksum calculates the checksum of the udp packet, given the total
// length of the packet and the checksum of the network-layer pseudo-header
// (excluding the total length) and the checksum of the payload.
func (b udp) calculateChecksum(partialchecksum uint16, totalLen uint16) uint16 {
// Add the length portion of the checksum to the pseudo-checksum.
tmp := make([]byte, 2)
binary.BigEndian.PutUint16(tmp, totalLen)
xsum := checksum(tmp, partialchecksum)
// Calculate the rest of the checksum.
return checksum(b[:udpMinimumSize], xsum)
}
// encode encodes all the fields of the udp header.
func (b udp) encode(u *udpFields) {
binary.BigEndian.PutUint16(b[udpSrcPort:], u.SrcPort)
binary.BigEndian.PutUint16(b[udpDstPort:], u.DstPort)
binary.BigEndian.PutUint16(b[udpLength:], u.Length)
binary.BigEndian.PutUint16(b[udpchecksum:], u.checksum)
}
func calculateChecksum(buf []byte, initial uint32) uint16 {
v := initial
l := len(buf)
if l&1 != 0 {
l--
v += uint32(buf[l]) << 8
}
for i := 0; i < l; i += 2 {
v += (uint32(buf[i]) << 8) + uint32(buf[i+1])
}
return checksumCombine(uint16(v), uint16(v>>16))
}
// checksum calculates the checksum (as defined in RFC 1071) of the bytes in the
// given byte array.
//
// The initial checksum must have been computed on an even number of bytes.
func checksum(buf []byte, initial uint16) uint16 {
return calculateChecksum(buf, uint32(initial))
}
// checksumCombine combines the two uint16 to form their checksum. This is done
// by adding them and the carry.
//
// Note that checksum a must have been computed on an even number of bytes.
func checksumCombine(a, b uint16) uint16 {
v := uint32(a) + uint32(b)
return uint16(v + v>>16)
}
// pseudoHeaderchecksum calculates the pseudo-header checksum for the
// given destination protocol and network address, ignoring the length
// field. pseudo-headers are needed by transport layers when calculating
// their own checksum.
func pseudoHeaderchecksum(protocol transportProtocolNumber, srcAddr net.IP, dstAddr net.IP) uint16 {
xsum := checksum([]byte(srcAddr), 0)
xsum = checksum([]byte(dstAddr), xsum)
return checksum([]byte{0, uint8(protocol)}, xsum)
}
func udp4pkt(packet []byte, dest *net.UDPAddr, src *net.UDPAddr) []byte {
ipLen := ipv4MinimumSize
udpLen := udpMinimumSize
h := make([]byte, 0, ipLen+udpLen+len(packet))
hdr := uio.NewBigEndianBuffer(h)
ipv4fields := &ipv4Fields{
IHL: ipv4MinimumSize,
TotalLength: uint16(ipLen + udpLen + len(packet)),
TTL: 64, // Per RFC 1700's recommendation for IP time to live
Protocol: uint8(udpProtocolNumber),
SrcAddr: src.IP.To4(),
DstAddr: dest.IP.To4(),
}
ipv4hdr := ipv4(hdr.WriteN(ipLen))
ipv4hdr.encode(ipv4fields)
ipv4hdr.setChecksum(^ipv4hdr.calculateChecksum())
udphdr := udp(hdr.WriteN(udpLen))
udphdr.encode(&udpFields{
SrcPort: uint16(src.Port),
DstPort: uint16(dest.Port),
Length: uint16(udpLen + len(packet)),
})
xsum := checksum(packet, pseudoHeaderchecksum(
ipv4hdr.transportProtocol(), ipv4fields.SrcAddr, ipv4fields.DstAddr))
udphdr.setChecksum(^udphdr.calculateChecksum(xsum, udphdr.length()))
hdr.WriteBytes(packet)
return hdr.Data()
}

View File

@ -0,0 +1,79 @@
// This is lease support for nclient4
package nclient4
import (
"context"
"fmt"
"net"
"time"
"github.com/insomniacslk/dhcp/dhcpv4"
)
// Lease contains a DHCPv4 lease after DORA.
// note: Lease doesn't include binding interface name
type Lease struct {
Offer *dhcpv4.DHCPv4
ACK *dhcpv4.DHCPv4
CreationTime time.Time
}
// Release send DHCPv4 release messsage to server, based on specified lease.
// release is sent as unicast per RFC2131, section 4.4.4.
// Note: some DHCP server requries of using assigned IP address as source IP,
// use nclient4.WithUnicast to create client for such case.
func (c *Client) Release(lease *Lease, modifiers ...dhcpv4.Modifier) error {
if lease == nil {
return fmt.Errorf("lease is nil")
}
req, err := dhcpv4.NewReleaseFromACK(lease.ACK, modifiers...)
if err != nil {
return fmt.Errorf("fail to create release request,%w", err)
}
_, err = c.conn.WriteTo(req.ToBytes(), &net.UDPAddr{IP: lease.ACK.Options.Get(dhcpv4.OptionServerIdentifier), Port: ServerPort})
if err == nil {
c.logger.PrintMessage("sent message:", req)
}
return err
}
// Renew sends a DHCPv4 request to the server to renew the given lease. The renewal information is
// sourced from the initial offer in the lease, and the ACK of the lease is updated to the ACK of
// the latest renewal. This avoids issues with DHCP servers that omit information needed to build a
// completely new lease from their renewal ACK (such as the Windows DHCP Server).
func (c *Client) Renew(ctx context.Context, lease *Lease, modifiers ...dhcpv4.Modifier) (*Lease, error) {
if lease == nil {
return nil, fmt.Errorf("lease is nil")
}
request, err := dhcpv4.NewRenewFromAck(lease.ACK, dhcpv4.PrependModifiers(modifiers,
dhcpv4.WithOption(dhcpv4.OptMaxMessageSize(MaxMessageSize)))...)
if err != nil {
return nil, fmt.Errorf("unable to create a request: %w", err)
}
// Servers are supposed to only respond to Requests containing their server identifier,
// but sometimes non-compliant servers respond anyway.
// Clients are not required to validate this field, but servers are required to
// include the server identifier in their Offer per RFC 2131 Section 4.3.1 Table 3.
response, err := c.SendAndRead(ctx, c.serverAddr, request, IsAll(
IsCorrectServer(lease.Offer.ServerIdentifier()),
IsMessageType(dhcpv4.MessageTypeAck, dhcpv4.MessageTypeNak)))
if err != nil {
return nil, fmt.Errorf("got an error while processing the request: %w", err)
}
if response.MessageType() == dhcpv4.MessageTypeNak {
return nil, &ErrNak{
Offer: lease.Offer,
Nak: response,
}
}
// Return a new lease with the latest ACK and updated creation time
return &Lease{
Offer: lease.Offer,
ACK: response,
CreationTime: time.Now(),
}, nil
}

View File

@ -0,0 +1,61 @@
package dhcpv4
import (
"fmt"
)
// AutoConfiguration implements encoding and decoding functions for a
// byte enumeration as used in RFC 2563, Section 2.
type AutoConfiguration byte
const (
DoNotAutoConfigure AutoConfiguration = 0
AutoConfigure AutoConfiguration = 1
)
var autoConfigureToString = map[AutoConfiguration]string{
DoNotAutoConfigure: "DoNotAutoConfigure",
AutoConfigure: "AutoConfigure",
}
// ToBytes returns a serialized stream of bytes for this option.
func (o AutoConfiguration) ToBytes() []byte {
return []byte{byte(o)}
}
// String returns a human-readable string for this option.
func (o AutoConfiguration) String() string {
s := autoConfigureToString[o]
if s != "" {
return s
}
return fmt.Sprintf("UNKNOWN (%d)", byte(o))
}
// FromBytes parses a a single byte into AutoConfiguration
func (o *AutoConfiguration) FromBytes(data []byte) error {
if len(data) == 1 {
*o = AutoConfiguration(data[0])
return nil
}
return fmt.Errorf("Invalid buffer length (%d)", len(data))
}
// GetByte parses any single-byte option
func GetByte(code OptionCode, o Options) (byte, error) {
data := o.Get(code)
if data == nil {
return 0, fmt.Errorf("option not present")
}
if len(data) != 1 {
return 0, fmt.Errorf("Invalid buffer length (%d)", len(data))
}
return data[0], nil
}
// OptAutoConfigure returns a new AutoConfigure option.
//
// The AutoConfigure option is described by RFC 2563, Section 2.
func OptAutoConfigure(autoconf AutoConfiguration) Option {
return Option{Code: OptionAutoConfigure, Value: autoconf}
}

View File

@ -0,0 +1,56 @@
package dhcpv4
import (
"math"
"time"
"github.com/u-root/uio/uio"
)
// MaxLeaseTime is the maximum lease time that can be encoded.
var MaxLeaseTime = math.MaxUint32 * time.Second
// Duration implements the IP address lease time option described by RFC 2132,
// Section 9.2.
type Duration time.Duration
// FromBytes parses a duration from a byte stream according to RFC 2132, Section 9.2.
func (d *Duration) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
*d = Duration(time.Duration(buf.Read32()) * time.Second)
return buf.FinError()
}
// ToBytes returns a serialized stream of bytes for this option.
func (d Duration) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.Write32(uint32(time.Duration(d) / time.Second))
return buf.Data()
}
// String returns a human-readable string for this option.
func (d Duration) String() string {
return time.Duration(d).String()
}
// OptIPAddressLeaseTime returns a new IP address lease time option.
//
// The IP address lease time option is described by RFC 2132, Section 9.2.
func OptIPAddressLeaseTime(d time.Duration) Option {
return Option{Code: OptionIPAddressLeaseTime, Value: Duration(d)}
}
// The IP address renew time option as described by RFC 2132, Section 9.11.
func OptRenewTimeValue(d time.Duration) Option {
return Option{Code: OptionRenewTimeValue, Value: Duration(d)}
}
// The IP address rebinding time option as described by RFC 2132, Section 9.12.
func OptRebindingTimeValue(d time.Duration) Option {
return Option{Code: OptionRebindingTimeValue, Value: Duration(d)}
}
// The IPv6-Only Preferred option is described by RFC 8925, Section 3.1
func OptIPv6OnlyPreferred(d time.Duration) Option {
return Option{Code: OptionIPv6OnlyPreferred, Value: Duration(d)}
}

View File

@ -0,0 +1,27 @@
package dhcpv4
import (
"fmt"
)
// OptionGeneric is an option that only contains the option code and associated
// data. Every option that does not have a specific implementation will fall
// back to this option.
type OptionGeneric struct {
Data []byte
}
// ToBytes returns a serialized generic option as a slice of bytes.
func (o OptionGeneric) ToBytes() []byte {
return o.Data
}
// String returns a human-readable representation of a generic option.
func (o OptionGeneric) String() string {
return fmt.Sprintf("%v", o.Data)
}
// OptGeneric returns a generic option.
func OptGeneric(code OptionCode, value []byte) Option {
return Option{Code: code, Value: OptionGeneric{value}}
}

View File

@ -0,0 +1,62 @@
package dhcpv4
import (
"net"
"github.com/u-root/uio/uio"
)
// IP implements DHCPv4 IP option marshaling and unmarshaling as described by
// RFC 2132, Sections 5.3, 9.1, 9.7, and others.
type IP net.IP
// FromBytes parses an IP from data in binary form.
func (i *IP) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
*i = IP(buf.CopyN(net.IPv4len))
return buf.FinError()
}
// ToBytes returns a serialized stream of bytes for this option.
func (i IP) ToBytes() []byte {
return []byte(net.IP(i).To4())
}
// String returns a human-readable IP.
func (i IP) String() string {
return net.IP(i).String()
}
// GetIP returns code out of o parsed as an IP.
func GetIP(code OptionCode, o Options) net.IP {
v := o.Get(code)
if v == nil {
return nil
}
var ip IP
if err := ip.FromBytes(v); err != nil {
return nil
}
return net.IP(ip)
}
// OptBroadcastAddress returns a new DHCPv4 Broadcast Address option.
//
// The broadcast address option is described in RFC 2132, Section 5.3.
func OptBroadcastAddress(ip net.IP) Option {
return Option{Code: OptionBroadcastAddress, Value: IP(ip)}
}
// OptRequestedIPAddress returns a new DHCPv4 Requested IP Address option.
//
// The requested IP address option is described by RFC 2132, Section 9.1.
func OptRequestedIPAddress(ip net.IP) Option {
return Option{Code: OptionRequestedIPAddress, Value: IP(ip)}
}
// OptServerIdentifier returns a new DHCPv4 Server Identifier option.
//
// The server identifier option is described by RFC 2132, Section 9.7.
func OptServerIdentifier(ip net.IP) Option {
return Option{Code: OptionServerIdentifier, Value: IP(ip)}
}

View File

@ -0,0 +1,103 @@
package dhcpv4
import (
"fmt"
"net"
"strings"
"github.com/u-root/uio/uio"
)
// IPs are IPv4 addresses from a DHCP packet as used and specified by options
// in RFC 2132, Sections 3.5 through 3.13, 8.2, 8.3, 8.5, 8.6, 8.9, and 8.10.
//
// IPs implements the OptionValue type.
type IPs []net.IP
// FromBytes parses an IPv4 address from a DHCP packet as used and specified by
// options in RFC 2132, Sections 3.5 through 3.13, 8.2, 8.3, 8.5, 8.6, 8.9, and
// 8.10.
func (i *IPs) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
if buf.Len() == 0 {
return fmt.Errorf("IP DHCP options must always list at least one IP")
}
*i = make(IPs, 0, buf.Len()/net.IPv4len)
for buf.Has(net.IPv4len) {
*i = append(*i, net.IP(buf.CopyN(net.IPv4len)))
}
return buf.FinError()
}
// ToBytes marshals IPv4 addresses to a DHCP packet as specified by RFC 2132,
// Section 3.5 et al.
func (i IPs) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
for _, ip := range i {
buf.WriteBytes(ip.To4())
}
return buf.Data()
}
// String returns a human-readable representation of a list of IPs.
func (i IPs) String() string {
s := make([]string, 0, len(i))
for _, ip := range i {
s = append(s, ip.String())
}
return strings.Join(s, ", ")
}
// GetIPs parses a list of IPs from code in o.
func GetIPs(code OptionCode, o Options) []net.IP {
v := o.Get(code)
if v == nil {
return nil
}
var ips IPs
if err := ips.FromBytes(v); err != nil {
return nil
}
return []net.IP(ips)
}
// OptRouter returns a new DHCPv4 Router option.
//
// The Router option is described by RFC 2132, Section 3.5.
func OptRouter(routers ...net.IP) Option {
return Option{
Code: OptionRouter,
Value: IPs(routers),
}
}
// WithRouter updates a packet with the DHCPv4 Router option.
func WithRouter(routers ...net.IP) Modifier {
return WithOption(OptRouter(routers...))
}
// OptNTPServers returns a new DHCPv4 NTP Server option.
//
// The NTP servers option is described by RFC 2132, Section 8.3.
func OptNTPServers(ntpServers ...net.IP) Option {
return Option{
Code: OptionNTPServers,
Value: IPs(ntpServers),
}
}
// OptDNS returns a new DHCPv4 Domain Name Server option.
//
// The DNS server option is described by RFC 2132, Section 3.8.
func OptDNS(servers ...net.IP) Option {
return Option{
Code: OptionDomainNameServer,
Value: IPs(servers),
}
}
// WithDNS modifies a packet with the DHCPv4 Domain Name Server option.
func WithDNS(servers ...net.IP) Modifier {
return WithOption(OptDNS(servers...))
}

View File

@ -0,0 +1,50 @@
package dhcpv4
import (
"fmt"
"github.com/u-root/uio/uio"
)
// Uint16 implements encoding and decoding functions for a uint16 as used in
// RFC 2132, Section 9.10.
type Uint16 uint16
// ToBytes returns a serialized stream of bytes for this option.
func (o Uint16) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
buf.Write16(uint16(o))
return buf.Data()
}
// String returns a human-readable string for this option.
func (o Uint16) String() string {
return fmt.Sprintf("%d", uint16(o))
}
// FromBytes decodes data into o as per RFC 2132, Section 9.10.
func (o *Uint16) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
*o = Uint16(buf.Read16())
return buf.FinError()
}
// GetUint16 parses a uint16 from code in o.
func GetUint16(code OptionCode, o Options) (uint16, error) {
v := o.Get(code)
if v == nil {
return 0, fmt.Errorf("option not present")
}
var u Uint16
if err := u.FromBytes(v); err != nil {
return 0, err
}
return uint16(u), nil
}
// OptMaxMessageSize returns a new DHCP Maximum Message Size option.
//
// The Maximum DHCP Message Size option is described by RFC 2132, Section 9.10.
func OptMaxMessageSize(size uint16) Option {
return Option{Code: OptionMaximumDHCPMessageSize, Value: Uint16(size)}
}

View File

@ -0,0 +1,6 @@
package dhcpv4
// OptMessageType returns a new DHCPv4 Message Type option.
func OptMessageType(m MessageType) Option {
return Option{Code: OptionDHCPMessageType, Value: m}
}

View File

@ -0,0 +1,23 @@
package dhcpv4
import (
"github.com/insomniacslk/dhcp/iana"
"github.com/insomniacslk/dhcp/rfc1035label"
)
// OptDomainSearch returns a new domain search option.
//
// The domain search option is described by RFC 3397, Section 2.
func OptDomainSearch(labels *rfc1035label.Labels) Option {
return Option{Code: OptionDNSDomainSearchList, Value: labels}
}
// OptClientArch returns a new Client System Architecture Type option.
func OptClientArch(archs ...iana.Arch) Option {
return Option{Code: OptionClientSystemArchitectureType, Value: iana.Archs(archs)}
}
// OptClientIdentifier returns a new Client Identifier option.
func OptClientIdentifier(ident []byte) Option {
return OptGeneric(OptionClientIdentifier, ident)
}

View File

@ -0,0 +1,72 @@
package dhcpv4
import (
"sort"
"strings"
"github.com/u-root/uio/uio"
)
// OptionCodeList is a list of DHCP option codes.
type OptionCodeList []OptionCode
// Has returns whether c is in the list.
func (ol OptionCodeList) Has(c OptionCode) bool {
for _, code := range ol {
if code == c {
return true
}
}
return false
}
// Add adds option codes in cs to ol.
func (ol *OptionCodeList) Add(cs ...OptionCode) {
for _, c := range cs {
if !ol.Has(c) {
*ol = append(*ol, c)
}
}
}
func (ol OptionCodeList) sort() {
sort.Slice(ol, func(i, j int) bool { return ol[i].Code() < ol[j].Code() })
}
// String returns a human-readable string for the option names.
func (ol OptionCodeList) String() string {
var names []string
ol.sort()
for _, code := range ol {
names = append(names, code.String())
}
return strings.Join(names, ", ")
}
// ToBytes returns a serialized stream of bytes for this option as defined by
// RFC 2132, Section 9.8.
func (ol OptionCodeList) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
for _, req := range ol {
buf.Write8(req.Code())
}
return buf.Data()
}
// FromBytes parses a byte stream for this option as described by RFC 2132,
// Section 9.8.
func (ol *OptionCodeList) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
*ol = make(OptionCodeList, 0, buf.Len())
for buf.Has(1) {
*ol = append(*ol, optionCode(buf.Read8()))
}
return buf.FinError()
}
// OptParameterRequestList returns a new DHCPv4 Parameter Request List.
//
// The parameter request list option is described by RFC 2132, Section 9.8.
func OptParameterRequestList(codes ...OptionCode) Option {
return Option{Code: OptionParameterRequestList, Value: OptionCodeList(codes)}
}

View File

@ -0,0 +1,100 @@
package dhcpv4
import (
"fmt"
)
// RelayOptions is like Options, but stringifies using the Relay Agent Specific
// option space.
type RelayOptions struct {
Options
}
var relayHumanizer = OptionHumanizer{
ValueHumanizer: func(code OptionCode, data []byte) fmt.Stringer {
var d OptionDecoder
switch code {
case LinkSelectionSubOption, ServerIdentifierOverrideSubOption:
d = &IPs{}
}
if d != nil && d.FromBytes(data) == nil {
return d
}
return raiSubOptionValue{data}
},
CodeHumanizer: func(c uint8) OptionCode {
return raiSubOptionCode(c)
},
}
// String prints the contained options using Relay Agent-specific option code parsing.
func (r RelayOptions) String() string {
return "\n" + r.Options.ToString(relayHumanizer)
}
// FromBytes parses relay agent options from data.
func (r *RelayOptions) FromBytes(data []byte) error {
r.Options = make(Options)
return r.Options.FromBytes(data)
}
// OptRelayAgentInfo returns a new DHCP Relay Agent Info option.
//
// The relay agent info option is described by RFC 3046.
func OptRelayAgentInfo(o ...Option) Option {
return Option{Code: OptionRelayAgentInformation, Value: RelayOptions{OptionsFromList(o...)}}
}
type raiSubOptionValue struct {
val []byte
}
func (rv raiSubOptionValue) String() string {
return fmt.Sprintf("%q (%v)", string(rv.val), rv.val)
}
type raiSubOptionCode uint8
func (o raiSubOptionCode) Code() uint8 {
return uint8(o)
}
func (o raiSubOptionCode) String() string {
if s, ok := raiSubOptionCodeToString[o]; ok {
return s
}
return fmt.Sprintf("unknown (%d)", o)
}
// Option 82 Relay Agention Information Sub Options
const (
AgentCircuitIDSubOption raiSubOptionCode = 1 // RFC 3046
AgentRemoteIDSubOption raiSubOptionCode = 2 // RFC 3046
DOCSISDeviceClassSubOption raiSubOptionCode = 4 // RFC 3256
LinkSelectionSubOption raiSubOptionCode = 5 // RFC 3527
SubscriberIDSubOption raiSubOptionCode = 6 // RFC 3993
RADIUSAttributesSubOption raiSubOptionCode = 7 // RFC 4014
AuthenticationSubOption raiSubOptionCode = 8 // RFC 4030
VendorSpecificInformationSubOption raiSubOptionCode = 9 // RFC 4243
RelayAgentFlagsSubOption raiSubOptionCode = 10 // RFC 5010
ServerIdentifierOverrideSubOption raiSubOptionCode = 11 // RFC 5107
RelaySourcePortSubOption raiSubOptionCode = 19 // RFC 8357
VirtualSubnetSelectionSubOption raiSubOptionCode = 151 // RFC 6607
VirtualSubnetSelectionControlSubOption raiSubOptionCode = 152 // RFC 6607
)
var raiSubOptionCodeToString = map[raiSubOptionCode]string{
AgentCircuitIDSubOption: "Agent Circuit ID Sub-option",
AgentRemoteIDSubOption: "Agent Remote ID Sub-option",
DOCSISDeviceClassSubOption: "DOCSIS Device Class Sub-option",
LinkSelectionSubOption: "Link Selection Sub-option",
SubscriberIDSubOption: "Subscriber ID Sub-option",
RADIUSAttributesSubOption: "RADIUS Attributes Sub-option",
AuthenticationSubOption: "Authentication Sub-option",
VendorSpecificInformationSubOption: "Vendor Specific Sub-option",
RelayAgentFlagsSubOption: "Relay Agent Flags Sub-option",
ServerIdentifierOverrideSubOption: "Server Identifier Override Sub-option",
RelaySourcePortSubOption: "Relay Source Port Sub-option",
VirtualSubnetSelectionSubOption: "Virtual Subnet Selection Sub-option",
VirtualSubnetSelectionControlSubOption: "Virtual Subnet Selection Control Sub-option",
}

View File

@ -0,0 +1,104 @@
package dhcpv4
import (
"fmt"
"net"
"strings"
"github.com/u-root/uio/uio"
)
// Route is a classless static route as per RFC 3442.
type Route struct {
// Dest is the destination network.
Dest *net.IPNet
// Router is the router to use for the given destination network.
Router net.IP
}
// Marshal implements uio.Marshaler.
//
// Format described in RFC 3442:
//
// <size of mask in number of bits>
// <destination address, omitting octets that must be zero per mask>
// <route IP>
func (r Route) Marshal(buf *uio.Lexer) {
ones, _ := r.Dest.Mask.Size()
buf.Write8(uint8(ones))
// Only write the non-zero octets.
dstLen := (ones + 7) / 8
buf.WriteBytes(r.Dest.IP.To4()[:dstLen])
buf.WriteBytes(r.Router.To4())
}
// Unmarshal implements uio.Unmarshaler.
func (r *Route) Unmarshal(buf *uio.Lexer) error {
maskSize := buf.Read8()
if maskSize > 32 {
return fmt.Errorf("invalid mask length %d in route option", maskSize)
}
r.Dest = &net.IPNet{
IP: make([]byte, net.IPv4len),
Mask: net.CIDRMask(int(maskSize), 32),
}
dstLen := (maskSize + 7) / 8
buf.ReadBytes(r.Dest.IP[:dstLen])
r.Router = buf.CopyN(net.IPv4len)
return buf.Error()
}
// String prints the destination network and router IP.
func (r *Route) String() string {
return fmt.Sprintf("route to %s via %s", r.Dest, r.Router)
}
// Routes is a collection of network routes.
type Routes []*Route
// FromBytes parses routes from a set of bytes as described by RFC 3442.
func (r *Routes) FromBytes(p []byte) error {
buf := uio.NewBigEndianBuffer(p)
for buf.Has(1) {
var route Route
if err := route.Unmarshal(buf); err != nil {
return err
}
*r = append(*r, &route)
}
return buf.FinError()
}
// ToBytes marshals a set of routes as described by RFC 3442.
func (r Routes) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
for _, route := range r {
route.Marshal(buf)
}
return buf.Data()
}
// String prints all routes.
func (r Routes) String() string {
s := make([]string, 0, len(r))
for _, route := range r {
s = append(s, route.String())
}
return strings.Join(s, "; ")
}
// OptClasslessStaticRoute returns a new DHCPv4 Classless Static Route
// option.
//
// The Classless Static Route option is described by RFC 3442.
func OptClasslessStaticRoute(routes ...*Route) Option {
return Option{
Code: OptionClasslessStaticRoute,
Value: Routes(routes),
}
}

View File

@ -0,0 +1,84 @@
package dhcpv4
// String represents an option encapsulating a string in IPv4 DHCP.
//
// This representation is shared by multiple options specified by RFC 2132,
// Sections 3.14, 3.16, 3.17, 3.19, and 3.20.
type String string
// ToBytes returns a serialized stream of bytes for this option.
func (o String) ToBytes() []byte {
return []byte(o)
}
// String returns a human-readable string.
func (o String) String() string {
return string(o)
}
// FromBytes parses a serialized stream of bytes into o.
func (o *String) FromBytes(data []byte) error {
*o = String(string(data))
return nil
}
// GetString parses an RFC 2132 string from o[code].
func GetString(code OptionCode, o Options) string {
v := o.Get(code)
if v == nil {
return ""
}
return string(v)
}
// OptDomainName returns a new DHCPv4 Domain Name option.
//
// The Domain Name option is described by RFC 2132, Section 3.17.
func OptDomainName(name string) Option {
return Option{Code: OptionDomainName, Value: String(name)}
}
// OptHostName returns a new DHCPv4 Host Name option.
//
// The Host Name option is described by RFC 2132, Section 3.14.
func OptHostName(name string) Option {
return Option{Code: OptionHostName, Value: String(name)}
}
// OptRootPath returns a new DHCPv4 Root Path option.
//
// The Root Path option is described by RFC 2132, Section 3.19.
func OptRootPath(name string) Option {
return Option{Code: OptionRootPath, Value: String(name)}
}
// OptBootFileName returns a new DHCPv4 Boot File Name option.
//
// The Bootfile Name option is described by RFC 2132, Section 9.5.
func OptBootFileName(name string) Option {
return Option{Code: OptionBootfileName, Value: String(name)}
}
// OptTFTPServerName returns a new DHCPv4 TFTP Server Name option.
//
// The TFTP Server Name option is described by RFC 2132, Section 9.4.
func OptTFTPServerName(name string) Option {
return Option{Code: OptionTFTPServerName, Value: String(name)}
}
// OptClassIdentifier returns a new DHCPv4 Class Identifier option.
//
// The Vendor Class Identifier option is described by RFC 2132, Section 9.13.
func OptClassIdentifier(name string) Option {
return Option{Code: OptionClassIdentifier, Value: String(name)}
}
// OptUserClass returns a new DHCPv4 User Class option.
func OptUserClass(name string) Option {
return Option{Code: OptionUserClassInformation, Value: String(name)}
}
// OptMessage returns a new DHCPv4 (Error) Message option.
func OptMessage(msg string) Option {
return Option{Code: OptionMessage, Value: String(msg)}
}

View File

@ -0,0 +1,55 @@
package dhcpv4
import (
"fmt"
"strings"
"github.com/u-root/uio/uio"
)
// Strings represents an option encapsulating a list of strings in IPv4 DHCP as
// specified in RFC 3004
//
// Strings implements the OptionValue type.
type Strings []string
// FromBytes parses Strings from a DHCP packet as specified by RFC 3004.
func (o *Strings) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
if buf.Len() == 0 {
return fmt.Errorf("Strings DHCP option must always list at least one String")
}
*o = make(Strings, 0)
for buf.Has(1) {
ucLen := buf.Read8()
if ucLen == 0 {
return fmt.Errorf("DHCP Strings must have length greater than 0")
}
*o = append(*o, string(buf.CopyN(int(ucLen))))
}
return buf.FinError()
}
// ToBytes marshals Strings to a DHCP packet as specified by RFC 3004.
func (o Strings) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
for _, uc := range o {
buf.Write8(uint8(len(uc)))
buf.WriteBytes([]byte(uc))
}
return buf.Data()
}
// String returns a human-readable representation of a list of Strings.
func (o Strings) String() string {
return strings.Join(o, ", ")
}
// OptRFC3004UserClass returns a new user class option according to RFC 3004.
func OptRFC3004UserClass(v []string) Option {
return Option{
Code: OptionUserClassInformation,
Value: Strings(v),
}
}

View File

@ -0,0 +1,40 @@
package dhcpv4
import (
"net"
"github.com/u-root/uio/uio"
)
// IPMask represents an option encapsulating the subnet mask.
//
// This option implements the subnet mask option in RFC 2132, Section 3.3.
type IPMask net.IPMask
// ToBytes returns a serialized stream of bytes for this option.
func (im IPMask) ToBytes() []byte {
if len(im) > net.IPv4len {
return im[:net.IPv4len]
}
return im
}
// String returns a human-readable string.
func (im IPMask) String() string {
return net.IPMask(im).String()
}
// FromBytes parses im from data per RFC 2132.
func (im *IPMask) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
*im = IPMask(buf.CopyN(net.IPv4len))
return buf.FinError()
}
// OptSubnetMask returns a new DHCPv4 SubnetMask option per RFC 2132, Section 3.3.
func OptSubnetMask(mask net.IPMask) Option {
return Option{
Code: OptionSubnetMask,
Value: IPMask(mask),
}
}

View File

@ -0,0 +1,65 @@
package dhcpv4
import (
"bytes"
"fmt"
"github.com/insomniacslk/dhcp/iana"
"github.com/u-root/uio/uio"
)
// VIVCIdentifier implements the vendor-identifying vendor class option
// described by RFC 3925.
type VIVCIdentifier struct {
// EntID is the enterprise ID.
EntID iana.EnterpriseID
Data []byte
}
// OptVIVC returns a new vendor-identifying vendor class option.
//
// The option is described by RFC 3925.
func OptVIVC(identifiers ...VIVCIdentifier) Option {
return Option{
Code: OptionVendorIdentifyingVendorClass,
Value: VIVCIdentifiers(identifiers),
}
}
// VIVCIdentifiers implements encoding and decoding methods for a DHCP option
// described in RFC 3925.
type VIVCIdentifiers []VIVCIdentifier
// FromBytes parses data into ids per RFC 3925.
func (ids *VIVCIdentifiers) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
for buf.Has(5) {
entID := iana.EnterpriseID(buf.Read32())
idLen := int(buf.Read8())
*ids = append(*ids, VIVCIdentifier{EntID: entID, Data: buf.CopyN(idLen)})
}
return buf.FinError()
}
// ToBytes returns a serialized stream of bytes for this option.
func (ids VIVCIdentifiers) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
for _, id := range ids {
buf.Write32(uint32(id.EntID))
buf.Write8(uint8(len(id.Data)))
buf.WriteBytes(id.Data)
}
return buf.Data()
}
// String returns a human-readable string for this option.
func (ids VIVCIdentifiers) String() string {
if len(ids) == 0 {
return ""
}
buf := bytes.Buffer{}
for _, id := range ids {
fmt.Fprintf(&buf, " %d:'%s',", id.EntID, id.Data)
}
return buf.String()[1 : buf.Len()-1]
}

380
vendor/github.com/insomniacslk/dhcp/dhcpv4/options.go generated vendored Normal file
View File

@ -0,0 +1,380 @@
package dhcpv4
import (
"errors"
"fmt"
"io"
"math"
"sort"
"strings"
"github.com/insomniacslk/dhcp/iana"
"github.com/insomniacslk/dhcp/rfc1035label"
"github.com/u-root/uio/uio"
)
var (
// ErrShortByteStream is an error that is thrown any time a short byte stream is
// detected during option parsing.
ErrShortByteStream = errors.New("short byte stream")
// ErrZeroLengthByteStream is an error that is thrown any time a zero-length
// byte stream is encountered.
ErrZeroLengthByteStream = errors.New("zero-length byte stream")
)
// OptionValue is an interface that all DHCP v4 options adhere to.
type OptionValue interface {
ToBytes() []byte
String() string
}
// Option is a DHCPv4 option and consists of a 1-byte option code and a value
// stream of bytes.
//
// The value is to be interpreted based on the option code.
type Option struct {
Code OptionCode
Value OptionValue
}
// String returns a human-readable version of this option.
func (o Option) String() string {
v := o.Value.String()
if strings.Contains(v, "\n") {
return fmt.Sprintf("%s:\n%s", o.Code, v)
}
return fmt.Sprintf("%s: %s", o.Code, v)
}
// Options is a collection of options.
type Options map[uint8][]byte
// OptionsFromList adds all given options to an options map.
func OptionsFromList(o ...Option) Options {
opts := make(Options)
for _, opt := range o {
opts.Update(opt)
}
return opts
}
// Get will attempt to get all options that match a DHCPv4 option
// from its OptionCode. If the option was not found it will return an
// empty list.
//
// According to RFC 3396, options that are specified more than once are
// concatenated, and hence this should always just return one option. This
// currently returns a list to be API compatible.
func (o Options) Get(code OptionCode) []byte {
return o[code.Code()]
}
// Has checks whether o has the given opcode.
func (o Options) Has(opcode OptionCode) bool {
_, ok := o[opcode.Code()]
return ok
}
// Del deletes the option matching the option code.
func (o Options) Del(opcode OptionCode) {
delete(o, opcode.Code())
}
// Update updates the existing options with the passed option, adding it
// at the end if not present already
func (o Options) Update(option Option) {
o[option.Code.Code()] = option.Value.ToBytes()
}
// ToBytes makes Options usable as an OptionValue as well.
//
// Used in the case of vendor-specific and relay agent options.
func (o Options) ToBytes() []byte {
return uio.ToBigEndian(o)
}
// FromBytes parses a sequence of bytes until the end and builds a list of
// options from it.
//
// The sequence should not contain the DHCP magic cookie.
//
// Returns an error if any invalid option or length is found.
func (o Options) FromBytes(data []byte) error {
return o.fromBytesCheckEnd(data, false)
}
const (
optPad = 0
optAgentInfo = 82
optEnd = 255
)
// FromBytesCheckEnd parses Options from byte sequences using the
// parsing function that is passed in as a paremeter
func (o Options) fromBytesCheckEnd(data []byte, checkEndOption bool) error {
if len(data) == 0 {
return nil
}
buf := uio.NewBigEndianBuffer(data)
var end bool
for buf.Len() >= 1 {
// 1 byte: option code
// 1 byte: option length n
// n bytes: data
code := buf.Read8()
if code == optPad {
continue
} else if code == optEnd {
end = true
break
}
length := int(buf.Read8())
// N bytes: option data
data := buf.Consume(length)
if data == nil {
return fmt.Errorf("error collecting options: %v", buf.Error())
}
data = data[:length:length]
// RFC 2131, Section 4.1 "Options may appear only once, [...].
// The client concatenates the values of multiple instances of
// the same option into a single parameter list for
// configuration."
//
// See also RFC 3396 for concatenation order and options longer
// than 255 bytes.
o[code] = append(o[code], data...)
}
// If we never read the End option, the sender of this packet screwed
// up.
if !end && checkEndOption {
return io.ErrUnexpectedEOF
}
return nil
}
// sortedKeys returns an ordered slice of option keys from the Options map, for
// use in serializing options to binary.
func (o Options) sortedKeys() []int {
// Send all values for a given key
var codes []int
var hasOptAgentInfo, hasOptEnd bool
for k := range o {
// RFC 3046 section 2.1 states that option 82 SHALL come last (ignoring End).
if k == optAgentInfo {
hasOptAgentInfo = true
continue
}
if k == optEnd {
hasOptEnd = true
continue
}
codes = append(codes, int(k))
}
sort.Ints(codes)
if hasOptAgentInfo {
codes = append(codes, optAgentInfo)
}
if hasOptEnd {
codes = append(codes, optEnd)
}
return codes
}
// Marshal writes options binary representations to b.
func (o Options) Marshal(b *uio.Lexer) {
for _, c := range o.sortedKeys() {
code := uint8(c)
// Even if the End option is in there, don't marshal it until
// the end.
// Don't write padding either, since the options are sorted
// it would always be written first which isn't useful
if code == optEnd || code == optPad {
continue
}
data := o[code]
// Ensure even 0-length options are written out
if len(data) == 0 {
b.Write8(code)
b.Write8(0)
continue
}
// RFC 3396: If more than 256 bytes of data are given, the
// option is simply listed multiple times.
for len(data) > 0 {
// 1 byte: option code
b.Write8(code)
n := len(data)
if n > math.MaxUint8 {
n = math.MaxUint8
}
// 1 byte: option length
b.Write8(uint8(n))
// N bytes: option data
b.WriteBytes(data[:n])
data = data[n:]
}
}
}
// String prints options using DHCP-specified option codes.
func (o Options) String() string {
return o.ToString(dhcpHumanizer)
}
// Summary prints options in human-readable values.
//
// Summary uses vendorParser to interpret the OptionVendorSpecificInformation option.
func (o Options) Summary(vendorDecoder OptionDecoder) string {
return o.ToString(OptionHumanizer{
ValueHumanizer: parserFor(vendorDecoder),
CodeHumanizer: func(c uint8) OptionCode {
return optionCode(c)
},
})
}
// OptionParser gives a human-legible interpretation of data for the given option code.
type OptionParser func(code OptionCode, data []byte) fmt.Stringer
// OptionHumanizer is used to interpret a set of Options for their option code
// name and values.
//
// There should be separate OptionHumanizers for each Option "space": DHCP,
// BSDP, Relay Agent Info, and others.
type OptionHumanizer struct {
ValueHumanizer OptionParser
CodeHumanizer func(code uint8) OptionCode
}
// Stringify returns a human-readable interpretation of the option code and its
// associated data.
func (oh OptionHumanizer) Stringify(code uint8, data []byte) string {
c := oh.CodeHumanizer(code)
val := oh.ValueHumanizer(c, data)
return fmt.Sprintf("%s: %s", c, val)
}
// dhcpHumanizer humanizes the set of DHCP option codes.
var dhcpHumanizer = OptionHumanizer{
ValueHumanizer: parseOption,
CodeHumanizer: func(c uint8) OptionCode {
return optionCode(c)
},
}
// ToString uses parse to parse options into human-readable values.
func (o Options) ToString(humanizer OptionHumanizer) string {
var ret string
for _, c := range o.sortedKeys() {
code := uint8(c)
v := o[code]
optString := humanizer.Stringify(code, v)
// If this option has sub structures, offset them accordingly.
if strings.Contains(optString, "\n") {
optString = strings.Replace(optString, "\n ", "\n ", -1)
}
ret += fmt.Sprintf(" %v\n", optString)
}
return ret
}
func parseOption(code OptionCode, data []byte) fmt.Stringer {
return parserFor(nil)(code, data)
}
func parserFor(vendorParser OptionDecoder) OptionParser {
return func(code OptionCode, data []byte) fmt.Stringer {
return getOption(code, data, vendorParser)
}
}
// OptionDecoder can decode a byte stream into a human-readable option.
type OptionDecoder interface {
fmt.Stringer
FromBytes([]byte) error
}
func getOption(code OptionCode, data []byte, vendorDecoder OptionDecoder) fmt.Stringer {
var d OptionDecoder
switch code {
case OptionRouter, OptionDomainNameServer, OptionNTPServers, OptionServerIdentifier:
d = &IPs{}
case OptionBroadcastAddress, OptionRequestedIPAddress:
d = &IP{}
case OptionClientSystemArchitectureType:
d = &iana.Archs{}
case OptionSubnetMask:
d = &IPMask{}
case OptionDHCPMessageType:
var mt MessageType
d = &mt
case OptionParameterRequestList:
d = &OptionCodeList{}
case OptionHostName, OptionDomainName, OptionRootPath,
OptionClassIdentifier, OptionTFTPServerName, OptionBootfileName,
OptionMessage, OptionReferenceToTZDatabase:
var s String
d = &s
case OptionRelayAgentInformation:
d = &RelayOptions{}
case OptionDNSDomainSearchList:
d = &rfc1035label.Labels{}
case OptionIPAddressLeaseTime, OptionRenewTimeValue,
OptionRebindingTimeValue, OptionIPv6OnlyPreferred, OptionArpCacheTimeout,
OptionTimeOffset:
var dur Duration
d = &dur
case OptionMaximumDHCPMessageSize:
var u Uint16
d = &u
case OptionUserClassInformation:
var s Strings
d = &s
if s.FromBytes(data) != nil {
var s String
d = &s
}
case OptionAutoConfigure:
var a AutoConfiguration
d = &a
case OptionVendorIdentifyingVendorClass:
d = &VIVCIdentifiers{}
case OptionVendorSpecificInformation:
d = vendorDecoder
case OptionClasslessStaticRoute:
d = &Routes{}
}
if d != nil && d.FromBytes(data) == nil {
return d
}
return OptionGeneric{data}
}

467
vendor/github.com/insomniacslk/dhcp/dhcpv4/types.go generated vendored Normal file
View File

@ -0,0 +1,467 @@
package dhcpv4
import (
"fmt"
"github.com/u-root/uio/uio"
)
// values from http://www.networksorcery.com/enp/protocol/dhcp.htm and
// http://www.networksorcery.com/enp/protocol/bootp/options.htm
// TransactionID represents a 4-byte DHCP transaction ID as defined in RFC 951,
// Section 3.
//
// The TransactionID is used to match DHCP replies to their original request.
type TransactionID [4]byte
// String prints a hex transaction ID.
func (xid TransactionID) String() string {
return fmt.Sprintf("0x%x", xid[:])
}
// MessageType represents the possible DHCP message types - DISCOVER, OFFER, etc
type MessageType byte
// DHCP message types
const (
// MessageTypeNone is not a real message type, it is used by certain
// functions to signal that no explicit message type is requested
MessageTypeNone MessageType = 0
MessageTypeDiscover MessageType = 1
MessageTypeOffer MessageType = 2
MessageTypeRequest MessageType = 3
MessageTypeDecline MessageType = 4
MessageTypeAck MessageType = 5
MessageTypeNak MessageType = 6
MessageTypeRelease MessageType = 7
MessageTypeInform MessageType = 8
)
// ToBytes returns the serialized version of this option described by RFC 2132,
// Section 9.6.
func (m MessageType) ToBytes() []byte {
return []byte{byte(m)}
}
// String prints a human-readable message type name.
func (m MessageType) String() string {
if s, ok := messageTypeToString[m]; ok {
return s
}
return fmt.Sprintf("unknown (%d)", byte(m))
}
// FromBytes reads a message type from data as described by RFC 2132, Section
// 9.6.
func (m *MessageType) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
*m = MessageType(buf.Read8())
return buf.FinError()
}
var messageTypeToString = map[MessageType]string{
MessageTypeDiscover: "DISCOVER",
MessageTypeOffer: "OFFER",
MessageTypeRequest: "REQUEST",
MessageTypeDecline: "DECLINE",
MessageTypeAck: "ACK",
MessageTypeNak: "NAK",
MessageTypeRelease: "RELEASE",
MessageTypeInform: "INFORM",
}
// OpcodeType represents a DHCPv4 opcode.
type OpcodeType uint8
// constants that represent valid values for OpcodeType
const (
OpcodeBootRequest OpcodeType = 1
OpcodeBootReply OpcodeType = 2
)
func (o OpcodeType) String() string {
if s, ok := opcodeToString[o]; ok {
return s
}
return fmt.Sprintf("unknown (%d)", uint8(o))
}
var opcodeToString = map[OpcodeType]string{
OpcodeBootRequest: "BootRequest",
OpcodeBootReply: "BootReply",
}
// OptionCode is a single byte representing the code for a given Option.
//
// OptionCode is an interface purely to support different stringers on options
// with the same Code value, as vendor-specific options use option codes that
// have the same value, but mean a different thing.
type OptionCode interface {
// Code is the 1 byte option code for the wire.
Code() uint8
// String returns the option's name.
String() string
}
// optionCode is a DHCP option code.
type optionCode uint8
// Code implements OptionCode.Code.
func (o optionCode) Code() uint8 {
return uint8(o)
}
// String returns an option name.
func (o optionCode) String() string {
if s, ok := optionCodeToString[o]; ok {
return s
}
return fmt.Sprintf("unknown (%d)", uint8(o))
}
// GenericOptionCode is an unnamed option code.
type GenericOptionCode uint8
// Code implements OptionCode.Code.
func (o GenericOptionCode) Code() uint8 {
return uint8(o)
}
// String returns the option's name.
func (o GenericOptionCode) String() string {
return fmt.Sprintf("unknown (%d)", uint8(o))
}
// DHCPv4 Options
const (
OptionPad optionCode = 0
OptionSubnetMask optionCode = 1
OptionTimeOffset optionCode = 2
OptionRouter optionCode = 3
OptionTimeServer optionCode = 4
OptionNameServer optionCode = 5
OptionDomainNameServer optionCode = 6
OptionLogServer optionCode = 7
OptionQuoteServer optionCode = 8
OptionLPRServer optionCode = 9
OptionImpressServer optionCode = 10
OptionResourceLocationServer optionCode = 11
OptionHostName optionCode = 12
OptionBootFileSize optionCode = 13
OptionMeritDumpFile optionCode = 14
OptionDomainName optionCode = 15
OptionSwapServer optionCode = 16
OptionRootPath optionCode = 17
OptionExtensionsPath optionCode = 18
OptionIPForwarding optionCode = 19
OptionNonLocalSourceRouting optionCode = 20
OptionPolicyFilter optionCode = 21
OptionMaximumDatagramAssemblySize optionCode = 22
OptionDefaultIPTTL optionCode = 23
OptionPathMTUAgingTimeout optionCode = 24
OptionPathMTUPlateauTable optionCode = 25
OptionInterfaceMTU optionCode = 26
OptionAllSubnetsAreLocal optionCode = 27
OptionBroadcastAddress optionCode = 28
OptionPerformMaskDiscovery optionCode = 29
OptionMaskSupplier optionCode = 30
OptionPerformRouterDiscovery optionCode = 31
OptionRouterSolicitationAddress optionCode = 32
OptionStaticRoutingTable optionCode = 33
OptionTrailerEncapsulation optionCode = 34
OptionArpCacheTimeout optionCode = 35
OptionEthernetEncapsulation optionCode = 36
OptionDefaulTCPTTL optionCode = 37
OptionTCPKeepaliveInterval optionCode = 38
OptionTCPKeepaliveGarbage optionCode = 39
OptionNetworkInformationServiceDomain optionCode = 40
OptionNetworkInformationServers optionCode = 41
OptionNTPServers optionCode = 42
OptionVendorSpecificInformation optionCode = 43
OptionNetBIOSOverTCPIPNameServer optionCode = 44
OptionNetBIOSOverTCPIPDatagramDistributionServer optionCode = 45
OptionNetBIOSOverTCPIPNodeType optionCode = 46
OptionNetBIOSOverTCPIPScope optionCode = 47
OptionXWindowSystemFontServer optionCode = 48
OptionXWindowSystemDisplayManger optionCode = 49
OptionRequestedIPAddress optionCode = 50
OptionIPAddressLeaseTime optionCode = 51
OptionOptionOverload optionCode = 52
OptionDHCPMessageType optionCode = 53
OptionServerIdentifier optionCode = 54
OptionParameterRequestList optionCode = 55
OptionMessage optionCode = 56
OptionMaximumDHCPMessageSize optionCode = 57
OptionRenewTimeValue optionCode = 58
OptionRebindingTimeValue optionCode = 59
OptionClassIdentifier optionCode = 60
OptionClientIdentifier optionCode = 61
OptionNetWareIPDomainName optionCode = 62
OptionNetWareIPInformation optionCode = 63
OptionNetworkInformationServicePlusDomain optionCode = 64
OptionNetworkInformationServicePlusServers optionCode = 65
OptionTFTPServerName optionCode = 66
OptionBootfileName optionCode = 67
OptionMobileIPHomeAgent optionCode = 68
OptionSimpleMailTransportProtocolServer optionCode = 69
OptionPostOfficeProtocolServer optionCode = 70
OptionNetworkNewsTransportProtocolServer optionCode = 71
OptionDefaultWorldWideWebServer optionCode = 72
OptionDefaultFingerServer optionCode = 73
OptionDefaultInternetRelayChatServer optionCode = 74
OptionStreetTalkServer optionCode = 75
OptionStreetTalkDirectoryAssistanceServer optionCode = 76
OptionUserClassInformation optionCode = 77
OptionSLPDirectoryAgent optionCode = 78
OptionSLPServiceScope optionCode = 79
OptionRapidCommit optionCode = 80
OptionFQDN optionCode = 81
OptionRelayAgentInformation optionCode = 82
OptionInternetStorageNameService optionCode = 83
// Option 84 returned in RFC 3679
OptionNDSServers optionCode = 85
OptionNDSTreeName optionCode = 86
OptionNDSContext optionCode = 87
OptionBCMCSControllerDomainNameList optionCode = 88
OptionBCMCSControllerIPv4AddressList optionCode = 89
OptionAuthentication optionCode = 90
OptionClientLastTransactionTime optionCode = 91
OptionAssociatedIP optionCode = 92
OptionClientSystemArchitectureType optionCode = 93
OptionClientNetworkInterfaceIdentifier optionCode = 94
OptionLDAP optionCode = 95
// Option 96 returned in RFC 3679
OptionClientMachineIdentifier optionCode = 97
OptionOpenGroupUserAuthentication optionCode = 98
OptionGeoConfCivic optionCode = 99
OptionIEEE10031TZString optionCode = 100
OptionReferenceToTZDatabase optionCode = 101
// Option 108 returned in RFC 8925
OptionIPv6OnlyPreferred optionCode = 108
// Options 102-111 returned in RFC 3679
OptionNetInfoParentServerAddress optionCode = 112
OptionNetInfoParentServerTag optionCode = 113
OptionURL optionCode = 114
// Option 115 returned in RFC 3679
OptionAutoConfigure optionCode = 116
OptionNameServiceSearch optionCode = 117
OptionSubnetSelection optionCode = 118
OptionDNSDomainSearchList optionCode = 119
OptionSIPServers optionCode = 120
OptionClasslessStaticRoute optionCode = 121
OptionCCC optionCode = 122
OptionGeoConf optionCode = 123
OptionVendorIdentifyingVendorClass optionCode = 124
OptionVendorIdentifyingVendorSpecific optionCode = 125
// Options 126-127 returned in RFC 3679
OptionTFTPServerIPAddress optionCode = 128
OptionCallServerIPAddress optionCode = 129
OptionDiscriminationString optionCode = 130
OptionRemoteStatisticsServerIPAddress optionCode = 131
Option8021PVLANID optionCode = 132
Option8021QL2Priority optionCode = 133
OptionDiffservCodePoint optionCode = 134
OptionHTTPProxyForPhoneSpecificApplications optionCode = 135
OptionPANAAuthenticationAgent optionCode = 136
OptionLoSTServer optionCode = 137
OptionCAPWAPAccessControllerAddresses optionCode = 138
OptionOPTIONIPv4AddressMoS optionCode = 139
OptionOPTIONIPv4FQDNMoS optionCode = 140
OptionSIPUAConfigurationServiceDomains optionCode = 141
OptionOPTIONIPv4AddressANDSF optionCode = 142
OptionOPTIONIPv6AddressANDSF optionCode = 143
// Options 144-149 returned in RFC 3679
OptionTFTPServerAddress optionCode = 150
OptionStatusCode optionCode = 151
OptionBaseTime optionCode = 152
OptionStartTimeOfState optionCode = 153
OptionQueryStartTime optionCode = 154
OptionQueryEndTime optionCode = 155
OptionDHCPState optionCode = 156
OptionDataSource optionCode = 157
// Options 158-174 returned in RFC 3679
OptionEtherboot optionCode = 175
OptionIPTelephone optionCode = 176
OptionEtherbootPacketCableAndCableHome optionCode = 177
// Options 178-207 returned in RFC 3679
OptionPXELinuxMagicString optionCode = 208
OptionPXELinuxConfigFile optionCode = 209
OptionPXELinuxPathPrefix optionCode = 210
OptionPXELinuxRebootTime optionCode = 211
OptionOPTION6RD optionCode = 212
OptionOPTIONv4AccessDomain optionCode = 213
// Options 214-219 returned in RFC 3679
OptionSubnetAllocation optionCode = 220
OptionVirtualSubnetAllocation optionCode = 221
// Options 222-223 returned in RFC 3679
// Options 224-254 are reserved for private use
OptionEnd optionCode = 255
)
var optionCodeToString = map[OptionCode]string{
OptionPad: "Pad",
OptionSubnetMask: "Subnet Mask",
OptionTimeOffset: "Time Offset",
OptionRouter: "Router",
OptionTimeServer: "Time Server",
OptionNameServer: "Name Server",
OptionDomainNameServer: "Domain Name Server",
OptionLogServer: "Log Server",
OptionQuoteServer: "Quote Server",
OptionLPRServer: "LPR Server",
OptionImpressServer: "Impress Server",
OptionResourceLocationServer: "Resource Location Server",
OptionHostName: "Host Name",
OptionBootFileSize: "Boot File Size",
OptionMeritDumpFile: "Merit Dump File",
OptionDomainName: "Domain Name",
OptionSwapServer: "Swap Server",
OptionRootPath: "Root Path",
OptionExtensionsPath: "Extensions Path",
OptionIPForwarding: "IP Forwarding enable/disable",
OptionNonLocalSourceRouting: "Non-local Source Routing enable/disable",
OptionPolicyFilter: "Policy Filter",
OptionMaximumDatagramAssemblySize: "Maximum Datagram Reassembly Size",
OptionDefaultIPTTL: "Default IP Time-to-live",
OptionPathMTUAgingTimeout: "Path MTU Aging Timeout",
OptionPathMTUPlateauTable: "Path MTU Plateau Table",
OptionInterfaceMTU: "Interface MTU",
OptionAllSubnetsAreLocal: "All Subnets Are Local",
OptionBroadcastAddress: "Broadcast Address",
OptionPerformMaskDiscovery: "Perform Mask Discovery",
OptionMaskSupplier: "Mask Supplier",
OptionPerformRouterDiscovery: "Perform Router Discovery",
OptionRouterSolicitationAddress: "Router Solicitation Address",
OptionStaticRoutingTable: "Static Routing Table",
OptionTrailerEncapsulation: "Trailer Encapsulation",
OptionArpCacheTimeout: "ARP Cache Timeout",
OptionEthernetEncapsulation: "Ethernet Encapsulation",
OptionDefaulTCPTTL: "Default TCP TTL",
OptionTCPKeepaliveInterval: "TCP Keepalive Interval",
OptionTCPKeepaliveGarbage: "TCP Keepalive Garbage",
OptionNetworkInformationServiceDomain: "Network Information Service Domain",
OptionNetworkInformationServers: "Network Information Servers",
OptionNTPServers: "NTP Servers",
OptionVendorSpecificInformation: "Vendor Specific Information",
OptionNetBIOSOverTCPIPNameServer: "NetBIOS over TCP/IP Name Server",
OptionNetBIOSOverTCPIPDatagramDistributionServer: "NetBIOS over TCP/IP Datagram Distribution Server",
OptionNetBIOSOverTCPIPNodeType: "NetBIOS over TCP/IP Node Type",
OptionNetBIOSOverTCPIPScope: "NetBIOS over TCP/IP Scope",
OptionXWindowSystemFontServer: "X Window System Font Server",
OptionXWindowSystemDisplayManger: "X Window System Display Manager",
OptionRequestedIPAddress: "Requested IP Address",
OptionIPAddressLeaseTime: "IP Addresses Lease Time",
OptionOptionOverload: "Option Overload",
OptionDHCPMessageType: "DHCP Message Type",
OptionServerIdentifier: "Server Identifier",
OptionParameterRequestList: "Parameter Request List",
OptionMessage: "Message",
OptionMaximumDHCPMessageSize: "Maximum DHCP Message Size",
OptionRenewTimeValue: "Renew Time Value",
OptionRebindingTimeValue: "Rebinding Time Value",
OptionClassIdentifier: "Class Identifier",
OptionClientIdentifier: "Client identifier",
OptionNetWareIPDomainName: "NetWare/IP Domain Name",
OptionNetWareIPInformation: "NetWare/IP Information",
OptionNetworkInformationServicePlusDomain: "Network Information Service+ Domain",
OptionNetworkInformationServicePlusServers: "Network Information Service+ Servers",
OptionTFTPServerName: "TFTP Server Name",
OptionBootfileName: "Bootfile Name",
OptionMobileIPHomeAgent: "Mobile IP Home Agent",
OptionSimpleMailTransportProtocolServer: "SMTP Server",
OptionPostOfficeProtocolServer: "POP Server",
OptionNetworkNewsTransportProtocolServer: "NNTP Server",
OptionDefaultWorldWideWebServer: "Default WWW Server",
OptionDefaultFingerServer: "Default Finger Server",
OptionDefaultInternetRelayChatServer: "Default IRC Server",
OptionStreetTalkServer: "StreetTalk Server",
OptionStreetTalkDirectoryAssistanceServer: "StreetTalk Directory Assistance Server",
OptionUserClassInformation: "User Class Information",
OptionSLPDirectoryAgent: "SLP DIrectory Agent",
OptionSLPServiceScope: "SLP Service Scope",
OptionRapidCommit: "Rapid Commit",
OptionFQDN: "FQDN",
OptionRelayAgentInformation: "Relay Agent Information",
OptionInternetStorageNameService: "Internet Storage Name Service",
// Option 84 returned in RFC 3679
OptionNDSServers: "NDS Servers",
OptionNDSTreeName: "NDS Tree Name",
OptionNDSContext: "NDS Context",
OptionBCMCSControllerDomainNameList: "BCMCS Controller Domain Name List",
OptionBCMCSControllerIPv4AddressList: "BCMCS Controller IPv4 Address List",
OptionAuthentication: "Authentication",
OptionClientLastTransactionTime: "Client Last Transaction Time",
OptionAssociatedIP: "Associated IP",
OptionClientSystemArchitectureType: "Client System Architecture Type",
OptionClientNetworkInterfaceIdentifier: "Client Network Interface Identifier",
OptionLDAP: "LDAP",
// Option 96 returned in RFC 3679
OptionClientMachineIdentifier: "Client Machine Identifier",
OptionOpenGroupUserAuthentication: "OpenGroup's User Authentication",
OptionGeoConfCivic: "GEOCONF_CIVIC",
OptionIEEE10031TZString: "IEEE 1003.1 TZ String",
OptionReferenceToTZDatabase: "Reference to the TZ Database",
// Option 108 returned in RFC 8925
OptionIPv6OnlyPreferred: "IPv6-Only Preferred",
// Options 102-111 returned in RFC 3679
OptionNetInfoParentServerAddress: "NetInfo Parent Server Address",
OptionNetInfoParentServerTag: "NetInfo Parent Server Tag",
OptionURL: "URL",
// Option 115 returned in RFC 3679
OptionAutoConfigure: "Auto-Configure",
OptionNameServiceSearch: "Name Service Search",
OptionSubnetSelection: "Subnet Selection",
OptionDNSDomainSearchList: "DNS Domain Search List",
OptionSIPServers: "SIP Servers",
OptionClasslessStaticRoute: "Classless Static Route",
OptionCCC: "CCC, CableLabs Client Configuration",
OptionGeoConf: "GeoConf",
OptionVendorIdentifyingVendorClass: "Vendor-Identifying Vendor Class",
OptionVendorIdentifyingVendorSpecific: "Vendor-Identifying Vendor-Specific",
// Options 126-127 returned in RFC 3679
OptionTFTPServerIPAddress: "TFTP Server IP Address",
OptionCallServerIPAddress: "Call Server IP Address",
OptionDiscriminationString: "Discrimination String",
OptionRemoteStatisticsServerIPAddress: "RemoteStatistics Server IP Address",
Option8021PVLANID: "802.1P VLAN ID",
Option8021QL2Priority: "802.1Q L2 Priority",
OptionDiffservCodePoint: "Diffserv Code Point",
OptionHTTPProxyForPhoneSpecificApplications: "HTTP Proxy for phone-specific applications",
OptionPANAAuthenticationAgent: "PANA Authentication Agent",
OptionLoSTServer: "LoST Server",
OptionCAPWAPAccessControllerAddresses: "CAPWAP Access Controller Addresses",
OptionOPTIONIPv4AddressMoS: "OPTION-IPv4_Address-MoS",
OptionOPTIONIPv4FQDNMoS: "OPTION-IPv4_FQDN-MoS",
OptionSIPUAConfigurationServiceDomains: "SIP UA Configuration Service Domains",
OptionOPTIONIPv4AddressANDSF: "OPTION-IPv4_Address-ANDSF",
OptionOPTIONIPv6AddressANDSF: "OPTION-IPv6_Address-ANDSF",
// Options 144-149 returned in RFC 3679
OptionTFTPServerAddress: "TFTP Server Address",
OptionStatusCode: "Status Code",
OptionBaseTime: "Base Time",
OptionStartTimeOfState: "Start Time of State",
OptionQueryStartTime: "Query Start Time",
OptionQueryEndTime: "Query End Time",
OptionDHCPState: "DHCP Staet",
OptionDataSource: "Data Source",
// Options 158-174 returned in RFC 3679
OptionEtherboot: "Etherboot",
OptionIPTelephone: "IP Telephone",
OptionEtherbootPacketCableAndCableHome: "Etherboot / PacketCable and CableHome",
// Options 178-207 returned in RFC 3679
OptionPXELinuxMagicString: "PXELinux Magic String",
OptionPXELinuxConfigFile: "PXELinux Config File",
OptionPXELinuxPathPrefix: "PXELinux Path Prefix",
OptionPXELinuxRebootTime: "PXELinux Reboot Time",
OptionOPTION6RD: "OPTION_6RD",
OptionOPTIONv4AccessDomain: "OPTION_V4_ACCESS_DOMAIN",
// Options 214-219 returned in RFC 3679
OptionSubnetAllocation: "Subnet Allocation",
OptionVirtualSubnetAllocation: "Virtual Subnet Selection",
// Options 222-223 returned in RFC 3679
// Options 224-254 are reserved for private use
OptionEnd: "End",
}

148
vendor/github.com/insomniacslk/dhcp/iana/archtype.go generated vendored Normal file
View File

@ -0,0 +1,148 @@
package iana
import (
"fmt"
"strings"
"github.com/u-root/uio/uio"
)
// Arch encodes an architecture type per RFC 4578, Section 2.1.
type Arch uint16
// See RFC 4578, 5970, and http://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#processor-architecture
const (
INTEL_X86PC Arch = 0
NEC_PC98 Arch = 1
EFI_ITANIUM Arch = 2
DEC_ALPHA Arch = 3
ARC_X86 Arch = 4
INTEL_LEAN_CLIENT Arch = 5
EFI_IA32 Arch = 6
EFI_X86_64 Arch = 7
EFI_XSCALE Arch = 8
EFI_BC Arch = 9
EFI_ARM32 Arch = 10
EFI_ARM64 Arch = 11
PPC_OPEN_FIRMWARE Arch = 12
PPC_EPAPR Arch = 13
PPC_OPAL Arch = 14
EFI_X86_HTTP Arch = 15
EFI_X86_64_HTTP Arch = 16
EFI_BC_HTTP Arch = 17
EFI_ARM32_HTTP Arch = 18
EFI_ARM64_HTTP Arch = 19
INTEL_X86PC_HTTP Arch = 20
UBOOT_ARM32 Arch = 21
UBOOT_ARM64 Arch = 22
UBOOT_ARM32_HTTP Arch = 23
UBOOT_ARM64_HTTP Arch = 24
EFI_RISCV32 Arch = 25
EFI_RISCV32_HTTP Arch = 26
EFI_RISCV64 Arch = 27
EFI_RISCV64_HTTP Arch = 28
EFI_RISCV128 Arch = 29
EFI_RISCV128_HTTP Arch = 30
S390_BASIC Arch = 31
S390_EXTENDED Arch = 32
EFI_MIPS32 Arch = 33
EFI_MIPS64 Arch = 34
EFI_SUNWAY32 Arch = 35
EFI_SUNWAY64 Arch = 36
)
// archTypeToStringMap maps an Arch to a mnemonic name
var archTypeToStringMap = map[Arch]string{
INTEL_X86PC: "Intel x86PC",
NEC_PC98: "NEC/PC98",
EFI_ITANIUM: "EFI Itanium",
DEC_ALPHA: "DEC Alpha",
ARC_X86: "Arc x86",
INTEL_LEAN_CLIENT: "Intel Lean Client",
EFI_IA32: "EFI IA32",
EFI_XSCALE: "EFI Xscale",
EFI_X86_64: "EFI x86-64",
EFI_BC: "EFI BC",
EFI_ARM32: "EFI ARM32",
EFI_ARM64: "EFI ARM64",
PPC_OPEN_FIRMWARE: "PowerPC Open Firmware",
PPC_EPAPR: "PowerPC ePAPR",
PPC_OPAL: "POWER OPAL v3",
EFI_X86_HTTP: "EFI x86 boot from HTTP",
EFI_X86_64_HTTP: "EFI x86-64 boot from HTTP",
EFI_BC_HTTP: "EFI BC boot from HTTP",
EFI_ARM32_HTTP: "EFI ARM32 boot from HTTP",
EFI_ARM64_HTTP: "EFI ARM64 boot from HTTP",
INTEL_X86PC_HTTP: "Intel x86PC boot from HTTP",
UBOOT_ARM32: "U-Boot ARM32",
UBOOT_ARM64: "U-Boot ARM64",
UBOOT_ARM32_HTTP: "U-boot ARM32 boot from HTTP",
UBOOT_ARM64_HTTP: "U-Boot ARM64 boot from HTTP",
EFI_RISCV32: "EFI RISC-V 32-bit",
EFI_RISCV32_HTTP: "EFI RISC-V 32-bit boot from HTTP",
EFI_RISCV64: "EFI RISC-V 64-bit",
EFI_RISCV64_HTTP: "EFI RISC-V 64-bit boot from HTTP",
EFI_RISCV128: "EFI RISC-V 128-bit",
EFI_RISCV128_HTTP: "EFI RISC-V 128-bit boot from HTTP",
S390_BASIC: "s390 Basic",
S390_EXTENDED: "s390 Extended",
EFI_MIPS32: "EFI MIPS32",
EFI_MIPS64: "EFI MIPS64",
EFI_SUNWAY32: "EFI Sunway 32-bit",
EFI_SUNWAY64: "EFI Sunway 64-bit",
}
// String returns a mnemonic name for a given architecture type.
func (a Arch) String() string {
if at := archTypeToStringMap[a]; at != "" {
return at
}
return "unknown"
}
// Archs represents multiple Arch values.
type Archs []Arch
// Contains returns whether b is one of the Archs in a.
func (a Archs) Contains(b Arch) bool {
for _, t := range a {
if t == b {
return true
}
}
return false
}
// ToBytes returns the serialized option defined by RFC 4578 (DHCPv4) and RFC
// 5970 (DHCPv6) as the Client System Architecture Option.
func (a Archs) ToBytes() []byte {
buf := uio.NewBigEndianBuffer(nil)
for _, at := range a {
buf.Write16(uint16(at))
}
return buf.Data()
}
// String returns the list of archs in a human-readable manner.
func (a Archs) String() string {
s := make([]string, 0, len(a))
for _, arch := range a {
s = append(s, arch.String())
}
return strings.Join(s, ", ")
}
// FromBytes parses a DHCP list of architecture types as defined by RFC 4578
// and RFC 5970.
func (a *Archs) FromBytes(data []byte) error {
buf := uio.NewBigEndianBuffer(data)
if buf.Len() == 0 {
return fmt.Errorf("must have at least one archtype if option is present")
}
*a = make([]Arch, 0, buf.Len()/2)
for buf.Has(2) {
*a = append(*a, Arch(buf.Read16()))
}
return buf.FinError()
}

25
vendor/github.com/insomniacslk/dhcp/iana/entid.go generated vendored Normal file
View File

@ -0,0 +1,25 @@
package iana
// EnterpriseID represents the Enterprise IDs as set by IANA
type EnterpriseID int
// See https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers for values
const (
EnterpriseIDCiscoSystems EnterpriseID = 9
EnterpriseIDCienaCorporation EnterpriseID = 1271
EnterpriseIDMellanoxTechnologiesLTD EnterpriseID = 33049
)
var enterpriseIDToStringMap = map[EnterpriseID]string{
EnterpriseIDCiscoSystems: "Cisco Systems",
EnterpriseIDCienaCorporation: "Ciena Corporation",
EnterpriseIDMellanoxTechnologiesLTD: "Mellanox Technologies LTD",
}
// String returns the vendor name for a given Enterprise ID
func (e EnterpriseID) String() string {
if vendor := enterpriseIDToStringMap[e]; vendor != "" {
return vendor
}
return "Unknown"
}

91
vendor/github.com/insomniacslk/dhcp/iana/hwtypes.go generated vendored Normal file
View File

@ -0,0 +1,91 @@
package iana
// HWType is a hardware type as per RFC 2132 and defined by the IANA.
type HWType uint16
// See IANA for values.
const (
_ HWType = iota // skip 0
HWTypeEthernet
HWTypeExperimentalEthernet
HWTypeAmateurRadioAX25
HWTypeProteonTokenRing
HWTypeChaos
HWTypeIEEE802
HWTypeARCNET
HWTypeHyperchannel
HWTypeLanstar
HWTypeAutonet
HWTypeLocalTalk
HWTypeLocalNet
HWTypeUltraLink
HWTypeSMDS
HWTypeFrameRelay
HWTypeATM
HWTypeHDLC
HWTypeFibreChannel
HWTypeATM2
HWTypeSerialLine
HWTypeATM3
HWTypeMILSTD188220
HWTypeMetricom
HWTypeIEEE1394
HWTypeMAPOS
HWTypeTwinaxial
HWTypeEUI64
HWTypeHIPARP
HWTypeISO7816
HWTypeARPSec
HWTypeIPsec
HWTypeInfiniband
HWTypeCAI
HWTypeWiegandInterface
HWTypePureIP
)
var hwTypeToString = map[HWType]string{
HWTypeEthernet: "Ethernet",
HWTypeExperimentalEthernet: "Experimental Ethernet",
HWTypeAmateurRadioAX25: "Amateur Radio AX.25",
HWTypeProteonTokenRing: "Proteon ProNET Token Ring",
HWTypeChaos: "Chaos",
HWTypeIEEE802: "IEEE 802",
HWTypeARCNET: "ARCNET",
HWTypeHyperchannel: "Hyperchannel",
HWTypeLanstar: "Lanstar",
HWTypeAutonet: "Autonet Short Address",
HWTypeLocalTalk: "LocalTalk",
HWTypeLocalNet: "LocalNet",
HWTypeUltraLink: "Ultra link",
HWTypeSMDS: "SMDS",
HWTypeFrameRelay: "Frame Relay",
HWTypeATM: "ATM",
HWTypeHDLC: "HDLC",
HWTypeFibreChannel: "Fibre Channel",
HWTypeATM2: "ATM 2",
HWTypeSerialLine: "Serial Line",
HWTypeATM3: "ATM 3",
HWTypeMILSTD188220: "MIL-STD-188-220",
HWTypeMetricom: "Metricom",
HWTypeIEEE1394: "IEEE 1394.1995",
HWTypeMAPOS: "MAPOS",
HWTypeTwinaxial: "Twinaxial",
HWTypeEUI64: "EUI-64",
HWTypeHIPARP: "HIPARP",
HWTypeISO7816: "IP and ARP over ISO 7816-3",
HWTypeARPSec: "ARPSec",
HWTypeIPsec: "IPsec tunnel",
HWTypeInfiniband: "Infiniband",
HWTypeCAI: "CAI, TIA-102 Project 125 Common Air Interface",
HWTypeWiegandInterface: "Wiegand Interface",
HWTypePureIP: "Pure IP",
}
// String implements fmt.Stringer.
func (h HWType) String() string {
hwtype := hwTypeToString[h]
if hwtype == "" {
hwtype = "unknown"
}
return hwtype
}

2
vendor/github.com/insomniacslk/dhcp/iana/iana.go generated vendored Normal file
View File

@ -0,0 +1,2 @@
// Package iana contains constants defined by IANA.
package iana

View File

@ -0,0 +1,77 @@
package iana
// StatusCode represents a IANA status code for DHCPv6
//
// IANA Status Codes for DHCPv6
// https://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#dhcpv6-parameters-5
type StatusCode uint16
// IANA status codes
const (
// RFC 3315 par. 24..4
StatusSuccess StatusCode = 0
StatusUnspecFail StatusCode = 1
StatusNoAddrsAvail StatusCode = 2
StatusNoBinding StatusCode = 3
StatusNotOnLink StatusCode = 4
StatusUseMulticast StatusCode = 5
StatusNoPrefixAvail StatusCode = 6
// RFC 5007
StatusUnknownQueryType StatusCode = 7
StatusMalformedQuery StatusCode = 8
StatusNotConfigured StatusCode = 9
StatusNotAllowed StatusCode = 10
// RFC 5460
StatusQueryTerminated StatusCode = 11
// RFC 7653
StatusDataMissing StatusCode = 12
StatusCatchUpComplete StatusCode = 13
StatusNotSupported StatusCode = 14
StatusTLSConnectionRefused StatusCode = 15
// RFC 8156
StatusAddressInUse StatusCode = 16
StatusConfigurationConflict StatusCode = 17
StatusMissingBindingInformation StatusCode = 18
StatusOutdatedBindingInformation StatusCode = 19
StatusServerShuttingDown StatusCode = 20
StatusDNSUpdateNotSupported StatusCode = 21
StatusExcessiveTimeSkew StatusCode = 22
)
// String returns a mnemonic name for a given status code
func (s StatusCode) String() string {
if sc := statusCodeToStringMap[s]; sc != "" {
return sc
}
return "Unknown"
}
var statusCodeToStringMap = map[StatusCode]string{
StatusSuccess: "Success",
StatusUnspecFail: "UnspecFail",
StatusNoAddrsAvail: "NoAddrsAvail",
StatusNoBinding: "NoBinding",
StatusNotOnLink: "NotOnLink",
StatusUseMulticast: "UseMulticast",
StatusNoPrefixAvail: "NoPrefixAvail",
// RFC 5007
StatusUnknownQueryType: "UnknownQueryType",
StatusMalformedQuery: "MalformedQuery",
StatusNotConfigured: "NotConfigured",
StatusNotAllowed: "NotAllowed",
// RFC 5460
StatusQueryTerminated: "QueryTerminated",
// RFC 7653
StatusDataMissing: "DataMissing",
StatusCatchUpComplete: "CatchUpComplete",
StatusNotSupported: "NotSupported",
StatusTLSConnectionRefused: "TLSConnectionRefused",
// RFC 8156
StatusAddressInUse: "AddressInUse",
StatusConfigurationConflict: "ConfigurationConflict",
StatusMissingBindingInformation: "MissingBindingInformation",
StatusOutdatedBindingInformation: "OutdatedBindingInformation",
StatusServerShuttingDown: "ServerShuttingDown",
StatusDNSUpdateNotSupported: "DNSUpdateNotSupported",
StatusExcessiveTimeSkew: "ExcessiveTimeSkew",
}

View File

@ -0,0 +1,19 @@
// +build aix freebsd openbsd netbsd dragonfly
package interfaces
import (
"net"
"golang.org/x/sys/unix"
)
// BindToInterface emulates linux's SO_BINDTODEVICE option for a socket by using
// IP_RECVIF.
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_RECVIF, iface.Index)
}

View File

@ -0,0 +1,19 @@
// +build darwin
package interfaces
import (
"net"
"golang.org/x/sys/unix"
)
// BindToInterface emulates linux's SO_BINDTODEVICE option for a socket by using
// 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)
}

View File

@ -0,0 +1,9 @@
// +build linux
package interfaces
import "golang.org/x/sys/unix"
func BindToInterface(fd int, ifname string) error {
return unix.BindToDevice(fd, ifname)
}

View File

@ -0,0 +1,8 @@
package interfaces
import "errors"
// BindToInterface fails on Windows.
func BindToInterface(fd int, ifname string) error {
return errors.New("not implemented on Windows")
}

View File

@ -0,0 +1,41 @@
package interfaces
import "net"
// InterfaceMatcher is a function type used to match the interfaces we want. See
// GetInterfacesFunc below for usage.
type InterfaceMatcher func(net.Interface) bool
// interfaceGetter is used for testing purposes
var interfaceGetter = net.Interfaces
// GetInterfacesFunc loops through the available network interfaces, and returns
// a list of interfaces for which the passed InterfaceMatcher function returns
// true.
func GetInterfacesFunc(matcher InterfaceMatcher) ([]net.Interface, error) {
ifaces, err := interfaceGetter()
if err != nil {
return nil, err
}
ret := make([]net.Interface, 0)
for _, iface := range ifaces {
if matcher(iface) {
ret = append(ret, iface)
}
}
return ret, nil
}
// GetLoopbackInterfaces returns a list of loopback interfaces.
func GetLoopbackInterfaces() ([]net.Interface, error) {
return GetInterfacesFunc(func(iface net.Interface) bool {
return iface.Flags&net.FlagLoopback != 0
})
}
// GetNonLoopbackInterfaces returns a list of non-loopback interfaces.
func GetNonLoopbackInterfaces() ([]net.Interface, error) {
return GetInterfacesFunc(func(iface net.Interface) bool {
return iface.Flags&net.FlagLoopback == 0
})
}

View File

@ -0,0 +1,173 @@
package rfc1035label
import (
"errors"
"fmt"
"strings"
)
// Labels represents RFC1035 labels
//
// This implements RFC 1035 labels, including compression.
// https://tools.ietf.org/html/rfc1035#section-4.1.4
type Labels struct {
// original contains the original bytes if the object was parsed from a byte
// sequence, or nil otherwise. The `original` field is necessary to deal
// with compressed labels. If the labels are further modified, the original
// content is invalidated and no compression will be used.
original []byte
// Labels contains the parsed labels. A change here invalidates the
// `original` object.
Labels []string
}
// same compares two string arrays
func same(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := 0; i < len(a); i++ {
if a[i] != b[i] {
return false
}
}
return true
}
// String prints labels.
func (l *Labels) String() string {
return fmt.Sprintf("%v", l.Labels)
}
// ToBytes returns a byte sequence representing the labels. If the original
// sequence is modified, the labels are parsed again, otherwise the original
// byte sequence is returned.
func (l *Labels) ToBytes() []byte {
// if the original byte sequence has been modified, invalidate it and
// serialize again.
// NOTE: this function is not thread-safe. If multiple threads modify
// the `Labels` field, the result may be wrong.
originalLabels, err := labelsFromBytes(l.original)
// if the original object has not been modified, or we cannot parse it,
// return the original bytes.
if err != nil || (l.original != nil && same(originalLabels, l.Labels)) {
return l.original
}
return labelsToBytes(l.Labels)
}
// Length returns the length in bytes of the serialized labels
func (l *Labels) Length() int {
return len(l.ToBytes())
}
// NewLabels returns an initialized Labels object.
func NewLabels() *Labels {
return &Labels{
Labels: make([]string, 0),
}
}
// FromBytes reads labels from a bytes stream according to RFC 1035.
func (l *Labels) FromBytes(data []byte) error {
labs, err := labelsFromBytes(data)
if err != nil {
return err
}
l.original = data
l.Labels = labs
return nil
}
// FromBytes returns a Labels object from the given byte sequence, or an error if
// any.
func FromBytes(data []byte) (*Labels, error) {
var l Labels
if err := l.FromBytes(data); err != nil {
return nil, err
}
return &l, nil
}
// ErrBufferTooShort is returned when the label cannot be parsed due to a wrong
// length or missing bytes.
var ErrBufferTooShort = errors.New("rfc1035label: buffer too short")
// fromBytes decodes a serialized stream and returns a list of labels
func labelsFromBytes(buf []byte) ([]string, error) {
var (
labels = make([]string, 0)
pos, oldPos int
label string
handlingPointer bool
)
for {
if pos >= len(buf) {
// interpret label without trailing zero-length byte as a partial
// domain name field as per RFC 4704 Section 4.2
if label != "" {
labels = append(labels, label)
}
break
}
length := int(buf[pos])
pos++
var chunk string
if length == 0 {
labels = append(labels, label)
label = ""
if handlingPointer {
pos = oldPos
handlingPointer = false
}
} else if length&0xc0 == 0xc0 {
// compression pointer
if handlingPointer {
return nil, errors.New("rfc1035label: cannot handle nested pointers")
}
handlingPointer = true
if pos+1 > len(buf) {
return nil, errors.New("rfc1035label: pointer buffer too short")
}
off := int(buf[pos-1]&^0xc0)<<8 + int(buf[pos])
oldPos = pos + 1
pos = off
} else {
if pos+length > len(buf) {
return nil, ErrBufferTooShort
}
chunk = string(buf[pos : pos+length])
if label != "" {
label += "."
}
label += chunk
pos += length
}
}
return labels, nil
}
// labelToBytes encodes a label and returns a serialized stream of bytes
func labelToBytes(label string) []byte {
var encodedLabel []byte
if len(label) == 0 {
return []byte{0}
}
for _, part := range strings.Split(label, ".") {
encodedLabel = append(encodedLabel, byte(len(part)))
encodedLabel = append(encodedLabel, []byte(part)...)
}
return append(encodedLabel, 0)
}
// labelsToBytes encodes a list of labels and returns a serialized stream of
// bytes
func labelsToBytes(labels []string) []byte {
var encodedLabels []byte
for _, label := range labels {
encodedLabels = append(encodedLabels, labelToBytes(label)...)
}
return encodedLabels
}

8
vendor/github.com/josharian/native/doc.go generated vendored Normal file
View File

@ -0,0 +1,8 @@
// Package native provides easy access to native byte order.
//
// Usage: use native.Endian where you need the native binary.ByteOrder.
//
// Please think twice before using this package.
// It can break program portability.
// Native byte order is usually not the right answer.
package native

14
vendor/github.com/josharian/native/endian_big.go generated vendored Normal file
View File

@ -0,0 +1,14 @@
//go:build mips || mips64 || ppc64 || s390x
// +build mips mips64 ppc64 s390x
package native
import "encoding/binary"
// Endian is the encoding/binary.ByteOrder implementation for the
// current CPU's native byte order.
var Endian = binary.BigEndian
// IsBigEndian is whether the current CPU's native byte order is big
// endian.
const IsBigEndian = true

31
vendor/github.com/josharian/native/endian_generic.go generated vendored Normal file
View File

@ -0,0 +1,31 @@
//go:build !mips && !mips64 && !ppc64 && !s390x && !amd64 && !386 && !arm && !arm64 && !loong64 && !mipsle && !mips64le && !ppc64le && !riscv64 && !wasm
// +build !mips,!mips64,!ppc64,!s390x,!amd64,!386,!arm,!arm64,!loong64,!mipsle,!mips64le,!ppc64le,!riscv64,!wasm
// This file is a fallback, so that package native doesn't break
// the instant the Go project adds support for a new architecture.
//
package native
import (
"encoding/binary"
"log"
"runtime"
"unsafe"
)
var Endian binary.ByteOrder
var IsBigEndian bool
func init() {
b := uint16(0xff) // one byte
if *(*byte)(unsafe.Pointer(&b)) == 0 {
Endian = binary.BigEndian
IsBigEndian = true
} else {
Endian = binary.LittleEndian
IsBigEndian = false
}
log.Printf("github.com/josharian/native: unrecognized arch %v (%v), please file an issue", runtime.GOARCH, Endian)
}

14
vendor/github.com/josharian/native/endian_little.go generated vendored Normal file
View File

@ -0,0 +1,14 @@
//go:build amd64 || 386 || arm || arm64 || loong64 || mipsle || mips64le || ppc64le || riscv64 || wasm
// +build amd64 386 arm arm64 loong64 mipsle mips64le ppc64le riscv64 wasm
package native
import "encoding/binary"
// Endian is the encoding/binary.ByteOrder implementation for the
// current CPU's native byte order.
var Endian = binary.LittleEndian
// IsBigEndian is whether the current CPU's native byte order is big
// endian.
const IsBigEndian = false

7
vendor/github.com/josharian/native/license generated vendored Normal file
View File

@ -0,0 +1,7 @@
Copyright 2020 Josh Bleecher Snyder
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

10
vendor/github.com/josharian/native/readme.md generated vendored Normal file
View File

@ -0,0 +1,10 @@
Package native provides easy access to native byte order.
`go get github.com/josharian/native`
Usage: Use `native.Endian` where you need the native binary.ByteOrder.
Please think twice before using this package.
It can break program portability.
Native byte order is usually not the right answer.

1
vendor/github.com/mdlayher/packet/.gitignore generated vendored Normal file
View File

@ -0,0 +1 @@
cmd/packet

27
vendor/github.com/mdlayher/packet/CHANGELOG.md generated vendored Normal file
View File

@ -0,0 +1,27 @@
# CHANGELOG
# v1.1.2
- [Improvement]: updated dependencies, test with Go 1.20.
# v1.1.1
- [Bug Fix]: fix test compilation on big endian machines.
# v1.1.0
**This is the first release of package packet that only supports Go 1.18+. Users
on older versions of Go must use v1.0.0.**
- [Improvement]: drop support for older versions of Go so we can begin using
modern versions of `x/sys` and other dependencies.
## v1.0.0
**This is the last release of package vsock that supports Go 1.17 and below.**
- Initial stable commit! The API is mostly a direct translation of the previous
`github.com/mdlayher/raw` package APIs, with some updates to make everything
focused explicitly on Linux and `AF_PACKET` sockets. Functionally, the two
packages are equivalent, and `*raw.Conn` is now backed by `*packet.Conn` in
the latest version of the `raw` package.

9
vendor/github.com/mdlayher/packet/LICENSE.md generated vendored Normal file
View File

@ -0,0 +1,9 @@
# MIT License
Copyright (C) 2022 Matt Layher
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

35
vendor/github.com/mdlayher/packet/README.md generated vendored Normal file
View File

@ -0,0 +1,35 @@
# packet [![Test Status](https://github.com/mdlayher/packet/workflows/Test/badge.svg)](https://github.com/mdlayher/packet/actions) [![Go Reference](https://pkg.go.dev/badge/github.com/mdlayher/packet.svg)](https://pkg.go.dev/github.com/mdlayher/packet) [![Go Report Card](https://goreportcard.com/badge/github.com/mdlayher/packet)](https://goreportcard.com/report/github.com/mdlayher/packet)
Package `packet` provides access to Linux packet sockets (`AF_PACKET`). MIT
Licensed.
## Stability
See the [CHANGELOG](./CHANGELOG.md) file for a description of changes between
releases.
This package has a stable v1 API and any future breaking changes will prompt
the release of a new major version. Features and bug fixes will continue to
occur in the v1.x.x series.
This package only supports the two most recent major versions of Go, mirroring
Go's own release policy. Older versions of Go may lack critical features and bug
fixes which are necessary for this package to function correctly.
## History
One of my first major Go networking projects was
[`github.com/mdlayher/raw`](https://github.com/mdlayher/raw), which provided
access to Linux `AF_PACKET` sockets and *BSD equivalent mechanisms for sending
and receiving Ethernet frames. However, the *BSD support languished and I lack
the expertise and time to properly maintain code for operating systems I do not
use on a daily basis.
Package `packet` is a successor to package `raw`, but exclusively focused on
Linux and `AF_PACKET` sockets. The APIs are nearly identical, but with a few
changes which take into account some of the lessons learned while working on
`raw`.
Users are highly encouraged to migrate any existing Linux uses of `raw` to
package `packet` instead. This package will be supported for the foreseeable
future and will receive continued updates as necessary.

2
vendor/github.com/mdlayher/packet/doc.go generated vendored Normal file
View File

@ -0,0 +1,2 @@
// Package packet provides access to Linux packet sockets (AF_PACKET).
package packet

241
vendor/github.com/mdlayher/packet/packet.go generated vendored Normal file
View File

@ -0,0 +1,241 @@
package packet
import (
"net"
"syscall"
"time"
"golang.org/x/net/bpf"
)
const (
// network is the network reported in net.OpError.
network = "packet"
// Operation names which may be returned in net.OpError.
opClose = "close"
opGetsockopt = "getsockopt"
opListen = "listen"
opRawControl = "raw-control"
opRawRead = "raw-read"
opRawWrite = "raw-write"
opRead = "read"
opSet = "set"
opSetsockopt = "setsockopt"
opSyscallConn = "syscall-conn"
opWrite = "write"
)
// Config contains options for a Conn.
type Config struct {
// Filter is an optional assembled BPF filter which can be applied to the
// Conn before bind(2) is called.
//
// The Conn.SetBPF method serves the same purpose once a Conn has already
// been opened, but setting Filter applies the BPF filter before the Conn is
// bound. This ensures that unexpected packets will not be captured before
// the Conn is opened.
Filter []bpf.RawInstruction
}
// Type is a socket type used when creating a Conn with Listen.
//enumcheck:exhaustive
type Type int
// Possible Type values. Note that the zero value is not valid: callers must
// always specify one of Raw or Datagram when calling Listen.
const (
_ Type = iota
Raw
Datagram
)
// Listen opens a packet sockets connection on the specified interface, using
// the given socket type and protocol values.
//
// The socket type must be one of the Type constants: Raw or Datagram.
//
// The Config specifies optional configuration for the Conn. A nil *Config
// applies the default configuration.
func Listen(ifi *net.Interface, socketType Type, protocol int, cfg *Config) (*Conn, error) {
l, err := listen(ifi, socketType, protocol, cfg)
if err != nil {
return nil, opError(opListen, err, &Addr{HardwareAddr: ifi.HardwareAddr})
}
return l, nil
}
// TODO(mdlayher): we want to support FileConn for advanced use cases, but this
// library would also need a big endian protocol value and an interface index.
// For now we won't bother, but reconsider in the future.
var (
_ net.PacketConn = &Conn{}
_ syscall.Conn = &Conn{}
_ bpf.Setter = &Conn{}
)
// A Conn is an Linux packet sockets (AF_PACKET) implementation of a
// net.PacketConn.
type Conn struct {
c *conn
// Metadata about the local connection.
addr *Addr
ifIndex int
protocol uint16
}
// Close closes the connection.
func (c *Conn) Close() error {
return c.opError(opClose, c.c.Close())
}
// LocalAddr returns the local network address. The Addr returned is shared by
// all invocations of LocalAddr, so do not modify it.
func (c *Conn) LocalAddr() net.Addr { return c.addr }
// ReadFrom implements the net.PacketConn ReadFrom method.
func (c *Conn) ReadFrom(b []byte) (int, net.Addr, error) {
return c.readFrom(b)
}
// WriteTo implements the net.PacketConn WriteTo method.
func (c *Conn) WriteTo(b []byte, addr net.Addr) (int, error) {
return c.writeTo(b, addr)
}
// SetDeadline implements the net.PacketConn SetDeadline method.
func (c *Conn) SetDeadline(t time.Time) error {
return c.opError(opSet, c.c.SetDeadline(t))
}
// SetReadDeadline implements the net.PacketConn SetReadDeadline method.
func (c *Conn) SetReadDeadline(t time.Time) error {
return c.opError(opSet, c.c.SetReadDeadline(t))
}
// SetWriteDeadline implements the net.PacketConn SetWriteDeadline method.
func (c *Conn) SetWriteDeadline(t time.Time) error {
return c.opError(opSet, c.c.SetWriteDeadline(t))
}
// SetBPF attaches an assembled BPF program to the Conn.
func (c *Conn) SetBPF(filter []bpf.RawInstruction) error {
return c.opError(opSetsockopt, c.c.SetBPF(filter))
}
// SetPromiscuous enables or disables promiscuous mode on the Conn, allowing it
// to receive traffic that is not addressed to the Conn's network interface.
func (c *Conn) SetPromiscuous(enable bool) error {
return c.setPromiscuous(enable)
}
// Stats contains statistics about a Conn reported by the Linux kernel.
type Stats struct {
// The total number of packets received.
Packets uint32
// The number of packets dropped.
Drops uint32
// The total number of times that a receive queue is frozen. May be zero if
// the Linux kernel is not new enough to support TPACKET_V3 statistics.
FreezeQueueCount uint32
}
// Stats retrieves statistics about the Conn from the Linux kernel.
//
// Note that calling Stats will reset the kernel's internal counters for this
// Conn. If you want to maintain cumulative statistics by polling Stats over
// time, you must do so in your calling code.
func (c *Conn) Stats() (*Stats, error) { return c.stats() }
// SyscallConn returns a raw network connection. This implements the
// syscall.Conn interface.
func (c *Conn) SyscallConn() (syscall.RawConn, error) {
rc, err := c.c.SyscallConn()
if err != nil {
return nil, c.opError(opSyscallConn, err)
}
return &rawConn{
rc: rc,
addr: c.addr,
}, nil
}
// opError is a convenience for the function opError that also passes the local
// and remote addresses of the Conn.
func (c *Conn) opError(op string, err error) error {
return opError(op, err, c.addr)
}
// TODO(mdlayher): see if we can port smarter net.OpError logic into
// socket.Conn's SyscallConn type to avoid the need for this wrapper.
var _ syscall.RawConn = &rawConn{}
// A rawConn is a syscall.RawConn that wraps an internal syscall.RawConn in order
// to produce net.OpError error values.
type rawConn struct {
rc syscall.RawConn
addr *Addr
}
// Control implements the syscall.RawConn Control method.
func (rc *rawConn) Control(fn func(fd uintptr)) error {
return rc.opError(opRawControl, rc.rc.Control(fn))
}
// Control implements the syscall.RawConn Read method.
func (rc *rawConn) Read(fn func(fd uintptr) (done bool)) error {
return rc.opError(opRawRead, rc.rc.Read(fn))
}
// Control implements the syscall.RawConn Write method.
func (rc *rawConn) Write(fn func(fd uintptr) (done bool)) error {
return rc.opError(opRawWrite, rc.rc.Write(fn))
}
// opError is a convenience for the function opError that also passes the
// address of the rawConn.
func (rc *rawConn) opError(op string, err error) error {
return opError(op, err, rc.addr)
}
var _ net.Addr = &Addr{}
// TODO(mdlayher): expose sll_hatype and sll_pkttype on receive Addr only.
// An Addr is a physical-layer address.
type Addr struct {
HardwareAddr net.HardwareAddr
}
// Network returns the address's network name, "packet".
func (a *Addr) Network() string { return network }
// String returns the string representation of an Addr.
func (a *Addr) String() string {
return a.HardwareAddr.String()
}
// opError unpacks err if possible, producing a net.OpError with the input
// parameters in order to implement net.PacketConn. As a convenience, opError
// returns nil if the input error is nil.
func opError(op string, err error, local net.Addr) error {
if err == nil {
return nil
}
// TODO(mdlayher): try to comply with net.PacketConn as best as we can; land
// a nettest.TestPacketConn API upstream.
return &net.OpError{
Op: op,
Net: network,
Addr: local,
Err: err,
}
}

248
vendor/github.com/mdlayher/packet/packet_linux.go generated vendored Normal file
View File

@ -0,0 +1,248 @@
//go:build linux
// +build linux
package packet
import (
"context"
"encoding/binary"
"errors"
"math"
"net"
"os"
"github.com/josharian/native"
"github.com/mdlayher/socket"
"golang.org/x/sys/unix"
)
// A conn is the net.PacketConn implementation for packet sockets. We can use
// socket.Conn directly on Linux to implement most of the necessary methods.
type conn = socket.Conn
// readFrom implements the net.PacketConn ReadFrom method using recvfrom(2).
func (c *Conn) readFrom(b []byte) (int, net.Addr, error) {
// From net.PacketConn documentation:
//
// "[ReadFrom] returns the number of bytes read (0 <= n <= len(p)) and any
// error encountered. Callers should always process the n > 0 bytes returned
// before considering the error err."
//
// c.opError will return nil if no error, but either way we return all the
// information that we have.
n, sa, err := c.c.Recvfrom(context.Background(), b, 0)
return n, fromSockaddr(sa), c.opError(opRead, err)
}
// writeTo implements the net.PacketConn WriteTo method.
func (c *Conn) writeTo(b []byte, addr net.Addr) (int, error) {
sa, err := c.toSockaddr("sendto", addr)
if err != nil {
return 0, c.opError(opWrite, err)
}
// TODO(mdlayher): it's curious that unix.Sendto does not return the number
// of bytes actually sent. Fake it for now, but investigate upstream.
if err := c.c.Sendto(context.Background(), b, 0, sa); err != nil {
return 0, c.opError(opWrite, err)
}
return len(b), nil
}
// setPromiscuous wraps setsockopt(2) for the unix.PACKET_MR_PROMISC option.
func (c *Conn) setPromiscuous(enable bool) error {
mreq := unix.PacketMreq{
Ifindex: int32(c.ifIndex),
Type: unix.PACKET_MR_PROMISC,
}
membership := unix.PACKET_DROP_MEMBERSHIP
if enable {
membership = unix.PACKET_ADD_MEMBERSHIP
}
return c.opError(
opSetsockopt,
c.c.SetsockoptPacketMreq(unix.SOL_PACKET, membership, &mreq),
)
}
// stats wraps getsockopt(2) for tpacket_stats* types.
func (c *Conn) stats() (*Stats, error) {
const (
level = unix.SOL_PACKET
name = unix.PACKET_STATISTICS
)
// Try to fetch V3 statistics first, they contain more detailed information.
if stats, err := c.c.GetsockoptTpacketStatsV3(level, name); err == nil {
return &Stats{
Packets: stats.Packets,
Drops: stats.Drops,
FreezeQueueCount: stats.Freeze_q_cnt,
}, nil
}
// There was an error fetching v3 stats, try to fall back.
stats, err := c.c.GetsockoptTpacketStats(level, name)
if err != nil {
return nil, c.opError(opGetsockopt, err)
}
return &Stats{
Packets: stats.Packets,
Drops: stats.Drops,
// FreezeQueueCount is not present.
}, nil
}
// listen is the entry point for Listen on Linux.
func listen(ifi *net.Interface, socketType Type, protocol int, cfg *Config) (*Conn, error) {
if cfg == nil {
// Default configuration.
cfg = &Config{}
}
// Convert Type to the matching SOCK_* constant.
var typ int
switch socketType {
case Raw:
typ = unix.SOCK_RAW
case Datagram:
typ = unix.SOCK_DGRAM
default:
return nil, errors.New("packet: invalid Type value")
}
// Protocol is intentionally zero in call to socket(2); we can set it on
// bind(2) instead. Package raw notes: "Do not specify a protocol to avoid
// capturing packets which to not match cfg.Filter."
c, err := socket.Socket(unix.AF_PACKET, typ, 0, network, nil)
if err != nil {
return nil, err
}
conn, err := bind(c, ifi.Index, protocol, cfg)
if err != nil {
_ = c.Close()
return nil, err
}
return conn, nil
}
// bind binds the *socket.Conn to finalize *Conn setup.
func bind(c *socket.Conn, ifIndex, protocol int, cfg *Config) (*Conn, error) {
if len(cfg.Filter) > 0 {
// The caller wants to apply a BPF filter before bind(2).
if err := c.SetBPF(cfg.Filter); err != nil {
return nil, err
}
}
// packet(7) says we sll_protocol must be in network byte order.
pnet, err := htons(protocol)
if err != nil {
return nil, err
}
// TODO(mdlayher): investigate the possibility of sll_ifindex = 0 because we
// could bind to any interface.
err = c.Bind(&unix.SockaddrLinklayer{
Protocol: pnet,
Ifindex: ifIndex,
})
if err != nil {
return nil, err
}
lsa, err := c.Getsockname()
if err != nil {
return nil, err
}
// Parse the physical layer address; sll_halen tells us how many bytes of
// sll_addr we should treat as valid.
lsall := lsa.(*unix.SockaddrLinklayer)
addr := make(net.HardwareAddr, lsall.Halen)
copy(addr, lsall.Addr[:])
return &Conn{
c: c,
addr: &Addr{HardwareAddr: addr},
ifIndex: ifIndex,
protocol: pnet,
}, nil
}
// fromSockaddr converts an opaque unix.Sockaddr to *Addr. If sa is nil, it
// returns nil. It panics if sa is not of type *unix.SockaddrLinklayer.
func fromSockaddr(sa unix.Sockaddr) *Addr {
if sa == nil {
return nil
}
sall := sa.(*unix.SockaddrLinklayer)
return &Addr{
// The syscall already allocated sa; just slice into it with the
// appropriate length and type conversion rather than making a copy.
HardwareAddr: net.HardwareAddr(sall.Addr[:sall.Halen]),
}
}
// toSockaddr converts a net.Addr to an opaque unix.Sockaddr. It returns an
// error if the fields cannot be packed into a *unix.SockaddrLinklayer.
func (c *Conn) toSockaddr(
op string,
addr net.Addr,
) (unix.Sockaddr, error) {
// The typical error convention for net.Conn types is
// net.OpError(os.SyscallError(syscall.Errno)), so all calls here should
// return os.SyscallError(syscall.Errno) so the caller can apply the final
// net.OpError wrapper.
// Ensure the correct Addr type.
a, ok := addr.(*Addr)
if !ok || a.HardwareAddr == nil {
return nil, os.NewSyscallError(op, unix.EINVAL)
}
// Pack Addr and Conn metadata into the appropriate sockaddr fields. From
// packet(7):
//
// "When you send packets it is enough to specify sll_family, sll_addr,
// sll_halen, sll_ifindex, and sll_protocol. The other fields should be 0."
//
// sll_family is set on the conversion to unix.RawSockaddrLinklayer.
sa := unix.SockaddrLinklayer{
Ifindex: c.ifIndex,
Protocol: c.protocol,
}
// Ensure the input address does not exceed the amount of space available;
// for example an IPoIB address is 20 bytes.
if len(a.HardwareAddr) > len(sa.Addr) {
return nil, os.NewSyscallError(op, unix.EINVAL)
}
sa.Halen = uint8(len(a.HardwareAddr))
copy(sa.Addr[:], a.HardwareAddr)
return &sa, nil
}
// htons converts a short (uint16) from host-to-network byte order.
func htons(i int) (uint16, error) {
if i < 0 || i > math.MaxUint16 {
return 0, errors.New("packet: protocol value out of range")
}
// Store as big endian, retrieve as native endian.
var b [2]byte
binary.BigEndian.PutUint16(b[:], uint16(i))
return native.Endian.Uint16(b[:]), nil
}

33
vendor/github.com/mdlayher/packet/packet_others.go generated vendored Normal file
View File

@ -0,0 +1,33 @@
//go:build !linux
// +build !linux
package packet
import (
"fmt"
"net"
"runtime"
"syscall"
"time"
"golang.org/x/net/bpf"
)
// errUnimplemented is returned by all functions on non-Linux platforms.
var errUnimplemented = fmt.Errorf("packet: not implemented on %s", runtime.GOOS)
func listen(_ *net.Interface, _ Type, _ int, _ *Config) (*Conn, error) { return nil, errUnimplemented }
func (*Conn) readFrom(_ []byte) (int, net.Addr, error) { return 0, nil, errUnimplemented }
func (*Conn) writeTo(_ []byte, _ net.Addr) (int, error) { return 0, errUnimplemented }
func (*Conn) setPromiscuous(_ bool) error { return errUnimplemented }
func (*Conn) stats() (*Stats, error) { return nil, errUnimplemented }
type conn struct{}
func (*conn) Close() error { return errUnimplemented }
func (*conn) SetDeadline(_ time.Time) error { return errUnimplemented }
func (*conn) SetReadDeadline(_ time.Time) error { return errUnimplemented }
func (*conn) SetWriteDeadline(_ time.Time) error { return errUnimplemented }
func (*conn) SetBPF(_ []bpf.RawInstruction) error { return errUnimplemented }
func (*conn) SyscallConn() (syscall.RawConn, error) { return nil, errUnimplemented }

94
vendor/github.com/mdlayher/socket/CHANGELOG.md generated vendored Normal file
View File

@ -0,0 +1,94 @@
# CHANGELOG
## v0.5.1
- [Improvement]: revert `go.mod` to Go 1.20 to [resolve an issue around Go
module version upgrades](https://github.com/mdlayher/socket/issues/13).
## v0.5.0
**This is the first release of package socket that only supports Go 1.21+.
Users on older versions of Go must use v0.4.1.**
- [Improvement]: drop support for older versions of Go.
- [New API]: add `socket.Conn` wrappers for various `Getsockopt` and
`Setsockopt` system calls.
## v0.4.1
- [Bug Fix] [commit](https://github.com/mdlayher/socket/commit/2a14ceef4da279de1f957c5761fffcc6c87bbd3b):
ensure `socket.Conn` can be used with non-socket file descriptors by handling
`ENOTSOCK` in the constructor.
## v0.4.0
**This is the first release of package socket that only supports Go 1.18+.
Users on older versions of Go must use v0.3.0.**
- [Improvement]: drop support for older versions of Go so we can begin using
modern versions of `x/sys` and other dependencies.
## v0.3.0
**This is the last release of package socket that supports Go 1.17 and below.**
- [New API/API change] [PR](https://github.com/mdlayher/socket/pull/8):
numerous `socket.Conn` methods now support context cancelation. Future
releases will continue adding support as needed.
- New `ReadContext` and `WriteContext` methods.
- `Connect`, `Recvfrom`, `Recvmsg`, `Sendmsg`, and `Sendto` methods now accept
a context.
- `Sendto` parameter order was also fixed to match the underlying syscall.
## v0.2.3
- [New API] [commit](https://github.com/mdlayher/socket/commit/a425d96e0f772c053164f8ce4c9c825380a98086):
`socket.Conn` has new `Pidfd*` methods for wrapping the `pidfd_*(2)` family of
system calls.
## v0.2.2
- [New API] [commit](https://github.com/mdlayher/socket/commit/a2429f1dfe8ec2586df5a09f50ead865276cd027):
`socket.Conn` has new `IoctlKCM*` methods for wrapping `ioctl(2)` for `AF_KCM`
operations.
## v0.2.1
- [New API] [commit](https://github.com/mdlayher/socket/commit/b18ddbe9caa0e34552b4409a3aa311cb460d2f99):
`socket.Conn` has a new `SetsockoptPacketMreq` method for wrapping
`setsockopt(2)` for `AF_PACKET` socket options.
## v0.2.0
- [New API] [commit](https://github.com/mdlayher/socket/commit/6e912a68523c45e5fd899239f4b46c402dd856da):
`socket.FileConn` can be used to create a `socket.Conn` from an existing
`os.File`, which may be provided by systemd socket activation or another
external mechanism.
- [API change] [commit](https://github.com/mdlayher/socket/commit/66d61f565188c23fe02b24099ddc856d538bf1a7):
`socket.Conn.Connect` now returns the `unix.Sockaddr` value provided by
`getpeername(2)`, since we have to invoke that system call anyway to verify
that a connection to a remote peer was successfully established.
- [Bug Fix] [commit](https://github.com/mdlayher/socket/commit/b60b2dbe0ac3caff2338446a150083bde8c5c19c):
check the correct error from `unix.GetsockoptInt` in the `socket.Conn.Connect`
method. Thanks @vcabbage!
## v0.1.2
- [Bug Fix]: `socket.Conn.Connect` now properly checks the `SO_ERROR` socket
option value after calling `connect(2)` to verify whether or not a connection
could successfully be established. This means that `Connect` should now report
an error for an `AF_INET` TCP connection refused or `AF_VSOCK` connection
reset by peer.
- [New API]: add `socket.Conn.Getpeername` for use in `Connect`, but also for
use by external callers.
## v0.1.1
- [New API]: `socket.Conn` now has `CloseRead`, `CloseWrite`, and `Shutdown`
methods.
- [Improvement]: internal rework to more robustly handle various errors.
## v0.1.0
- Initial unstable release. Most functionality has been developed and ported
from package [`netlink`](https://github.com/mdlayher/netlink).

9
vendor/github.com/mdlayher/socket/LICENSE.md generated vendored Normal file
View File

@ -0,0 +1,9 @@
# MIT License
Copyright (C) 2021 Matt Layher
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

23
vendor/github.com/mdlayher/socket/README.md generated vendored Normal file
View File

@ -0,0 +1,23 @@
# socket [![Test Status](https://github.com/mdlayher/socket/workflows/Test/badge.svg)](https://github.com/mdlayher/socket/actions) [![Go Reference](https://pkg.go.dev/badge/github.com/mdlayher/socket.svg)](https://pkg.go.dev/github.com/mdlayher/socket) [![Go Report Card](https://goreportcard.com/badge/github.com/mdlayher/socket)](https://goreportcard.com/report/github.com/mdlayher/socket)
Package `socket` provides a low-level network connection type which integrates
with Go's runtime network poller to provide asynchronous I/O and deadline
support. MIT Licensed.
This package focuses on UNIX-like operating systems which make use of BSD
sockets system call APIs. It is meant to be used as a foundation for the
creation of operating system-specific socket packages, for socket families such
as Linux's `AF_NETLINK`, `AF_PACKET`, or `AF_VSOCK`. This package should not be
used directly in end user applications.
Any use of package socket should be guarded by build tags, as one would also
use when importing the `syscall` or `golang.org/x/sys` packages.
## Stability
See the [CHANGELOG](./CHANGELOG.md) file for a description of changes between
releases.
This package only supports the two most recent major versions of Go, mirroring
Go's own release policy. Older versions of Go may lack critical features and bug
fixes which are necessary for this package to function correctly.

23
vendor/github.com/mdlayher/socket/accept.go generated vendored Normal file
View File

@ -0,0 +1,23 @@
//go:build !dragonfly && !freebsd && !illumos && !linux
// +build !dragonfly,!freebsd,!illumos,!linux
package socket
import (
"fmt"
"runtime"
"golang.org/x/sys/unix"
)
const sysAccept = "accept"
// accept wraps accept(2).
func accept(fd, flags int) (int, unix.Sockaddr, error) {
if flags != 0 {
// These operating systems have no support for flags to accept(2).
return 0, nil, fmt.Errorf("socket: Conn.Accept flags are ineffective on %s", runtime.GOOS)
}
return unix.Accept(fd)
}

15
vendor/github.com/mdlayher/socket/accept4.go generated vendored Normal file
View File

@ -0,0 +1,15 @@
//go:build dragonfly || freebsd || illumos || linux
// +build dragonfly freebsd illumos linux
package socket
import (
"golang.org/x/sys/unix"
)
const sysAccept = "accept4"
// accept wraps accept4(2).
func accept(fd, flags int) (int, unix.Sockaddr, error) {
return unix.Accept4(fd, flags)
}

894
vendor/github.com/mdlayher/socket/conn.go generated vendored Normal file
View File

@ -0,0 +1,894 @@
package socket
import (
"context"
"errors"
"io"
"os"
"sync"
"sync/atomic"
"syscall"
"time"
"golang.org/x/sys/unix"
)
// Lock in an expected public interface for convenience.
var _ interface {
io.ReadWriteCloser
syscall.Conn
SetDeadline(t time.Time) error
SetReadDeadline(t time.Time) error
SetWriteDeadline(t time.Time) error
} = &Conn{}
// A Conn is a low-level network connection which integrates with Go's runtime
// network poller to provide asynchronous I/O and deadline support.
//
// Many of a Conn's blocking methods support net.Conn deadlines as well as
// cancelation via context. Note that passing a context with a deadline set will
// override any of the previous deadlines set by calls to the SetDeadline family
// of methods.
type Conn struct {
// Indicates whether or not Conn.Close has been called. Must be accessed
// atomically. Atomics definitions must come first in the Conn struct.
closed uint32
// A unique name for the Conn which is also associated with derived file
// descriptors such as those created by accept(2).
name string
// facts contains information we have determined about Conn to trigger
// alternate behavior in certain functions.
facts facts
// Provides access to the underlying file registered with the runtime
// network poller, and arbitrary raw I/O calls.
fd *os.File
rc syscall.RawConn
}
// facts contains facts about a Conn.
type facts struct {
// isStream reports whether this is a streaming descriptor, as opposed to a
// packet-based descriptor like a UDP socket.
isStream bool
// zeroReadIsEOF reports Whether a zero byte read indicates EOF. This is
// false for a message based socket connection.
zeroReadIsEOF bool
}
// A Config contains options for a Conn.
type Config struct {
// NetNS specifies the Linux network namespace the Conn will operate in.
// This option is unsupported on other operating systems.
//
// If set (non-zero), Conn will enter the specified network namespace and an
// error will occur in Socket if the operation fails.
//
// If not set (zero), a best-effort attempt will be made to enter the
// network namespace of the calling thread: this means that any changes made
// to the calling thread's network namespace will also be reflected in Conn.
// If this operation fails (due to lack of permissions or because network
// namespaces are disabled by kernel configuration), Socket will not return
// an error, and the Conn will operate in the default network namespace of
// the process. This enables non-privileged use of Conn in applications
// which do not require elevated privileges.
//
// Entering a network namespace is a privileged operation (root or
// CAP_SYS_ADMIN are required), and most applications should leave this set
// to 0.
NetNS int
}
// High-level methods which provide convenience over raw system calls.
// Close closes the underlying file descriptor for the Conn, which also causes
// all in-flight I/O operations to immediately unblock and return errors. Any
// subsequent uses of Conn will result in EBADF.
func (c *Conn) Close() error {
// The caller has expressed an intent to close the socket, so immediately
// increment s.closed to force further calls to result in EBADF before also
// closing the file descriptor to unblock any outstanding operations.
//
// Because other operations simply check for s.closed != 0, we will permit
// double Close, which would increment s.closed beyond 1.
if atomic.AddUint32(&c.closed, 1) != 1 {
// Multiple Close calls.
return nil
}
return os.NewSyscallError("close", c.fd.Close())
}
// CloseRead shuts down the reading side of the Conn. Most callers should just
// use Close.
func (c *Conn) CloseRead() error { return c.Shutdown(unix.SHUT_RD) }
// CloseWrite shuts down the writing side of the Conn. Most callers should just
// use Close.
func (c *Conn) CloseWrite() error { return c.Shutdown(unix.SHUT_WR) }
// Read reads directly from the underlying file descriptor.
func (c *Conn) Read(b []byte) (int, error) { return c.fd.Read(b) }
// ReadContext reads from the underlying file descriptor with added support for
// context cancelation.
func (c *Conn) ReadContext(ctx context.Context, b []byte) (int, error) {
if c.facts.isStream && len(b) > maxRW {
b = b[:maxRW]
}
n, err := readT(c, ctx, "read", func(fd int) (int, error) {
return unix.Read(fd, b)
})
if n == 0 && err == nil && c.facts.zeroReadIsEOF {
return 0, io.EOF
}
return n, os.NewSyscallError("read", err)
}
// Write writes directly to the underlying file descriptor.
func (c *Conn) Write(b []byte) (int, error) { return c.fd.Write(b) }
// WriteContext writes to the underlying file descriptor with added support for
// context cancelation.
func (c *Conn) WriteContext(ctx context.Context, b []byte) (int, error) {
var (
n, nn int
err error
)
doErr := c.write(ctx, "write", func(fd int) error {
max := len(b)
if c.facts.isStream && max-nn > maxRW {
max = nn + maxRW
}
n, err = unix.Write(fd, b[nn:max])
if n > 0 {
nn += n
}
if nn == len(b) {
return err
}
if n == 0 && err == nil {
err = io.ErrUnexpectedEOF
return nil
}
return err
})
if doErr != nil {
return 0, doErr
}
return nn, os.NewSyscallError("write", err)
}
// SetDeadline sets both the read and write deadlines associated with the Conn.
func (c *Conn) SetDeadline(t time.Time) error { return c.fd.SetDeadline(t) }
// SetReadDeadline sets the read deadline associated with the Conn.
func (c *Conn) SetReadDeadline(t time.Time) error { return c.fd.SetReadDeadline(t) }
// SetWriteDeadline sets the write deadline associated with the Conn.
func (c *Conn) SetWriteDeadline(t time.Time) error { return c.fd.SetWriteDeadline(t) }
// ReadBuffer gets the size of the operating system's receive buffer associated
// with the Conn.
func (c *Conn) ReadBuffer() (int, error) {
return c.GetsockoptInt(unix.SOL_SOCKET, unix.SO_RCVBUF)
}
// WriteBuffer gets the size of the operating system's transmit buffer
// associated with the Conn.
func (c *Conn) WriteBuffer() (int, error) {
return c.GetsockoptInt(unix.SOL_SOCKET, unix.SO_SNDBUF)
}
// SetReadBuffer sets the size of the operating system's receive buffer
// associated with the Conn.
//
// When called with elevated privileges on Linux, the SO_RCVBUFFORCE option will
// be used to override operating system limits. Otherwise SO_RCVBUF is used
// (which obeys operating system limits).
func (c *Conn) SetReadBuffer(bytes int) error { return c.setReadBuffer(bytes) }
// SetWriteBuffer sets the size of the operating system's transmit buffer
// associated with the Conn.
//
// When called with elevated privileges on Linux, the SO_SNDBUFFORCE option will
// be used to override operating system limits. Otherwise SO_SNDBUF is used
// (which obeys operating system limits).
func (c *Conn) SetWriteBuffer(bytes int) error { return c.setWriteBuffer(bytes) }
// SyscallConn returns a raw network connection. This implements the
// syscall.Conn interface.
//
// SyscallConn is intended for advanced use cases, such as getting and setting
// arbitrary socket options using the socket's file descriptor. If possible,
// those operations should be performed using methods on Conn instead.
//
// Once invoked, it is the caller's responsibility to ensure that operations
// performed using Conn and the syscall.RawConn do not conflict with each other.
func (c *Conn) SyscallConn() (syscall.RawConn, error) {
if atomic.LoadUint32(&c.closed) != 0 {
return nil, os.NewSyscallError("syscallconn", unix.EBADF)
}
// TODO(mdlayher): mutex or similar to enforce syscall.RawConn contract of
// FD remaining valid for duration of calls?
return c.rc, nil
}
// Socket wraps the socket(2) system call to produce a Conn. domain, typ, and
// proto are passed directly to socket(2), and name should be a unique name for
// the socket type such as "netlink" or "vsock".
//
// The cfg parameter specifies optional configuration for the Conn. If nil, no
// additional configuration will be applied.
//
// If the operating system supports SOCK_CLOEXEC and SOCK_NONBLOCK, they are
// automatically applied to typ to mirror the standard library's socket flag
// behaviors.
func Socket(domain, typ, proto int, name string, cfg *Config) (*Conn, error) {
if cfg == nil {
cfg = &Config{}
}
if cfg.NetNS == 0 {
// Non-Linux or no network namespace.
return socket(domain, typ, proto, name)
}
// Linux only: create Conn in the specified network namespace.
return withNetNS(cfg.NetNS, func() (*Conn, error) {
return socket(domain, typ, proto, name)
})
}
// socket is the internal, cross-platform entry point for socket(2).
func socket(domain, typ, proto int, name string) (*Conn, error) {
var (
fd int
err error
)
for {
fd, err = unix.Socket(domain, typ|socketFlags, proto)
switch {
case err == nil:
// Some OSes already set CLOEXEC with typ.
if !flagCLOEXEC {
unix.CloseOnExec(fd)
}
// No error, prepare the Conn.
return New(fd, name)
case !ready(err):
// System call interrupted or not ready, try again.
continue
case err == unix.EINVAL, err == unix.EPROTONOSUPPORT:
// On Linux, SOCK_NONBLOCK and SOCK_CLOEXEC were introduced in
// 2.6.27. On FreeBSD, both flags were introduced in FreeBSD 10.
// EINVAL and EPROTONOSUPPORT check for earlier versions of these
// OSes respectively.
//
// Mirror what the standard library does when creating file
// descriptors: avoid racing a fork/exec with the creation of new
// file descriptors, so that child processes do not inherit socket
// file descriptors unexpectedly.
//
// For a more thorough explanation, see similar work in the Go tree:
// func sysSocket in net/sock_cloexec.go, as well as the detailed
// comment in syscall/exec_unix.go.
syscall.ForkLock.RLock()
fd, err = unix.Socket(domain, typ, proto)
if err != nil {
syscall.ForkLock.RUnlock()
return nil, os.NewSyscallError("socket", err)
}
unix.CloseOnExec(fd)
syscall.ForkLock.RUnlock()
return New(fd, name)
default:
// Unhandled error.
return nil, os.NewSyscallError("socket", err)
}
}
}
// FileConn returns a copy of the network connection corresponding to the open
// file. It is the caller's responsibility to close the file when finished.
// Closing the Conn does not affect the File, and closing the File does not
// affect the Conn.
func FileConn(f *os.File, name string) (*Conn, error) {
// First we'll try to do fctnl(2) with F_DUPFD_CLOEXEC because we can dup
// the file descriptor and set the flag in one syscall.
fd, err := unix.FcntlInt(f.Fd(), unix.F_DUPFD_CLOEXEC, 0)
switch err {
case nil:
// OK, ready to set up non-blocking I/O.
return New(fd, name)
case unix.EINVAL:
// The kernel rejected our fcntl(2), fall back to separate dup(2) and
// setting close on exec.
//
// Mirror what the standard library does when creating file descriptors:
// avoid racing a fork/exec with the creation of new file descriptors,
// so that child processes do not inherit socket file descriptors
// unexpectedly.
syscall.ForkLock.RLock()
fd, err := unix.Dup(fd)
if err != nil {
syscall.ForkLock.RUnlock()
return nil, os.NewSyscallError("dup", err)
}
unix.CloseOnExec(fd)
syscall.ForkLock.RUnlock()
return New(fd, name)
default:
// Any other errors.
return nil, os.NewSyscallError("fcntl", err)
}
}
// New wraps an existing file descriptor to create a Conn. name should be a
// unique name for the socket type such as "netlink" or "vsock".
//
// Most callers should use Socket or FileConn to construct a Conn. New is
// intended for integrating with specific system calls which provide a file
// descriptor that supports asynchronous I/O. The file descriptor is immediately
// set to nonblocking mode and registered with Go's runtime network poller for
// future I/O operations.
//
// Unlike FileConn, New does not duplicate the existing file descriptor in any
// way. The returned Conn takes ownership of the underlying file descriptor.
func New(fd int, name string) (*Conn, error) {
// All Conn I/O is nonblocking for integration with Go's runtime network
// poller. Depending on the OS this might already be set but it can't hurt
// to set it again.
if err := unix.SetNonblock(fd, true); err != nil {
return nil, os.NewSyscallError("setnonblock", err)
}
// os.NewFile registers the non-blocking file descriptor with the runtime
// poller, which is then used for most subsequent operations except those
// that require raw I/O via SyscallConn.
//
// See also: https://golang.org/pkg/os/#NewFile
f := os.NewFile(uintptr(fd), name)
rc, err := f.SyscallConn()
if err != nil {
return nil, err
}
c := &Conn{
name: name,
fd: f,
rc: rc,
}
// Probe the file descriptor for socket settings.
sotype, err := c.GetsockoptInt(unix.SOL_SOCKET, unix.SO_TYPE)
switch {
case err == nil:
// File is a socket, check its properties.
c.facts = facts{
isStream: sotype == unix.SOCK_STREAM,
zeroReadIsEOF: sotype != unix.SOCK_DGRAM && sotype != unix.SOCK_RAW,
}
case errors.Is(err, unix.ENOTSOCK):
// File is not a socket, treat it as a regular file.
c.facts = facts{
isStream: true,
zeroReadIsEOF: true,
}
default:
return nil, err
}
return c, nil
}
// Low-level methods which provide raw system call access.
// Accept wraps accept(2) or accept4(2) depending on the operating system, but
// returns a Conn for the accepted connection rather than a raw file descriptor.
//
// If the operating system supports accept4(2) (which allows flags),
// SOCK_CLOEXEC and SOCK_NONBLOCK are automatically applied to flags to mirror
// the standard library's socket flag behaviors.
//
// If the operating system only supports accept(2) (which does not allow flags)
// and flags is not zero, an error will be returned.
//
// Accept obeys context cancelation and uses the deadline set on the context to
// cancel accepting the next connection. If a deadline is set on ctx, this
// deadline will override any previous deadlines set using SetDeadline or
// SetReadDeadline. Upon return, the read deadline is cleared.
func (c *Conn) Accept(ctx context.Context, flags int) (*Conn, unix.Sockaddr, error) {
type ret struct {
nfd int
sa unix.Sockaddr
}
r, err := readT(c, ctx, sysAccept, func(fd int) (ret, error) {
// Either accept(2) or accept4(2) depending on the OS.
nfd, sa, err := accept(fd, flags|socketFlags)
return ret{nfd, sa}, err
})
if err != nil {
// internal/poll, context error, or user function error.
return nil, nil, err
}
// Successfully accepted a connection, wrap it in a Conn for use by the
// caller.
ac, err := New(r.nfd, c.name)
if err != nil {
return nil, nil, err
}
return ac, r.sa, nil
}
// Bind wraps bind(2).
func (c *Conn) Bind(sa unix.Sockaddr) error {
return c.control("bind", func(fd int) error { return unix.Bind(fd, sa) })
}
// Connect wraps connect(2). In order to verify that the underlying socket is
// connected to a remote peer, Connect calls getpeername(2) and returns the
// unix.Sockaddr from that call.
//
// Connect obeys context cancelation and uses the deadline set on the context to
// cancel connecting to a remote peer. If a deadline is set on ctx, this
// deadline will override any previous deadlines set using SetDeadline or
// SetWriteDeadline. Upon return, the write deadline is cleared.
func (c *Conn) Connect(ctx context.Context, sa unix.Sockaddr) (unix.Sockaddr, error) {
const op = "connect"
// TODO(mdlayher): it would seem that trying to connect to unbound vsock
// listeners by calling Connect multiple times results in ECONNRESET for the
// first and nil error for subsequent calls. Do we need to memoize the
// error? Check what the stdlib behavior is.
var (
// Track progress between invocations of the write closure. We don't
// have an explicit WaitWrite call like internal/poll does, so we have
// to wait until the runtime calls the closure again to indicate we can
// write.
progress uint32
// Capture closure sockaddr and error.
rsa unix.Sockaddr
err error
)
doErr := c.write(ctx, op, func(fd int) error {
if atomic.AddUint32(&progress, 1) == 1 {
// First call: initiate connect.
return unix.Connect(fd, sa)
}
// Subsequent calls: the runtime network poller indicates fd is
// writable. Check for errno.
errno, gerr := c.GetsockoptInt(unix.SOL_SOCKET, unix.SO_ERROR)
if gerr != nil {
return gerr
}
if errno != 0 {
// Connection is still not ready or failed. If errno indicates
// the socket is not ready, we will wait for the next write
// event. Otherwise we propagate this errno back to the as a
// permanent error.
uerr := unix.Errno(errno)
err = uerr
return uerr
}
// According to internal/poll, it's possible for the runtime network
// poller to spuriously wake us and return errno 0 for SO_ERROR.
// Make sure we are actually connected to a peer.
peer, err := c.Getpeername()
if err != nil {
// internal/poll unconditionally goes back to WaitWrite.
// Synthesize an error that will do the same for us.
return unix.EAGAIN
}
// Connection complete.
rsa = peer
return nil
})
if doErr != nil {
// internal/poll or context error.
return nil, doErr
}
if err == unix.EISCONN {
// TODO(mdlayher): is this block obsolete with the addition of the
// getsockopt SO_ERROR check above?
//
// EISCONN is reported if the socket is already established and should
// not be treated as an error.
// - Darwin reports this for at least TCP sockets
// - Linux reports this for at least AF_VSOCK sockets
return rsa, nil
}
return rsa, os.NewSyscallError(op, err)
}
// Getsockname wraps getsockname(2).
func (c *Conn) Getsockname() (unix.Sockaddr, error) {
return controlT(c, "getsockname", unix.Getsockname)
}
// Getpeername wraps getpeername(2).
func (c *Conn) Getpeername() (unix.Sockaddr, error) {
return controlT(c, "getpeername", unix.Getpeername)
}
// GetsockoptICMPv6Filter wraps getsockopt(2) for *unix.ICMPv6Filter values.
func (c *Conn) GetsockoptICMPv6Filter(level, opt int) (*unix.ICMPv6Filter, error) {
return controlT(c, "getsockopt", func(fd int) (*unix.ICMPv6Filter, error) {
return unix.GetsockoptICMPv6Filter(fd, level, opt)
})
}
// GetsockoptInt wraps getsockopt(2) for integer values.
func (c *Conn) GetsockoptInt(level, opt int) (int, error) {
return controlT(c, "getsockopt", func(fd int) (int, error) {
return unix.GetsockoptInt(fd, level, opt)
})
}
// GetsockoptString wraps getsockopt(2) for string values.
func (c *Conn) GetsockoptString(level, opt int) (string, error) {
return controlT(c, "getsockopt", func(fd int) (string, error) {
return unix.GetsockoptString(fd, level, opt)
})
}
// Listen wraps listen(2).
func (c *Conn) Listen(n int) error {
return c.control("listen", func(fd int) error { return unix.Listen(fd, n) })
}
// Recvmsg wraps recvmsg(2).
func (c *Conn) Recvmsg(ctx context.Context, p, oob []byte, flags int) (int, int, int, unix.Sockaddr, error) {
type ret struct {
n, oobn, recvflags int
from unix.Sockaddr
}
r, err := readT(c, ctx, "recvmsg", func(fd int) (ret, error) {
n, oobn, recvflags, from, err := unix.Recvmsg(fd, p, oob, flags)
return ret{n, oobn, recvflags, from}, err
})
if r.n == 0 && err == nil && c.facts.zeroReadIsEOF {
return 0, 0, 0, nil, io.EOF
}
return r.n, r.oobn, r.recvflags, r.from, err
}
// Recvfrom wraps recvfrom(2).
func (c *Conn) Recvfrom(ctx context.Context, p []byte, flags int) (int, unix.Sockaddr, error) {
type ret struct {
n int
addr unix.Sockaddr
}
out, err := readT(c, ctx, "recvfrom", func(fd int) (ret, error) {
n, addr, err := unix.Recvfrom(fd, p, flags)
return ret{n, addr}, err
})
if out.n == 0 && err == nil && c.facts.zeroReadIsEOF {
return 0, nil, io.EOF
}
return out.n, out.addr, err
}
// Sendmsg wraps sendmsg(2).
func (c *Conn) Sendmsg(ctx context.Context, p, oob []byte, to unix.Sockaddr, flags int) (int, error) {
return writeT(c, ctx, "sendmsg", func(fd int) (int, error) {
return unix.SendmsgN(fd, p, oob, to, flags)
})
}
// Sendto wraps sendto(2).
func (c *Conn) Sendto(ctx context.Context, p []byte, flags int, to unix.Sockaddr) error {
return c.write(ctx, "sendto", func(fd int) error {
return unix.Sendto(fd, p, flags, to)
})
}
// SetsockoptICMPv6Filter wraps setsockopt(2) for *unix.ICMPv6Filter values.
func (c *Conn) SetsockoptICMPv6Filter(level, opt int, filter *unix.ICMPv6Filter) error {
return c.control("setsockopt", func(fd int) error {
return unix.SetsockoptICMPv6Filter(fd, level, opt, filter)
})
}
// SetsockoptInt wraps setsockopt(2) for integer values.
func (c *Conn) SetsockoptInt(level, opt, value int) error {
return c.control("setsockopt", func(fd int) error {
return unix.SetsockoptInt(fd, level, opt, value)
})
}
// SetsockoptString wraps setsockopt(2) for string values.
func (c *Conn) SetsockoptString(level, opt int, value string) error {
return c.control("setsockopt", func(fd int) error {
return unix.SetsockoptString(fd, level, opt, value)
})
}
// Shutdown wraps shutdown(2).
func (c *Conn) Shutdown(how int) error {
return c.control("shutdown", func(fd int) error { return unix.Shutdown(fd, how) })
}
// Conn low-level read/write/control functions. These functions mirror the
// syscall.RawConn APIs but the input closures return errors rather than
// booleans.
// read wraps readT to execute a function and capture its error result. This is
// a convenience wrapper for functions which don't return any extra values.
func (c *Conn) read(ctx context.Context, op string, f func(fd int) error) error {
_, err := readT(c, ctx, op, func(fd int) (struct{}, error) {
return struct{}{}, f(fd)
})
return err
}
// write executes f, a write function, against the associated file descriptor.
// op is used to create an *os.SyscallError if the file descriptor is closed.
func (c *Conn) write(ctx context.Context, op string, f func(fd int) error) error {
_, err := writeT(c, ctx, op, func(fd int) (struct{}, error) {
return struct{}{}, f(fd)
})
return err
}
// readT executes c.rc.Read for op using the input function, returning a newly
// allocated result T.
func readT[T any](c *Conn, ctx context.Context, op string, f func(fd int) (T, error)) (T, error) {
return rwT(c, rwContext[T]{
Context: ctx,
Type: read,
Op: op,
Do: f,
})
}
// writeT executes c.rc.Write for op using the input function, returning a newly
// allocated result T.
func writeT[T any](c *Conn, ctx context.Context, op string, f func(fd int) (T, error)) (T, error) {
return rwT(c, rwContext[T]{
Context: ctx,
Type: write,
Op: op,
Do: f,
})
}
// readWrite indicates if an operation intends to read or write.
type readWrite bool
// Possible readWrite values.
const (
read readWrite = false
write readWrite = true
)
// An rwContext provides arguments to rwT.
type rwContext[T any] struct {
// The caller's context passed for cancelation.
Context context.Context
// The type of an operation: read or write.
Type readWrite
// The name of the operation used in errors.
Op string
// The actual function to perform.
Do func(fd int) (T, error)
}
// rwT executes c.rc.Read or c.rc.Write (depending on the value of rw.Type) for
// rw.Op using the input function, returning a newly allocated result T.
//
// It obeys context cancelation and the rw.Context must not be nil.
func rwT[T any](c *Conn, rw rwContext[T]) (T, error) {
if atomic.LoadUint32(&c.closed) != 0 {
// If the file descriptor is already closed, do nothing.
return *new(T), os.NewSyscallError(rw.Op, unix.EBADF)
}
if err := rw.Context.Err(); err != nil {
// Early exit due to context cancel.
return *new(T), os.NewSyscallError(rw.Op, err)
}
var (
// The read or write function used to access the runtime network poller.
poll func(func(uintptr) bool) error
// The read or write function used to set the matching deadline.
deadline func(time.Time) error
)
if rw.Type == write {
poll = c.rc.Write
deadline = c.SetWriteDeadline
} else {
poll = c.rc.Read
deadline = c.SetReadDeadline
}
var (
// Whether or not the context carried a deadline we are actively using
// for cancelation.
setDeadline bool
// Signals for the cancelation watcher goroutine.
wg sync.WaitGroup
doneC = make(chan struct{})
// Atomic: reports whether we have to disarm the deadline.
needDisarm atomic.Bool
)
// On cancel, clean up the watcher.
defer func() {
close(doneC)
wg.Wait()
}()
if d, ok := rw.Context.Deadline(); ok {
// The context has an explicit deadline. We will use it for cancelation
// but disarm it after poll for the next call.
if err := deadline(d); err != nil {
return *new(T), err
}
setDeadline = true
needDisarm.Store(true)
} else {
// The context does not have an explicit deadline. We have to watch for
// cancelation so we can propagate that signal to immediately unblock
// the runtime network poller.
//
// TODO(mdlayher): is it possible to detect a background context vs a
// context with possible future cancel?
wg.Add(1)
go func() {
defer wg.Done()
select {
case <-rw.Context.Done():
// Cancel the operation. Make the caller disarm after poll
// returns.
needDisarm.Store(true)
_ = deadline(time.Unix(0, 1))
case <-doneC:
// Nothing to do.
}
}()
}
var (
t T
err error
)
pollErr := poll(func(fd uintptr) bool {
t, err = rw.Do(int(fd))
return ready(err)
})
if needDisarm.Load() {
_ = deadline(time.Time{})
}
if pollErr != nil {
if rw.Context.Err() != nil || (setDeadline && errors.Is(pollErr, os.ErrDeadlineExceeded)) {
// The caller canceled the operation or we set a deadline internally
// and it was reached.
//
// Unpack a plain context error. We wait for the context to be done
// to synchronize state externally. Otherwise we have noticed I/O
// timeout wakeups when we set a deadline but the context was not
// yet marked done.
<-rw.Context.Done()
return *new(T), os.NewSyscallError(rw.Op, rw.Context.Err())
}
// Error from syscall.RawConn methods. Conventionally the standard
// library does not wrap internal/poll errors in os.NewSyscallError.
return *new(T), pollErr
}
// Result from user function.
return t, os.NewSyscallError(rw.Op, err)
}
// control executes Conn.control for op using the input function.
func (c *Conn) control(op string, f func(fd int) error) error {
_, err := controlT(c, op, func(fd int) (struct{}, error) {
return struct{}{}, f(fd)
})
return err
}
// controlT executes c.rc.Control for op using the input function, returning a
// newly allocated result T.
func controlT[T any](c *Conn, op string, f func(fd int) (T, error)) (T, error) {
if atomic.LoadUint32(&c.closed) != 0 {
// If the file descriptor is already closed, do nothing.
return *new(T), os.NewSyscallError(op, unix.EBADF)
}
var (
t T
err error
)
doErr := c.rc.Control(func(fd uintptr) {
// Repeatedly attempt the syscall(s) invoked by f until completion is
// indicated by the return value of ready or the context is canceled.
//
// The last values for t and err are captured outside of the closure for
// use when the loop breaks.
for {
t, err = f(int(fd))
if ready(err) {
return
}
}
})
if doErr != nil {
// Error from syscall.RawConn methods. Conventionally the standard
// library does not wrap internal/poll errors in os.NewSyscallError.
return *new(T), doErr
}
// Result from user function.
return t, os.NewSyscallError(op, err)
}
// ready indicates readiness based on the value of err.
func ready(err error) bool {
switch err {
case unix.EAGAIN, unix.EINPROGRESS, unix.EINTR:
// When a socket is in non-blocking mode, we might see a variety of errors:
// - EAGAIN: most common case for a socket read not being ready
// - EINPROGRESS: reported by some sockets when first calling connect
// - EINTR: system call interrupted, more frequently occurs in Go 1.14+
// because goroutines can be asynchronously preempted
//
// Return false to let the poller wait for readiness. See the source code
// for internal/poll.FD.RawRead for more details.
return false
default:
// Ready regardless of whether there was an error or no error.
return true
}
}
// Darwin and FreeBSD can't read or write 2GB+ files at a time,
// even on 64-bit systems.
// The same is true of socket implementations on many systems.
// See golang.org/issue/7812 and golang.org/issue/16266.
// Use 1GB instead of, say, 2GB-1, to keep subsequent reads aligned.
const maxRW = 1 << 30

118
vendor/github.com/mdlayher/socket/conn_linux.go generated vendored Normal file
View File

@ -0,0 +1,118 @@
//go:build linux
// +build linux
package socket
import (
"context"
"os"
"unsafe"
"golang.org/x/net/bpf"
"golang.org/x/sys/unix"
)
// IoctlKCMClone wraps ioctl(2) for unix.KCMClone values, but returns a Conn
// rather than a raw file descriptor.
func (c *Conn) IoctlKCMClone() (*Conn, error) {
info, err := controlT(c, "ioctl", unix.IoctlKCMClone)
if err != nil {
return nil, err
}
// Successful clone, wrap in a Conn for use by the caller.
return New(int(info.Fd), c.name)
}
// IoctlKCMAttach wraps ioctl(2) for unix.KCMAttach values.
func (c *Conn) IoctlKCMAttach(info unix.KCMAttach) error {
return c.control("ioctl", func(fd int) error {
return unix.IoctlKCMAttach(fd, info)
})
}
// IoctlKCMUnattach wraps ioctl(2) for unix.KCMUnattach values.
func (c *Conn) IoctlKCMUnattach(info unix.KCMUnattach) error {
return c.control("ioctl", func(fd int) error {
return unix.IoctlKCMUnattach(fd, info)
})
}
// PidfdGetfd wraps pidfd_getfd(2) for a Conn which wraps a pidfd, but returns a
// Conn rather than a raw file descriptor.
func (c *Conn) PidfdGetfd(targetFD, flags int) (*Conn, error) {
outFD, err := controlT(c, "pidfd_getfd", func(fd int) (int, error) {
return unix.PidfdGetfd(fd, targetFD, flags)
})
if err != nil {
return nil, err
}
// Successful getfd, wrap in a Conn for use by the caller.
return New(outFD, c.name)
}
// PidfdSendSignal wraps pidfd_send_signal(2) for a Conn which wraps a Linux
// pidfd.
func (c *Conn) PidfdSendSignal(sig unix.Signal, info *unix.Siginfo, flags int) error {
return c.control("pidfd_send_signal", func(fd int) error {
return unix.PidfdSendSignal(fd, sig, info, flags)
})
}
// SetBPF attaches an assembled BPF program to a Conn.
func (c *Conn) SetBPF(filter []bpf.RawInstruction) error {
// We can't point to the first instruction in the array if no instructions
// are present.
if len(filter) == 0 {
return os.NewSyscallError("setsockopt", unix.EINVAL)
}
prog := unix.SockFprog{
Len: uint16(len(filter)),
Filter: (*unix.SockFilter)(unsafe.Pointer(&filter[0])),
}
return c.SetsockoptSockFprog(unix.SOL_SOCKET, unix.SO_ATTACH_FILTER, &prog)
}
// RemoveBPF removes a BPF filter from a Conn.
func (c *Conn) RemoveBPF() error {
// 0 argument is ignored.
return c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_DETACH_FILTER, 0)
}
// SetsockoptPacketMreq wraps setsockopt(2) for unix.PacketMreq values.
func (c *Conn) SetsockoptPacketMreq(level, opt int, mreq *unix.PacketMreq) error {
return c.control("setsockopt", func(fd int) error {
return unix.SetsockoptPacketMreq(fd, level, opt, mreq)
})
}
// SetsockoptSockFprog wraps setsockopt(2) for unix.SockFprog values.
func (c *Conn) SetsockoptSockFprog(level, opt int, fprog *unix.SockFprog) error {
return c.control("setsockopt", func(fd int) error {
return unix.SetsockoptSockFprog(fd, level, opt, fprog)
})
}
// GetsockoptTpacketStats wraps getsockopt(2) for unix.TpacketStats values.
func (c *Conn) GetsockoptTpacketStats(level, name int) (*unix.TpacketStats, error) {
return controlT(c, "getsockopt", func(fd int) (*unix.TpacketStats, error) {
return unix.GetsockoptTpacketStats(fd, level, name)
})
}
// GetsockoptTpacketStatsV3 wraps getsockopt(2) for unix.TpacketStatsV3 values.
func (c *Conn) GetsockoptTpacketStatsV3(level, name int) (*unix.TpacketStatsV3, error) {
return controlT(c, "getsockopt", func(fd int) (*unix.TpacketStatsV3, error) {
return unix.GetsockoptTpacketStatsV3(fd, level, name)
})
}
// Waitid wraps waitid(2).
func (c *Conn) Waitid(idType int, info *unix.Siginfo, options int, rusage *unix.Rusage) error {
return c.read(context.Background(), "waitid", func(fd int) error {
return unix.Waitid(idType, fd, info, options, rusage)
})
}

13
vendor/github.com/mdlayher/socket/doc.go generated vendored Normal file
View File

@ -0,0 +1,13 @@
// Package socket provides a low-level network connection type which integrates
// with Go's runtime network poller to provide asynchronous I/O and deadline
// support.
//
// This package focuses on UNIX-like operating systems which make use of BSD
// sockets system call APIs. It is meant to be used as a foundation for the
// creation of operating system-specific socket packages, for socket families
// such as Linux's AF_NETLINK, AF_PACKET, or AF_VSOCK. This package should not
// be used directly in end user applications.
//
// Any use of package socket should be guarded by build tags, as one would also
// use when importing the syscall or golang.org/x/sys packages.
package socket

150
vendor/github.com/mdlayher/socket/netns_linux.go generated vendored Normal file
View File

@ -0,0 +1,150 @@
//go:build linux
// +build linux
package socket
import (
"errors"
"fmt"
"os"
"runtime"
"golang.org/x/sync/errgroup"
"golang.org/x/sys/unix"
)
// errNetNSDisabled is returned when network namespaces are unavailable on
// a given system.
var errNetNSDisabled = errors.New("socket: Linux network namespaces are not enabled on this system")
// withNetNS invokes fn within the context of the network namespace specified by
// fd, while also managing the logic required to safely do so by manipulating
// thread-local state.
func withNetNS(fd int, fn func() (*Conn, error)) (*Conn, error) {
var (
eg errgroup.Group
conn *Conn
)
eg.Go(func() error {
// Retrieve and store the calling OS thread's network namespace so the
// thread can be reassigned to it after creating a socket in another network
// namespace.
runtime.LockOSThread()
ns, err := threadNetNS()
if err != nil {
// No thread-local manipulation, unlock.
runtime.UnlockOSThread()
return err
}
defer ns.Close()
// Beyond this point, the thread's network namespace is poisoned. Do not
// unlock the OS thread until all network namespace manipulation completes
// to avoid returning to the caller with altered thread-local state.
// Assign the current OS thread the goroutine is locked to to the given
// network namespace.
if err := ns.Set(fd); err != nil {
return err
}
// Attempt Conn creation and unconditionally restore the original namespace.
c, err := fn()
if nerr := ns.Restore(); nerr != nil {
// Failed to restore original namespace. Return an error and allow the
// runtime to terminate the thread.
if err == nil {
_ = c.Close()
}
return nerr
}
// No more thread-local state manipulation; return the new Conn.
runtime.UnlockOSThread()
conn = c
return nil
})
if err := eg.Wait(); err != nil {
return nil, err
}
return conn, nil
}
// A netNS is a handle that can manipulate network namespaces.
//
// Operations performed on a netNS must use runtime.LockOSThread before
// manipulating any network namespaces.
type netNS struct {
// The handle to a network namespace.
f *os.File
// Indicates if network namespaces are disabled on this system, and thus
// operations should become a no-op or return errors.
disabled bool
}
// threadNetNS constructs a netNS using the network namespace of the calling
// thread. If the namespace is not the default namespace, runtime.LockOSThread
// should be invoked first.
func threadNetNS() (*netNS, error) {
return fileNetNS(fmt.Sprintf("/proc/self/task/%d/ns/net", unix.Gettid()))
}
// fileNetNS opens file and creates a netNS. fileNetNS should only be called
// directly in tests.
func fileNetNS(file string) (*netNS, error) {
f, err := os.Open(file)
switch {
case err == nil:
return &netNS{f: f}, nil
case os.IsNotExist(err):
// Network namespaces are not enabled on this system. Use this signal
// to return errors elsewhere if the caller explicitly asks for a
// network namespace to be set.
return &netNS{disabled: true}, nil
default:
return nil, err
}
}
// Close releases the handle to a network namespace.
func (n *netNS) Close() error {
return n.do(func() error { return n.f.Close() })
}
// FD returns a file descriptor which represents the network namespace.
func (n *netNS) FD() int {
if n.disabled {
// No reasonable file descriptor value in this case, so specify a
// non-existent one.
return -1
}
return int(n.f.Fd())
}
// Restore restores the original network namespace for the calling thread.
func (n *netNS) Restore() error {
return n.do(func() error { return n.Set(n.FD()) })
}
// Set sets a new network namespace for the current thread using fd.
func (n *netNS) Set(fd int) error {
return n.do(func() error {
return os.NewSyscallError("setns", unix.Setns(fd, unix.CLONE_NEWNET))
})
}
// do runs fn if network namespaces are enabled on this system.
func (n *netNS) do(fn func() error) error {
if n.disabled {
return errNetNSDisabled
}
return fn()
}

14
vendor/github.com/mdlayher/socket/netns_others.go generated vendored Normal file
View File

@ -0,0 +1,14 @@
//go:build !linux
// +build !linux
package socket
import (
"fmt"
"runtime"
)
// withNetNS returns an error on non-Linux systems.
func withNetNS(_ int, _ func() (*Conn, error)) (*Conn, error) {
return nil, fmt.Errorf("socket: Linux network namespace support is not available on %s", runtime.GOOS)
}

24
vendor/github.com/mdlayher/socket/setbuffer_linux.go generated vendored Normal file
View File

@ -0,0 +1,24 @@
//go:build linux
// +build linux
package socket
import "golang.org/x/sys/unix"
// setReadBuffer wraps the SO_RCVBUF{,FORCE} setsockopt(2) options.
func (c *Conn) setReadBuffer(bytes int) error {
err := c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_RCVBUFFORCE, bytes)
if err != nil {
err = c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_RCVBUF, bytes)
}
return err
}
// setWriteBuffer wraps the SO_SNDBUF{,FORCE} setsockopt(2) options.
func (c *Conn) setWriteBuffer(bytes int) error {
err := c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_SNDBUFFORCE, bytes)
if err != nil {
err = c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_SNDBUF, bytes)
}
return err
}

16
vendor/github.com/mdlayher/socket/setbuffer_others.go generated vendored Normal file
View File

@ -0,0 +1,16 @@
//go:build !linux
// +build !linux
package socket
import "golang.org/x/sys/unix"
// setReadBuffer wraps the SO_RCVBUF setsockopt(2) option.
func (c *Conn) setReadBuffer(bytes int) error {
return c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_RCVBUF, bytes)
}
// setWriteBuffer wraps the SO_SNDBUF setsockopt(2) option.
func (c *Conn) setWriteBuffer(bytes int) error {
return c.SetsockoptInt(unix.SOL_SOCKET, unix.SO_SNDBUF, bytes)
}

View File

@ -0,0 +1,12 @@
//go:build !darwin
// +build !darwin
package socket
import "golang.org/x/sys/unix"
const (
// These operating systems support CLOEXEC and NONBLOCK socket options.
flagCLOEXEC = true
socketFlags = unix.SOCK_CLOEXEC | unix.SOCK_NONBLOCK
)

11
vendor/github.com/mdlayher/socket/typ_none.go generated vendored Normal file
View File

@ -0,0 +1,11 @@
//go:build darwin
// +build darwin
package socket
const (
// These operating systems do not support CLOEXEC and NONBLOCK socket
// options.
flagCLOEXEC = false
socketFlags = 0
)

36
vendor/github.com/pierrec/lz4/v4/.gitignore generated vendored Normal file
View File

@ -0,0 +1,36 @@
# Created by https://www.gitignore.io/api/macos
### macOS ###
*.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# End of https://www.gitignore.io/api/macos
cmd/*/*exe
.idea
fuzz/*.zip

28
vendor/github.com/pierrec/lz4/v4/LICENSE generated vendored Normal file
View File

@ -0,0 +1,28 @@
Copyright (c) 2015, Pierre Curto
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of xxHash nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

92
vendor/github.com/pierrec/lz4/v4/README.md generated vendored Normal file
View File

@ -0,0 +1,92 @@
# lz4 : LZ4 compression in pure Go
[![Go Reference](https://pkg.go.dev/badge/github.com/pierrec/lz4/v4.svg)](https://pkg.go.dev/github.com/pierrec/lz4/v4)
[![CI](https://github.com/pierrec/lz4/workflows/ci/badge.svg)](https://github.com/pierrec/lz4/actions)
[![Go Report Card](https://goreportcard.com/badge/github.com/pierrec/lz4)](https://goreportcard.com/report/github.com/pierrec/lz4)
[![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/pierrec/lz4.svg?style=social)](https://github.com/pierrec/lz4/tags)
## Overview
This package provides a streaming interface to [LZ4 data streams](http://fastcompression.blogspot.fr/2013/04/lz4-streaming-format-final.html) as well as low level compress and uncompress functions for LZ4 data blocks.
The implementation is based on the reference C [one](https://github.com/lz4/lz4).
## Install
Assuming you have the go toolchain installed:
```
go get github.com/pierrec/lz4/v4
```
There is a command line interface tool to compress and decompress LZ4 files.
```
go install github.com/pierrec/lz4/v4/cmd/lz4c@latest
```
Usage
```
Usage of lz4c:
-version
print the program version
Subcommands:
Compress the given files or from stdin to stdout.
compress [arguments] [<file name> ...]
-bc
enable block checksum
-l int
compression level (0=fastest)
-sc
disable stream checksum
-size string
block max size [64K,256K,1M,4M] (default "4M")
Uncompress the given files or from stdin to stdout.
uncompress [arguments] [<file name> ...]
```
## Example
```
// Compress and uncompress an input string.
s := "hello world"
r := strings.NewReader(s)
// The pipe will uncompress the data from the writer.
pr, pw := io.Pipe()
zw := lz4.NewWriter(pw)
zr := lz4.NewReader(pr)
go func() {
// Compress the input string.
_, _ = io.Copy(zw, r)
_ = zw.Close() // Make sure the writer is closed
_ = pw.Close() // Terminate the pipe
}()
_, _ = io.Copy(os.Stdout, zr)
// Output:
// hello world
```
## Contributing
Contributions are very welcome for bug fixing, performance improvements...!
- Open an issue with a proper description
- Send a pull request with appropriate test case(s)
## Contributors
Thanks to all [contributors](https://github.com/pierrec/lz4/graphs/contributors) so far!
Special thanks to [@Zariel](https://github.com/Zariel) for his asm implementation of the decoder.
Special thanks to [@greatroar](https://github.com/greatroar) for his work on the asm implementations of the decoder for amd64 and arm64.
Special thanks to [@klauspost](https://github.com/klauspost) for his work on optimizing the code.

222
vendor/github.com/pierrec/lz4/v4/compressing_reader.go generated vendored Normal file
View File

@ -0,0 +1,222 @@
package lz4
import (
"errors"
"io"
"github.com/pierrec/lz4/v4/internal/lz4block"
"github.com/pierrec/lz4/v4/internal/lz4errors"
"github.com/pierrec/lz4/v4/internal/lz4stream"
)
type crState int
const (
crStateInitial crState = iota
crStateReading
crStateFlushing
crStateDone
)
type CompressingReader struct {
state crState
src io.ReadCloser // source reader
level lz4block.CompressionLevel // how hard to try
frame *lz4stream.Frame // frame being built
in []byte
out ovWriter
handler func(int)
}
// NewCompressingReader creates a reader which reads compressed data from
// raw stream. This makes it a logical opposite of a normal lz4.Reader.
// We require an io.ReadCloser as an underlying source for compatibility
// with Go's http.Request.
func NewCompressingReader(src io.ReadCloser) *CompressingReader {
zrd := &CompressingReader {
frame: lz4stream.NewFrame(),
}
_ = zrd.Apply(DefaultBlockSizeOption, DefaultChecksumOption, defaultOnBlockDone)
zrd.Reset(src)
return zrd
}
// Source exposes the underlying source stream for introspection and control.
func (zrd *CompressingReader) Source() io.ReadCloser {
return zrd.src
}
// Close simply invokes the underlying stream Close method. This method is
// provided for the benefit of Go http client/server, which relies on Close
// for goroutine termination.
func (zrd *CompressingReader) Close() error {
return zrd.src.Close()
}
// Apply applies useful options to the lz4 encoder.
func (zrd *CompressingReader) Apply(options ...Option) (err error) {
if zrd.state != crStateInitial {
return lz4errors.ErrOptionClosedOrError
}
zrd.Reset(zrd.src)
for _, o := range options {
if err = o(zrd); err != nil {
return
}
}
return
}
func (*CompressingReader) private() {}
func (zrd *CompressingReader) init() error {
zrd.frame.InitW(&zrd.out, 1, false)
size := zrd.frame.Descriptor.Flags.BlockSizeIndex()
zrd.in = size.Get()
return zrd.frame.Descriptor.Write(zrd.frame, &zrd.out)
}
// Read allows reading of lz4 compressed data
func (zrd *CompressingReader) Read(p []byte) (n int, err error) {
defer func() {
if err != nil {
zrd.state = crStateDone
}
}()
if !zrd.out.reset(p) {
return len(p), nil
}
switch zrd.state {
case crStateInitial:
err = zrd.init()
if err != nil {
return
}
zrd.state = crStateReading
case crStateDone:
return 0, errors.New("This reader is done")
case crStateFlushing:
if zrd.out.dataPos > 0 {
n = zrd.out.dataPos
zrd.out.data = nil
zrd.out.dataPos = 0
return
} else {
zrd.state = crStateDone
return 0, io.EOF
}
}
for zrd.state == crStateReading {
block := zrd.frame.Blocks.Block
var rCount int
rCount, err = io.ReadFull(zrd.src, zrd.in)
switch err {
case nil:
err = block.Compress(
zrd.frame, zrd.in[ : rCount], zrd.level,
).Write(zrd.frame, &zrd.out)
zrd.handler(len(block.Data))
if err != nil {
return
}
if zrd.out.dataPos == len(zrd.out.data) {
n = zrd.out.dataPos
zrd.out.dataPos = 0
zrd.out.data = nil
return
}
case io.EOF, io.ErrUnexpectedEOF: // read may be partial
if rCount > 0 {
err = block.Compress(
zrd.frame, zrd.in[ : rCount], zrd.level,
).Write(zrd.frame, &zrd.out)
zrd.handler(len(block.Data))
if err != nil {
return
}
}
err = zrd.frame.CloseW(&zrd.out, 1)
if err != nil {
return
}
zrd.state = crStateFlushing
n = zrd.out.dataPos
zrd.out.dataPos = 0
zrd.out.data = nil
return
default:
return
}
}
err = lz4errors.ErrInternalUnhandledState
return
}
// Reset makes the stream usable again; mostly handy to reuse lz4 encoder
// instances.
func (zrd *CompressingReader) Reset(src io.ReadCloser) {
zrd.frame.Reset(1)
zrd.state = crStateInitial
zrd.src = src
zrd.out.clear()
}
type ovWriter struct {
data []byte
ov []byte
dataPos int
ovPos int
}
func (wr *ovWriter) Write(p []byte) (n int, err error) {
count := copy(wr.data[wr.dataPos : ], p)
wr.dataPos += count
if count < len(p) {
wr.ov = append(wr.ov, p[count : ]...)
}
return len(p), nil
}
func (wr *ovWriter) reset(out []byte) bool {
ovRem := len(wr.ov) - wr.ovPos
if ovRem >= len(out) {
wr.ovPos += copy(out, wr.ov[wr.ovPos : ])
return false
}
if ovRem > 0 {
copy(out, wr.ov[wr.ovPos : ])
wr.ov = wr.ov[ : 0]
wr.ovPos = 0
wr.dataPos = ovRem
} else if wr.ovPos > 0 {
wr.ov = wr.ov[ : 0]
wr.ovPos = 0
wr.dataPos = 0
}
wr.data = out
return true
}
func (wr *ovWriter) clear() {
wr.data = nil
wr.dataPos = 0
wr.ov = wr.ov[ : 0]
wr.ovPos = 0
}

View File

@ -0,0 +1,481 @@
package lz4block
import (
"encoding/binary"
"math/bits"
"sync"
"github.com/pierrec/lz4/v4/internal/lz4errors"
)
const (
// The following constants are used to setup the compression algorithm.
minMatch = 4 // the minimum size of the match sequence size (4 bytes)
winSizeLog = 16 // LZ4 64Kb window size limit
winSize = 1 << winSizeLog
winMask = winSize - 1 // 64Kb window of previous data for dependent blocks
// hashLog determines the size of the hash table used to quickly find a previous match position.
// Its value influences the compression speed and memory usage, the lower the faster,
// but at the expense of the compression ratio.
// 16 seems to be the best compromise for fast compression.
hashLog = 16
htSize = 1 << hashLog
mfLimit = 10 + minMatch // The last match cannot start within the last 14 bytes.
)
func recoverBlock(e *error) {
if r := recover(); r != nil && *e == nil {
*e = lz4errors.ErrInvalidSourceShortBuffer
}
}
// blockHash hashes the lower 6 bytes into a value < htSize.
func blockHash(x uint64) uint32 {
const prime6bytes = 227718039650203
return uint32(((x << (64 - 48)) * prime6bytes) >> (64 - hashLog))
}
func CompressBlockBound(n int) int {
return n + n/255 + 16
}
func UncompressBlock(src, dst, dict []byte) (int, error) {
if len(src) == 0 {
return 0, nil
}
if di := decodeBlock(dst, src, dict); di >= 0 {
return di, nil
}
return 0, lz4errors.ErrInvalidSourceShortBuffer
}
type Compressor struct {
// Offsets are at most 64kiB, so we can store only the lower 16 bits of
// match positions: effectively, an offset from some 64kiB block boundary.
//
// When we retrieve such an offset, we interpret it as relative to the last
// block boundary si &^ 0xffff, or the one before, (si &^ 0xffff) - 0x10000,
// depending on which of these is inside the current window. If a table
// entry was generated more than 64kiB back in the input, we find out by
// inspecting the input stream.
table [htSize]uint16
// Bitmap indicating which positions in the table are in use.
// This allows us to quickly reset the table for reuse,
// without having to zero everything.
inUse [htSize / 32]uint32
}
// Get returns the position of a presumptive match for the hash h.
// The match may be a false positive due to a hash collision or an old entry.
// If si < winSize, the return value may be negative.
func (c *Compressor) get(h uint32, si int) int {
h &= htSize - 1
i := 0
if c.inUse[h/32]&(1<<(h%32)) != 0 {
i = int(c.table[h])
}
i += si &^ winMask
if i >= si {
// Try previous 64kiB block (negative when in first block).
i -= winSize
}
return i
}
func (c *Compressor) put(h uint32, si int) {
h &= htSize - 1
c.table[h] = uint16(si)
c.inUse[h/32] |= 1 << (h % 32)
}
func (c *Compressor) reset() { c.inUse = [htSize / 32]uint32{} }
var compressorPool = sync.Pool{New: func() interface{} { return new(Compressor) }}
func CompressBlock(src, dst []byte) (int, error) {
c := compressorPool.Get().(*Compressor)
n, err := c.CompressBlock(src, dst)
compressorPool.Put(c)
return n, err
}
func (c *Compressor) CompressBlock(src, dst []byte) (int, error) {
// Zero out reused table to avoid non-deterministic output (issue #65).
c.reset()
// Return 0, nil only if the destination buffer size is < CompressBlockBound.
isNotCompressible := len(dst) < CompressBlockBound(len(src))
// adaptSkipLog sets how quickly the compressor begins skipping blocks when data is incompressible.
// This significantly speeds up incompressible data and usually has very small impact on compression.
// bytes to skip = 1 + (bytes since last match >> adaptSkipLog)
const adaptSkipLog = 7
// si: Current position of the search.
// anchor: Position of the current literals.
var si, di, anchor int
sn := len(src) - mfLimit
if sn <= 0 {
goto lastLiterals
}
// Fast scan strategy: the hash table only stores the last 4 bytes sequences.
for si < sn {
// Hash the next 6 bytes (sequence)...
match := binary.LittleEndian.Uint64(src[si:])
h := blockHash(match)
h2 := blockHash(match >> 8)
// We check a match at s, s+1 and s+2 and pick the first one we get.
// Checking 3 only requires us to load the source one.
ref := c.get(h, si)
ref2 := c.get(h2, si+1)
c.put(h, si)
c.put(h2, si+1)
offset := si - ref
if offset <= 0 || offset >= winSize || uint32(match) != binary.LittleEndian.Uint32(src[ref:]) {
// No match. Start calculating another hash.
// The processor can usually do this out-of-order.
h = blockHash(match >> 16)
ref3 := c.get(h, si+2)
// Check the second match at si+1
si += 1
offset = si - ref2
if offset <= 0 || offset >= winSize || uint32(match>>8) != binary.LittleEndian.Uint32(src[ref2:]) {
// No match. Check the third match at si+2
si += 1
offset = si - ref3
c.put(h, si)
if offset <= 0 || offset >= winSize || uint32(match>>16) != binary.LittleEndian.Uint32(src[ref3:]) {
// Skip one extra byte (at si+3) before we check 3 matches again.
si += 2 + (si-anchor)>>adaptSkipLog
continue
}
}
}
// Match found.
lLen := si - anchor // Literal length.
// We already matched 4 bytes.
mLen := 4
// Extend backwards if we can, reducing literals.
tOff := si - offset - 1
for lLen > 0 && tOff >= 0 && src[si-1] == src[tOff] {
si--
tOff--
lLen--
mLen++
}
// Add the match length, so we continue search at the end.
// Use mLen to store the offset base.
si, mLen = si+mLen, si+minMatch
// Find the longest match by looking by batches of 8 bytes.
for si+8 <= sn {
x := binary.LittleEndian.Uint64(src[si:]) ^ binary.LittleEndian.Uint64(src[si-offset:])
if x == 0 {
si += 8
} else {
// Stop is first non-zero byte.
si += bits.TrailingZeros64(x) >> 3
break
}
}
mLen = si - mLen
if di >= len(dst) {
return 0, lz4errors.ErrInvalidSourceShortBuffer
}
if mLen < 0xF {
dst[di] = byte(mLen)
} else {
dst[di] = 0xF
}
// Encode literals length.
if lLen < 0xF {
dst[di] |= byte(lLen << 4)
} else {
dst[di] |= 0xF0
di++
l := lLen - 0xF
for ; l >= 0xFF && di < len(dst); l -= 0xFF {
dst[di] = 0xFF
di++
}
if di >= len(dst) {
return 0, lz4errors.ErrInvalidSourceShortBuffer
}
dst[di] = byte(l)
}
di++
// Literals.
if di+lLen > len(dst) {
return 0, lz4errors.ErrInvalidSourceShortBuffer
}
copy(dst[di:di+lLen], src[anchor:anchor+lLen])
di += lLen + 2
anchor = si
// Encode offset.
if di > len(dst) {
return 0, lz4errors.ErrInvalidSourceShortBuffer
}
dst[di-2], dst[di-1] = byte(offset), byte(offset>>8)
// Encode match length part 2.
if mLen >= 0xF {
for mLen -= 0xF; mLen >= 0xFF && di < len(dst); mLen -= 0xFF {
dst[di] = 0xFF
di++
}
if di >= len(dst) {
return 0, lz4errors.ErrInvalidSourceShortBuffer
}
dst[di] = byte(mLen)
di++
}
// Check if we can load next values.
if si >= sn {
break
}
// Hash match end-2
h = blockHash(binary.LittleEndian.Uint64(src[si-2:]))
c.put(h, si-2)
}
lastLiterals:
if isNotCompressible && anchor == 0 {
// Incompressible.
return 0, nil
}
// Last literals.
if di >= len(dst) {
return 0, lz4errors.ErrInvalidSourceShortBuffer
}
lLen := len(src) - anchor
if lLen < 0xF {
dst[di] = byte(lLen << 4)
} else {
dst[di] = 0xF0
di++
for lLen -= 0xF; lLen >= 0xFF && di < len(dst); lLen -= 0xFF {
dst[di] = 0xFF
di++
}
if di >= len(dst) {
return 0, lz4errors.ErrInvalidSourceShortBuffer
}
dst[di] = byte(lLen)
}
di++
// Write the last literals.
if isNotCompressible && di >= anchor {
// Incompressible.
return 0, nil
}
if di+len(src)-anchor > len(dst) {
return 0, lz4errors.ErrInvalidSourceShortBuffer
}
di += copy(dst[di:di+len(src)-anchor], src[anchor:])
return di, nil
}
// blockHash hashes 4 bytes into a value < winSize.
func blockHashHC(x uint32) uint32 {
const hasher uint32 = 2654435761 // Knuth multiplicative hash.
return x * hasher >> (32 - winSizeLog)
}
type CompressorHC struct {
// hashTable: stores the last position found for a given hash
// chainTable: stores previous positions for a given hash
hashTable, chainTable [htSize]int
needsReset bool
}
var compressorHCPool = sync.Pool{New: func() interface{} { return new(CompressorHC) }}
func CompressBlockHC(src, dst []byte, depth CompressionLevel) (int, error) {
c := compressorHCPool.Get().(*CompressorHC)
n, err := c.CompressBlock(src, dst, depth)
compressorHCPool.Put(c)
return n, err
}
func (c *CompressorHC) CompressBlock(src, dst []byte, depth CompressionLevel) (_ int, err error) {
if c.needsReset {
// Zero out reused table to avoid non-deterministic output (issue #65).
c.hashTable = [htSize]int{}
c.chainTable = [htSize]int{}
}
c.needsReset = true // Only false on first call.
defer recoverBlock(&err)
// Return 0, nil only if the destination buffer size is < CompressBlockBound.
isNotCompressible := len(dst) < CompressBlockBound(len(src))
// adaptSkipLog sets how quickly the compressor begins skipping blocks when data is incompressible.
// This significantly speeds up incompressible data and usually has very small impact on compression.
// bytes to skip = 1 + (bytes since last match >> adaptSkipLog)
const adaptSkipLog = 7
var si, di, anchor int
sn := len(src) - mfLimit
if sn <= 0 {
goto lastLiterals
}
if depth == 0 {
depth = winSize
}
for si < sn {
// Hash the next 4 bytes (sequence).
match := binary.LittleEndian.Uint32(src[si:])
h := blockHashHC(match)
// Follow the chain until out of window and give the longest match.
mLen := 0
offset := 0
for next, try := c.hashTable[h], depth; try > 0 && next > 0 && si-next < winSize; next, try = c.chainTable[next&winMask], try-1 {
// The first (mLen==0) or next byte (mLen>=minMatch) at current match length
// must match to improve on the match length.
if src[next+mLen] != src[si+mLen] {
continue
}
ml := 0
// Compare the current position with a previous with the same hash.
for ml < sn-si {
x := binary.LittleEndian.Uint64(src[next+ml:]) ^ binary.LittleEndian.Uint64(src[si+ml:])
if x == 0 {
ml += 8
} else {
// Stop is first non-zero byte.
ml += bits.TrailingZeros64(x) >> 3
break
}
}
if ml < minMatch || ml <= mLen {
// Match too small (<minMath) or smaller than the current match.
continue
}
// Found a longer match, keep its position and length.
mLen = ml
offset = si - next
// Try another previous position with the same hash.
}
c.chainTable[si&winMask] = c.hashTable[h]
c.hashTable[h] = si
// No match found.
if mLen == 0 {
si += 1 + (si-anchor)>>adaptSkipLog
continue
}
// Match found.
// Update hash/chain tables with overlapping bytes:
// si already hashed, add everything from si+1 up to the match length.
winStart := si + 1
if ws := si + mLen - winSize; ws > winStart {
winStart = ws
}
for si, ml := winStart, si+mLen; si < ml; {
match >>= 8
match |= uint32(src[si+3]) << 24
h := blockHashHC(match)
c.chainTable[si&winMask] = c.hashTable[h]
c.hashTable[h] = si
si++
}
lLen := si - anchor
si += mLen
mLen -= minMatch // Match length does not include minMatch.
if mLen < 0xF {
dst[di] = byte(mLen)
} else {
dst[di] = 0xF
}
// Encode literals length.
if lLen < 0xF {
dst[di] |= byte(lLen << 4)
} else {
dst[di] |= 0xF0
di++
l := lLen - 0xF
for ; l >= 0xFF; l -= 0xFF {
dst[di] = 0xFF
di++
}
dst[di] = byte(l)
}
di++
// Literals.
copy(dst[di:di+lLen], src[anchor:anchor+lLen])
di += lLen
anchor = si
// Encode offset.
di += 2
dst[di-2], dst[di-1] = byte(offset), byte(offset>>8)
// Encode match length part 2.
if mLen >= 0xF {
for mLen -= 0xF; mLen >= 0xFF; mLen -= 0xFF {
dst[di] = 0xFF
di++
}
dst[di] = byte(mLen)
di++
}
}
if isNotCompressible && anchor == 0 {
// Incompressible.
return 0, nil
}
// Last literals.
lastLiterals:
lLen := len(src) - anchor
if lLen < 0xF {
dst[di] = byte(lLen << 4)
} else {
dst[di] = 0xF0
di++
lLen -= 0xF
for ; lLen >= 0xFF; lLen -= 0xFF {
dst[di] = 0xFF
di++
}
dst[di] = byte(lLen)
}
di++
// Write the last literals.
if isNotCompressible && di >= anchor {
// Incompressible.
return 0, nil
}
di += copy(dst[di:di+len(src)-anchor], src[anchor:])
return di, nil
}

View File

@ -0,0 +1,87 @@
// Package lz4block provides LZ4 BlockSize types and pools of buffers.
package lz4block
import "sync"
const (
Block64Kb uint32 = 1 << (16 + iota*2)
Block256Kb
Block1Mb
Block4Mb
Block8Mb = 2 * Block4Mb
)
var (
BlockPool64K = sync.Pool{New: func() interface{} { return make([]byte, Block64Kb) }}
BlockPool256K = sync.Pool{New: func() interface{} { return make([]byte, Block256Kb) }}
BlockPool1M = sync.Pool{New: func() interface{} { return make([]byte, Block1Mb) }}
BlockPool4M = sync.Pool{New: func() interface{} { return make([]byte, Block4Mb) }}
BlockPool8M = sync.Pool{New: func() interface{} { return make([]byte, Block8Mb) }}
)
func Index(b uint32) BlockSizeIndex {
switch b {
case Block64Kb:
return 4
case Block256Kb:
return 5
case Block1Mb:
return 6
case Block4Mb:
return 7
case Block8Mb: // only valid in legacy mode
return 3
}
return 0
}
func IsValid(b uint32) bool {
return Index(b) > 0
}
type BlockSizeIndex uint8
func (b BlockSizeIndex) IsValid() bool {
switch b {
case 4, 5, 6, 7:
return true
}
return false
}
func (b BlockSizeIndex) Get() []byte {
var buf interface{}
switch b {
case 4:
buf = BlockPool64K.Get()
case 5:
buf = BlockPool256K.Get()
case 6:
buf = BlockPool1M.Get()
case 7:
buf = BlockPool4M.Get()
case 3:
buf = BlockPool8M.Get()
}
return buf.([]byte)
}
func Put(buf []byte) {
// Safeguard: do not allow invalid buffers.
switch c := cap(buf); uint32(c) {
case Block64Kb:
BlockPool64K.Put(buf[:c])
case Block256Kb:
BlockPool256K.Put(buf[:c])
case Block1Mb:
BlockPool1M.Put(buf[:c])
case Block4Mb:
BlockPool4M.Put(buf[:c])
case Block8Mb:
BlockPool8M.Put(buf[:c])
}
}
type CompressionLevel uint32
const Fast CompressionLevel = 0

View File

@ -0,0 +1,448 @@
// +build !appengine
// +build gc
// +build !noasm
#include "go_asm.h"
#include "textflag.h"
// AX scratch
// BX scratch
// CX literal and match lengths
// DX token, match offset
//
// DI &dst
// SI &src
// R8 &dst + len(dst)
// R9 &src + len(src)
// R11 &dst
// R12 short output end
// R13 short input end
// R14 &dict
// R15 len(dict)
// func decodeBlock(dst, src, dict []byte) int
TEXT ·decodeBlock(SB), NOSPLIT, $48-80
MOVQ dst_base+0(FP), DI
MOVQ DI, R11
MOVQ dst_len+8(FP), R8
ADDQ DI, R8
MOVQ src_base+24(FP), SI
MOVQ src_len+32(FP), R9
CMPQ R9, $0
JE err_corrupt
ADDQ SI, R9
MOVQ dict_base+48(FP), R14
MOVQ dict_len+56(FP), R15
// shortcut ends
// short output end
MOVQ R8, R12
SUBQ $32, R12
// short input end
MOVQ R9, R13
SUBQ $16, R13
XORL CX, CX
loop:
// token := uint32(src[si])
MOVBLZX (SI), DX
INCQ SI
// lit_len = token >> 4
// if lit_len > 0
// CX = lit_len
MOVL DX, CX
SHRL $4, CX
// if lit_len != 0xF
CMPL CX, $0xF
JEQ lit_len_loop
CMPQ DI, R12
JAE copy_literal
CMPQ SI, R13
JAE copy_literal
// copy shortcut
// A two-stage shortcut for the most common case:
// 1) If the literal length is 0..14, and there is enough space,
// enter the shortcut and copy 16 bytes on behalf of the literals
// (in the fast mode, only 8 bytes can be safely copied this way).
// 2) Further if the match length is 4..18, copy 18 bytes in a similar
// manner; but we ensure that there's enough space in the output for
// those 18 bytes earlier, upon entering the shortcut (in other words,
// there is a combined check for both stages).
// copy literal
MOVOU (SI), X0
MOVOU X0, (DI)
ADDQ CX, DI
ADDQ CX, SI
MOVL DX, CX
ANDL $0xF, CX
// The second stage: prepare for match copying, decode full info.
// If it doesn't work out, the info won't be wasted.
// offset := uint16(data[:2])
MOVWLZX (SI), DX
TESTL DX, DX
JE err_corrupt
ADDQ $2, SI
JC err_short_buf
MOVQ DI, AX
SUBQ DX, AX
JC err_corrupt
CMPQ AX, DI
JA err_short_buf
// if we can't do the second stage then jump straight to read the
// match length, we already have the offset.
CMPL CX, $0xF
JEQ match_len_loop_pre
CMPL DX, $8
JLT match_len_loop_pre
CMPQ AX, R11
JB match_len_loop_pre
// memcpy(op + 0, match + 0, 8);
MOVQ (AX), BX
MOVQ BX, (DI)
// memcpy(op + 8, match + 8, 8);
MOVQ 8(AX), BX
MOVQ BX, 8(DI)
// memcpy(op +16, match +16, 2);
MOVW 16(AX), BX
MOVW BX, 16(DI)
LEAQ const_minMatch(DI)(CX*1), DI
// shortcut complete, load next token
JMP loopcheck
// Read the rest of the literal length:
// do { BX = src[si++]; lit_len += BX } while (BX == 0xFF).
lit_len_loop:
CMPQ SI, R9
JAE err_short_buf
MOVBLZX (SI), BX
INCQ SI
ADDQ BX, CX
CMPB BX, $0xFF
JE lit_len_loop
copy_literal:
// bounds check src and dst
MOVQ SI, AX
ADDQ CX, AX
JC err_short_buf
CMPQ AX, R9
JA err_short_buf
MOVQ DI, BX
ADDQ CX, BX
JC err_short_buf
CMPQ BX, R8
JA err_short_buf
// Copy literals of <=48 bytes through the XMM registers.
CMPQ CX, $48
JGT memmove_lit
// if len(dst[di:]) < 48
MOVQ R8, AX
SUBQ DI, AX
CMPQ AX, $48
JLT memmove_lit
// if len(src[si:]) < 48
MOVQ R9, BX
SUBQ SI, BX
CMPQ BX, $48
JLT memmove_lit
MOVOU (SI), X0
MOVOU 16(SI), X1
MOVOU 32(SI), X2
MOVOU X0, (DI)
MOVOU X1, 16(DI)
MOVOU X2, 32(DI)
ADDQ CX, SI
ADDQ CX, DI
JMP finish_lit_copy
memmove_lit:
// memmove(to, from, len)
MOVQ DI, 0(SP)
MOVQ SI, 8(SP)
MOVQ CX, 16(SP)
// Spill registers. Increment SI, DI now so we don't need to save CX.
ADDQ CX, DI
ADDQ CX, SI
MOVQ DI, 24(SP)
MOVQ SI, 32(SP)
MOVL DX, 40(SP)
CALL runtime·memmove(SB)
// restore registers
MOVQ 24(SP), DI
MOVQ 32(SP), SI
MOVL 40(SP), DX
// recalc initial values
MOVQ dst_base+0(FP), R8
MOVQ R8, R11
ADDQ dst_len+8(FP), R8
MOVQ src_base+24(FP), R9
ADDQ src_len+32(FP), R9
MOVQ dict_base+48(FP), R14
MOVQ dict_len+56(FP), R15
MOVQ R8, R12
SUBQ $32, R12
MOVQ R9, R13
SUBQ $16, R13
finish_lit_copy:
// CX := mLen
// free up DX to use for offset
MOVL DX, CX
ANDL $0xF, CX
CMPQ SI, R9
JAE end
// offset
// si += 2
// DX := int(src[si-2]) | int(src[si-1])<<8
ADDQ $2, SI
JC err_short_buf
CMPQ SI, R9
JA err_short_buf
MOVWQZX -2(SI), DX
// 0 offset is invalid
TESTL DX, DX
JEQ err_corrupt
match_len_loop_pre:
// if mlen != 0xF
CMPB CX, $0xF
JNE copy_match
// do { BX = src[si++]; mlen += BX } while (BX == 0xFF).
match_len_loop:
CMPQ SI, R9
JAE err_short_buf
MOVBLZX (SI), BX
INCQ SI
ADDQ BX, CX
CMPB BX, $0xFF
JE match_len_loop
copy_match:
ADDQ $const_minMatch, CX
// check we have match_len bytes left in dst
// di+match_len < len(dst)
MOVQ DI, AX
ADDQ CX, AX
JC err_short_buf
CMPQ AX, R8
JA err_short_buf
// DX = offset
// CX = match_len
// BX = &dst + (di - offset)
MOVQ DI, BX
SUBQ DX, BX
// check BX is within dst
// if BX < &dst
JC copy_match_from_dict
CMPQ BX, R11
JBE copy_match_from_dict
// if offset + match_len < di
LEAQ (BX)(CX*1), AX
CMPQ DI, AX
JA copy_interior_match
// AX := len(dst[:di])
// MOVQ DI, AX
// SUBQ R11, AX
// copy 16 bytes at a time
// if di-offset < 16 copy 16-(di-offset) bytes to di
// then do the remaining
copy_match_loop:
// for match_len >= 0
// dst[di] = dst[i]
// di++
// i++
MOVB (BX), AX
MOVB AX, (DI)
INCQ DI
INCQ BX
DECQ CX
JNZ copy_match_loop
JMP loopcheck
copy_interior_match:
CMPQ CX, $16
JGT memmove_match
// if len(dst[di:]) < 16
MOVQ R8, AX
SUBQ DI, AX
CMPQ AX, $16
JLT memmove_match
MOVOU (BX), X0
MOVOU X0, (DI)
ADDQ CX, DI
XORL CX, CX
JMP loopcheck
copy_match_from_dict:
// CX = match_len
// BX = &dst + (di - offset)
// AX = offset - di = dict_bytes_available => count of bytes potentially covered by the dictionary
MOVQ R11, AX
SUBQ BX, AX
// BX = len(dict) - dict_bytes_available
MOVQ R15, BX
SUBQ AX, BX
JS err_short_dict
ADDQ R14, BX
// if match_len > dict_bytes_available, match fits entirely within external dictionary : just copy
CMPQ CX, AX
JLT memmove_match
// The match stretches over the dictionary and our block
// 1) copy what comes from the dictionary
// AX = dict_bytes_available = copy_size
// BX = &dict_end - copy_size
// CX = match_len
// memmove(to, from, len)
MOVQ DI, 0(SP)
MOVQ BX, 8(SP)
MOVQ AX, 16(SP)
// store extra stuff we want to recover
// spill
MOVQ DI, 24(SP)
MOVQ SI, 32(SP)
MOVQ CX, 40(SP)
CALL runtime·memmove(SB)
// restore registers
MOVQ 16(SP), AX // copy_size
MOVQ 24(SP), DI
MOVQ 32(SP), SI
MOVQ 40(SP), CX // match_len
// recalc initial values
MOVQ dst_base+0(FP), R8
MOVQ R8, R11 // TODO: make these sensible numbers
ADDQ dst_len+8(FP), R8
MOVQ src_base+24(FP), R9
ADDQ src_len+32(FP), R9
MOVQ dict_base+48(FP), R14
MOVQ dict_len+56(FP), R15
MOVQ R8, R12
SUBQ $32, R12
MOVQ R9, R13
SUBQ $16, R13
// di+=copy_size
ADDQ AX, DI
// 2) copy the rest from the current block
// CX = match_len - copy_size = rest_size
SUBQ AX, CX
MOVQ R11, BX
// check if we have a copy overlap
// AX = &dst + rest_size
MOVQ CX, AX
ADDQ BX, AX
// if &dst + rest_size > di, copy byte by byte
CMPQ AX, DI
JA copy_match_loop
memmove_match:
// memmove(to, from, len)
MOVQ DI, 0(SP)
MOVQ BX, 8(SP)
MOVQ CX, 16(SP)
// Spill registers. Increment DI now so we don't need to save CX.
ADDQ CX, DI
MOVQ DI, 24(SP)
MOVQ SI, 32(SP)
CALL runtime·memmove(SB)
// restore registers
MOVQ 24(SP), DI
MOVQ 32(SP), SI
// recalc initial values
MOVQ dst_base+0(FP), R8
MOVQ R8, R11 // TODO: make these sensible numbers
ADDQ dst_len+8(FP), R8
MOVQ src_base+24(FP), R9
ADDQ src_len+32(FP), R9
MOVQ R8, R12
SUBQ $32, R12
MOVQ R9, R13
SUBQ $16, R13
MOVQ dict_base+48(FP), R14
MOVQ dict_len+56(FP), R15
XORL CX, CX
loopcheck:
// for si < len(src)
CMPQ SI, R9
JB loop
end:
// Remaining length must be zero.
TESTQ CX, CX
JNE err_corrupt
SUBQ R11, DI
MOVQ DI, ret+72(FP)
RET
err_corrupt:
MOVQ $-1, ret+72(FP)
RET
err_short_buf:
MOVQ $-2, ret+72(FP)
RET
err_short_dict:
MOVQ $-3, ret+72(FP)
RET

Some files were not shown because too many files have changed in this diff Show More