summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrew Dunham <andrew@du.nham.ca>2023-03-02 13:25:58 -0500
committerAndrew Dunham <andrew@du.nham.ca>2023-03-02 13:26:00 -0500
commit1ac1ed41e65ac3265ac932768c17bb986274f5fc (patch)
tree0afefae0b190222e991d8ec66cb1c284b8636e9f
parent9e7ab9665a7cfa3d28ce9f5b6536fefb7a94a071 (diff)
downloadtailscale-andrew/cloudenv-location.tar.xz
tailscale-andrew/cloudenv-location.zip
util/cloudenv: add ApproximateLocation to Cloudandrew/cloudenv-location
This function will return the approximate location if running in a cloud environment with a known region. Currently only AWS is supported. Change-Id: Ic4f14de4c76c7bd37d71b4eb7813e97f3878ff59
-rw-r--r--util/cloudenv/cloudenv.go110
1 files changed, 110 insertions, 0 deletions
diff --git a/util/cloudenv/cloudenv.go b/util/cloudenv/cloudenv.go
index 3c86339e4..03443c9ec 100644
--- a/util/cloudenv/cloudenv.go
+++ b/util/cloudenv/cloudenv.go
@@ -5,8 +5,10 @@
package cloudenv
import (
+ "bytes"
"context"
"encoding/json"
+ "io"
"log"
"net"
"net/http"
@@ -181,3 +183,111 @@ func getCloud() Cloud {
// TODO: more, as needed.
return ""
}
+
+// ApproximateLocation returns the approximate geographic location of the
+// region that the current cloud host is running in.
+//
+// If the current host is not running in the cloud, if cloud provider is not
+// supported, or if the current region could not be determined, then (0, 0)
+// will be returned.
+func (c Cloud) ApproximateLocation() (lat, lon float64) {
+ switch c {
+ case AWS:
+ loc := getApproximateLocationAWS()
+ return loc.lat, loc.lon
+ case GCP:
+ // TODO
+ case Azure:
+ // TODO
+ }
+ return 0, 0
+}
+
+type location struct{ lat, lon float64 }
+
+var noLocation location
+
+var approximateAWSRegionLocation = map[string]location{
+ "af-south-1": {-33.928992, 18.417396}, // "CPT" / Cape Town, South Africa
+ "ap-east-1": {22.2793278, 114.1628131}, // "HKG" / Hong Kong
+ "ap-northeast-1": {35.6812665, 139.757653}, // "NRT" / Tokyo, Japan
+ "ap-northeast-2": {37.5666791, 126.9782914}, // "ICN" / Seoul, Korea
+ "ap-northeast-3": {34.661629, 135.4999268}, // "KIX" / Osaka, Japan
+ "ap-south-1": {19.0785451, 72.878176}, // "BOM" / Mumbai, India
+ "ap-south-2": {17.38878595, 78.46106473}, // "HYD" / Hyderabad, India
+ "ap-southeast-1": {1.357107, 103.8194992}, // "SIN" / Singapore
+ "ap-southeast-2": {-33.8698439, 151.2082848}, // "SYD" / Sydney, Australia
+ "ap-southeast-3": {-6.1753942, 106.827183}, // "CGK" / Jakarta, Indonesia
+ "ap-southeast-4": {-37.8142176, 144.9631608}, // "MEL" / Melbourne, Australia
+ "ca-central-1": {45.5031824, -73.5698065}, // "YUL" / Montreal, Canada
+ "cn-north-1": {39.906217, 116.3912757}, // "BJS" / Beijing
+ "cn-northwest-1": {37.4999947, 105.1928783}, // "ZHY" / Zhongwei
+ "eu-central-1": {50.1106444, 8.6820917}, // "FRA" / Frankfurt, Germany
+ "eu-central-2": {47.3744489, 8.5410422}, // "ZRH" / Zurich, Switzerland
+ "eu-north-1": {59.3251172, 18.0710935}, // "ARN" / Stockholm, Sweden
+ "eu-south-1": {45.4641943, 9.1896346}, // "MXP" / Milan, Italy
+ "eu-south-2": {41.6521342, -0.8809428}, // "ZAZ" / Zaragoza, Spain
+ "eu-west-1": {53.3498006, -6.2602964}, // "DUB" / Dublin, Ireland
+ "eu-west-2": {51.5073359, -0.12765}, // "LHR" / London, England
+ "eu-west-3": {48.8588897, 2.32004102}, // "CDG" / Paris, France
+ "me-south-1": {26.1551249, 50.5344606}, // "BAH" / Bahrain
+ "sa-east-1": {-23.5506507, -46.6333824}, // "GRU" / São Paulo, Brazil
+ "us-east-1": {38.8950368, -77.0365427}, // "IAD" / Washington D.C., USA
+ "us-east-2": {39.9622601, -83.0007065}, // "CMH" / Columbus, Ohio, USA
+ "us-gov-east-1": {39.9622601, -83.0007065}, // "CMH" / Columbus, Ohio, USA
+ "us-gov-west-1": {45.5202471, -122.674194}, // "PDX" / Portland, Oregon, USA
+ "us-west-1": {37.7790262, -122.419906}, // "SFO" / San Francisco, California, USA
+ "us-west-2": {45.5202471, -122.674194}, // "PDX" / Portland, Oregon, USA
+
+ // NOTE: it's not public where in Dubai this is
+ "me-central-1": {25.07428234, 55.18853865}, // Dubai
+}
+
+func getApproximateLocationAWS() location {
+ const maxWait = 2 * time.Second
+ tr := &http.Transport{
+ DisableKeepAlives: true,
+ Dial: (&net.Dialer{
+ Timeout: maxWait,
+ }).Dial,
+ }
+ ctx, cancel := context.WithTimeout(context.Background(), maxWait)
+ defer cancel()
+
+ req, err := http.NewRequestWithContext(ctx, "PUT", "http://"+CommonNonRoutableMetadataIP+"/latest/api/token", nil)
+ if err != nil {
+ return noLocation
+ }
+ req.Header.Set("X-aws-ec2-metadata-token-ttl-seconds", "30")
+
+ res, err := tr.RoundTrip(req)
+ if err != nil {
+ return noLocation
+ }
+ token, err := io.ReadAll(res.Body)
+ res.Body.Close()
+ if err != nil {
+ return noLocation
+ }
+
+ req, err = http.NewRequestWithContext(ctx, "GET", "http://"+CommonNonRoutableMetadataIP+"/latest/dynamic/instance-identity/document", nil)
+ if err != nil {
+ return noLocation
+ }
+ req.Header.Set("X-aws-ec2-metadata-token", string(bytes.TrimSpace(token)))
+
+ res, err = tr.RoundTrip(req)
+ if err != nil {
+ return noLocation
+ }
+ defer res.Body.Close()
+
+ var identityDocument struct {
+ Region string `json:"region"`
+ }
+ if err := json.NewDecoder(res.Body).Decode(&identityDocument); err != nil {
+ return noLocation
+ }
+
+ return approximateAWSRegionLocation[identityDocument.Region]
+}