netsh.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package netsh
  14. import (
  15. "fmt"
  16. "net"
  17. "os"
  18. "strings"
  19. "sync"
  20. "time"
  21. "k8s.io/klog"
  22. utilexec "k8s.io/utils/exec"
  23. )
  24. // Interface is an injectable interface for running netsh commands. Implementations must be goroutine-safe.
  25. type Interface interface {
  26. // EnsurePortProxyRule checks if the specified redirect exists, if not creates it
  27. EnsurePortProxyRule(args []string) (bool, error)
  28. // DeletePortProxyRule deletes the specified portproxy rule. If the rule did not exist, return error.
  29. DeletePortProxyRule(args []string) error
  30. // EnsureIPAddress checks if the specified IP Address is added to vEthernet (HNSTransparent) interface, if not, add it. If the address existed, return true.
  31. EnsureIPAddress(args []string, ip net.IP) (bool, error)
  32. // DeleteIPAddress checks if the specified IP address is present and, if so, deletes it.
  33. DeleteIPAddress(args []string) error
  34. // Restore runs `netsh exec` to restore portproxy or addresses using a file.
  35. // TODO Check if this is required, most likely not
  36. Restore(args []string) error
  37. // GetInterfaceToAddIP returns the interface name where Service IP needs to be added
  38. // IP Address needs to be added for netsh portproxy to redirect traffic
  39. // Reads Environment variable INTERFACE_TO_ADD_SERVICE_IP, if it is not defined then "vEthernet (HNSTransparent)" is returned
  40. GetInterfaceToAddIP() string
  41. }
  42. const (
  43. cmdNetsh string = "netsh"
  44. )
  45. // runner implements Interface in terms of exec("netsh").
  46. type runner struct {
  47. mu sync.Mutex
  48. exec utilexec.Interface
  49. }
  50. // New returns a new Interface which will exec netsh.
  51. func New(exec utilexec.Interface) Interface {
  52. runner := &runner{
  53. exec: exec,
  54. }
  55. return runner
  56. }
  57. // EnsurePortProxyRule checks if the specified redirect exists, if not creates it.
  58. func (runner *runner) EnsurePortProxyRule(args []string) (bool, error) {
  59. klog.V(4).Infof("running netsh interface portproxy add v4tov4 %v", args)
  60. out, err := runner.exec.Command(cmdNetsh, args...).CombinedOutput()
  61. if err == nil {
  62. return true, nil
  63. }
  64. if ee, ok := err.(utilexec.ExitError); ok {
  65. // netsh uses exit(0) to indicate a success of the operation,
  66. // as compared to a malformed commandline, for example.
  67. if ee.Exited() && ee.ExitStatus() != 0 {
  68. return false, nil
  69. }
  70. }
  71. return false, fmt.Errorf("error checking portproxy rule: %v: %s", err, out)
  72. }
  73. // DeletePortProxyRule deletes the specified portproxy rule. If the rule did not exist, return error.
  74. func (runner *runner) DeletePortProxyRule(args []string) error {
  75. klog.V(4).Infof("running netsh interface portproxy delete v4tov4 %v", args)
  76. out, err := runner.exec.Command(cmdNetsh, args...).CombinedOutput()
  77. if err == nil {
  78. return nil
  79. }
  80. if ee, ok := err.(utilexec.ExitError); ok {
  81. // netsh uses exit(0) to indicate a success of the operation,
  82. // as compared to a malformed commandline, for example.
  83. if ee.Exited() && ee.ExitStatus() == 0 {
  84. return nil
  85. }
  86. }
  87. return fmt.Errorf("error deleting portproxy rule: %v: %s", err, out)
  88. }
  89. // 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.
  90. func (runner *runner) EnsureIPAddress(args []string, ip net.IP) (bool, error) {
  91. // Check if the ip address exists
  92. intName := runner.GetInterfaceToAddIP()
  93. argsShowAddress := []string{
  94. "interface", "ipv4", "show", "address",
  95. "name=" + intName,
  96. }
  97. ipToCheck := ip.String()
  98. exists, _ := checkIPExists(ipToCheck, argsShowAddress, runner)
  99. if exists == true {
  100. klog.V(4).Infof("not adding IP address %q as it already exists", ipToCheck)
  101. return true, nil
  102. }
  103. // IP Address is not already added, add it now
  104. klog.V(4).Infof("running netsh interface ipv4 add address %v", args)
  105. out, err := runner.exec.Command(cmdNetsh, args...).CombinedOutput()
  106. if err == nil {
  107. // Once the IP Address is added, it takes a bit to initialize and show up when querying for it
  108. // Query all the IP addresses and see if the one we added is present
  109. // PS: We are using netsh interface ipv4 show address here to query all the IP addresses, instead of
  110. // querying net.InterfaceAddrs() as it returns the IP address as soon as it is added even though it is uninitialized
  111. klog.V(3).Infof("Waiting until IP: %v is added to the network adapter", ipToCheck)
  112. for {
  113. if exists, _ := checkIPExists(ipToCheck, argsShowAddress, runner); exists {
  114. return true, nil
  115. }
  116. time.Sleep(500 * time.Millisecond)
  117. }
  118. }
  119. if ee, ok := err.(utilexec.ExitError); ok {
  120. // netsh uses exit(0) to indicate a success of the operation,
  121. // as compared to a malformed commandline, for example.
  122. if ee.Exited() && ee.ExitStatus() != 0 {
  123. return false, nil
  124. }
  125. }
  126. return false, fmt.Errorf("error adding ipv4 address: %v: %s", err, out)
  127. }
  128. // DeleteIPAddress checks if the specified IP address is present and, if so, deletes it.
  129. func (runner *runner) DeleteIPAddress(args []string) error {
  130. klog.V(4).Infof("running netsh interface ipv4 delete address %v", args)
  131. out, err := runner.exec.Command(cmdNetsh, args...).CombinedOutput()
  132. if err == nil {
  133. return nil
  134. }
  135. if ee, ok := err.(utilexec.ExitError); ok {
  136. // netsh uses exit(0) to indicate a success of the operation,
  137. // as compared to a malformed commandline, for example.
  138. if ee.Exited() && ee.ExitStatus() == 0 {
  139. return nil
  140. }
  141. }
  142. return fmt.Errorf("error deleting ipv4 address: %v: %s", err, out)
  143. }
  144. // GetInterfaceToAddIP returns the interface name where Service IP needs to be added
  145. // IP Address needs to be added for netsh portproxy to redirect traffic
  146. // Reads Environment variable INTERFACE_TO_ADD_SERVICE_IP, if it is not defined then "vEthernet (HNS Internal NIC)" is returned
  147. func (runner *runner) GetInterfaceToAddIP() string {
  148. if iface := os.Getenv("INTERFACE_TO_ADD_SERVICE_IP"); len(iface) > 0 {
  149. return iface
  150. }
  151. return "vEthernet (HNS Internal NIC)"
  152. }
  153. // Restore is part of Interface.
  154. func (runner *runner) Restore(args []string) error {
  155. return nil
  156. }
  157. // checkIPExists checks if an IP address exists in 'netsh interface ipv4 show address' output
  158. func checkIPExists(ipToCheck string, args []string, runner *runner) (bool, error) {
  159. ipAddress, err := runner.exec.Command(cmdNetsh, args...).CombinedOutput()
  160. if err != nil {
  161. return false, err
  162. }
  163. ipAddressString := string(ipAddress[:])
  164. klog.V(3).Infof("Searching for IP: %v in IP dump: %v", ipToCheck, ipAddressString)
  165. showAddressArray := strings.Split(ipAddressString, "\n")
  166. for _, showAddress := range showAddressArray {
  167. if strings.Contains(showAddress, "IP") {
  168. ipFromNetsh := getIP(showAddress)
  169. if ipFromNetsh == ipToCheck {
  170. return true, nil
  171. }
  172. }
  173. }
  174. return false, nil
  175. }
  176. // getIP gets ip from showAddress (e.g. "IP Address: 10.96.0.4").
  177. func getIP(showAddress string) string {
  178. list := strings.SplitN(showAddress, ":", 2)
  179. if len(list) != 2 {
  180. return ""
  181. }
  182. return strings.TrimSpace(list[1])
  183. }