1
2
3 package uvm
4
5 import (
6 "context"
7 "fmt"
8 "os"
9
10 "github.com/Microsoft/go-winio"
11 "github.com/Microsoft/go-winio/pkg/guid"
12 "github.com/containerd/ttrpc"
13 "github.com/pkg/errors"
14 "github.com/sirupsen/logrus"
15
16 "github.com/Microsoft/hcsshim/hcn"
17 "github.com/Microsoft/hcsshim/internal/hcs/resourcepaths"
18 hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
19 "github.com/Microsoft/hcsshim/internal/hns"
20 "github.com/Microsoft/hcsshim/internal/log"
21 "github.com/Microsoft/hcsshim/internal/ncproxyttrpc"
22 "github.com/Microsoft/hcsshim/internal/protocol/guestrequest"
23 "github.com/Microsoft/hcsshim/internal/protocol/guestresource"
24 "github.com/Microsoft/hcsshim/osversion"
25 )
26
27 var (
28
29
30 ErrNetNSAlreadyAttached = errors.New("network namespace already added")
31
32
33 ErrNetNSNotFound = errors.New("network namespace not found")
34
35
36 ErrNICNotFound = errors.New("NIC not found in network namespace")
37 )
38
39
40
41
42 func (uvm *UtilityVM) SetupNetworkNamespace(ctx context.Context, nsid string) error {
43 nsidInsideUVM := nsid
44
45
46 endpoints, err := GetNamespaceEndpoints(ctx, nsid)
47 if err != nil {
48 return err
49 }
50
51
52
53 hcnNamespace, err := hcn.GetNamespaceByID(nsid)
54 if err != nil {
55 return err
56 }
57
58 if err = uvm.AddNetNS(ctx, hcnNamespace); err != nil {
59 return err
60 }
61
62 if err = uvm.AddEndpointsToNS(ctx, nsidInsideUVM, endpoints); err != nil {
63
64 if removeErr := uvm.RemoveNetNS(ctx, nsidInsideUVM); removeErr != nil {
65 log.G(ctx).Warn(removeErr)
66 }
67 return err
68 }
69 return nil
70 }
71
72
73 func GetNamespaceEndpoints(ctx context.Context, netNS string) ([]*hns.HNSEndpoint, error) {
74 op := "uvm::GetNamespaceEndpoints"
75 l := log.G(ctx).WithField("netns-id", netNS)
76 l.Debug(op + " - Begin")
77 defer func() {
78 l.Debug(op + " - End")
79 }()
80
81 ids, err := hns.GetNamespaceEndpoints(netNS)
82 if err != nil {
83 return nil, err
84 }
85 var endpoints []*hns.HNSEndpoint
86 for _, id := range ids {
87 endpoint, err := hns.GetHNSEndpointByID(id)
88 if err != nil {
89 return nil, err
90 }
91 endpoints = append(endpoints, endpoint)
92 }
93 return endpoints, nil
94 }
95
96
97 func (uvm *UtilityVM) NCProxyEnabled() bool {
98 return uvm.ncProxyClientAddress != ""
99 }
100
101 type ncproxyClient struct {
102 raw *ttrpc.Client
103 ncproxyttrpc.NetworkConfigProxyService
104 }
105
106 func (n *ncproxyClient) Close() error {
107 return n.raw.Close()
108 }
109
110 func (uvm *UtilityVM) GetNCProxyClient() (*ncproxyClient, error) {
111 conn, err := winio.DialPipe(uvm.ncProxyClientAddress, nil)
112 if err != nil {
113 return nil, errors.Wrap(err, "failed to connect to ncproxy service")
114 }
115 raw := ttrpc.NewClient(conn, ttrpc.WithOnClose(func() { conn.Close() }))
116 return &ncproxyClient{raw, ncproxyttrpc.NewNetworkConfigProxyClient(raw)}, nil
117 }
118
119
120
121 type NetworkConfigType uint8
122
123 const (
124 NetworkRequestSetup NetworkConfigType = iota
125 NetworkRequestTearDown
126 )
127
128 var ErrNoNetworkSetup = errors.New("no network setup present for UVM")
129
130
131
132
133
134 func (uvm *UtilityVM) CreateAndAssignNetworkSetup(ctx context.Context, addr, containerID string) (err error) {
135 if uvm.NCProxyEnabled() {
136 if addr == "" || containerID == "" {
137 return errors.New("received empty field(s) for external network setup")
138 }
139 setup, err := NewExternalNetworkSetup(ctx, uvm, addr, containerID)
140 if err != nil {
141 return err
142 }
143 uvm.networkSetup = setup
144 } else {
145 uvm.networkSetup = NewInternalNetworkSetup(uvm)
146 }
147 return nil
148 }
149
150
151
152 func (uvm *UtilityVM) ConfigureNetworking(ctx context.Context, nsid string) error {
153 if uvm.networkSetup != nil {
154 return uvm.networkSetup.ConfigureNetworking(ctx, nsid, NetworkRequestSetup)
155 }
156 return ErrNoNetworkSetup
157 }
158
159
160
161 func (uvm *UtilityVM) TearDownNetworking(ctx context.Context, nsid string) error {
162 if uvm.networkSetup != nil {
163 return uvm.networkSetup.ConfigureNetworking(ctx, nsid, NetworkRequestTearDown)
164 }
165 return ErrNoNetworkSetup
166 }
167
168
169
170 type NetworkSetup interface {
171 ConfigureNetworking(ctx context.Context, namespaceID string, configType NetworkConfigType) error
172 }
173
174
175
176 type internalNetworkSetup struct {
177 vm *UtilityVM
178 }
179
180 func NewInternalNetworkSetup(vm *UtilityVM) NetworkSetup {
181 return &internalNetworkSetup{vm}
182 }
183
184 func (i *internalNetworkSetup) ConfigureNetworking(ctx context.Context, namespaceID string, configType NetworkConfigType) error {
185 switch configType {
186 case NetworkRequestSetup:
187 if err := i.vm.SetupNetworkNamespace(ctx, namespaceID); err != nil {
188 return err
189 }
190 case NetworkRequestTearDown:
191 if err := i.vm.RemoveNetNS(ctx, namespaceID); err != nil {
192 return err
193 }
194 default:
195 return fmt.Errorf("network configuration type %d is not known", configType)
196 }
197
198 return nil
199 }
200
201
202
203
204 type externalNetworkSetup struct {
205 vm *UtilityVM
206 caAddr string
207 containerID string
208 }
209
210
211
212 func NewExternalNetworkSetup(ctx context.Context, vm *UtilityVM, caAddr, containerID string) (NetworkSetup, error) {
213 if err := setupAndServe(ctx, caAddr, vm); err != nil {
214 return nil, err
215 }
216
217 return &externalNetworkSetup{
218 vm,
219 caAddr,
220 containerID,
221 }, nil
222 }
223
224 func (e *externalNetworkSetup) ConfigureNetworking(ctx context.Context, namespaceID string, configType NetworkConfigType) error {
225 client, err := e.vm.GetNCProxyClient()
226 if err != nil {
227 return errors.Wrapf(err, "no ncproxy client for UVM %q", e.vm.ID())
228 }
229 defer client.Close()
230
231 netReq := &ncproxyttrpc.ConfigureNetworkingInternalRequest{
232 ContainerID: e.containerID,
233 }
234
235 switch configType {
236 case NetworkRequestSetup:
237 if err := e.vm.AddNetNSByID(ctx, namespaceID); err != nil {
238 return err
239 }
240
241 registerReq := &ncproxyttrpc.RegisterComputeAgentRequest{
242 ContainerID: e.containerID,
243 AgentAddress: e.caAddr,
244 }
245 if _, err := client.RegisterComputeAgent(ctx, registerReq); err != nil {
246 return err
247 }
248
249 netReq.RequestType = ncproxyttrpc.RequestTypeInternal_Setup
250 if _, err := client.ConfigureNetworking(ctx, netReq); err != nil {
251 return err
252 }
253 case NetworkRequestTearDown:
254 netReq.RequestType = ncproxyttrpc.RequestTypeInternal_Teardown
255 if _, err := client.ConfigureNetworking(ctx, netReq); err != nil {
256 return err
257 }
258
259 unregisterReq := &ncproxyttrpc.UnregisterComputeAgentRequest{
260 ContainerID: e.containerID,
261 }
262 if _, err := client.UnregisterComputeAgent(ctx, unregisterReq); err != nil {
263 return err
264 }
265 default:
266 return fmt.Errorf("network configuration type %d is not known", configType)
267 }
268
269 return nil
270 }
271
272
273
274 type NetworkEndpoints struct {
275 EndpointIDs []string
276
277 Namespace string
278 }
279
280
281 func (endpoints *NetworkEndpoints) Release(ctx context.Context) error {
282 for _, endpoint := range endpoints.EndpointIDs {
283 err := hns.RemoveNamespaceEndpoint(endpoints.Namespace, endpoint)
284 if err != nil {
285 if !os.IsNotExist(err) {
286 return err
287 }
288 log.G(ctx).WithFields(logrus.Fields{
289 "endpointID": endpoint,
290 "netID": endpoints.Namespace,
291 }).Warn("removing endpoint from namespace: does not exist")
292 }
293 }
294 endpoints.EndpointIDs = nil
295 err := hns.RemoveNamespace(endpoints.Namespace)
296 if err != nil && !os.IsNotExist(err) {
297 return err
298 }
299 return nil
300 }
301
302
303
304
305
306
307
308 func (uvm *UtilityVM) AddNetNS(ctx context.Context, hcnNamespace *hcn.HostComputeNamespace) error {
309 uvm.m.Lock()
310 defer uvm.m.Unlock()
311 if _, ok := uvm.namespaces[hcnNamespace.Id]; ok {
312 return ErrNetNSAlreadyAttached
313 }
314
315 if uvm.isNetworkNamespaceSupported() {
316
317
318 if uvm.operatingSystem == "windows" {
319 guestNamespace := hcsschema.ModifySettingRequest{
320 GuestRequest: guestrequest.ModificationRequest{
321 ResourceType: guestresource.ResourceTypeNetworkNamespace,
322 RequestType: guestrequest.RequestTypeAdd,
323 Settings: hcnNamespace,
324 },
325 }
326 if err := uvm.modify(ctx, &guestNamespace); err != nil {
327 return err
328 }
329 }
330 }
331
332 if uvm.namespaces == nil {
333 uvm.namespaces = make(map[string]*namespaceInfo)
334 }
335 uvm.namespaces[hcnNamespace.Id] = &namespaceInfo{
336 nics: make(map[string]*nicInfo),
337 }
338 return nil
339 }
340
341
342
343
344
345 func (uvm *UtilityVM) AddNetNSByID(ctx context.Context, id string) error {
346 hcnNamespace, err := hcn.GetNamespaceByID(id)
347 if err != nil {
348 return err
349 }
350
351 if err = uvm.AddNetNS(ctx, hcnNamespace); err != nil {
352 return err
353 }
354 return nil
355 }
356
357
358
359
360
361 func (uvm *UtilityVM) AddEndpointToNSWithID(ctx context.Context, nsID, nicID string, endpoint *hns.HNSEndpoint) error {
362 uvm.m.Lock()
363 defer uvm.m.Unlock()
364 ns, ok := uvm.namespaces[nsID]
365 if !ok {
366 return ErrNetNSNotFound
367 }
368 if _, ok := ns.nics[endpoint.Id]; !ok {
369 if nicID == "" {
370 id, err := guid.NewV4()
371 if err != nil {
372 return err
373 }
374 nicID = id.String()
375 }
376 if err := uvm.addNIC(ctx, nicID, endpoint); err != nil {
377 return err
378 }
379 ns.nics[endpoint.Id] = &nicInfo{
380 ID: nicID,
381 Endpoint: endpoint,
382 }
383 }
384 return nil
385 }
386
387
388
389
390
391
392 func (uvm *UtilityVM) AddEndpointsToNS(ctx context.Context, id string, endpoints []*hns.HNSEndpoint) error {
393 uvm.m.Lock()
394 defer uvm.m.Unlock()
395
396 ns, ok := uvm.namespaces[id]
397 if !ok {
398 return ErrNetNSNotFound
399 }
400
401 for _, endpoint := range endpoints {
402 if _, ok := ns.nics[endpoint.Id]; !ok {
403 nicID, err := guid.NewV4()
404 if err != nil {
405 return err
406 }
407 if err := uvm.addNIC(ctx, nicID.String(), endpoint); err != nil {
408 return err
409 }
410 ns.nics[endpoint.Id] = &nicInfo{
411 ID: nicID.String(),
412 Endpoint: endpoint,
413 }
414 }
415 }
416 return nil
417 }
418
419
420
421
422
423 func (uvm *UtilityVM) RemoveNetNS(ctx context.Context, id string) error {
424 uvm.m.Lock()
425 defer uvm.m.Unlock()
426 if ns, ok := uvm.namespaces[id]; ok {
427 for _, ninfo := range ns.nics {
428 if err := uvm.removeNIC(ctx, ninfo.ID, ninfo.Endpoint); err != nil {
429 return err
430 }
431 ns.nics[ninfo.Endpoint.Id] = nil
432 }
433
434 if uvm.isNetworkNamespaceSupported() {
435 if uvm.operatingSystem == "windows" {
436 hcnNamespace, err := hcn.GetNamespaceByID(id)
437 if err != nil {
438 return err
439 }
440 guestNamespace := hcsschema.ModifySettingRequest{
441 GuestRequest: guestrequest.ModificationRequest{
442 ResourceType: guestresource.ResourceTypeNetworkNamespace,
443 RequestType: guestrequest.RequestTypeRemove,
444 Settings: hcnNamespace,
445 },
446 }
447 if err := uvm.modify(ctx, &guestNamespace); err != nil {
448 return err
449 }
450 }
451 }
452 delete(uvm.namespaces, id)
453 }
454 return nil
455 }
456
457
458
459
460
461
462 func (uvm *UtilityVM) RemoveEndpointsFromNS(ctx context.Context, id string, endpoints []*hns.HNSEndpoint) error {
463 uvm.m.Lock()
464 defer uvm.m.Unlock()
465
466 ns, ok := uvm.namespaces[id]
467 if !ok {
468 return ErrNetNSNotFound
469 }
470
471 for _, endpoint := range endpoints {
472 if ninfo, ok := ns.nics[endpoint.Id]; ok && ninfo != nil {
473 if err := uvm.removeNIC(ctx, ninfo.ID, ninfo.Endpoint); err != nil {
474 return err
475 }
476 delete(ns.nics, endpoint.Id)
477 }
478 }
479 return nil
480 }
481
482
483
484
485
486
487 func (uvm *UtilityVM) RemoveEndpointFromNS(ctx context.Context, id string, endpoint *hns.HNSEndpoint) error {
488 uvm.m.Lock()
489 defer uvm.m.Unlock()
490
491 ns, ok := uvm.namespaces[id]
492 if !ok {
493 return ErrNetNSNotFound
494 }
495
496 if ninfo, ok := ns.nics[endpoint.Id]; ok && ninfo != nil {
497 if err := uvm.removeNIC(ctx, ninfo.ID, ninfo.Endpoint); err != nil {
498 return err
499 }
500 delete(ns.nics, endpoint.Id)
501 } else {
502 return ErrNICNotFound
503 }
504 return nil
505 }
506
507
508 func (uvm *UtilityVM) isNetworkNamespaceSupported() bool {
509 return uvm.guestCaps.NamespaceAddRequestSupported
510 }
511
512 func getNetworkModifyRequest(adapterID string, requestType guestrequest.RequestType, settings interface{}) interface{} {
513 if osversion.Build() >= osversion.RS5 {
514 return guestrequest.NetworkModifyRequest{
515 AdapterId: adapterID,
516 RequestType: requestType,
517 Settings: settings,
518 }
519 }
520 return guestrequest.RS4NetworkModifyRequest{
521 AdapterInstanceId: adapterID,
522 RequestType: requestType,
523 Settings: settings,
524 }
525 }
526
527
528 func (uvm *UtilityVM) addNIC(ctx context.Context, id string, endpoint *hns.HNSEndpoint) error {
529
530 if uvm.operatingSystem == "windows" {
531 preAddRequest := hcsschema.ModifySettingRequest{
532 GuestRequest: guestrequest.ModificationRequest{
533 ResourceType: guestresource.ResourceTypeNetwork,
534 RequestType: guestrequest.RequestTypeAdd,
535 Settings: getNetworkModifyRequest(
536 id,
537 guestrequest.RequestTypePreAdd,
538 endpoint),
539 },
540 }
541 if err := uvm.modify(ctx, &preAddRequest); err != nil {
542 return err
543 }
544 }
545
546
547 request := hcsschema.ModifySettingRequest{
548 RequestType: guestrequest.RequestTypeAdd,
549 ResourcePath: fmt.Sprintf(resourcepaths.NetworkResourceFormat, id),
550 Settings: hcsschema.NetworkAdapter{
551 EndpointId: endpoint.Id,
552 MacAddress: endpoint.MacAddress,
553 },
554 }
555
556 if uvm.operatingSystem == "windows" {
557 request.GuestRequest = guestrequest.ModificationRequest{
558 ResourceType: guestresource.ResourceTypeNetwork,
559 RequestType: guestrequest.RequestTypeAdd,
560 Settings: getNetworkModifyRequest(
561 id,
562 guestrequest.RequestTypeAdd,
563 nil),
564 }
565 } else {
566
567 s := &guestresource.LCOWNetworkAdapter{
568 NamespaceID: endpoint.Namespace.ID,
569 ID: id,
570 MacAddress: endpoint.MacAddress,
571 IPAddress: endpoint.IPAddress.String(),
572 PrefixLength: endpoint.PrefixLength,
573 GatewayAddress: endpoint.GatewayAddress,
574 DNSSuffix: endpoint.DNSSuffix,
575 DNSServerList: endpoint.DNSServerList,
576 EnableLowMetric: endpoint.EnableLowMetric,
577 EncapOverhead: endpoint.EncapOverhead,
578 }
579 if v6 := endpoint.IPv6Address.To16(); v6 != nil && !v6.IsUnspecified() {
580
581
582 s.IPv6Address = v6.String()
583 s.IPv6PrefixLength = endpoint.IPv6PrefixLength
584 s.IPv6GatewayAddress = endpoint.GatewayAddressV6
585 log.G(ctx).WithFields(logrus.Fields{
586 "ip": s.IPAddress,
587 "prefixLength": s.IPv6PrefixLength,
588 "gateway": s.IPv6GatewayAddress,
589 }).Debug("adding IPv6 settings")
590 }
591 if uvm.isNetworkNamespaceSupported() {
592 request.GuestRequest = guestrequest.ModificationRequest{
593 ResourceType: guestresource.ResourceTypeNetwork,
594 RequestType: guestrequest.RequestTypeAdd,
595 Settings: s,
596 }
597 }
598 }
599
600 if err := uvm.modify(ctx, &request); err != nil {
601 return err
602 }
603
604 return nil
605 }
606
607 func (uvm *UtilityVM) removeNIC(ctx context.Context, id string, endpoint *hns.HNSEndpoint) error {
608 request := hcsschema.ModifySettingRequest{
609 RequestType: guestrequest.RequestTypeRemove,
610 ResourcePath: fmt.Sprintf(resourcepaths.NetworkResourceFormat, id),
611 Settings: hcsschema.NetworkAdapter{
612 EndpointId: endpoint.Id,
613 MacAddress: endpoint.MacAddress,
614 },
615 }
616
617 if uvm.operatingSystem == "windows" {
618 request.GuestRequest = hcsschema.ModifySettingRequest{
619 RequestType: guestrequest.RequestTypeRemove,
620 Settings: getNetworkModifyRequest(
621 id,
622 guestrequest.RequestTypeRemove,
623 nil),
624 }
625 } else {
626
627 if uvm.isNetworkNamespaceSupported() {
628 request.GuestRequest = guestrequest.ModificationRequest{
629 ResourceType: guestresource.ResourceTypeNetwork,
630 RequestType: guestrequest.RequestTypeRemove,
631 Settings: &guestresource.LCOWNetworkAdapter{
632 NamespaceID: endpoint.Namespace.ID,
633 ID: endpoint.Id,
634 },
635 }
636 }
637 }
638
639 if err := uvm.modify(ctx, &request); err != nil {
640 return err
641 }
642 return nil
643 }
644
645
646 func (uvm *UtilityVM) RemoveAllNICs(ctx context.Context) error {
647 for _, ns := range uvm.namespaces {
648 for _, ninfo := range ns.nics {
649 if err := uvm.removeNIC(ctx, ninfo.ID, ninfo.Endpoint); err != nil {
650 return err
651 }
652 }
653 }
654 return nil
655 }
656
657
658 func (uvm *UtilityVM) UpdateNIC(ctx context.Context, id string, settings *hcsschema.NetworkAdapter) error {
659 req := &hcsschema.ModifySettingRequest{
660 RequestType: guestrequest.RequestTypeUpdate,
661 ResourcePath: fmt.Sprintf(resourcepaths.NetworkResourceFormat, id),
662 Settings: settings,
663 }
664 return uvm.modify(ctx, req)
665 }
666
667
668
669 func (uvm *UtilityVM) AddNICInGuest(ctx context.Context, cfg *guestresource.LCOWNetworkAdapter) error {
670 if !uvm.isNetworkNamespaceSupported() {
671 return fmt.Errorf("guest does not support network namespaces and cannot add VF NIC %+v", cfg)
672 }
673 request := hcsschema.ModifySettingRequest{}
674 request.GuestRequest = guestrequest.ModificationRequest{
675 ResourceType: guestresource.ResourceTypeNetwork,
676 RequestType: guestrequest.RequestTypeAdd,
677 Settings: cfg,
678 }
679
680 return uvm.modify(ctx, &request)
681 }
682
683
684
685 func (uvm *UtilityVM) RemoveNICInGuest(ctx context.Context, cfg *guestresource.LCOWNetworkAdapter) error {
686 if !uvm.isNetworkNamespaceSupported() {
687 return fmt.Errorf("guest does not support network namespaces and cannot remove VF NIC %+v", cfg)
688 }
689 request := hcsschema.ModifySettingRequest{}
690 request.GuestRequest = guestrequest.ModificationRequest{
691 ResourceType: guestresource.ResourceTypeNetwork,
692 RequestType: guestrequest.RequestTypeRemove,
693 Settings: cfg,
694 }
695
696 return uvm.modify(ctx, &request)
697 }
698
View as plain text