@@ -2,9 +2,170 @@ package dhcp
22
33import (
44 "context"
5+ "golang.org/x/sys/windows"
56 "net"
7+ "regexp"
8+ "time"
9+
10+ "github.com/pkg/errors"
11+ "go.uber.org/zap"
12+ )
13+
14+ const (
15+ dummyIPAddressStr = "169.254.128.10"
16+ dummySubnetMask = "255.255.128.0"
17+ addIPAddressTimeout = 10 * time .Second
18+ deleteIPAddressTimeout = 2 * time .Second
19+
20+ socketTimeout = 1000
621)
722
8- func (c * DHCP ) DiscoverRequest (_ context.Context , _ net.HardwareAddr , _ string ) error {
23+ var (
24+ dummyIPAddress = net .IPv4 (169 , 254 , 128 , 10 )
25+ // matches if the string fully consists of zero or more alphanumeric, dots, dashes, parentheses, spaces, or underscores
26+ allowedInput = regexp .MustCompile (`^[a-zA-Z0-9._\-\(\) ]*$` )
27+ )
28+
29+ type Socket struct {
30+ fd windows.Handle
31+ destAddr windows.SockaddrInet4
32+ }
33+
34+ func NewSocket (destAddr windows.SockaddrInet4 ) (* Socket , error ) {
35+ // Create a raw socket using windows.WSASocket
36+ fd , err := windows .WSASocket (windows .AF_INET , windows .SOCK_RAW , windows .IPPROTO_UDP , nil , 0 , windows .WSA_FLAG_OVERLAPPED )
37+ ret := & Socket {
38+ fd : fd ,
39+ destAddr : destAddr ,
40+ }
41+ if err != nil {
42+ return ret , errors .Wrap (err , "error creating socket" )
43+ }
44+ defer windows .Closesocket (fd )
45+
46+ // Set IP_HDRINCL to indicate that we are including our own IP header
47+ err = windows .SetsockoptInt (fd , windows .IPPROTO_IP , windows .IP_HDRINCL , 1 )
48+ if err != nil {
49+ return ret , errors .Wrap (err , "error setting IP_HDRINCL" )
50+ }
51+ // Set the SO_BROADCAST option or else we get an error saying that we access a socket in a way forbidden by its access perms
52+ err = windows .SetsockoptInt (windows .Handle (fd ), windows .SOL_SOCKET , windows .SO_BROADCAST , 1 )
53+ if err != nil {
54+ return ret , errors .Wrap (err , "error setting SO_BROADCAST" )
55+ }
56+ // Set timeout
57+ if err = windows .SetsockoptInt (windows .Handle (fd ), windows .SOL_SOCKET , windows .SO_RCVTIMEO , socketTimeout ); err != nil {
58+ return ret , errors .Wrap (err , "error setting receive timeout" )
59+ }
60+ return ret , nil
61+ }
62+
63+ func (s * Socket ) Write (packetBytes []byte ) (int , error ) {
64+ err := windows .Sendto (s .fd , packetBytes , 0 , & s .destAddr )
65+ if err != nil {
66+ return 0 , errors .Wrap (err , "failed windows send to" )
67+ }
68+ return len (packetBytes ), nil
69+ }
70+ func (s * Socket ) Read (p []byte ) (n int , err error ) {
71+ n , _ , innerErr := windows .Recvfrom (s .fd , p , 0 )
72+ if innerErr != nil {
73+ return 0 , errors .Wrap (err , "failed windows recv from" )
74+ }
75+ return n , nil
76+ }
77+
78+ func (s * Socket ) Close () error {
79+ // do not attempt to close invalid fd (happens on socket creation failure)
80+ if s .fd == windows .InvalidHandle {
81+ return nil
82+ }
83+ // Ensure the file descriptor is closed when done
84+ if err := windows .Close (s .fd ); err != nil {
85+ return errors .Wrap (err , "error closing dhcp windows socket" )
86+ }
87+ return nil
88+ }
89+
90+ // issues a dhcp discover request on an interface by assigning an ip to that interface
91+ // then, sends a packet with that interface's dummy ip, and then unassigns the dummy ip
92+ func (c * DHCP ) DiscoverRequest (ctx context.Context , macAddress net.HardwareAddr , ifName string ) error {
93+ // validate interface name
94+ if ! allowedInput .MatchString (ifName ) {
95+ return errors .New ("invalid dhcp discover request interface name" )
96+ }
97+ // delete dummy ip off the interface if it already exists
98+ ret , err := c .execClient .ExecuteCommand (ctx , "netsh" , "interface" , "ipv4" , "delete" , "address" , ifName , dummyIPAddressStr )
99+ if err != nil {
100+ c .logger .Info ("Could not remove dummy ip" , zap .String ("output" , ret ), zap .Error (err ))
101+ }
102+ time .Sleep (deleteIPAddressTimeout )
103+
104+ // create dummy ip so we can direct the packet to the correct interface
105+ ret , err = c .execClient .ExecuteCommand (ctx , "netsh" , "interface" , "ipv4" , "add" , "address" , ifName , dummyIPAddressStr , dummySubnetMask )
106+ if err != nil {
107+ return errors .Wrap (err , "failed to add dummy ip to interface: " + ret )
108+ }
109+ // ensure we always remove the dummy ip we added from the interface
110+ defer func () {
111+ ret , err := c .execClient .ExecuteCommand (ctx , "netsh" , "interface" , "ipv4" , "delete" , "address" , ifName , dummyIPAddressStr )
112+ if err != nil {
113+ c .logger .Info ("Could not remove dummy ip on leaving function" , zap .String ("output" , ret ), zap .Error (err ))
114+ }
115+ }()
116+ // it takes time for the address to be assigned
117+ time .Sleep (addIPAddressTimeout )
118+
119+ // now begin the dhcp request
120+ txid , err := GenerateTransactionID ()
121+ if err != nil {
122+ return errors .Wrap (err , "failed to generate transaction id" )
123+ }
124+
125+ // Prepare an IP and UDP header
126+ raddr := & net.UDPAddr {IP : net .IPv4bcast , Port : dhcpServerPort }
127+ laddr := & net.UDPAddr {IP : dummyIPAddress , Port : dhcpClientPort }
128+
129+ dhcpDiscover , err := buildDHCPDiscover (macAddress , txid )
130+ if err != nil {
131+ return errors .Wrap (err , "failed to build dhcp discover" )
132+ }
133+
134+ // Fill out the headers, add payload, and construct the full packet
135+ bytesToSend , err := MakeRawUDPPacket (dhcpDiscover , * raddr , * laddr )
136+ if err != nil {
137+ return errors .Wrap (err , "failed to make raw udp packet" )
138+ }
139+
140+ destAddr := windows.SockaddrInet4 {
141+ Addr : [4 ]byte {255 , 255 , 255 , 255 }, // Destination IP
142+ Port : 67 , // Destination Port
143+ }
144+ // create new socket for writing and reading
145+ sock , err := NewSocket (destAddr )
146+ defer func () {
147+ // always clean up the socket, even if we fail while setting options
148+ closeErr := sock .Close ()
149+ if closeErr != nil {
150+ c .logger .Error ("Error closing dhcp socket:" , zap .Error (closeErr ))
151+ }
152+ }()
153+ if err != nil {
154+ return errors .Wrap (err , "failed to create socket" )
155+ }
156+
157+ _ , err = sock .Write (bytesToSend )
158+ if err != nil {
159+ return errors .Wrap (err , "failed to write to dhcp socket" )
160+ }
161+
162+ c .logger .Info ("DHCP Discover packet was sent successfully" , zap .Any ("transactionID" , txid ))
163+
164+ // Wait for DHCP response (Offer)
165+ err = c .receiveDHCPResponse (ctx , sock , txid )
166+ if err != nil {
167+ return errors .Wrap (err , "failed to read from dhcp socket" )
168+ }
169+
9170 return nil
10171}
0 commit comments