package edge import ( "context" "net" "net/http" "net/http/httputil" "github.com/carlosframework/platform/internal/geoip" "github.com/carlosframework/platform/internal/tunnelclient" ) // geoResultKey carries the request's one geo answer (spec §8.2): computed // at ingress or adopted from a trusted transport, read by setGeo, the cache // path (through r.Header) and analytics. type geoResultKey struct{} func geoAnswerFrom(ctx context.Context) (geoip.Answer, bool) { a, ok := ctx.Value(geoResultKey{}).(geoip.Answer) return a, ok } // fromTunnel is the tunnel client's mark (geoip spec §8.4, D2): set on // every request tunnelclient serves, never by anything a client sends. var fromTunnel = tunnelclient.FromTunnel // decideGeo is step 3 of spec §8.1. hopPresent says the request ARRIVED // carrying X-Carlos-Hop (captured before acceptHop strips it); hopped says // acceptHop verified it. func (e *Edge) decideGeo(r *http.Request, c geoip.Captured, hopPresent, hopped, isDoor bool) geoip.Answer { switch { case isDoor: return geoip.Off() // RemoteAddr is this box (door.go) case hopped: return c.Adopt() // the sending edge looked up; never trust our own peer case hopPresent: return geoip.Off() // rejected hop: RemoteAddr is the sending edge (codex R2-1) case fromTunnel(r.Context()): return c.Adopt() // the hub looked up (spec §8.4) } ip := net.ParseIP(clientIP(r)) return geoip.FromSnapshot(e.geoLookup(ip)) } // setGeo is every Rewrite's geo step (spec §8.3): after hop-by-hop removal, // delete every geo header from the outbound request and write the answer // back from the context. forwardsToEdge adds X-Carlos-Hop-Geo for the next // edge; an app never receives it. func setGeo(pr *httputil.ProxyRequest, forwardsToEdge bool) { geoip.StripAll(pr.Out.Header) a, ok := geoAnswerFrom(pr.In.Context()) if !ok { a = geoip.Off() // scheduler calls through UnixProxy and anything else that never passed ServeHTTP } a.WriteApp(pr.Out.Header) if forwardsToEdge { pr.Out.Header.Set(geoip.HeaderHop, a.HopValue()) } }