| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "net" |
| 5 | "strings" |
| 6 | |
| 7 | "github.com/carlosframework/platform/internal/edgesfile" |
| 8 | ) |
| 9 | |
| 10 | // BoxAddresses are the addresses this box publishes in its edges file for |
| 11 | // other edges to dial (region HA spec 2 §6). Public is for an edge in |
| 12 | // another region. Private, with PrivateCIDR (the region network it lies |
| 13 | // in), is for an edge in the same region. They are routing data only: an |
| 14 | // edge dials them, and nothing grants anything on them. "" is an address |
| 15 | // the source could not report, or one that failed validation, and nothing |
| 16 | // is published for it. |
| 17 | type BoxAddresses struct { |
| 18 | Private string |
| 19 | PrivateCIDR string |
| 20 | Public string |
| 21 | } |
| 22 | |
| 23 | // AddressSource reads this box's addresses afresh on every call. The edges |
| 24 | // file publisher asks it before every publication and publishes only what |
| 25 | // it reports, so an address the box no longer holds is never republished. |
| 26 | // Every source is root-configured: the cloud's metadata service, or |
| 27 | // host.env. None reads a file the agent can write. |
| 28 | // |
| 29 | // ok is false when the source could not tell: a metadata request that |
| 30 | // failed, timed out or answered something other than "here it is" or "there |
| 31 | // is none". Unknown is not blank. A caller that gets !ok writes nothing and |
| 32 | // leaves what it published before standing; only an authoritative answer, |
| 33 | // blank fields included, changes the published address. |
| 34 | type AddressSource interface { |
| 35 | BoxAddresses() (BoxAddresses, bool) |
| 36 | } |
| 37 | |
| 38 | // envAddresses is the provider-neutral source (§25), from host.env: |
| 39 | // CARLOS_PRIVATE_IP with CARLOS_PRIVATE_CIDR, and CARLOS_PUBLIC_IP. A field |
| 40 | // whose setting is present is the answer for that field, and a value that |
| 41 | // does not validate publishes nothing: an operator's setting is never |
| 42 | // silently replaced. CARLOS_PRIVATE_IP without CARLOS_PRIVATE_CIDR is |
| 43 | // invalid (census ruling region-ha-2a). A field left unset comes from |
| 44 | // provider, the cloud's adapter, when there is one. |
| 45 | type envAddresses struct { |
| 46 | getenv func(string) string |
| 47 | provider AddressSource |
| 48 | } |
| 49 | |
| 50 | func (s envAddresses) BoxAddresses() (BoxAddresses, bool) { |
| 51 | ip := strings.TrimSpace(s.getenv("CARLOS_PRIVATE_IP")) |
| 52 | cidr := strings.TrimSpace(s.getenv("CARLOS_PRIVATE_CIDR")) |
| 53 | public := strings.TrimSpace(s.getenv("CARLOS_PUBLIC_IP")) |
| 54 | privateSet, publicSet := ip != "" || cidr != "", public != "" |
| 55 | out := BoxAddresses{} |
| 56 | if privateSet { |
| 57 | out.Private, out.PrivateCIDR = edgesfile.PrivateAddress(ip, []string{cidr}) |
| 58 | } |
| 59 | if publicSet { |
| 60 | out.Public = ipv4Only(public) |
| 61 | } |
| 62 | if (!privateSet || !publicSet) && s.provider != nil { |
| 63 | p, ok := s.provider.BoxAddresses() |
| 64 | if !ok { |
| 65 | return BoxAddresses{}, false |
| 66 | } |
| 67 | if !privateSet { |
| 68 | out.Private, out.PrivateCIDR = edgesfile.PrivateAddress(p.Private, []string{p.PrivateCIDR}) |
| 69 | } |
| 70 | if !publicSet { |
| 71 | out.Public = ipv4Only(p.Public) |
| 72 | } |
| 73 | } |
| 74 | return out, true |
| 75 | } |
| 76 | |
| 77 | // ipv4Only is s when it is an IPv4 address, in its canonical form, and "" |
| 78 | // otherwise. |
| 79 | func ipv4Only(s string) string { |
| 80 | ip := net.ParseIP(strings.TrimSpace(s)) |
| 81 | if ip == nil || ip.To4() == nil { |
| 82 | return "" |
| 83 | } |
| 84 | return ip.To4().String() |
| 85 | } |
| 86 | |