rke2/pkg/windows/utils.go

371 lines
9.5 KiB
Go

//go:build windows
// +build windows
package windows
import (
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"net/url"
"regexp"
"strings"
"text/template"
"time"
"github.com/Microsoft/hcsshim"
wapi "github.com/iamacarpet/go-win64api"
"github.com/libp2p/go-netroute"
"github.com/sirupsen/logrus"
opv1 "github.com/tigera/operator/api/v1"
"k8s.io/apimachinery/pkg/util/wait"
)
var (
replaceSlashWin = template.FuncMap{
"replace": func(s string) string {
return strings.ReplaceAll(s, "\\", "\\\\")
},
}
)
type networkProvider interface {
GetHNSNetworkByName(name string) (*hcsshim.HNSNetwork, error)
}
type defaultNetworkProvider struct{}
func (d defaultNetworkProvider) GetHNSNetworkByName(name string) (*hcsshim.HNSNetwork, error) {
return hcsshim.GetHNSNetworkByName(name)
}
var (
netProvider networkProvider = defaultNetworkProvider{}
httpClient *http.Client = http.DefaultClient
)
// createHnsNetwork creates the network that will connect nodes and returns its managementIP
func createHnsNetwork(backend string, networkAdapter string) (string, error) {
var network hcsshim.HNSNetwork
// Check if the interface already exists
hcsnetwork, err := hcsshim.GetHNSNetworkByName(CalicoHnsNetworkName)
if err == nil {
return hcsnetwork.ManagementIP, nil
}
if backend == "vxlan" {
// Ignoring the return because both true and false without an error represent that the firewall rule was created or already exists
if _, err := wapi.FirewallRuleAdd("OverlayTraffic4789UDP", "Overlay network traffic UDP", "", "4789", wapi.NET_FW_IP_PROTOCOL_UDP, wapi.NET_FW_PROFILE2_ALL); err != nil {
return "", fmt.Errorf("error creating firewall rules: %v", err)
}
logrus.Infof("Creating VXLAN network using the vxlanAdapter: %s", networkAdapter)
network = hcsshim.HNSNetwork{
Type: "Overlay",
Name: CalicoHnsNetworkName,
NetworkAdapterName: networkAdapter,
Subnets: []hcsshim.Subnet{
{
AddressPrefix: "192.168.255.0/30",
GatewayAddress: "192.168.255.1",
Policies: []json.RawMessage{
[]byte("{ \"Type\": \"VSID\", \"VSID\": 9999 }"),
},
},
},
}
} else {
network = hcsshim.HNSNetwork{
Type: "L2Bridge",
Name: CalicoHnsNetworkName,
NetworkAdapterName: networkAdapter,
Subnets: []hcsshim.Subnet{
{
AddressPrefix: "192.168.255.0/30",
GatewayAddress: "192.168.255.1",
},
},
}
}
if _, err := network.Create(); err != nil {
return "", fmt.Errorf("error creating the %s network: %v", CalicoHnsNetworkName, err)
}
// Check if network exists. If it does not after 5 minutes, fail
for start := time.Now(); time.Since(start) < 5*time.Minute; {
network, err := hcsshim.GetHNSNetworkByName(CalicoHnsNetworkName)
if err == nil {
return network.ManagementIP, nil
}
}
return "", fmt.Errorf("failed to create %s network", CalicoHnsNetworkName)
}
// nodeAddressAutodetection processes the HelmChartConfig info and returns the nodeAddressAutodetection method expected by Calico config
func nodeAddressAutodetection(autoDetect opv1.NodeAddressAutodetection) (string, error) {
if autoDetect.FirstFound != nil && *autoDetect.FirstFound {
return "first-found", nil
}
if autoDetect.CanReach != "" {
return "can-reach=" + autoDetect.CanReach, nil
}
if autoDetect.Interface != "" {
return "interface=" + autoDetect.Interface, nil
}
if len(autoDetect.CIDRS) > 0 {
return "cidrs=" + strings.Join(autoDetect.CIDRS, ","), nil
}
return "", errors.New("the passed autoDetect value is not supported")
}
// deleteAllNetworks deletes all hns networks
func deleteAllNetworks() error {
networks, err := hcsshim.HNSListNetworkRequest("GET", "", "")
if err != nil {
return err
}
var ips []string
for _, network := range networks {
if network.Name != "nat" {
logrus.Debugf("Deleting network: %s before starting calico", network.Name)
ips = append(ips, network.ManagementIP)
_, err = network.Delete()
if err != nil {
return err
}
}
}
// HNS overlay networks restart the physical interface when they are deleted. Wait until it comes back before returning
// TODO: Replace with non-deprecated PollUntilContextTimeout when our and Kubernetes code migrate to it
waitErr := wait.Poll(2*time.Second, 30*time.Second, func() (bool, error) {
for _, ip := range ips {
logrus.Debugf("Calico is waiting for the interface with ip: %s to come back", ip)
_, err := findInterface(ip)
if err != nil {
return false, nil
}
}
return true, nil
})
if waitErr == wait.ErrWaitTimeout {
return fmt.Errorf("timed out waiting for the network interfaces to come back")
}
return nil
}
// platformType returns the platform where we are running
func platformType() (string, error) {
aksNet, _ := netProvider.GetHNSNetworkByName("azure")
if aksNet != nil {
return "aks", nil
}
eksNet, _ := netProvider.GetHNSNetworkByName("vpcbr*")
if eksNet != nil {
return "eks", nil
}
// EC2
hostnameEc2Uri := "http://169.254.169.254/latest/meta-data/local-hostname"
ec2Resp, err := httpClient.Get(hostnameEc2Uri)
if err != nil && hasTimedOut(err) {
logrus.Warnf("Timed out trying to get EC2 instance metadata from %q; defaulting to 'bare-metal' Calico platform", hostnameEc2Uri)
return "bare-metal", nil
}
if ec2Resp != nil {
defer ec2Resp.Body.Close()
if ec2Resp.StatusCode == http.StatusOK {
return "ec2", nil
}
}
// GCE
hostnameGoogleUri := "http://metadata.google.internal/computeMetadata/v1/instance/hostname"
req, err := http.NewRequest("GET", hostnameGoogleUri, nil)
if err != nil {
return "", err
}
req.Header.Add("Metadata-Flavor", "Google")
gceResp, err := httpClient.Do(req)
if err != nil && hasTimedOut(err) {
logrus.Warnf("Timed out trying to get GCE instance metadata from %q; defaulting to 'bare-metal' Calico platform", hostnameGoogleUri)
return "bare-metal", nil
}
if gceResp != nil {
defer gceResp.Body.Close()
if gceResp.StatusCode == http.StatusOK {
return "gce", nil
}
}
return "bare-metal", nil
}
func hasTimedOut(err error) bool {
switch err := err.(type) {
case *url.Error:
if err, ok := err.Err.(net.Error); ok && err.Timeout() {
return true
}
case net.Error:
if err.Timeout() {
return true
}
case *net.OpError:
if err.Timeout() {
return true
}
}
errTxt := "use of closed network connection"
if err != nil && strings.Contains(err.Error(), errTxt) {
return true
}
return false
}
func autoConfigureIpam(it string) bool {
if it == "host-local" {
return true
}
return false
}
// setMetaDataServerRoute returns the metadata server for gce and ec2
func setMetaDataServerRoute(mgmt string) error {
ip := net.ParseIP(mgmt)
if ip == nil {
return fmt.Errorf("not a valid ip")
}
metaIp := net.ParseIP("169.254.169.254/32")
router, err := netroute.New()
if err != nil {
return err
}
_, _, preferredSrc, err := router.Route(ip)
if err != nil {
return err
}
_, _, _, err = router.RouteWithSrc(nil, preferredSrc, metaIp) // input not used on windows
return err
}
// findInterfaceRegEx finds the interface that matches the regex
func findInterfaceRegEx(expression string) (string, error) {
// We remove any whitespace character
ifRegexes := regexp.MustCompile(`\s*,\s*`).Split(expression, -1)
// Prepare the regex expression (e.g. (eth.?)|( wlan0)|( docker*))
includeRegexes, err := regexp.Compile("(" + strings.Join(ifRegexes, ")|(") + ")")
iFaces, err := net.Interfaces()
if err != nil {
fmt.Println(err)
}
for _, iFace := range iFaces {
include := includeRegexes.MatchString(iFace.Name)
if include {
return iFace.Name, nil
}
}
return "", fmt.Errorf("no interface matches the set expression")
}
// findInterfaceReach finds the interface used to reach the dest
func findInterfaceReach(dest string) (string, error) {
destUdpAddress := fmt.Sprintf("[%s]:80", dest)
conn, err := net.Dial("udp4", destUdpAddress)
if err != nil {
return "", err
}
defer conn.Close()
localAddress := conn.LocalAddr()
if localAddress == nil {
return "", fmt.Errorf("no interface can route IP: %s", dest)
}
foundIf, err := findInterface(strings.Split(localAddress.String(), ":")[0])
if err != nil {
return "", err
}
return foundIf, nil
}
// findInterfaceCIDR returns the name of the interface whose IP is in the passed CIDR
func findInterfaceCIDR(cidrs []string) (string, error) {
var foundIf string
for _, cidr := range cidrs {
_, network, err := net.ParseCIDR(cidr)
if err != nil {
return "", err
}
interfaces, err := net.Interfaces()
if err != nil {
return "", err
}
for _, i := range interfaces {
addrs, err := i.Addrs()
if err != nil {
return "", err
}
for _, addr := range addrs {
address := strings.Split(addr.String(), "/")[0]
if network.Contains(net.ParseIP(address)) {
if (foundIf != "") && (foundIf != i.Name) {
return "", fmt.Errorf("the passed CIDRs are routed in different interfaces")
}
foundIf = i.Name
}
}
}
}
if foundIf == "" {
logrus.Errorf("no interface routes cidrs: %v", cidrs)
}
return foundIf, nil
}
// findInterface returns the name of the interface that contains the passed ip
func findInterface(ip string) (string, error) {
iFaces, err := net.Interfaces()
if err != nil {
return "", err
}
for _, iFace := range iFaces {
addrs, err := iFace.Addrs()
if err != nil {
return "", err
}
logrus.Debugf("evaluating if the interface: %s with addresses %v, contains ip: %s", iFace.Name, addrs, ip)
for _, addr := range addrs {
if strings.Contains(addr.String(), ip) {
return iFace.Name, nil
}
}
}
return "", fmt.Errorf("no interface has the ip: %s", ip)
}