k8s.io/kubernetes@v1.29.3/test/images/agnhost/no-snat-test/main.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package nosnat 18 19 import ( 20 "fmt" 21 "io" 22 "net/http" 23 "os" 24 "strings" 25 26 "github.com/spf13/cobra" 27 "k8s.io/component-base/logs" 28 netutils "k8s.io/utils/net" 29 ) 30 31 // CmdNoSnatTest is used by agnhost Cobra. 32 var CmdNoSnatTest = &cobra.Command{ 33 Use: "no-snat-test", 34 Short: "Creates the /checknosnat and /whoami endpoints", 35 Long: `Serves the following endpoints on the given port (defaults to "8080"). 36 37 - /whoami - returns the request's IP address. 38 - /checknosnat - queries "ip/whoami" for each provided IP ("/checknosnat?ips=ip1,ip2"), 39 and if all the response bodies match the "POD_IP" environment variable, it will return a 200 response, 500 otherwise.`, 40 Args: cobra.MaximumNArgs(0), 41 Run: main, 42 } 43 44 var port string 45 46 func init() { 47 CmdNoSnatTest.Flags().StringVar(&port, "port", "8080", "The port to serve /checknosnat and /whoami endpoints on.") 48 } 49 50 // ip = target for /whoami query 51 // rip = returned ip 52 // pip = this pod's ip 53 // nip = this node's ip 54 55 type masqTester struct { 56 Port string 57 } 58 59 func main(cmd *cobra.Command, args []string) { 60 m := &masqTester{ 61 Port: port, 62 } 63 64 logs.InitLogs() 65 defer logs.FlushLogs() 66 67 if err := m.Run(); err != nil { 68 fmt.Fprintf(os.Stderr, "%v\n", err) 69 os.Exit(1) 70 } 71 } 72 73 func (m *masqTester) Run() error { 74 // pip is the current pod's IP and nip is the current node's IP 75 // pull the pip and nip out of the env 76 pip, ok := os.LookupEnv("POD_IP") 77 if !ok { 78 return fmt.Errorf("POD_IP env var was not present in the environment") 79 } 80 nip, ok := os.LookupEnv("NODE_IP") 81 if !ok { 82 return fmt.Errorf("NODE_IP env var was not present in the environment") 83 } 84 85 // validate that pip and nip are ip addresses. 86 if netutils.ParseIPSloppy(pip) == nil { 87 return fmt.Errorf("POD_IP env var contained %q, which is not an IP address", pip) 88 } 89 if netutils.ParseIPSloppy(nip) == nil { 90 return fmt.Errorf("NODE_IP env var contained %q, which is not an IP address", nip) 91 } 92 93 // register handlers 94 http.HandleFunc("/whoami", whoami) 95 http.HandleFunc("/checknosnat", mkChecknosnat(pip, nip)) 96 97 // spin up the server 98 return http.ListenAndServe(":"+m.Port, nil) 99 } 100 101 type handler func(http.ResponseWriter, *http.Request) 102 103 func joinErrors(errs []error, sep string) string { 104 strs := make([]string, len(errs)) 105 for i, err := range errs { 106 strs[i] = err.Error() 107 } 108 return strings.Join(strs, sep) 109 } 110 111 // Builds checknosnat handler, using pod and node ip of current location 112 func mkChecknosnat(pip string, nip string) handler { 113 // Queries /whoami for each provided ip, resp 200 if all resp bodies match this Pod's ip, 500 otherwise 114 return func(w http.ResponseWriter, req *http.Request) { 115 errs := []error{} 116 ips := strings.Split(req.URL.Query().Get("ips"), ",") 117 for _, ip := range ips { 118 if err := check(ip, pip, nip); err != nil { 119 errs = append(errs, err) 120 } 121 } 122 if len(errs) > 0 { 123 w.WriteHeader(500) 124 fmt.Fprintf(w, "%s", joinErrors(errs, ", ")) 125 return 126 } 127 w.WriteHeader(200) 128 } 129 } 130 131 // Writes the req.RemoteAddr into the response, req.RemoteAddr is the address of the incoming connection 132 func whoami(w http.ResponseWriter, req *http.Request) { 133 fmt.Fprintf(w, "%s", req.RemoteAddr) 134 } 135 136 // Queries ip/whoami and compares response to pip, uses nip to differentiate SNAT from other potential failure modes 137 func check(ip string, pip string, nip string) error { 138 url := fmt.Sprintf("http://%s/whoami", ip) 139 resp, err := http.Get(url) 140 if err != nil { 141 return err 142 } 143 defer resp.Body.Close() 144 body, err := io.ReadAll(resp.Body) 145 if err != nil { 146 return err 147 } 148 rips := strings.Split(string(body), ":") 149 if rips == nil || len(rips) == 0 { 150 return fmt.Errorf("Invalid returned ip %q from %q", string(body), url) 151 } 152 rip := rips[0] 153 if rip != pip { 154 if rip == nip { 155 return fmt.Errorf("Returned ip %q != my Pod ip %q, == my Node ip %q - SNAT", rip, pip, nip) 156 } 157 return fmt.Errorf("Returned ip %q != my Pod ip %q or my Node ip %q - SNAT to unexpected ip (possible SNAT through unexpected interface on the way into another node)", rip, pip, nip) 158 } 159 return nil 160 }