mirror of https://github.com/k3s-io/k3s
210 lines
7.3 KiB
Go
210 lines
7.3 KiB
Go
/*
|
|
Copyright 2016 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package netsh
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"k8s.io/klog/v2"
|
|
utilexec "k8s.io/utils/exec"
|
|
)
|
|
|
|
// Interface is an injectable interface for running netsh commands. Implementations must be goroutine-safe.
|
|
type Interface interface {
|
|
// EnsurePortProxyRule checks if the specified redirect exists, if not creates it
|
|
EnsurePortProxyRule(args []string) (bool, error)
|
|
// DeletePortProxyRule deletes the specified portproxy rule. If the rule did not exist, return error.
|
|
DeletePortProxyRule(args []string) error
|
|
// EnsureIPAddress checks if the specified IP Address is added to vEthernet (HNSTransparent) interface, if not, add it. If the address existed, return true.
|
|
EnsureIPAddress(args []string, ip net.IP) (bool, error)
|
|
// DeleteIPAddress checks if the specified IP address is present and, if so, deletes it.
|
|
DeleteIPAddress(args []string) error
|
|
// Restore runs `netsh exec` to restore portproxy or addresses using a file.
|
|
// TODO Check if this is required, most likely not
|
|
Restore(args []string) error
|
|
|
|
// GetInterfaceToAddIP returns the interface name where Service IP needs to be added
|
|
// IP Address needs to be added for netsh portproxy to redirect traffic
|
|
// Reads Environment variable INTERFACE_TO_ADD_SERVICE_IP, if it is not defined then "vEthernet (HNSTransparent)" is returned
|
|
GetInterfaceToAddIP() string
|
|
}
|
|
|
|
const (
|
|
cmdNetsh string = "netsh"
|
|
)
|
|
|
|
// runner implements Interface in terms of exec("netsh").
|
|
type runner struct {
|
|
exec utilexec.Interface
|
|
}
|
|
|
|
// New returns a new Interface which will exec netsh.
|
|
func New(exec utilexec.Interface) Interface {
|
|
runner := &runner{
|
|
exec: exec,
|
|
}
|
|
return runner
|
|
}
|
|
|
|
// EnsurePortProxyRule checks if the specified redirect exists, if not creates it.
|
|
func (runner *runner) EnsurePortProxyRule(args []string) (bool, error) {
|
|
klog.V(4).Infof("running netsh interface portproxy add v4tov4 %v", args)
|
|
out, err := runner.exec.Command(cmdNetsh, args...).CombinedOutput()
|
|
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
if ee, ok := err.(utilexec.ExitError); ok {
|
|
// netsh uses exit(0) to indicate a success of the operation,
|
|
// as compared to a malformed commandline, for example.
|
|
if ee.Exited() && ee.ExitStatus() != 0 {
|
|
return false, nil
|
|
}
|
|
}
|
|
return false, fmt.Errorf("error checking portproxy rule: %v: %s", err, out)
|
|
|
|
}
|
|
|
|
// DeletePortProxyRule deletes the specified portproxy rule. If the rule did not exist, return error.
|
|
func (runner *runner) DeletePortProxyRule(args []string) error {
|
|
klog.V(4).Infof("running netsh interface portproxy delete v4tov4 %v", args)
|
|
out, err := runner.exec.Command(cmdNetsh, args...).CombinedOutput()
|
|
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if ee, ok := err.(utilexec.ExitError); ok {
|
|
// netsh uses exit(0) to indicate a success of the operation,
|
|
// as compared to a malformed commandline, for example.
|
|
if ee.Exited() && ee.ExitStatus() == 0 {
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("error deleting portproxy rule: %v: %s", err, out)
|
|
}
|
|
|
|
// EnsureIPAddress checks if the specified IP Address is added to interface identified by Environment variable INTERFACE_TO_ADD_SERVICE_IP, if not, add it. If the address existed, return true.
|
|
func (runner *runner) EnsureIPAddress(args []string, ip net.IP) (bool, error) {
|
|
// Check if the ip address exists
|
|
intName := runner.GetInterfaceToAddIP()
|
|
argsShowAddress := []string{
|
|
"interface", "ipv4", "show", "address",
|
|
"name=" + intName,
|
|
}
|
|
|
|
ipToCheck := ip.String()
|
|
|
|
exists, _ := checkIPExists(ipToCheck, argsShowAddress, runner)
|
|
if exists == true {
|
|
klog.V(4).Infof("not adding IP address %q as it already exists", ipToCheck)
|
|
return true, nil
|
|
}
|
|
|
|
// IP Address is not already added, add it now
|
|
klog.V(4).Infof("running netsh interface ipv4 add address %v", args)
|
|
out, err := runner.exec.Command(cmdNetsh, args...).CombinedOutput()
|
|
|
|
if err == nil {
|
|
// Once the IP Address is added, it takes a bit to initialize and show up when querying for it
|
|
// Query all the IP addresses and see if the one we added is present
|
|
// PS: We are using netsh interface ipv4 show address here to query all the IP addresses, instead of
|
|
// querying net.InterfaceAddrs() as it returns the IP address as soon as it is added even though it is uninitialized
|
|
klog.V(3).Infof("Waiting until IP: %v is added to the network adapter", ipToCheck)
|
|
for {
|
|
if exists, _ := checkIPExists(ipToCheck, argsShowAddress, runner); exists {
|
|
return true, nil
|
|
}
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
}
|
|
if ee, ok := err.(utilexec.ExitError); ok {
|
|
// netsh uses exit(0) to indicate a success of the operation,
|
|
// as compared to a malformed commandline, for example.
|
|
if ee.Exited() && ee.ExitStatus() != 0 {
|
|
return false, nil
|
|
}
|
|
}
|
|
return false, fmt.Errorf("error adding ipv4 address: %v: %s", err, out)
|
|
}
|
|
|
|
// DeleteIPAddress checks if the specified IP address is present and, if so, deletes it.
|
|
func (runner *runner) DeleteIPAddress(args []string) error {
|
|
klog.V(4).Infof("running netsh interface ipv4 delete address %v", args)
|
|
out, err := runner.exec.Command(cmdNetsh, args...).CombinedOutput()
|
|
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if ee, ok := err.(utilexec.ExitError); ok {
|
|
// netsh uses exit(0) to indicate a success of the operation,
|
|
// as compared to a malformed commandline, for example.
|
|
if ee.Exited() && ee.ExitStatus() == 0 {
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("error deleting ipv4 address: %v: %s", err, out)
|
|
}
|
|
|
|
// GetInterfaceToAddIP returns the interface name where Service IP needs to be added
|
|
// IP Address needs to be added for netsh portproxy to redirect traffic
|
|
// Reads Environment variable INTERFACE_TO_ADD_SERVICE_IP, if it is not defined then "vEthernet (HNS Internal NIC)" is returned
|
|
func (runner *runner) GetInterfaceToAddIP() string {
|
|
if iface := os.Getenv("INTERFACE_TO_ADD_SERVICE_IP"); len(iface) > 0 {
|
|
return iface
|
|
}
|
|
return "vEthernet (HNS Internal NIC)"
|
|
}
|
|
|
|
// Restore is part of Interface.
|
|
func (runner *runner) Restore(args []string) error {
|
|
return nil
|
|
}
|
|
|
|
// checkIPExists checks if an IP address exists in 'netsh interface ipv4 show address' output
|
|
func checkIPExists(ipToCheck string, args []string, runner *runner) (bool, error) {
|
|
ipAddress, err := runner.exec.Command(cmdNetsh, args...).CombinedOutput()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
ipAddressString := string(ipAddress[:])
|
|
klog.V(3).Infof("Searching for IP: %v in IP dump: %v", ipToCheck, ipAddressString)
|
|
showAddressArray := strings.Split(ipAddressString, "\n")
|
|
for _, showAddress := range showAddressArray {
|
|
if strings.Contains(showAddress, "IP") {
|
|
ipFromNetsh := getIP(showAddress)
|
|
if ipFromNetsh == ipToCheck {
|
|
return true, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// getIP gets ip from showAddress (e.g. "IP Address: 10.96.0.4").
|
|
func getIP(showAddress string) string {
|
|
list := strings.SplitN(showAddress, ":", 2)
|
|
if len(list) != 2 {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(list[1])
|
|
}
|