...

Source file src/github.com/Microsoft/hcsshim/cmd/ncproxy/hcn.go

Documentation: github.com/Microsoft/hcsshim/cmd/ncproxy

     1  //go:build windows
     2  
     3  package main
     4  
     5  import (
     6  	"context"
     7  	"encoding/json"
     8  	"fmt"
     9  	"net"
    10  	"strings"
    11  
    12  	"github.com/Microsoft/hcsshim/hcn"
    13  	"github.com/Microsoft/hcsshim/internal/log"
    14  	ncproxygrpc "github.com/Microsoft/hcsshim/pkg/ncproxy/ncproxygrpc/v1"
    15  	"github.com/pkg/errors"
    16  )
    17  
    18  func hcnEndpointToEndpointResponse(ep *hcn.HostComputeEndpoint) (_ *ncproxygrpc.GetEndpointResponse, err error) {
    19  	hcnEndpointResp := &ncproxygrpc.HcnEndpointSettings{
    20  		Name:        ep.Name,
    21  		Macaddress:  ep.MacAddress,
    22  		NetworkName: ep.HostComputeNetwork,
    23  		DnsSetting: &ncproxygrpc.DnsSetting{
    24  			ServerIpAddrs: ep.Dns.ServerList,
    25  			Domain:        ep.Dns.Domain,
    26  			Search:        ep.Dns.Search,
    27  		},
    28  	}
    29  
    30  	policies, err := parseEndpointPolicies(ep.Policies)
    31  	if err != nil {
    32  		return nil, err
    33  	}
    34  	hcnEndpointResp.Policies = policies
    35  
    36  	ipConfigInfos := ep.IpConfigurations
    37  	// there may be one ipv4 and/or one ipv6 configuration for an endpoint
    38  	if len(ipConfigInfos) == 0 || len(ipConfigInfos) > 2 {
    39  		return nil, errors.Errorf("invalid number (%v) of ip configuration information for endpoint %v", len(ipConfigInfos), ep.Name)
    40  	}
    41  	for _, ipConfig := range ipConfigInfos {
    42  		ip := net.ParseIP(ipConfig.IpAddress)
    43  		if ip == nil {
    44  			return nil, errors.Errorf("failed to parse IP address %v", ipConfig.IpAddress)
    45  		}
    46  		if ip.To4() != nil {
    47  			// this is an IPv4 address
    48  			hcnEndpointResp.Ipaddress = ipConfig.IpAddress
    49  			hcnEndpointResp.IpaddressPrefixlength = uint32(ipConfig.PrefixLength)
    50  		} else {
    51  			// this is an IPv6 address
    52  			hcnEndpointResp.Ipv6Address = ipConfig.IpAddress
    53  			hcnEndpointResp.Ipv6AddressPrefixlength = uint32(ipConfig.PrefixLength)
    54  		}
    55  	}
    56  
    57  	return &ncproxygrpc.GetEndpointResponse{
    58  		Namespace: ep.HostComputeNamespace,
    59  		ID:        ep.Id,
    60  		Endpoint: &ncproxygrpc.EndpointSettings{
    61  			Settings: &ncproxygrpc.EndpointSettings_HcnEndpoint{
    62  				HcnEndpoint: hcnEndpointResp,
    63  			},
    64  		},
    65  	}, nil
    66  }
    67  
    68  func modifyEndpoint(ctx context.Context, id string, policies []hcn.EndpointPolicy, requestType hcn.RequestType) error {
    69  	endpointRequest := hcn.PolicyEndpointRequest{
    70  		Policies: policies,
    71  	}
    72  
    73  	settingsJSON, err := json.Marshal(endpointRequest)
    74  	if err != nil {
    75  		return err
    76  	}
    77  
    78  	requestMessage := &hcn.ModifyEndpointSettingRequest{
    79  		ResourceType: hcn.EndpointResourceTypePolicy,
    80  		RequestType:  requestType,
    81  		Settings:     settingsJSON,
    82  	}
    83  
    84  	return hcn.ModifyEndpointSettings(id, requestMessage)
    85  }
    86  
    87  func parseEndpointPolicies(policies []hcn.EndpointPolicy) (*ncproxygrpc.HcnEndpointPolicies, error) {
    88  	results := &ncproxygrpc.HcnEndpointPolicies{}
    89  	for _, policy := range policies {
    90  		switch policy.Type {
    91  		case hcn.PortMapping:
    92  			portMapSettings := &hcn.PortnameEndpointPolicySetting{}
    93  			if err := json.Unmarshal(policy.Settings, portMapSettings); err != nil {
    94  				return nil, err
    95  			}
    96  			results.PortnamePolicySetting = &ncproxygrpc.PortNameEndpointPolicySetting{
    97  				PortName: portMapSettings.Name,
    98  			}
    99  		case hcn.IOV:
   100  			iovSettings := &hcn.IovPolicySetting{}
   101  			if err := json.Unmarshal(policy.Settings, iovSettings); err != nil {
   102  				return nil, err
   103  			}
   104  			results.IovPolicySettings = &ncproxygrpc.IovEndpointPolicySetting{
   105  				IovOffloadWeight:    iovSettings.IovOffloadWeight,
   106  				QueuePairsRequested: iovSettings.QueuePairsRequested,
   107  				InterruptModeration: iovSettings.InterruptModeration,
   108  			}
   109  		}
   110  	}
   111  	return results, nil
   112  }
   113  
   114  func constructEndpointPolicies(req *ncproxygrpc.HcnEndpointPolicies) ([]hcn.EndpointPolicy, error) {
   115  	policies := []hcn.EndpointPolicy{}
   116  	if req.IovPolicySettings != nil {
   117  		iovSettings := hcn.IovPolicySetting{
   118  			IovOffloadWeight:    req.IovPolicySettings.IovOffloadWeight,
   119  			QueuePairsRequested: req.IovPolicySettings.QueuePairsRequested,
   120  			InterruptModeration: req.IovPolicySettings.InterruptModeration,
   121  		}
   122  		iovJSON, err := json.Marshal(iovSettings)
   123  		if err != nil {
   124  			return []hcn.EndpointPolicy{}, errors.Wrap(err, "failed to marshal IovPolicySettings")
   125  		}
   126  		policy := hcn.EndpointPolicy{
   127  			Type:     hcn.IOV,
   128  			Settings: iovJSON,
   129  		}
   130  		policies = append(policies, policy)
   131  	}
   132  
   133  	if req.PortnamePolicySetting != nil {
   134  		portPolicy := hcn.PortnameEndpointPolicySetting{
   135  			Name: req.PortnamePolicySetting.PortName,
   136  		}
   137  		portPolicyJSON, err := json.Marshal(portPolicy)
   138  		if err != nil {
   139  			return []hcn.EndpointPolicy{}, errors.Wrap(err, "failed to marshal portname")
   140  		}
   141  		policy := hcn.EndpointPolicy{
   142  			Type:     hcn.PortName,
   143  			Settings: portPolicyJSON,
   144  		}
   145  		policies = append(policies, policy)
   146  	}
   147  
   148  	return policies, nil
   149  }
   150  
   151  func createHCNNetwork(ctx context.Context, req *ncproxygrpc.HostComputeNetworkSettings) (*hcn.HostComputeNetwork, error) {
   152  	// Check if the network already exists, and if so return error.
   153  	_, err := hcn.GetNetworkByName(req.Name)
   154  	if err == nil {
   155  		return nil, errors.Errorf("network with name %q already exists", req.Name)
   156  	}
   157  
   158  	policies := []hcn.NetworkPolicy{}
   159  	if req.SwitchName != "" {
   160  		// Get the layer ID from the external switch. HNS will create a transparent network for
   161  		// any external switch that is created not through HNS so this is what we're
   162  		// searching for here. If the network exists, the vSwitch with this name exists.
   163  		extSwitch, err := hcn.GetNetworkByName(req.SwitchName)
   164  		if err != nil {
   165  			if _, ok := err.(hcn.NetworkNotFoundError); ok {
   166  				return nil, errors.Errorf("no network/switch with name `%s` found", req.SwitchName)
   167  			}
   168  			return nil, errors.Wrapf(err, "failed to get network/switch with name %q", req.SwitchName)
   169  		}
   170  
   171  		// Get layer ID and use this as the basis for what to layer the new network over.
   172  		if extSwitch.Health.Extra.LayeredOn == "" {
   173  			return nil, errors.Errorf("no layer ID found for network %q found", extSwitch.Id)
   174  		}
   175  
   176  		layerPolicy := hcn.LayerConstraintNetworkPolicySetting{LayerId: extSwitch.Health.Extra.LayeredOn}
   177  		data, err := json.Marshal(layerPolicy)
   178  		if err != nil {
   179  			return nil, errors.Wrap(err, "failed to marshal layer policy")
   180  		}
   181  
   182  		netPolicy := hcn.NetworkPolicy{
   183  			Type:     hcn.LayerConstraint,
   184  			Settings: data,
   185  		}
   186  		policies = append(policies, netPolicy)
   187  	}
   188  
   189  	subnets := make([]hcn.Subnet, 0, len(req.SubnetIpaddressPrefix)+len(req.SubnetIpaddressPrefixIpv6))
   190  	for _, addrPrefix := range req.SubnetIpaddressPrefix {
   191  		subnet := hcn.Subnet{
   192  			IpAddressPrefix: addrPrefix,
   193  			Routes: []hcn.Route{
   194  				{
   195  					NextHop:           req.DefaultGateway,
   196  					DestinationPrefix: "0.0.0.0/0",
   197  				},
   198  			},
   199  		}
   200  		subnets = append(subnets, subnet)
   201  	}
   202  
   203  	if len(req.SubnetIpaddressPrefixIpv6) != 0 {
   204  		if err := hcn.IPv6DualStackSupported(); err != nil {
   205  			// a request was made for an IPv6 address on a system that doesn't support IPv6
   206  			return nil, fmt.Errorf("IPv6 address requested but not supported: %v", err)
   207  		}
   208  	}
   209  
   210  	for _, ipv6AddrPrefix := range req.SubnetIpaddressPrefixIpv6 {
   211  		subnet := hcn.Subnet{
   212  			IpAddressPrefix: ipv6AddrPrefix,
   213  			Routes: []hcn.Route{
   214  				{
   215  					NextHop:           req.DefaultGatewayIpv6,
   216  					DestinationPrefix: "::/0",
   217  				},
   218  			},
   219  		}
   220  		subnets = append(subnets, subnet)
   221  	}
   222  
   223  	ipam := hcn.Ipam{
   224  		Type:    req.IpamType.String(),
   225  		Subnets: subnets,
   226  	}
   227  
   228  	network := &hcn.HostComputeNetwork{
   229  		Name:     req.Name,
   230  		Type:     hcn.NetworkType(req.Mode.String()),
   231  		Ipams:    []hcn.Ipam{ipam},
   232  		Policies: policies,
   233  		SchemaVersion: hcn.SchemaVersion{
   234  			Major: 2,
   235  			Minor: 2,
   236  		},
   237  	}
   238  
   239  	network, err = network.Create()
   240  	if err != nil {
   241  		return nil, errors.Wrapf(err, "failed to create HNS network %q", req.Name)
   242  	}
   243  
   244  	return network, nil
   245  }
   246  
   247  func hcnNetworkToNetworkResponse(ctx context.Context, network *hcn.HostComputeNetwork) (*ncproxygrpc.GetNetworkResponse, error) {
   248  	var (
   249  		ipamType                  int32
   250  		defaultGateway            string
   251  		defaultGatewayIPv6        string
   252  		switchName                string
   253  		subnetIPAddressPrefixes   []string
   254  		subnetIPv6AddressPrefixes []string
   255  	)
   256  
   257  	for _, ipam := range network.Ipams {
   258  		// all ipams should have the same type so just keep the last one
   259  		ipamType = ncproxygrpc.HostComputeNetworkSettings_IpamType_value[ipam.Type]
   260  		for _, subnet := range ipam.Subnets {
   261  			// split prefix off string so we can check if this is ipv4 or ipv6
   262  			ipParts := strings.Split(subnet.IpAddressPrefix, "/")
   263  			ipPrefix := net.ParseIP(ipParts[0])
   264  			if ipPrefix == nil {
   265  				return nil, fmt.Errorf("failed to parse IP address %v", ipPrefix)
   266  			}
   267  			if ipPrefix.To4() != nil {
   268  				// this is an IPv4 address
   269  				subnetIPAddressPrefixes = append(subnetIPAddressPrefixes, subnet.IpAddressPrefix)
   270  				if len(subnet.Routes) != 0 {
   271  					defaultGateway = subnet.Routes[0].NextHop
   272  				}
   273  			} else {
   274  				// this is an IPv6 address
   275  				subnetIPv6AddressPrefixes = append(subnetIPv6AddressPrefixes, subnet.IpAddressPrefix)
   276  				if len(subnet.Routes) != 0 {
   277  					defaultGatewayIPv6 = subnet.Routes[0].NextHop
   278  				}
   279  			}
   280  		}
   281  	}
   282  
   283  	mode := ncproxygrpc.HostComputeNetworkSettings_NetworkMode_value[string(network.Type)]
   284  
   285  	if network.Health.Extra.SwitchGuid != "" {
   286  		extSwitch, err := hcn.GetNetworkByID(network.Health.Extra.SwitchGuid)
   287  		if err != nil {
   288  			return nil, err
   289  		}
   290  		switchName = extSwitch.Name
   291  	}
   292  
   293  	settings := &ncproxygrpc.HostComputeNetworkSettings{
   294  		Name:                      network.Name,
   295  		Mode:                      ncproxygrpc.HostComputeNetworkSettings_NetworkMode(mode),
   296  		SwitchName:                switchName,
   297  		IpamType:                  ncproxygrpc.HostComputeNetworkSettings_IpamType(ipamType),
   298  		SubnetIpaddressPrefix:     subnetIPAddressPrefixes,
   299  		DefaultGateway:            defaultGateway,
   300  		SubnetIpaddressPrefixIpv6: subnetIPv6AddressPrefixes,
   301  		DefaultGatewayIpv6:        defaultGatewayIPv6,
   302  	}
   303  
   304  	var startMac, endMac string
   305  	switch n := len(network.MacPool.Ranges); {
   306  	case n < 1:
   307  		return nil, fmt.Errorf("network %s(%s) MAC pool is empty", network.Name, network.Id)
   308  	case n > 1:
   309  		log.G(ctx).WithField("networkName", network.Name).Warn("network has multiple MAC pools, only returning the first")
   310  		fallthrough
   311  	default:
   312  		startMac = network.MacPool.Ranges[0].StartMacAddress
   313  		endMac = network.MacPool.Ranges[0].EndMacAddress
   314  	}
   315  
   316  	return &ncproxygrpc.GetNetworkResponse{
   317  		ID: network.Id,
   318  		Network: &ncproxygrpc.Network{
   319  			Settings: &ncproxygrpc.Network_HcnNetwork{
   320  				HcnNetwork: settings,
   321  			},
   322  		},
   323  		MacRange: &ncproxygrpc.MacRange{
   324  			StartMacAddress: startMac,
   325  			EndMacAddress:   endMac,
   326  		},
   327  	}, nil
   328  }
   329  
   330  func createHCNEndpoint(ctx context.Context, network *hcn.HostComputeNetwork, req *ncproxygrpc.HcnEndpointSettings) (*hcn.HostComputeEndpoint, error) {
   331  	// Construct ip config.
   332  	ipConfigs := []hcn.IpConfig{}
   333  	if req.Ipaddress != "" && req.IpaddressPrefixlength != 0 {
   334  		ipv4Config := hcn.IpConfig{
   335  			IpAddress:    req.Ipaddress,
   336  			PrefixLength: uint8(req.IpaddressPrefixlength),
   337  		}
   338  		ipConfigs = append(ipConfigs, ipv4Config)
   339  	}
   340  
   341  	if req.Ipv6Address != "" && req.Ipv6AddressPrefixlength != 0 {
   342  		if err := hcn.IPv6DualStackSupported(); err != nil {
   343  			// a request was made for an IPv6 address on a system that doesn't support IPv6
   344  			return nil, fmt.Errorf("IPv6 address requested but not supported: %v", err)
   345  		}
   346  		ipv6Config := hcn.IpConfig{
   347  			IpAddress:    req.Ipv6Address,
   348  			PrefixLength: uint8(req.Ipv6AddressPrefixlength),
   349  		}
   350  		ipConfigs = append(ipConfigs, ipv6Config)
   351  	}
   352  
   353  	var err error
   354  	policies := []hcn.EndpointPolicy{}
   355  	if req.Policies != nil {
   356  		policies, err = constructEndpointPolicies(req.Policies)
   357  		if err != nil {
   358  			return nil, errors.Wrap(err, "failed to construct endpoint policies")
   359  		}
   360  	}
   361  
   362  	endpoint := &hcn.HostComputeEndpoint{
   363  		Name:               req.Name,
   364  		HostComputeNetwork: network.Id,
   365  		MacAddress:         req.Macaddress,
   366  		IpConfigurations:   ipConfigs,
   367  		Policies:           policies,
   368  		SchemaVersion: hcn.SchemaVersion{
   369  			Major: 2,
   370  			Minor: 0,
   371  		},
   372  	}
   373  
   374  	if req.DnsSetting != nil {
   375  		endpoint.Dns = hcn.Dns{
   376  			ServerList: req.DnsSetting.ServerIpAddrs,
   377  			Domain:     req.DnsSetting.Domain,
   378  			Search:     req.DnsSetting.Search,
   379  		}
   380  	}
   381  	endpoint, err = endpoint.Create()
   382  	if err != nil {
   383  		return nil, errors.Wrap(err, "failed to create HNS endpoint")
   384  	}
   385  
   386  	return endpoint, nil
   387  }
   388  
   389  // getHostDefaultNamespace returns the first namespace found that has type [hcn.NamespaceTypeHostDefault],
   390  // or an error if none is found.
   391  func getHostDefaultNamespace() (string, error) {
   392  	namespaces, err := hcn.ListNamespaces()
   393  	if err != nil {
   394  		return "", errors.Wrapf(err, "failed list namespaces")
   395  	}
   396  
   397  	for _, ns := range namespaces {
   398  		if ns.Type == hcn.NamespaceTypeHostDefault {
   399  			return ns.Id, nil
   400  		}
   401  	}
   402  	return "", errors.New("unable to find default host namespace to attach to")
   403  }
   404  

View as plain text