├── .travis.yml ├── README.md ├── UNLICENSE ├── pubip.go ├── pubip_test.go └── settings.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.3 5 | - 1.4 6 | - 1.5 7 | - 1.6 8 | - 1.7.x 9 | - master 10 | 11 | install: 12 | - go get -t -v ./... 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pubip 2 | 3 | A simple package for getting your public IP address by several services. It's 4 | inspired by [go-ipify](https://github.com/rdegges/go-ipify). 5 | 6 | [![GitHub License](https://img.shields.io/badge/license-Unlicense-blue.svg)](https://raw.githubusercontent.com/chyeh/pubip/master/UNLICENSE) 7 | [![GoDoc](https://godoc.org/github.com/chyeh/pubip?status.svg)](https://godoc.org/github.com/chyeh/pubip) 8 | [![Build Status](https://travis-ci.org/chyeh/pubip.svg?branch=master)](https://travis-ci.org/chyeh/pubip) 9 | 10 | 11 | ## Introduction 12 | 13 | In short, It validates the results from several services and returns the IP 14 | address if a valid one is found. If you have ever tried to deploy services in 15 | China, you would understand what the [fallacies of distributed computing](fallacies of distributed computing) are. 16 | Based on the assumption that the services your program depends on are not always 17 | available, it's better to have more backups services. This package gives you the 18 | public IP address from several [APIs](https://github.com/chyeh/pubip/blob/master/settings.go#L12) that I found. 19 | 20 | 21 | ## Installation 22 | 23 | To install `pubip`, simply run: 24 | 25 | ```console 26 | $ go get -u github.com/chyeh/pubip 27 | ``` 28 | 29 | This will install the latest version of the package automatically. 30 | 31 | 32 | ## Usage 33 | 34 | Here's a simple example: 35 | 36 | ```go 37 | package main 38 | 39 | import ( 40 | "fmt" 41 | "github.com/chyeh/pubip" 42 | ) 43 | 44 | func main() { 45 | ip, err := pubip.Get() 46 | if err != nil { 47 | fmt.Println("Couldn't get my IP address:", err) 48 | } else { 49 | fmt.Println("My IP address is:", ip) 50 | } 51 | } 52 | ``` 53 | 54 | For more details, please take a look at the [GoDoc](https://godoc.org/github.com/chyeh/pubip). 55 | 56 | ## Error handling 57 | 58 | It returns an error when the followings happen: 59 | 60 | - It fails to get at least 3 results from the services 61 | - The results from different services are not identical 62 | 63 | 64 | ## Contributing 65 | 66 | Just send me a PR or open an issue. Please include tests for your changes. 67 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /pubip.go: -------------------------------------------------------------------------------- 1 | // Package pubip gets your public IP address from several services 2 | package pubip 3 | 4 | import ( 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "net" 9 | "net/http" 10 | "reflect" 11 | "strconv" 12 | "strings" 13 | "time" 14 | 15 | "github.com/jpillora/backoff" 16 | ) 17 | 18 | // GetIPBy queries an API to retrieve a `net.IP` of this machine's public IP 19 | // address. 20 | // 21 | // Usage: 22 | // 23 | // package main 24 | // 25 | // import ( 26 | // "fmt" 27 | // "github.com/chyeh/pubip" 28 | // ) 29 | // 30 | // func main() { 31 | // ip, err := pubip.GetIPBy("https://api.ipify.org") 32 | // if err != nil { 33 | // fmt.Println("Couldn't get my IP address:", err) 34 | // } else { 35 | // fmt.Println("My IP address is:", ip) 36 | // } 37 | // } 38 | func GetIPBy(dest string) (net.IP, error) { 39 | b := &backoff.Backoff{ 40 | Jitter: true, 41 | } 42 | client := &http.Client{} 43 | 44 | req, err := http.NewRequest("GET", dest, nil) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | for tries := 0; tries < MaxTries; tries++ { 50 | resp, err := client.Do(req) 51 | if err != nil { 52 | d := b.Duration() 53 | time.Sleep(d) 54 | continue 55 | } 56 | 57 | defer resp.Body.Close() 58 | 59 | body, err := ioutil.ReadAll(resp.Body) 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | if resp.StatusCode != 200 { 65 | return nil, errors.New(dest + " status code " + strconv.Itoa(resp.StatusCode) + ", body: " + string(body)) 66 | } 67 | 68 | tb := strings.TrimSpace(string(body)) 69 | ip := net.ParseIP(tb) 70 | if ip == nil { 71 | return nil, errors.New("IP address not valid: " + tb) 72 | } 73 | return ip, nil 74 | } 75 | 76 | return nil, errors.New("Failed to reach " + dest) 77 | } 78 | 79 | // GetIPStrBy queries an API to retrieve a `string` of this machine's public IP 80 | // address. 81 | // 82 | // Usage: 83 | // 84 | // package main 85 | // 86 | // import ( 87 | // "fmt" 88 | // "github.com/chyeh/pubip" 89 | // ) 90 | // 91 | // func main() { 92 | // ip, err := pubip.GetIPBy("https://api.ipify.org") 93 | // if err != nil { 94 | // fmt.Println("Couldn't get my IP address:", err) 95 | // } else { 96 | // fmt.Println("My IP address is:", ip) 97 | // } 98 | // } 99 | func GetIPStrBy(dest string) (string, error) { 100 | ip, err := GetIPBy(dest) 101 | return ip.String(), err 102 | } 103 | 104 | func detailErr(err error, errs []error) error { 105 | errStrs := []string{err.Error()} 106 | for _, e := range errs { 107 | errStrs = append(errStrs, e.Error()) 108 | } 109 | j := strings.Join(errStrs, "\n") 110 | return errors.New(j) 111 | } 112 | 113 | func validate(rs []net.IP) (net.IP, error) { 114 | if rs == nil { 115 | return nil, fmt.Errorf("Failed to get any result from %d APIs", len(APIURIs)) 116 | } 117 | if len(rs) < 3 { 118 | return nil, fmt.Errorf("Less than %d results from %d APIs", 3, len(APIURIs)) 119 | } 120 | first := rs[0] 121 | for i := 1; i < len(rs); i++ { 122 | if !reflect.DeepEqual(first, rs[i]) { //first != rs[i] { 123 | return nil, fmt.Errorf("Results are not identical: %s", rs) 124 | } 125 | } 126 | return first, nil 127 | } 128 | 129 | func worker(d string, r chan<- net.IP, e chan<- error) { 130 | ip, err := GetIPBy(d) 131 | if err != nil { 132 | e <- err 133 | return 134 | } 135 | r <- ip 136 | } 137 | 138 | // Get queries several APIs to retrieve a `net.IP` of this machine's public IP 139 | // address. 140 | // 141 | // Usage: 142 | // 143 | // package main 144 | // 145 | // import ( 146 | // "fmt" 147 | // "github.com/chyeh/pubip" 148 | // ) 149 | // 150 | // func main() { 151 | // ip, err := pubip.Get() 152 | // if err != nil { 153 | // fmt.Println("Couldn't get my IP address:", err) 154 | // } else { 155 | // fmt.Println("My IP address is:", ip) 156 | // } 157 | // } 158 | func Get() (net.IP, error) { 159 | var results []net.IP 160 | resultCh := make(chan net.IP, len(APIURIs)) 161 | var errs []error 162 | errCh := make(chan error, len(APIURIs)) 163 | 164 | for _, d := range APIURIs { 165 | go worker(d, resultCh, errCh) 166 | } 167 | for { 168 | select { 169 | case err := <-errCh: 170 | errs = append(errs, err) 171 | case r := <-resultCh: 172 | results = append(results, r) 173 | case <-time.After(Timeout): 174 | r, err := validate(results) 175 | if err != nil { 176 | return nil, detailErr(err, errs) 177 | } 178 | return r, nil 179 | } 180 | } 181 | } 182 | 183 | // GetStr queries several APIs to retrieve a `string` of this machine's public 184 | // IP address. 185 | // 186 | // Usage: 187 | // 188 | // package main 189 | // 190 | // import ( 191 | // "fmt" 192 | // "github.com/chyeh/pubip" 193 | // ) 194 | // 195 | // func main() { 196 | // ip, err := pubip.Get() 197 | // if err != nil { 198 | // fmt.Println("Couldn't get my IP address:", err) 199 | // } else { 200 | // fmt.Println("My IP address is:", ip) 201 | // } 202 | // } 203 | func GetStr() (string, error) { 204 | ip, err := Get() 205 | return ip.String(), err 206 | } 207 | -------------------------------------------------------------------------------- /pubip_test.go: -------------------------------------------------------------------------------- 1 | package pubip 2 | 3 | import ( 4 | "net" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestIsValidate(t *testing.T) { 10 | tests := []struct { 11 | input []net.IP 12 | expected net.IP 13 | }{ 14 | {nil, nil}, 15 | {[]net.IP{}, nil}, 16 | {[]net.IP{net.ParseIP("192.168.1.1")}, nil}, 17 | {[]net.IP{net.ParseIP("192.168.1.1"), net.ParseIP("192.168.1.1")}, nil}, 18 | {[]net.IP{net.ParseIP("192.168.1.1"), net.ParseIP("192.168.1.1"), net.ParseIP("192.168.1.1")}, net.ParseIP("192.168.1.1")}, 19 | {[]net.IP{net.ParseIP("192.168.1.1"), net.ParseIP("192.168.1.2")}, nil}, 20 | {[]net.IP{net.ParseIP("192.168.1.1"), net.ParseIP("192.168.1.1"), net.ParseIP("192.168.1.2")}, nil}, 21 | } 22 | for i, v := range tests { 23 | actual, _ := validate(v.input) 24 | expected := v.expected 25 | t.Logf("Check case %d: %s(actual) == %s(expected)", i, actual, expected) 26 | if !reflect.DeepEqual(actual, expected) { 27 | t.Errorf("Error on case %d: %s(actual) != %s(expected)", i, actual, expected) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /settings.go: -------------------------------------------------------------------------------- 1 | package pubip 2 | 3 | import "time" 4 | 5 | // Version indicates the version of this package. 6 | const Version = "1.0.0" 7 | 8 | // MaxTries is the maximum amount of tries to attempt to one service. 9 | const MaxTries = 3 10 | 11 | // APIURIs is the URIs of the services. 12 | var APIURIs = []string{ 13 | "https://api.ipify.org", 14 | "http://myexternalip.com/raw", 15 | "http://ipinfo.io/ip", 16 | "http://ipecho.net/plain", 17 | "http://icanhazip.com", 18 | "http://ifconfig.me/ip", 19 | "http://ident.me", 20 | "http://checkip.amazonaws.com", 21 | "http://bot.whatismyipaddress.com", 22 | "http://whatismyip.akamai.com", 23 | "http://wgetip.com", 24 | "http://ip.appspot.com", 25 | "http://ip.tyk.nu", 26 | "https://shtuff.it/myip/short", 27 | } 28 | 29 | // Timeout sets the time limit of collecting results from different services. 30 | var Timeout = 2 * time.Second 31 | --------------------------------------------------------------------------------