-
Notifications
You must be signed in to change notification settings - Fork 260
fix: issue dhcp request on windows secondary interfaces #3047
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 9 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
c197355
move dhcp code to common file
QxBytes ee8dde1
create initial windows dhcp client
QxBytes b773fee
use node network interface frontend nic
QxBytes 4b357e2
address linter issues
QxBytes fd36e39
address feedback, move send request to before hns network creation
QxBytes 5990ef7
add delay before returning from windows dhcp otherwise net adapter no…
QxBytes 03efe8e
silence non-error and always run command to remove dummy ip
QxBytes e1edebe
address linter issues
QxBytes 84acbc9
move retrier to separate package and reuse code
QxBytes 8a56efb
leverage autoconfig ipv4 address for dhcp (tested)
QxBytes ac4f51c
address feedback
QxBytes 0e0414e
promote nmagent retry to package, include final retry error, and adap…
QxBytes 524d002
address linter issues
QxBytes bf974f6
wrap in temporary error to retry
QxBytes File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,260 @@ | ||
| package dhcp | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "context" | ||
| "crypto/rand" | ||
| "encoding/binary" | ||
| "io" | ||
| "net" | ||
| "time" | ||
|
|
||
| "github.com/pkg/errors" | ||
| "go.uber.org/zap" | ||
| "golang.org/x/net/ipv4" | ||
| ) | ||
|
|
||
| const ( | ||
| dhcpDiscover = 1 | ||
| bootRequest = 1 | ||
| ethPAll = 0x0003 | ||
| MaxUDPReceivedPacketSize = 8192 | ||
| dhcpServerPort = 67 | ||
| dhcpClientPort = 68 | ||
| dhcpOpCodeReply = 2 | ||
| bootpMinLen = 300 | ||
| bytesInAddress = 4 // bytes in an ip address | ||
| macBytes = 6 // bytes in a mac address | ||
| udpProtocol = 17 | ||
|
|
||
| opRequest = 1 | ||
| htypeEthernet = 1 | ||
| hlenEthernet = 6 | ||
| hops = 0 | ||
| secs = 0 | ||
| flags = 0x8000 // Broadcast flag | ||
| ) | ||
|
|
||
| // 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 | ||
|
|
||
| var ( | ||
| magicCookie = []byte{0x63, 0x82, 0x53, 0x63} // DHCP magic cookie | ||
| DefaultReadTimeout = 3 * time.Second | ||
| DefaultTimeout = 3 * time.Second | ||
| ) | ||
|
|
||
| type ExecClient interface { | ||
| ExecuteCommand(ctx context.Context, command string, args ...string) (string, error) | ||
| } | ||
|
|
||
| type DHCP struct { | ||
| logger *zap.Logger | ||
| execClient ExecClient | ||
| } | ||
|
|
||
| func New(logger *zap.Logger, plc ExecClient) *DHCP { | ||
estebancams marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return &DHCP{ | ||
| logger: logger, | ||
| execClient: plc, | ||
| } | ||
| } | ||
|
|
||
| // GenerateTransactionID generates a random 32-bits number suitable for use as TransactionID | ||
| func GenerateTransactionID() (TransactionID, error) { | ||
| var xid TransactionID | ||
| _, err := rand.Read(xid[:]) | ||
| if err != nil { | ||
| return xid, errors.Errorf("could not get random number: %v", err) | ||
| } | ||
| return xid, nil | ||
| } | ||
|
|
||
| // Build DHCP Discover Packet | ||
| func buildDHCPDiscover(mac net.HardwareAddr, txid TransactionID) ([]byte, error) { | ||
| if len(mac) != macBytes { | ||
| return nil, errors.Errorf("invalid MAC address length") | ||
| } | ||
|
|
||
| var packet bytes.Buffer | ||
|
|
||
| // BOOTP header | ||
| packet.WriteByte(opRequest) // op: BOOTREQUEST (1) | ||
| packet.WriteByte(htypeEthernet) // htype: Ethernet (1) | ||
| packet.WriteByte(hlenEthernet) // hlen: MAC address length (6) | ||
| packet.WriteByte(hops) // hops: 0 | ||
| packet.Write(txid[:]) // xid: Transaction ID (4 bytes) | ||
| err := binary.Write(&packet, binary.BigEndian, uint16(secs)) // secs: Seconds elapsed | ||
| if err != nil { | ||
| return nil, errors.Wrap(err, "failed to write seconds elapsed") | ||
| } | ||
| err = binary.Write(&packet, binary.BigEndian, uint16(flags)) // flags: Broadcast flag | ||
| if err != nil { | ||
| return nil, errors.Wrap(err, "failed to write broadcast flag") | ||
| } | ||
|
|
||
| // Client IP address (0.0.0.0) | ||
| packet.Write(make([]byte, bytesInAddress)) | ||
| // Your IP address (0.0.0.0) | ||
| packet.Write(make([]byte, bytesInAddress)) | ||
| // Server IP address (0.0.0.0) | ||
| packet.Write(make([]byte, bytesInAddress)) | ||
| // Gateway IP address (0.0.0.0) | ||
| packet.Write(make([]byte, bytesInAddress)) | ||
|
|
||
| // chaddr: Client hardware address (MAC address) | ||
| paddingBytes := 10 | ||
| packet.Write(mac) // MAC address (6 bytes) | ||
| packet.Write(make([]byte, paddingBytes)) // Padding to 16 bytes | ||
|
|
||
| // sname: Server host name (64 bytes) | ||
| serverHostNameBytes := 64 | ||
| packet.Write(make([]byte, serverHostNameBytes)) | ||
| // file: Boot file name (128 bytes) | ||
| bootFileNameBytes := 128 | ||
| packet.Write(make([]byte, bootFileNameBytes)) | ||
|
|
||
| // Magic cookie (DHCP) | ||
| err = binary.Write(&packet, binary.BigEndian, magicCookie) | ||
| if err != nil { | ||
| return nil, errors.Wrap(err, "failed to write magic cookie") | ||
| } | ||
|
|
||
| // DHCP options (minimal required options for DISCOVER) | ||
| packet.Write([]byte{ | ||
| 53, 1, 1, // Option 53: DHCP Message Type (1 = DHCP Discover) | ||
| 55, 3, 1, 3, 6, // Option 55: Parameter Request List (1 = Subnet Mask, 3 = Router, 6 = DNS) | ||
| 255, // End option | ||
| }) | ||
|
|
||
| // padding length to 300 bytes | ||
| var value uint8 // default is zero | ||
| if packet.Len() < bootpMinLen { | ||
| packet.Write(bytes.Repeat([]byte{value}, bootpMinLen-packet.Len())) | ||
| } | ||
|
|
||
| return packet.Bytes(), nil | ||
| } | ||
|
|
||
| // MakeRawUDPPacket converts a payload (a serialized packet) into a | ||
| // raw UDP packet for the specified serverAddr from the specified clientAddr. | ||
| func MakeRawUDPPacket(payload []byte, serverAddr, clientAddr net.UDPAddr) ([]byte, error) { | ||
| udpBytes := 8 | ||
| udp := make([]byte, udpBytes) | ||
| binary.BigEndian.PutUint16(udp[:2], uint16(clientAddr.Port)) | ||
| binary.BigEndian.PutUint16(udp[2:4], uint16(serverAddr.Port)) | ||
| totalLen := uint16(udpBytes + len(payload)) | ||
| binary.BigEndian.PutUint16(udp[4:6], totalLen) | ||
| binary.BigEndian.PutUint16(udp[6:8], 0) // try to offload the checksum | ||
|
|
||
| headerVersion := 4 | ||
| headerLen := 20 | ||
| headerTTL := 64 | ||
|
|
||
| h := ipv4.Header{ | ||
| Version: headerVersion, // nolint | ||
| Len: headerLen, // nolint | ||
| TotalLen: headerLen + len(udp) + len(payload), | ||
| TTL: headerTTL, | ||
| Protocol: udpProtocol, // UDP | ||
| Dst: serverAddr.IP, | ||
| Src: clientAddr.IP, | ||
| } | ||
| ret, err := h.Marshal() | ||
| if err != nil { | ||
| return nil, errors.Wrap(err, "failed to marshal when making udp packet") | ||
| } | ||
| ret = append(ret, udp...) | ||
| ret = append(ret, payload...) | ||
| return ret, nil | ||
| } | ||
|
|
||
| // Receive DHCP response packet using reader | ||
| func (c *DHCP) receiveDHCPResponse(ctx context.Context, reader io.ReadCloser, xid TransactionID) error { | ||
| recvErrors := make(chan error, 1) | ||
| // Recvfrom is a blocking call, so if something goes wrong with its timeout it won't return. | ||
|
|
||
| // Additionally, the timeout on the socket (on the Read(...)) call is how long until the socket times out and gives an error, | ||
| // but it won't error if we do get some sort of data within the time out period. | ||
|
|
||
| // If we get some data (even if it is not the packet we are looking for, like wrong txid, wrong response opcode etc.) | ||
| // then we continue in the for loop. We then call recvfrom again which will reset the timeout period | ||
| // Without the secondary timeout at the bottom of the function, we could stay stuck in the for loop as long as we receive packets. | ||
| go func(errs chan<- error) { | ||
| // loop will only exit if there is an error, context canceled, or we find our reply packet | ||
| for { | ||
| if ctx.Err() != nil { | ||
| errs <- ctx.Err() | ||
| return | ||
| } | ||
|
|
||
| buf := make([]byte, MaxUDPReceivedPacketSize) | ||
| // Blocks until data received or timeout period is reached | ||
| n, innerErr := reader.Read(buf) | ||
| if innerErr != nil { | ||
| errs <- innerErr | ||
| return | ||
| } | ||
| // check header | ||
| var iph ipv4.Header | ||
| if err := iph.Parse(buf[:n]); err != nil { | ||
| // skip non-IP data | ||
| continue | ||
| } | ||
| if iph.Protocol != udpProtocol { | ||
| // skip non-UDP packets | ||
| continue | ||
| } | ||
| udph := buf[iph.Len:n] | ||
| // source is from dhcp server if receiving | ||
| srcPort := int(binary.BigEndian.Uint16(udph[0:2])) | ||
| if srcPort != dhcpServerPort { | ||
| continue | ||
| } | ||
| // client is to dhcp client if receiving | ||
| dstPort := int(binary.BigEndian.Uint16(udph[2:4])) | ||
| if dstPort != dhcpClientPort { | ||
| continue | ||
| } | ||
| // check payload | ||
| pLen := int(binary.BigEndian.Uint16(udph[4:6])) | ||
| payload := buf[iph.Len+8 : iph.Len+pLen] | ||
|
|
||
| // retrieve opcode from payload | ||
| opcode := payload[0] // opcode is first byte | ||
| // retrieve txid from payload | ||
| txidOffset := 4 // after 4 bytes, the txid starts | ||
| // the txid is 4 bytes, so we take four bytes after the offset | ||
| txid := payload[txidOffset : txidOffset+4] | ||
|
|
||
| c.logger.Info("Received packet", zap.Int("opCode", int(opcode)), zap.Any("transactionID", TransactionID(txid))) | ||
| if opcode != dhcpOpCodeReply { | ||
| continue // opcode is not a reply, so continue | ||
| } | ||
| c.logger.Info("Received DHCP reply packet", zap.Int("opCode", int(opcode)), zap.Any("transactionID", TransactionID(txid))) | ||
| if TransactionID(txid) == xid { | ||
| break | ||
| } | ||
| } | ||
| // only occurs if we find our reply packet successfully | ||
| // a nil error means a reply was found for this txid | ||
| recvErrors <- nil | ||
| }(recvErrors) | ||
|
|
||
| // sends a message on repeat after timeout, but only the first one matters | ||
| ticker := time.NewTicker(DefaultReadTimeout) | ||
| defer ticker.Stop() | ||
|
|
||
| select { | ||
| case err := <-recvErrors: | ||
| if err != nil { | ||
| return errors.Wrap(err, "error during receiving") | ||
| } | ||
| case <-ticker.C: | ||
| return errors.New("timed out waiting for replies") | ||
| } | ||
| return nil | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.