mirror of https://github.com/k3s-io/k3s
336 lines
9.0 KiB
Go
336 lines
9.0 KiB
Go
|
package netsh
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"regexp"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
|
||
|
"errors"
|
||
|
|
||
|
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
|
||
|
// 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
|
||
|
// Get the interface name that has the default gateway
|
||
|
GetDefaultGatewayIfaceName() (string, error)
|
||
|
// Get a list of interfaces and addresses
|
||
|
GetInterfaces() ([]Ipv4Interface, error)
|
||
|
// Gets an interface by name
|
||
|
GetInterfaceByName(name string) (Ipv4Interface, error)
|
||
|
// Gets an interface by ip address in the format a.b.c.d
|
||
|
GetInterfaceByIP(ipAddr string) (Ipv4Interface, error)
|
||
|
// Enable forwarding on the interface (name or index)
|
||
|
EnableForwarding(iface string) error
|
||
|
}
|
||
|
|
||
|
const (
|
||
|
cmdNetsh string = "netsh"
|
||
|
)
|
||
|
|
||
|
// runner implements Interface in terms of exec("netsh").
|
||
|
type runner struct {
|
||
|
mu sync.Mutex
|
||
|
exec utilexec.Interface
|
||
|
}
|
||
|
|
||
|
// Ipv4Interface models IPv4 interface output from: netsh interface ipv4 show addresses
|
||
|
type Ipv4Interface struct {
|
||
|
Idx int
|
||
|
Name string
|
||
|
InterfaceMetric int
|
||
|
DhcpEnabled bool
|
||
|
IpAddress string
|
||
|
SubnetPrefix int
|
||
|
GatewayMetric int
|
||
|
DefaultGatewayAddress string
|
||
|
}
|
||
|
|
||
|
// New returns a new Interface which will exec netsh.
|
||
|
func New(exec utilexec.Interface) Interface {
|
||
|
|
||
|
if exec == nil {
|
||
|
exec = utilexec.New()
|
||
|
}
|
||
|
|
||
|
runner := &runner{
|
||
|
exec: exec,
|
||
|
}
|
||
|
return runner
|
||
|
}
|
||
|
|
||
|
func (runner *runner) GetInterfaces() ([]Ipv4Interface, error) {
|
||
|
interfaces, interfaceError := runner.getIpAddressConfigurations()
|
||
|
|
||
|
if interfaceError != nil {
|
||
|
return nil, interfaceError
|
||
|
}
|
||
|
|
||
|
indexMap, indexError := runner.getNetworkInterfaceParameters()
|
||
|
|
||
|
if indexError != nil {
|
||
|
return nil, indexError
|
||
|
}
|
||
|
|
||
|
// zip them up
|
||
|
for i := 0; i < len(interfaces); i++ {
|
||
|
name := interfaces[i].Name
|
||
|
|
||
|
if val, ok := indexMap[name]; ok {
|
||
|
interfaces[i].Idx = val
|
||
|
} else {
|
||
|
return nil, fmt.Errorf("no index found for interface \"%v\"", name)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return interfaces, nil
|
||
|
}
|
||
|
|
||
|
// GetInterfaces uses the show addresses command and returns a formatted structure
|
||
|
func (runner *runner) getIpAddressConfigurations() ([]Ipv4Interface, error) {
|
||
|
args := []string{
|
||
|
"interface", "ipv4", "show", "addresses",
|
||
|
}
|
||
|
|
||
|
output, err := runner.exec.Command(cmdNetsh, args...).CombinedOutput()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
interfacesString := string(output[:])
|
||
|
|
||
|
outputLines := strings.Split(interfacesString, "\n")
|
||
|
var interfaces []Ipv4Interface
|
||
|
var currentInterface Ipv4Interface
|
||
|
quotedPattern := regexp.MustCompile("\\\"(.*?)\\\"")
|
||
|
cidrPattern := regexp.MustCompile("\\/(.*?)\\ ")
|
||
|
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
for _, outputLine := range outputLines {
|
||
|
if strings.Contains(outputLine, "Configuration for interface") {
|
||
|
if currentInterface != (Ipv4Interface{}) {
|
||
|
interfaces = append(interfaces, currentInterface)
|
||
|
}
|
||
|
match := quotedPattern.FindStringSubmatch(outputLine)
|
||
|
currentInterface = Ipv4Interface{
|
||
|
Name: match[1],
|
||
|
}
|
||
|
} else {
|
||
|
parts := strings.SplitN(outputLine, ":", 2)
|
||
|
if len(parts) != 2 {
|
||
|
continue
|
||
|
}
|
||
|
key := strings.TrimSpace(parts[0])
|
||
|
value := strings.TrimSpace(parts[1])
|
||
|
if strings.HasPrefix(key, "DHCP enabled") {
|
||
|
if value == "Yes" {
|
||
|
currentInterface.DhcpEnabled = true
|
||
|
}
|
||
|
} else if strings.HasPrefix(key, "InterfaceMetric") {
|
||
|
if val, err := strconv.Atoi(value); err == nil {
|
||
|
currentInterface.InterfaceMetric = val
|
||
|
}
|
||
|
} else if strings.HasPrefix(key, "Gateway Metric") {
|
||
|
if val, err := strconv.Atoi(value); err == nil {
|
||
|
currentInterface.GatewayMetric = val
|
||
|
}
|
||
|
} else if strings.HasPrefix(key, "Subnet Prefix") {
|
||
|
match := cidrPattern.FindStringSubmatch(value)
|
||
|
if val, err := strconv.Atoi(match[1]); err == nil {
|
||
|
currentInterface.SubnetPrefix = val
|
||
|
}
|
||
|
} else if strings.HasPrefix(key, "IP Address") {
|
||
|
currentInterface.IpAddress = value
|
||
|
} else if strings.HasPrefix(key, "Default Gateway") {
|
||
|
currentInterface.DefaultGatewayAddress = value
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// add the last one
|
||
|
if currentInterface != (Ipv4Interface{}) {
|
||
|
interfaces = append(interfaces, currentInterface)
|
||
|
}
|
||
|
|
||
|
if len(interfaces) == 0 {
|
||
|
return nil, fmt.Errorf("no interfaces found in netsh output: %v", interfacesString)
|
||
|
}
|
||
|
|
||
|
return interfaces, nil
|
||
|
}
|
||
|
|
||
|
func (runner *runner) getNetworkInterfaceParameters() (map[string]int, error) {
|
||
|
args := []string{
|
||
|
"interface", "ipv4", "show", "interfaces",
|
||
|
}
|
||
|
|
||
|
output, err := runner.exec.Command(cmdNetsh, args...).CombinedOutput()
|
||
|
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Split output by line
|
||
|
outputString := string(output[:])
|
||
|
outputString = strings.TrimSpace(outputString)
|
||
|
var outputLines = strings.Split(outputString, "\n")
|
||
|
|
||
|
if len(outputLines) < 3 {
|
||
|
return nil, errors.New("unexpected netsh output:\n" + outputString)
|
||
|
}
|
||
|
|
||
|
// Remove first two lines of header text
|
||
|
outputLines = outputLines[2:]
|
||
|
|
||
|
indexMap := make(map[string]int)
|
||
|
|
||
|
reg := regexp.MustCompile("\\s{2,}")
|
||
|
|
||
|
for _, line := range outputLines {
|
||
|
|
||
|
line = strings.TrimSpace(line)
|
||
|
|
||
|
// Split the line by two or more whitespace characters, returning all substrings (n < 0)
|
||
|
splitLine := reg.Split(line, -1)
|
||
|
|
||
|
name := splitLine[4]
|
||
|
if idx, err := strconv.Atoi(splitLine[0]); err == nil {
|
||
|
indexMap[name] = idx
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return indexMap, nil
|
||
|
}
|
||
|
|
||
|
// Enable forwarding on the interface (name or index)
|
||
|
func (runner *runner) EnableForwarding(iface string) error {
|
||
|
args := []string{
|
||
|
"int", "ipv4", "set", "int", strconv.Quote(iface), "for=en",
|
||
|
}
|
||
|
cmd := strings.Join(args, " ")
|
||
|
if stdout, err := runner.exec.Command(cmdNetsh, args...).CombinedOutput(); err != nil {
|
||
|
return fmt.Errorf("failed to enable forwarding on [%v], error: %v. cmd: %v. stdout: %v", iface, err.Error(), cmd, string(stdout))
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// EnsurePortProxyRule checks if the specified redirect exists, if not creates it.
|
||
|
func (runner *runner) EnsurePortProxyRule(args []string) (bool, error) {
|
||
|
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 {
|
||
|
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)
|
||
|
}
|
||
|
|
||
|
// DeleteIPAddress checks if the specified IP address is present and, if so, deletes it.
|
||
|
func (runner *runner) DeleteIPAddress(args []string) error {
|
||
|
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)
|
||
|
}
|
||
|
|
||
|
func (runner *runner) GetDefaultGatewayIfaceName() (string, error) {
|
||
|
interfaces, err := runner.GetInterfaces()
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
for _, iface := range interfaces {
|
||
|
if iface.DefaultGatewayAddress != "" {
|
||
|
return iface.Name, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// return "not found"
|
||
|
return "", fmt.Errorf("Default interface not found")
|
||
|
}
|
||
|
|
||
|
func (runner *runner) GetInterfaceByName(name string) (Ipv4Interface, error) {
|
||
|
interfaces, err := runner.GetInterfaces()
|
||
|
if err != nil {
|
||
|
return Ipv4Interface{}, err
|
||
|
}
|
||
|
|
||
|
for _, iface := range interfaces {
|
||
|
if iface.Name == name {
|
||
|
return iface, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// return "not found"
|
||
|
return Ipv4Interface{}, fmt.Errorf("Interface not found: %v", name)
|
||
|
}
|
||
|
|
||
|
func (runner *runner) GetInterfaceByIP(ipAddr string) (Ipv4Interface, error) {
|
||
|
interfaces, err := runner.GetInterfaces()
|
||
|
if err != nil {
|
||
|
return Ipv4Interface{}, err
|
||
|
}
|
||
|
|
||
|
for _, iface := range interfaces {
|
||
|
if iface.IpAddress == ipAddr {
|
||
|
return iface, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// return "not found"
|
||
|
return Ipv4Interface{}, fmt.Errorf("Interface not found: %v", ipAddr)
|
||
|
}
|
||
|
|
||
|
// Restore is part of Interface.
|
||
|
func (runner *runner) Restore(args []string) error {
|
||
|
return nil
|
||
|
}
|