| 1 | package edge |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "net" |
| 6 | "net/http" |
| 7 | "net/http/httputil" |
| 8 | |
| 9 | "github.com/carlosframework/platform/internal/geoip" |
| 10 | "github.com/carlosframework/platform/internal/tunnelclient" |
| 11 | ) |
| 12 | |
| 13 | // geoResultKey carries the request's one geo answer (spec §8.2): computed |
| 14 | // at ingress or adopted from a trusted transport, read by setGeo, the cache |
| 15 | // path (through r.Header) and analytics. |
| 16 | type geoResultKey struct{} |
| 17 | |
| 18 | func geoAnswerFrom(ctx context.Context) (geoip.Answer, bool) { |
| 19 | a, ok := ctx.Value(geoResultKey{}).(geoip.Answer) |
| 20 | return a, ok |
| 21 | } |
| 22 | |
| 23 | // fromTunnel is the tunnel client's mark (geoip spec §8.4, D2): set on |
| 24 | // every request tunnelclient serves, never by anything a client sends. |
| 25 | var fromTunnel = tunnelclient.FromTunnel |
| 26 | |
| 27 | // decideGeo is step 3 of spec §8.1. hopPresent says the request ARRIVED |
| 28 | // carrying X-Carlos-Hop (captured before acceptHop strips it); hopped says |
| 29 | // acceptHop verified it. |
| 30 | func (e *Edge) decideGeo(r *http.Request, c geoip.Captured, hopPresent, hopped, isDoor bool) geoip.Answer { |
| 31 | switch { |
| 32 | case isDoor: |
| 33 | return geoip.Off() // RemoteAddr is this box (door.go) |
| 34 | case hopped: |
| 35 | return c.Adopt() // the sending edge looked up; never trust our own peer |
| 36 | case hopPresent: |
| 37 | return geoip.Off() // rejected hop: RemoteAddr is the sending edge (codex R2-1) |
| 38 | case fromTunnel(r.Context()): |
| 39 | return c.Adopt() // the hub looked up (spec §8.4) |
| 40 | } |
| 41 | ip := net.ParseIP(clientIP(r)) |
| 42 | return geoip.FromSnapshot(e.geoLookup(ip)) |
| 43 | } |
| 44 | |
| 45 | // setGeo is every Rewrite's geo step (spec §8.3): after hop-by-hop removal, |
| 46 | // delete every geo header from the outbound request and write the answer |
| 47 | // back from the context. forwardsToEdge adds X-Carlos-Hop-Geo for the next |
| 48 | // edge; an app never receives it. |
| 49 | func setGeo(pr *httputil.ProxyRequest, forwardsToEdge bool) { |
| 50 | geoip.StripAll(pr.Out.Header) |
| 51 | a, ok := geoAnswerFrom(pr.In.Context()) |
| 52 | if !ok { |
| 53 | a = geoip.Off() // scheduler calls through UnixProxy and anything else that never passed ServeHTTP |
| 54 | } |
| 55 | a.WriteApp(pr.Out.Header) |
| 56 | if forwardsToEdge { |
| 57 | pr.Out.Header.Set(geoip.HeaderHop, a.HopValue()) |
| 58 | } |
| 59 | } |
| 60 | |