From ed6198f71a1a3b4f702ba1d8587ea9f66a8ff59f Mon Sep 17 00:00:00 2001 From: Adrian Rumpold Date: Thu, 27 Jan 2022 11:28:51 +0100 Subject: [PATCH] feat: Initial revision --- .gitignore | 23 ++++++++ go.mod | 9 +++ go.sum | 51 ++++++++++++++++ main.go | 166 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 249 insertions(+) create mode 100644 .gitignore create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..13623c8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ + +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +go-dhcp + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2d8f71e --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module github.com/adrianokf/go-dhcp + +go 1.17 + +require ( + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.7.0 // indirect + go.uber.org/zap v1.20.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..58f324c --- /dev/null +++ b/go.sum @@ -0,0 +1,51 @@ +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/zap v1.20.0 h1:N4oPlghZwYG55MlU6LXk/Zp00FVNE9X9wrYO8CEs4lc= +go.uber.org/zap v1.20.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +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/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= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..668183b --- /dev/null +++ b/main.go @@ -0,0 +1,166 @@ +package main + +import ( + "bytes" + "encoding/binary" + "net" + "unsafe" + + "go.uber.org/zap" +) + +type DhcpOp uint8 + +const ( + BOOTREQUEST DhcpOp = iota + 1 + BOOTREPLY +) + +type DhcpMessage struct { + Op DhcpOp + Htype uint8 + Hlen uint8 + Hops uint8 + Xid uint32 + Secs uint16 + Flags uint16 + Ciaddr uint32 + Yiaddr uint32 + Siaddr uint32 + Giaddr uint32 + Chaddr [16]byte + Sname [64]byte + File [128]byte + Magic [4]byte +} + +func (dhcp DhcpMessage) Debug(s *zap.SugaredLogger) { + s.Debugf("op=%x, htype=%x, hlen=%x, hops=%x", dhcp.Op, dhcp.Htype, dhcp.Hlen, dhcp.Hops) + s.Debugf("xid=%x", dhcp.Xid) + s.Debugf("secs=%d, flags=%x", dhcp.Secs, dhcp.Flags) + + s.Debugf("ciaddr=%x", dhcp.Ciaddr) + s.Debugf("siaddr=%x", dhcp.Siaddr) + s.Debugf("giaddr=%x", dhcp.Giaddr) + s.Debugf("chaddr=%x", dhcp.Chaddr) + + s.Debugf("sname=%s", string(dhcp.Sname[:])) + s.Debugf("file=%s", string(dhcp.File[:])) + s.Debugf("magic=%x", dhcp.Magic) +} + +func parseOptions(data []byte) { + s := zap.S() + i := 0 + for i < len(data) { + code := data[i] + if code == 255 { + s.Debug("Found END option at offset ", i) + break + } + + if code == 0 { + s.Debug("Found padding option at offset ", i) + i += 1 + continue + } + + size := int(data[i+1]) + payload := data[i+2 : i+2+size] + + s.Debugf("code=%d, size=%d, payload=%x", code, size, payload) + i += size + 2 + } +} + +func prepareOffer(request DhcpMessage) DhcpMessage { + var sname [64]byte + var file [128]byte + copy(sname[:], "go-dhcp-server") + + dhcp := DhcpMessage{ + Op: BOOTREPLY, + Htype: 1, // Ethernet + Hlen: 6, // Ethernet address length + Hops: 0, + Secs: 0, + Flags: request.Flags, + Xid: request.Xid, + Siaddr: binary.BigEndian.Uint32(net.IPv4(10, 0, 0, 1).To4()), + Ciaddr: 0, + Yiaddr: binary.BigEndian.Uint32(net.IPv4(10, 0, 0, 2).To4()), + Giaddr: request.Giaddr, + Chaddr: request.Chaddr, + Magic: [4]byte{0x63, 0x82, 0x53, 0x63}, + Sname: sname, + File: file, + } + return dhcp +} + +func handleMsg(data []byte, remote *net.UDPAddr) { + s := zap.S() + + s.Debugf("Connection from client %v", remote.IP) + + var dhcp DhcpMessage + reader := bytes.NewReader(data) + binary.Read(reader, binary.BigEndian, &dhcp) + dhcp.Debug(s) + + if dhcp.Magic != [4]byte{0x63, 0x82, 0x53, 0x63} { + panic("Invalid DHCP magic field") + } + + optDataOffset := int(unsafe.Sizeof(dhcp)) + optData := data[optDataOffset:] + s.Debug("Raw options data:", optData) + + parseOptions(optData) + + offer := prepareOffer(dhcp) + localAddr, _ := net.ResolveUDPAddr("udp", "172.17.0.1:68") + clientAddr, _ := net.ResolveUDPAddr("udp", "255.255.255.255:68") + conn, err := net.DialUDP("udp", localAddr, clientAddr) + if err != nil { + panic(err) + } + defer conn.Close() + + s.Info("Sending DHCPOFFER...") + buf := make([]byte, 0) + w := bytes.NewBuffer(buf) + binary.Write(w, binary.BigEndian, offer) + w.Write([]byte{53, 1, 2}) + w.WriteByte(255) + msg := w.Bytes() + s.Debug("Msg", msg) + conn.Write(msg) +} + +func main() { + // Set up logging + logger, _ := zap.NewDevelopment() + defer logger.Sync() // flushes buffer, if any + zap.ReplaceGlobals(logger) + + addr, _ := net.ResolveUDPAddr("udp4", ":67") + conn, err := net.ListenUDP("udp4", addr) + if err != nil { + panic(err) + } + defer conn.Close() + + zap.L().Info("Listening for incoming connections") + + for { + buf := make([]byte, 1024) + rlen, remote, err := conn.ReadFromUDP(buf[:]) + if err != nil { + panic(err) + } + + // Do stuff with the read bytes + handleMsg(buf[0:rlen], remote) + } +}