From d5f6cad9082b3f650db08ca89ca7e51278ce3f46 Mon Sep 17 00:00:00 2001 From: Nico Berlee Date: Sun, 22 Aug 2021 16:59:12 +0200 Subject: [PATCH] Example code to support ForwardedIPs the right way Warning this code has a few problems: - It is not plugged in to Talos. as I could not find the needed hooks - `ip` is not available, we should use the talos api's here, which need modification as scope is always global - consecutive calls to func ForwardedIPs, ForwardedIPs should use the waiting parameter in the url, to make the thread block until there is a new update of the metadata - the helper functions are mostly copied from google-agent, not sure we may use them in Talos. --- .../pkg/runtime/v1alpha1/platform/gcp/gcp.go | 168 +++++++++++++++++- 1 file changed, 167 insertions(+), 1 deletion(-) diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/gcp/gcp.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/gcp/gcp.go index 557510fe66c..d6834d90117 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/gcp/gcp.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/gcp/gcp.go @@ -5,6 +5,7 @@ package gcp import ( + "bytes" "context" "encoding/json" "fmt" @@ -13,6 +14,9 @@ import ( "net" "net/http" + "os/exec" + "strings" + "github.com/talos-systems/go-procfs/procfs" "github.com/talos-systems/talos/internal/app/machined/pkg/runtime" @@ -25,7 +29,7 @@ import ( const ( // GCUserDataEndpoint is the local metadata endpoint inside of DO. GCUserDataEndpoint = "http://metadata.google.internal/computeMetadata/v1/instance/attributes/user-data" - // GCExternalIPEndpoint displays all external addresses associated with the instance. + // GCExternalIPEndpoint displays all addresses associated with the instance. GCExternalIPEndpoint = "http://metadata.google.internal/computeMetadata/v1/instance/network-interfaces/?recursive=true" ) @@ -39,6 +43,7 @@ func (g *GCP) Name() string { // Configuration implements the platform.Platform interface. func (g *GCP) Configuration(ctx context.Context) ([]byte, error) { + log.Printf("fetching machine config from: %q", GCUserDataEndpoint) return download.Download(ctx, GCUserDataEndpoint, @@ -107,6 +112,167 @@ func (g *GCP) ExternalIPs(ctx context.Context) (addrs []net.IP, err error) { return addrs, err } +func forwardedIPs(ctx context.Context) (err error) { + var ( + body []byte + req *http.Request + resp *http.Response + ) + // maybe run a second time with &wait_for_change=true + if req, err = http.NewRequestWithContext(ctx, "GET", GCExternalIPEndpoint, nil); err != nil { + return + } + + req.Header.Add("Metadata-Flavor", "Google") + + client := &http.Client{} + if resp, err = client.Do(req); err != nil { + return + } + + //nolint:errcheck + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("failed to retrieve forwarded addresses for instance") + } + + type metadata []struct { + ForwardedIP []string `json:"forwardedIps"` + Mac string `json:"mac"` + } + + if body, err = ioutil.ReadAll(resp.Body); err != nil { + return + } + + m := metadata{} + if err = json.Unmarshal(body, &m); err != nil { + return + } + + for _, networkInterface := range m { + iface, err := getInterfaceByMAC(networkInterface.Mac) + if err != nil { + continue + } + + var configuredRoutes []net.IP = nil + var desiredRoutes []net.IP = nil + for _, ip := range networkInterface.ForwardedIP { + desiredRoutes = append(desiredRoutes, net.ParseIP(ip)) + } + configuredRoutes, err = getLocalRoutes(iface.Name) + if err != nil { + continue + } + for _, desiredRoute := range desiredRoutes { + if !containsIP(desiredRoute, configuredRoutes) { + args := fmt.Sprintf("route add to local %s/32 scope host dev %s proto 66", desiredRoute, iface.Name) + runCmd(exec.Command("ip", strings.Split(args, " ")...)) + } + } + + for _, configuredRoute := range configuredRoutes { + if !containsIP(configuredRoute, desiredRoutes) { + args := fmt.Sprintf("route delete to local %s/32 scope host dev %s proto 66", configuredRoute, iface.Name) + runCmd(exec.Command("ip", strings.Split(args, " ")...)) + } + } + } + + return err +} +func containsIP(s net.IP, ss []net.IP) bool { + for _, a := range ss { + if a.Equal(s) { + return true + } + } + return false +} + +// === functions below are copied from https://github.com/GoogleCloudPlatform/guest-agent/blob/main/google_guest_agent/addresses.go +func getLocalRoutes(ifname string) ([]net.IP, error) { + args := fmt.Sprintf("route list table local type local scope host dev %s proto 66", ifname) + out := runCmdOutput(exec.Command("ip", strings.Split(args, " ")...)) + if out.ExitCode() != 0 { + return nil, error(out) + } + var res []net.IP + for _, line := range strings.Split(out.Stdout(), "\n") { + line = strings.TrimPrefix(line, "local ") + line = strings.TrimSpace(line) + if line != "" { + res = append(res, net.ParseIP(line)) + } + } + return res, nil +} + +func getInterfaceByMAC(mac string) (net.Interface, error) { + hwaddr, err := net.ParseMAC(mac) + if err != nil { + return net.Interface{}, err + } + interfaces, err := net.Interfaces() + if err != nil { + return net.Interface{}, err + } + + for _, iface := range interfaces { + if iface.HardwareAddr.String() == hwaddr.String() { + return iface, nil + } + } + return net.Interface{}, fmt.Errorf("no interface found with MAC %s", mac) +} + +func runCmd(cmd *exec.Cmd) error { + res := runCmdOutput(cmd) + if res.ExitCode() != 0 { + return res + } + return nil +} + +type execResult struct { + // Return code. Set to -1 if we failed to run the command. + code int + // Stderr or err.Error if we failed to run the command. + err string + // Stdout or "" if we failed to run the command. + out string +} + +func (e execResult) Error() string { + return strings.TrimSuffix(e.err, "\n") +} + +func (e execResult) ExitCode() int { + return e.code +} + +func (e execResult) Stdout() string { + return e.out +} + +func runCmdOutput(cmd *exec.Cmd) *execResult { + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + err := cmd.Run() + if err != nil { + if ee, ok := err.(*exec.ExitError); ok { + return &execResult{code: ee.ExitCode(), out: stdout.String(), err: stderr.String()} + } + return &execResult{code: -1, err: err.Error()} + } + return &execResult{code: 0, out: stdout.String()} +} + +// === end // KernelArgs implements the runtime.Platform interface. func (g *GCP) KernelArgs() procfs.Parameters { return []*procfs.Parameter{