diff --git a/example/Dockerfile b/example/Dockerfile index 5dd7af2..960ef68 100644 --- a/example/Dockerfile +++ b/example/Dockerfile @@ -10,6 +10,6 @@ RUN apk add --no-cache curl bind-tools COPY --from=build /app /app # This is how you integrate with runsd: -ADD https://github.com/ahmetb/runsd/releases/download/v0.0.0-rc.4/runsd /runsd +ADD https://github.com/ahmetb/runsd/releases/download/v0.0.0-rc.5/runsd /runsd RUN chmod +x /runsd ENTRYPOINT ["/runsd", "-v=5", "--", "/app"] diff --git a/example/main.go b/example/main.go index 4753d19..c14456e 100644 --- a/example/main.go +++ b/example/main.go @@ -16,6 +16,7 @@ package main import ( "fmt" + "io" "log" "net/http" "os" @@ -26,6 +27,8 @@ import ( func main() { http.HandleFunc("/", home) http.HandleFunc("/curl", curl) + http.HandleFunc("/dig", dig) + http.HandleFunc("/resolv.conf", resolvconf) port := "8080" if v := os.Getenv("PORT"); v != "" { port = v @@ -108,3 +111,24 @@ func curl(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "curl failed: %v\n", err) } } + +func dig(w http.ResponseWriter, r *http.Request) { + domain := r.URL.Query().Get("domain") + if domain == "" { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintln(w, "?domain=[...] is missing") + return + } + fmt.Fprintf(w, "$ dig +search A %s\n\n", domain) + cmd := exec.Command("dig", "+search", "A", domain) + cmd.Stdout = w + cmd.Stderr = w + if err := cmd.Run(); err != nil { + fmt.Fprintf(w, "dig failed: %v\n", err) + } +} + +func resolvconf(w http.ResponseWriter, r *http.Request) { + f, _ := os.Open("/etc/resolv.conf") + io.Copy(w, f) +} diff --git a/runsd/authn.go b/runsd/authn.go index 7d3aa85..12a4e1f 100644 --- a/runsd/authn.go +++ b/runsd/authn.go @@ -27,5 +27,5 @@ func identityToken(audience string) (string, error) { } func identityTokenFromMetadata(audience string) (string, error) { - return queryMetadata("http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=" + audience) + return queryMetadata("http://metadata.google.internal./computeMetadata/v1/instance/service-accounts/default/identity?audience=" + audience) } diff --git a/runsd/dns.go b/runsd/dns.go index 1236def..ca8c43b 100644 --- a/runsd/dns.go +++ b/runsd/dns.go @@ -33,38 +33,51 @@ func (d *dnsHijack) handler() dns.Handler { mux := dns.NewServeMux() mux.HandleFunc(d.domain, d.handleLocal) mux.HandleFunc(".", d.recurse) + klog.V(1).Infof("dnsmux=%#v", mux) return mux } +func loggingHandler(d dns.HandlerFunc) dns.HandlerFunc { + return func(w dns.ResponseWriter, r *dns.Msg) { + for i, q := range r.Question { + klog.V(5).Infof("[dns] > Q%d: type=%v name=%v", i, dns.TypeToString[q.Qtype], q.Name) + } + d(w, r) + } +} + func (d *dnsHijack) newServer(addr string) *dns.Server { return &dns.Server{ Addr: addr, Net: "udp", - Handler: d.handler(), + Handler: loggingHandler(d.handler().ServeDNS), } } func (d *dnsHijack) handleLocal(w dns.ResponseWriter, msg *dns.Msg) { for _, q := range msg.Question { dots := strings.Count(q.Name, ".") - klog.V(5).Infof("[dns] type=%v name=%v dots=%d", dns.TypeToString[q.Qtype], q.Name, dots) if q.Qtype != dns.TypeA && q.Qtype != dns.TypeAAAA { - klog.V(4).Infof("unsupported dns msg type: %s, defer", dns.TypeToString[q.Qtype]) - d.recurse(w, msg) + klog.V(4).Infof("[dns] < unsupported dns msg type: %s, defer", dns.TypeToString[q.Qtype]) + d.recurse(w, msg) // TODO probably should not do this since original resolver won’t know about local domains return } if dots != d.dots { - klog.V(4).Infof("[dns] type=%v name=%v is too short or long (need ndots=%d; got=%d), nxdomain", dns.TypeToString[q.Qtype], q.Name, d.dots, dots) + klog.V(4).Infof("[dns] < type=%v name=%v is too short or long (need ndots=%d; got=%d), nxdomain", dns.TypeToString[q.Qtype], q.Name, d.dots, dots) nxdomain(w, msg) return } - parts := strings.SplitN(strings.TrimSuffix(q.Name, "."+d.domain+"."), ".", 2) + parts := strings.SplitN(strings.TrimSuffix(q.Name, "."+d.domain), ".", 2) + if len(parts) < 2 { + klog.V(4).Infof("[dns] < name=%q not enough segments to parse", q.Name) + return + } region := parts[1] _, ok := cloudRunRegionCodes[region] if !ok { - klog.V(4).Infof("[dns] unknown region=%q from name=%q, nxdomain", region, q.Name) + klog.V(4).Infof("[dns] < unknown region=%q from name=%q, nxdomain", region, q.Name) nxdomain(w, msg) return } @@ -74,7 +87,7 @@ func (d *dnsHijack) handleLocal(w dns.ResponseWriter, msg *dns.Msg) { r.SetReply(msg) r.Authoritative = true for _, q := range msg.Question { - klog.V(5).Infof("[dns] MATCH type=%v name=%v", dns.TypeToString[q.Qtype], q.Name) + klog.V(5).Infof("[dns] < MATCH type=%v name=%v", dns.TypeToString[q.Qtype], q.Name) switch q.Qtype { case dns.TypeA: r.Answer = append(r.Answer, &dns.A{ @@ -105,20 +118,21 @@ func (d *dnsHijack) handleLocal(w dns.ResponseWriter, msg *dns.Msg) { // recurse proxies the message to the backend nameserver. func (d *dnsHijack) recurse(w dns.ResponseWriter, msg *dns.Msg) { - klog.V(5).Infof("[dns] recursing type=%s name=%v", dns.TypeToString[msg.Question[0].Qtype], msg.Question[0].Name) - r, err := dns.Exchange(msg, net.JoinHostPort(d.nameserver, "53")) + klog.V(5).Infof("[dns] >> recursing type=%s name=%v", dns.TypeToString[msg.Question[0].Qtype], msg.Question[0].Name) + r, rtt, err := new(dns.Client).Exchange(msg, net.JoinHostPort(d.nameserver, "53")) if err != nil { - klog.V(4).Infof("WARNING: recursive dns fail: %v, servfail", err) + klog.V(4).Infof("[dns] << WARNING: recursive dns fail: %v, servfail", err) servfail(w, msg) return } - r.SetReply(msg) - r.RecursionAvailable = true - w.WriteMsg(r) - klog.V(5).Infof("[dns] recursed type=%s name=%v resp_code=%d", + klog.V(5).Infof(r.String()) + klog.V(5).Infof("[dns] << recursed type=%s name=%v rcode=%s answers=%d rtt=%v", dns.TypeToString[msg.Question[0].Qtype], msg.Question[0].Name, - r.Rcode) + dns.RcodeToString[r.Rcode], len(r.Answer), rtt) + + // r.SetReply(msg) // TODO(ahmetb): not sure why but removing this actually preserves the response hdrs and other sections well + w.WriteMsg(r) } // nxdomain sends an authoritative NXDOMAIN (domain not found) reply diff --git a/runsd/main.go b/runsd/main.go index 82d137f..6ed1629 100644 --- a/runsd/main.go +++ b/runsd/main.go @@ -28,7 +28,7 @@ import ( const ( resolvConf = "/etc/resolv.conf" - defaultInternalDomain = "run.internal" + defaultInternalDomain = "run.internal." defaultNdots = 4 defaultDnsPort = "53" defaultHTTPProxyPort = "80" @@ -62,7 +62,7 @@ func main() { defer klog.Flush() flag.StringVar(&flResolvConf, "resolv_conf_file", resolvConf, "[debug-only] path to resolv.conf(5) file to read/write") flag.StringVar(&flInternalDomain, "domain", defaultInternalDomain, "internal zone (without a trailing dot)") - flag.IntVar(&flNdots, "ndots", defaultNdots, "ndots setting for resolv conf (e.g. for -domain=a.b this should be 4)") + flag.IntVar(&flNdots, "ndots", defaultNdots, "ndots setting for resolv conf (e.g. for -domain=a.b. this should be 4)") flag.StringVar(&flNameserver, "nameserver", "", "override used nameserver (default: from -resolv_conf_file)") flag.StringVar(&flRegion, "gcp_region", "", "[debug-only] override GCP region (do not infer from metadata svc)") flag.BoolVar(&flSkipDNSServer, "skip_dns_hijack", false, "[debug-only] do not start a DNS server for service discovery") @@ -101,7 +101,7 @@ func main() { } else { klog.Exitf("no nameservers in %s and no nameserver is specified as option", flResolvConf) } - klog.V(3).Infof("using backend nameserver in process: %s", useNameserver) + klog.V(3).Infof("original nameserver: %s, ndots=%d", useNameserver, flNdots) // do not hijack dns for this process net.DefaultResolver = resolver(net.JoinHostPort(useNameserver, "53")) @@ -167,11 +167,11 @@ func main() { klog.V(4).Infof("hijacking resolv.conf file=%s", flResolvConf) searchDomains := append(cloudRunZones(region, flInternalDomain), rc.Search...) - if err := configureResolvConf(flResolvConf, []string{ - ipv4Loopback.String(), // to resolve local domains - net.IPv6loopback.String(), // to resolve local domains - //useNameserver, // TODO: probably not necessary to resolve external domains since we return NS records? - }, searchDomains, flNdots); err != nil { + resolvers := []string{ipv4Loopback.String()} + if ipv6OK { + resolvers = append(resolvers, net.IPv6loopback.String()) + } + if err := configureResolvConf(flResolvConf, resolvers, searchDomains, flNdots); err != nil { klog.Fatal(err) } klog.V(1).Info("dns hijack setup complete")