Skip to content

Commit f7e1103

Browse files
committed
Refactoring
- Use layers.DHCPOpt instead of byte - Add AddOption and AddParamRequest - Add request flag to command
1 parent 448faa5 commit f7e1103

9 files changed

+225
-92
lines changed

client.go

+35-18
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,6 @@ type Client struct {
3636
wg sync.WaitGroup // For graceful shutdown
3737
}
3838

39-
// Option is a DHCP option field
40-
type Option struct {
41-
Type layers.DHCPOpt
42-
Data []byte
43-
}
44-
4539
// Lease is an assignment by the DHCP server
4640
type Lease struct {
4741
ServerID net.IP
@@ -62,23 +56,46 @@ type Lease struct {
6256
}
6357

6458
// DefaultParamsRequestList is a list of params to be requested from the server
65-
var DefaultParamsRequestList = []byte{
66-
1, // Subnet Mask
67-
3, // Router
68-
4, // Time Server
69-
6, // Domain Name Server
70-
15, // Domain Name
71-
26, // Interface MTU
72-
42, // Network Time Protocol Servers
59+
var DefaultParamsRequestList = []layers.DHCPOpt{
60+
layers.DHCPOptSubnetMask, // Subnet Mask
61+
layers.DHCPOptRouter, // Router
62+
layers.DHCPOptTimeServer, // Time Server
63+
layers.DHCPOptDNS, // Domain Name Server
64+
layers.DHCPOptDomainName, // Domain Name
65+
layers.DHCPOptInterfaceMTU, // Interface MTU
66+
layers.DHCPOptNTPServers, // Network Time Protocol Servers
67+
}
68+
69+
// AddOption adds an DHCP option
70+
func (client *Client) AddOption(optType layers.DHCPOpt, data []byte) {
71+
client.DHCPOptions = append(client.DHCPOptions, Option{optType, data})
72+
}
73+
74+
// AddParamRequest adds an parameter to parameter request list, if not included yet.
75+
func (client *Client) AddParamRequest(dhcpOpt layers.DHCPOpt) {
76+
77+
// search for existing parameter request list
78+
for i := range client.DHCPOptions {
79+
if client.DHCPOptions[i].Type == layers.DHCPOptParamsRequest {
80+
// extend existing list
81+
client.DHCPOptions[i].AddByte(byte(dhcpOpt))
82+
return
83+
}
84+
}
85+
86+
// option not added yet
87+
client.AddOption(layers.DHCPOptParamsRequest, []byte{byte(dhcpOpt)})
7388
}
7489

7590
// Start starts the client
7691
func (client *Client) Start() {
77-
if len(client.DHCPOptions) == 0 {
78-
client.DHCPOptions = []Option{
79-
{layers.DHCPOptHostname, []byte(client.Hostname)},
80-
{layers.DHCPOptParamsRequest, DefaultParamsRequestList},
92+
93+
// Add default DHCP options if none added yet.
94+
if client.DHCPOptions == nil {
95+
for _, param := range DefaultParamsRequestList {
96+
client.AddParamRequest(param)
8197
}
98+
client.AddOption(layers.DHCPOptHostname, []byte(client.Hostname))
8299
}
83100

84101
if client.notify != nil {

client_test.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package dhclient
2+
3+
import (
4+
"testing"
5+
6+
"github.com/google/gopacket/layers"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestAddParamRequest(t *testing.T) {
11+
assert := assert.New(t)
12+
client := Client{}
13+
assert.Len(client.DHCPOptions, 0)
14+
15+
// Add one option
16+
client.AddOption(layers.DHCPOptHostname, []byte("example.com"))
17+
assert.Len(client.DHCPOptions, 1)
18+
19+
// Add first param request
20+
client.AddParamRequest(layers.DHCPOptSubnetMask)
21+
assert.Len(client.DHCPOptions, 2)
22+
assert.Len(client.DHCPOptions[1].Data, 1)
23+
24+
// Add second param request
25+
client.AddParamRequest(layers.DHCPOptRouter)
26+
assert.Len(client.DHCPOptions[1].Data, 2)
27+
28+
// Add existing param request
29+
client.AddParamRequest(layers.DHCPOptRouter)
30+
assert.Len(client.DHCPOptions[1].Data, 2)
31+
32+
}

cmd/dhclient/byte_list.go

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"strconv"
7+
)
8+
9+
type byteList []byte
10+
11+
func (list *byteList) Set(value string) error {
12+
u, err := strconv.ParseUint(value, 0, 8)
13+
if err != nil {
14+
return err
15+
}
16+
17+
*list = append(*list, byte(u))
18+
return nil
19+
}
20+
21+
func (list *byteList) String() string {
22+
out := new(bytes.Buffer)
23+
for _, b := range *list {
24+
if out.Len() != 0 {
25+
out.WriteByte(' ')
26+
}
27+
fmt.Fprintf(out, "%d", b)
28+
}
29+
return out.String()
30+
}

cmd/dhclient/byte_list_test.go

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestByteList(t *testing.T) {
10+
assert := assert.New(t)
11+
12+
m := byteList{}
13+
14+
assert.EqualError(m.Set("foo"), "strconv.ParseUint: parsing \"foo\": invalid syntax")
15+
16+
assert.NoError(m.Set("65"))
17+
assert.NoError(m.Set("0x0f"))
18+
assert.Len(m, 2)
19+
assert.Equal("65 15", m.String())
20+
}

cmd/dhclient/main.go

+28-33
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,30 @@
11
package main
22

33
import (
4-
"encoding/hex"
54
"flag"
65
"fmt"
76
"log"
87
"net"
98
"os"
109
"os/signal"
11-
"strings"
1210
"syscall"
1311

1412
"github.com/digineo/go-dhclient"
1513
"github.com/google/gopacket/layers"
1614
)
1715

18-
var options = make(optionMap)
16+
var (
17+
options = optionList{}
18+
requestParams = byteList{}
19+
)
1920

2021
func init() {
2122
flag.Usage = func() {
2223
fmt.Printf("syntax: %s [flags] IFNAME\n", os.Args[0])
2324
flag.PrintDefaults()
2425
}
25-
flag.Var(&options, "option", "custom DHCP option (code,value)")
26+
flag.Var(&options, "option", "custom DHCP option for the request (code,value)")
27+
flag.Var(&requestParams, "request", "Additional value for the DHCP Request List Option 55 (code)")
2628
}
2729

2830
func main() {
@@ -34,47 +36,40 @@ func main() {
3436
os.Exit(1)
3537
}
3638

37-
log.Println(options)
38-
39-
hostname, _ := os.Hostname()
4039
ifname := flag.Arg(0)
41-
4240
iface, err := net.InterfaceByName(ifname)
4341
if err != nil {
4442
fmt.Printf("unable to find interface %s: %s\n", ifname, err)
4543
os.Exit(1)
4644
}
4745

48-
dhcpOptions := []dhclient.Option{
49-
{Type: layers.DHCPOptHostname, Data: []byte(hostname)},
50-
{Type: layers.DHCPOptParamsRequest, Data: dhclient.DefaultParamsRequestList},
46+
client := dhclient.Client{
47+
Iface: iface,
48+
OnBound: func(lease *dhclient.Lease) {
49+
log.Printf("Bound: %+v", lease)
50+
},
5151
}
5252

53-
for k, v := range options {
54-
var data []byte
55-
56-
if strings.HasPrefix(v, "0x") {
57-
data, err = hex.DecodeString(v[2:])
58-
if err != nil {
59-
fmt.Printf("value \"%s\" is invalid: %s\n", v, err)
60-
os.Exit(1)
61-
}
62-
} else {
63-
data = []byte(v)
64-
}
53+
// Add requests for default options
54+
for _, param := range dhclient.DefaultParamsRequestList {
55+
log.Printf("Requesting default option %d", param)
56+
client.AddParamRequest(layers.DHCPOpt(param))
57+
}
6558

66-
dhcpOptions = append(dhcpOptions,
67-
dhclient.Option{Type: layers.DHCPOpt(k), Data: data},
68-
)
59+
// Add requests for custom options
60+
for _, param := range requestParams {
61+
log.Printf("Requesting custom option %d", param)
62+
client.AddParamRequest(layers.DHCPOpt(param))
6963
}
7064

71-
client := dhclient.Client{
72-
Iface: iface,
73-
Hostname: hostname,
74-
OnBound: func(lease *dhclient.Lease) {
75-
log.Printf("Bound: %+v", lease)
76-
},
77-
DHCPOptions: dhcpOptions,
65+
// Add hostname option
66+
hostname, _ := os.Hostname()
67+
client.AddOption(layers.DHCPOptHostname, []byte(hostname))
68+
69+
// Add custom options
70+
for _, option := range options {
71+
log.Printf("Adding option %d=0x%x", option.Type, option.Data)
72+
client.AddOption(option.Type, option.Data)
7873
}
7974

8075
client.Start()

cmd/dhclient/option_list.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"encoding/hex"
6+
"errors"
7+
"fmt"
8+
"strconv"
9+
"strings"
10+
11+
"github.com/google/gopacket/layers"
12+
13+
dhclient "github.com/digineo/go-dhclient"
14+
)
15+
16+
type optionList []dhclient.Option
17+
18+
func (m *optionList) Set(arg string) error {
19+
i := strings.Index(arg, ",")
20+
if i < 0 {
21+
return errors.New("invalid \"code,value\" pair")
22+
}
23+
24+
code, err := strconv.Atoi(arg[:i])
25+
if err != nil {
26+
return fmt.Errorf("option code \"%s\" is invalid", arg[:i])
27+
}
28+
29+
value := arg[i+1:]
30+
var data []byte
31+
if strings.HasPrefix(value, "0x") {
32+
data, err = hex.DecodeString(value[2:])
33+
if err != nil {
34+
return err
35+
}
36+
} else {
37+
data = []byte(value)
38+
}
39+
40+
*m = append(*m, dhclient.Option{Type: layers.DHCPOpt(code), Data: data})
41+
return nil
42+
}
43+
44+
func (m optionList) String() string {
45+
out := new(bytes.Buffer)
46+
for _, option := range m {
47+
if out.Len() != 0 {
48+
out.WriteByte(' ')
49+
}
50+
fmt.Fprintf(out, "%d,0x%x", option.Type, option.Data)
51+
}
52+
return out.String()
53+
}

cmd/dhclient/option_map_test.go cmd/dhclient/option_list_test.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@ import (
66
"github.com/stretchr/testify/assert"
77
)
88

9-
func TestOptionMap(t *testing.T) {
9+
func TestOptionList(t *testing.T) {
1010
assert := assert.New(t)
1111

12-
m := make(optionMap)
12+
m := optionList{}
1313

1414
assert.EqualError(m.Set("foo"), "invalid \"code,value\" pair")
1515
assert.EqualError(m.Set(","), "option code \"\" is invalid")
1616
assert.EqualError(m.Set("0x12,foo"), "option code \"0x12\" is invalid")
1717

18+
assert.NoError(m.Set("7,0x01020304"))
1819
assert.NoError(m.Set("65,foo"))
19-
assert.Equal("65,foo", m.String())
20+
assert.Len(m, 2)
21+
assert.Equal("7,0x01020304 65,0x666f6f", m.String())
2022
}

cmd/dhclient/option_map.go

-38
This file was deleted.

option.go

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package dhclient
2+
3+
import (
4+
"github.com/google/gopacket/layers"
5+
)
6+
7+
// Option is a DHCP option field
8+
type Option struct {
9+
Type layers.DHCPOpt
10+
Data []byte
11+
}
12+
13+
// AddByte ensures a specific byte is included in the data
14+
func (option *Option) AddByte(b byte) {
15+
for _, o := range option.Data {
16+
if o == b {
17+
// already included
18+
return
19+
}
20+
}
21+
option.Data = append(option.Data, b)
22+
}

0 commit comments

Comments
 (0)