Skip to content

Commit ee8dde1

Browse files
committed
create initial windows dhcp client
1 parent c197355 commit ee8dde1

File tree

4 files changed

+197
-7
lines changed

4 files changed

+197
-7
lines changed

cni/network/network.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,9 @@ func NewPlugin(name string,
131131
}
132132

133133
nl := netlink.NewNetlink()
134+
plc := platform.NewExecClient(logger)
134135
// Setup network manager.
135-
nm, err := network.NewNetworkManager(nl, platform.NewExecClient(logger), &netio.NetIO{}, network.NewNamespaceClient(), iptables.NewClient(), dhcp.New(logger))
136+
nm, err := network.NewNetworkManager(nl, plc, &netio.NetIO{}, network.NewNamespaceClient(), iptables.NewClient(), dhcp.New(logger, plc))
136137
if err != nil {
137138
return nil, err
138139
}
@@ -1526,7 +1527,7 @@ func (plugin *NetPlugin) validateArgs(args *cniSkel.CmdArgs, nwCfg *cni.NetworkC
15261527
if !allowedInput.MatchString(args.ContainerID) || !allowedInput.MatchString(args.IfName) {
15271528
return errors.New("invalid args value")
15281529
}
1529-
if !allowedInput.MatchString(nwCfg.Bridge) {
1530+
if !allowedInput.MatchString(nwCfg.Bridge) || !allowedInput.MatchString(nwCfg.Master) {
15301531
return errors.New("invalid network config value")
15311532
}
15321533

dhcp/dhcp.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,19 @@ var (
4747
DefaultTimeout = 3 * time.Second
4848
)
4949

50+
type ExecClient interface {
51+
ExecuteCommand(ctx context.Context, command string, args ...string) (string, error)
52+
}
53+
5054
type DHCP struct {
51-
logger *zap.Logger
55+
logger *zap.Logger
56+
execClient ExecClient
5257
}
5358

54-
func New(logger *zap.Logger) *DHCP {
59+
func New(logger *zap.Logger, plc ExecClient) *DHCP {
5560
return &DHCP{
56-
logger: logger,
61+
logger: logger,
62+
execClient: plc,
5763
}
5864
}
5965

dhcp/dhcp_windows.go

Lines changed: 162 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,170 @@ package dhcp
22

33
import (
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
}

network/endpoint_windows.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"net"
1010
"strings"
11+
"time"
1112

1213
"github.com/Azure/azure-container-networking/cns"
1314
"github.com/Azure/azure-container-networking/netio"
@@ -141,6 +142,21 @@ func (nw *network) getEndpointWithVFDevice(plc platform.ExecClient, epInfo *Endp
141142
return ep, nil
142143
}
143144

145+
func (nw *network) sendDHCPDiscoverOnSecondary(client dhcpClient, mac net.HardwareAddr, ifName string) error {
146+
// issue dhcp discover packet to ensure mapping created for dns via wireserver to work
147+
// we do not use the response for anything
148+
numSecs := 15 // we need to wait for the address to be assigned
149+
timeout := time.Duration(numSecs) * time.Second
150+
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(timeout))
151+
defer cancel()
152+
logger.Info("Sending DHCP packet", zap.Any("macAddress", mac), zap.String("ifName", ifName))
153+
err := client.DiscoverRequest(ctx, mac, ifName)
154+
if err != nil {
155+
return errors.Wrapf(err, "failed to issue dhcp discover packet to create mapping in host")
156+
}
157+
return nil
158+
}
159+
144160
// newEndpointImpl creates a new endpoint in the network.
145161
func (nw *network) newEndpointImpl(
146162
cli apipaClient,
@@ -150,12 +166,18 @@ func (nw *network) newEndpointImpl(
150166
_ EndpointClient,
151167
_ NamespaceClientInterface,
152168
_ ipTablesClient,
153-
_ dhcpClient,
169+
dhcpc dhcpClient,
154170
epInfo *EndpointInfo,
155171
) (*endpoint, error) {
156172
if epInfo.NICType == cns.BackendNIC {
157173
return nw.getEndpointWithVFDevice(plc, epInfo)
158174
}
175+
if epInfo.NICType == cns.DelegatedVMNIC {
176+
// use master interface name, interface name, or adapter name?
177+
if err := nw.sendDHCPDiscoverOnSecondary(dhcpc, epInfo.MacAddress, epInfo.MasterIfName); err != nil {
178+
return nil, err
179+
}
180+
}
159181

160182
if useHnsV2, err := UseHnsV2(epInfo.NetNsPath); useHnsV2 {
161183
if err != nil {

0 commit comments

Comments
 (0)