Skip to content

Commit

Permalink
Merge branch 'master' into travis_build_macos
Browse files Browse the repository at this point in the history
  • Loading branch information
insomniacslk authored Apr 4, 2019
2 parents 519b3e8 + 175868d commit bd8ef91
Show file tree
Hide file tree
Showing 181 changed files with 6,776 additions and 5,748 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
.*.swp
examples/client6/client6
examples/client4/client4
examples/packetcrafting6/packetcrafting6
2 changes: 1 addition & 1 deletion .travis/tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ done

# Skip go1.9 for this check. rtr7/router7 depends on miekg/dns, which does not
# support go1.9
if [ "$TRAVIS_GO_VERSION" = "1.9" ]
if [[ "$TRAVIS_GO_VERSION" =~ ^1.(9|10|11)$ ]]
then
exit 0
fi
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
* 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)
199 changes: 8 additions & 191 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,199 +31,15 @@ go get -u github.com/insomniacslk/dhcp/dhcpv{4,6}
The sections below will illustrate how to use the `dhcpv6` and `dhcpv4`
packages.

See more example code at https://github.com/insomniacslk/exdhcp


## DHCPv6 client

To run a DHCPv6 transaction on the interface "eth0":

```go
package main

import (
"log"

"github.com/insomniacslk/dhcp/dhcpv6"
)


func main() {
// NewClient sets up a new DHCPv6 client with default values
// for read and write timeouts, for destination address and listening
// address
client := dhcpv6.NewClient()

// Exchange runs a Solicit-Advertise-Request-Reply transaction on the
// specified network interface, and returns a list of DHCPv6 packets
// (a "conversation") and an error if any. Notice that Exchange may
// return a non-empty packet list even if there is an error. This is
// intended, because the transaction may fail at any point, and we
// still want to know what packets were exchanged until then.
// A default Solicit packet will be used during the "conversation",
// which can be manipulated by using modifiers.
conversation, err := client.Exchange("eth0")

// Summary() prints a verbose representation of the exchanged packets.
for _, packet := range conversation {
log.Print(packet.Summary())
}
// error handling is done *after* printing, so we still print the
// exchanged packets if any, as explained above.
if err != nil {
log.Fatal(err)
}
}
```


## DHCPv6 packet crafting and manipulation

```go
package main

import (
"log"
"net"

"github.com/insomniacslk/dhcp/dhcpv6"
"github.com/insomniacslk/dhcp/iana"
)

func main() {
// In this example we create and manipulate a DHCPv6 solicit packet
// and encapsulate it in a relay packet. To to this, we use
// `dhcpv6.DHCPv6Message` and `dhcpv6.DHCPv6Relay`, two structures
// that implement the `dhcpv6.DHCPv6` interface.
// Then print the wire-format representation of the packet.

// Create the DHCPv6 Solicit first, using the interface "eth0"
// to get the MAC address
msg, err := dhcpv6.NewSolicitForInterface("eth0")
if err != nil {
log.Fatal(err)
}

// In this example I want to redact the MAC address of my
// network interface, so instead of replacing it manually,
// I will show how to use modifiers for the purpose.
// A Modifier is simply a function that can be applied on
// a DHCPv6 object to manipulate it. Here we use it to
// replace the MAC address with a dummy one.
// Modifiers can be passed to many functions, for example
// to constructors, `Exchange()`, `Solicit()`, etc. Check
// the source code to know where to use them.
// Existing modifiers are implemented in dhcpv6/modifiers.go .
mac, err := net.ParseMAC("00:fa:ce:b0:0c:00")
if err != nil {
log.Fatal(err)
}
duid := dhcpv6.Duid{
Type: dhcpv6.DUID_LLT,
HwType: iana.HwTypeEthernet,
Time: dhcpv6.GetTime(),
LinkLayerAddr: mac,
}
// As suggested above, an alternative is to call
// dhcpv6.NewSolicitForInterface("eth0", dhcpv6.WithCLientID(duid))
msg = dhcpv6.WithClientID(duid)(msg)

// Now encapsulate the message in a DHCPv6 relay.
// As per RFC3315, the link-address and peer-address have
// to be set by the relay agent. We use dummy values here.
linkAddr := net.ParseIP("2001:0db8::1")
peerAddr := net.ParseIP("2001:0db8::2")
relay, err := dhcpv6.EncapsulateRelay(msg, dhcpv6.MessageTypeRelayForward, linkAddr, peerAddr)
if err != nil {
log.Fatal(err)
}

// Print a verbose representation of the relay packet, that will also
// show a short representation of the inner Solicit message.
// To print a detailed summary of the inner packet, extract it
// first from the relay using `relay.GetInnerMessage()`.
log.Print(relay.Summary())

// And finally, print the bytes that would be sent on the wire
log.Print(relay.ToBytes())
* [dhcpv6 client](examples/client6/)
* [dhcpv6 server](examples/server6/)
* [dhcpv6 packet crafting](examples/packetcrafting6)
* TODO dhcpv4 client
* TODO dhcpv4 server
* TODO dhcpv4 packet crafting

// Note: there are many more functions in the library, check them
// out in the source code. For example, if you want to decode a
// byte stream into a DHCPv6 message or relay, you can use
// `dhcpv6.FromBytes`.
}
```

The output (slightly modified for readability) is
```
$ go run main.go
2018/11/08 13:56:31 DHCPv6Relay
messageType=RELAY-FORW
hopcount=0
linkaddr=2001:db8::1
peeraddr=2001:db8::2
options=[OptRelayMsg{relaymsg=DHCPv6Message(messageType=SOLICIT transactionID=0x9e0242, 4 options)}]
2018/11/08 13:56:31 [12 0 32 1 13 184 0 0 0 0 0 0 0 0 0 0 0 1 32 1 13 184
0 0 0 0 0 0 0 0 0 0 0 2 0 9 0 52 1 158 2 66 0 1 0 14
0 1 0 1 35 118 253 15 0 250 206 176 12 0 0 6 0 4 0 23
0 24 0 8 0 2 0 0 0 3 0 12 250 206 176 12 0 0 14 16 0
0 21 24]
```

## DHCPv6 server

A DHCPv6 server requires the user to implement a request handler. Basically the
user has to provide the logic to answer to each packet. The library offers a few
facilities to forge response packets, e.g. `NewAdvertiseFromSolicit`,
`NewReplyFromDHCPv6Message` and so on. Look at the source code to see what's
available.

An example server that will print (but not reply to) the client's request is
shown below:

```go
package main

import (
"log"
"net"

"github.com/insomniacslk/dhcp/dhcpv6"
)

func handler(conn net.PacketConn, peer net.Addr, m dhcpv6.DHCPv6) {
// this function will just print the received DHCPv6 message, without replying
log.Print(m.Summary())
}

func main() {
laddr := net.UDPAddr{
IP: net.ParseIP("::1"),
Port: dhcpv6.DefaultServerPort,
}
server := dhcpv6.NewServer(laddr, handler)

defer server.Close()
if err := server.ActivateAndServe(); err != nil {
log.Panic(err)
}
}
```

## DHCPv4 client

TODO


## DHCPv4 packet parsing

TODO


## DHCPv4 server

TODO
See more example code at https://github.com/insomniacslk/exdhcp


# Public projects that use it
Expand All @@ -235,3 +51,4 @@ TODO
* Bender from Pinterest, a library for load-testing, https://github.com/pinterest/bender
* FBender from Facebook, a tool for load-testing based on Bender, https://github.com/facebookincubator/fbender
* CoreDHCP, a fast, multithreaded, modular and extensible DHCP server, https://github.com/coredhcp/coredhcp
* u-root, an embeddable root file system, https://github.com/u-root/u-root
11 changes: 6 additions & 5 deletions dhcpv4/async/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import (
"sync"
"time"

"github.com/fanliao/go-promise"
promise "github.com/fanliao/go-promise"
"github.com/insomniacslk/dhcp/dhcpv4"
"github.com/insomniacslk/dhcp/dhcpv4/client4"
)

// Default ports
Expand Down Expand Up @@ -41,8 +42,8 @@ type Client struct {
// NewClient creates an asynchronous client
func NewClient() *Client {
return &Client{
ReadTimeout: dhcpv4.DefaultReadTimeout,
WriteTimeout: dhcpv4.DefaultWriteTimeout,
ReadTimeout: client4.DefaultReadTimeout,
WriteTimeout: client4.DefaultWriteTimeout,
}
}

Expand Down Expand Up @@ -159,7 +160,7 @@ func (c *Client) receive(_ *dhcpv4.DHCPv4) {

c.connection.SetReadDeadline(time.Now().Add(c.ReadTimeout))
for {
buffer := make([]byte, dhcpv4.MaxUDPReceivedPacketSize)
buffer := make([]byte, client4.MaxUDPReceivedPacketSize)
n, _, _, _, err := c.connection.ReadMsgUDP(buffer, oobdata)
if err != nil {
if err, ok := err.(net.Error); !ok || !err.Timeout() {
Expand Down Expand Up @@ -196,7 +197,7 @@ func (c *Client) remoteAddr() (*net.UDPAddr, error) {
// Returns a future which resolves to response and error.
func (c *Client) Send(message *dhcpv4.DHCPv4, modifiers ...dhcpv4.Modifier) *promise.Future {
for _, mod := range modifiers {
message = mod(message)
mod(message)
}

p := promise.NewPromise()
Expand Down
7 changes: 4 additions & 3 deletions dhcpv4/async/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"

"github.com/insomniacslk/dhcp/dhcpv4"
"github.com/insomniacslk/dhcp/dhcpv4/client4"
"github.com/stretchr/testify/require"
)

Expand All @@ -19,7 +20,7 @@ func serve(ctx context.Context, addr *net.UDPAddr, response *dhcpv4.DHCPv4) erro
go func() {
defer conn.Close()
oobdata := []byte{}
buffer := make([]byte, dhcpv4.MaxUDPReceivedPacketSize)
buffer := make([]byte, client4.MaxUDPReceivedPacketSize)
for {
select {
case <-ctx.Done():
Expand Down Expand Up @@ -48,8 +49,8 @@ func serve(ctx context.Context, addr *net.UDPAddr, response *dhcpv4.DHCPv4) erro
func TestNewClient(t *testing.T) {
c := NewClient()
require.NotNil(t, c)
require.Equal(t, c.ReadTimeout, dhcpv4.DefaultReadTimeout)
require.Equal(t, c.ReadTimeout, dhcpv4.DefaultWriteTimeout)
require.Equal(t, c.ReadTimeout, client4.DefaultReadTimeout)
require.Equal(t, c.ReadTimeout, client4.DefaultWriteTimeout)
}

func TestOpenInvalidAddrFailes(t *testing.T) {
Expand Down
18 changes: 18 additions & 0 deletions dhcpv4/bindtodevice_bsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// +build freebsd openbsd netbsd

package dhcpv4

import (
"net"
"syscall"
)

// 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 syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_RECVIF, iface.Index)
}
41 changes: 38 additions & 3 deletions dhcpv4/bsdp/boot_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package bsdp
import (
"fmt"

"github.com/insomniacslk/dhcp/dhcpv4"
"github.com/u-root/u-root/pkg/uio"
)

Expand All @@ -18,9 +19,9 @@ const (
// 4 - 127 are reserved for future use.
)

// BootImageTypeToString maps the different BootImageTypes to human-readable
// bootImageTypeToString maps the different BootImageTypes to human-readable
// representations.
var BootImageTypeToString = map[BootImageType]string{
var bootImageTypeToString = map[BootImageType]string{
BootImageTypeMacOS9: "macOS 9",
BootImageTypeMacOSX: "macOS",
BootImageTypeMacOSXServer: "macOS Server",
Expand All @@ -35,6 +36,16 @@ type BootImageID struct {
Index uint16
}

// ToBytes implements dhcpv4.OptionValue.
func (b BootImageID) ToBytes() []byte {
return uio.ToBigEndian(b)
}

// FromBytes reads data into b.
func (b *BootImageID) FromBytes(data []byte) error {
return uio.FromBigEndian(b, data)
}

// Marshal writes the binary representation to buf.
func (b BootImageID) Marshal(buf *uio.Lexer) {
var byte0 byte
Expand All @@ -55,7 +66,7 @@ func (b BootImageID) String() string {
} else {
s += " uninstallable"
}
t, ok := BootImageTypeToString[b.ImageType]
t, ok := bootImageTypeToString[b.ImageType]
if !ok {
t = "unknown"
}
Expand Down Expand Up @@ -99,3 +110,27 @@ func (b *BootImage) Unmarshal(buf *uio.Lexer) error {
b.Name = string(buf.Consume(int(nameLength)))
return buf.Error()
}

func getBootImageID(code dhcpv4.OptionCode, o dhcpv4.Options) *BootImageID {
v := o.Get(code)
if v == nil {
return nil
}
var b BootImageID
if err := uio.FromBigEndian(&b, v); err != nil {
return nil
}
return &b
}

// OptDefaultBootImageID returns a new default boot image ID option as per
// BSDP.
func OptDefaultBootImageID(b BootImageID) dhcpv4.Option {
return dhcpv4.Option{Code: OptionDefaultBootImageID, Value: b}
}

// OptSelectedBootImageID returns a new selected boot image ID option as per
// BSDP.
func OptSelectedBootImageID(b BootImageID) dhcpv4.Option {
return dhcpv4.Option{Code: OptionSelectedBootImageID, Value: b}
}
Loading

0 comments on commit bd8ef91

Please sign in to comment.