github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/core/ip/resolver.go (about) 1 /* 2 * Copyright (C) 2017 The "MysteriumNetwork/node" Authors. 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 package ip 19 20 import ( 21 "net" 22 "sync" 23 24 "github.com/pkg/errors" 25 "github.com/rs/zerolog/log" 26 27 "github.com/mysteriumnetwork/node/requests" 28 ) 29 30 const apiClient = "goclient-v0.1" 31 32 // Resolver allows resolving current public and outbound IPs 33 type Resolver interface { 34 GetOutboundIP() (string, error) 35 GetPublicIP() (string, error) 36 GetProxyIP(proxyPort int) (string, error) 37 } 38 39 // ResolverImpl represents data required to operate resolving 40 type ResolverImpl struct { 41 bindAddress string 42 url string 43 httpClient *requests.HTTPClient 44 fallbacks []string 45 } 46 47 // NewResolver creates new ip-detector resolver with default timeout of one minute 48 func NewResolver(httpClient *requests.HTTPClient, bindAddress, url string, fallbacks []string) *ResolverImpl { 49 return &ResolverImpl{ 50 bindAddress: bindAddress, 51 url: url, 52 httpClient: httpClient, 53 fallbacks: fallbacks, 54 } 55 } 56 57 type ipResponse struct { 58 IP string `json:"IP"` 59 } 60 61 // declared as var for override in test 62 var checkAddress = "8.8.8.8:53" 63 64 // GetOutboundIP returns current outbound IP as string for current system 65 func (r *ResolverImpl) GetOutboundIP() (string, error) { 66 ip, err := r.getOutboundIP() 67 if err != nil { 68 return "", nil 69 } 70 return ip.String(), nil 71 } 72 73 func (r *ResolverImpl) getOutboundIP() (net.IP, error) { 74 ipAddress := net.ParseIP(r.bindAddress) 75 localIPAddress := net.UDPAddr{IP: ipAddress} 76 77 dialer := net.Dialer{LocalAddr: &localIPAddress} 78 79 conn, err := dialer.Dial("udp4", checkAddress) 80 if err != nil { 81 return nil, errors.Wrap(err, "failed to determine outbound IP") 82 } 83 defer conn.Close() 84 85 return conn.LocalAddr().(*net.UDPAddr).IP, nil 86 } 87 88 // GetPublicIP returns current public IP 89 func (r *ResolverImpl) GetPublicIP() (string, error) { 90 var ipResponse ipResponse 91 92 request, err := requests.NewGetRequest(r.url, "", nil) 93 if err != nil { 94 log.Error().Err(err).Msg("") 95 return "", err 96 } 97 request.Header.Set("User-Agent", apiClient) 98 request.Header.Set("Accept", "application/json") 99 100 err = r.httpClient.DoRequestAndParseResponse(request, &ipResponse) 101 if err != nil { 102 log.Err(err).Msg("could not reach location service, will use fallbacks") 103 return r.findPublicIPViaFallbacks() 104 } 105 106 return ipResponse.IP, nil 107 } 108 109 // GetProxyIP returns proxy public IP 110 func (r *ResolverImpl) GetProxyIP(proxyPort int) (string, error) { 111 var ipResponse ipResponse 112 113 request, err := requests.NewGetRequest(r.url, "", nil) 114 if err != nil { 115 log.Error().Err(err).Msg("") 116 return "", err 117 } 118 119 request.Header.Set("User-Agent", apiClient) 120 request.Header.Set("Accept", "application/json") 121 122 err = r.httpClient.DoRequestViaProxyAndParseResponse(request, &ipResponse, proxyPort) 123 if err != nil { 124 log.Err(err).Msg("could not reach location service, will use fallbacks") 125 return r.findPublicIPViaFallbacks() 126 } 127 128 return ipResponse.IP, nil 129 } 130 131 func (r *ResolverImpl) findPublicIPViaFallbacks() (string, error) { 132 // To prevent blocking for a long time on a service that might be dead, use the following fallback mechanic: 133 // Choose 3 fallback addresses at random and execute lookups on them in parallel. 134 // Return the first successful result or an error if such occurs. 135 // This prevents providers from not being able to provide sessions due to not having a fresh public IP address. 136 desiredLength := 3 137 res := make(chan string, desiredLength) 138 wg := sync.WaitGroup{} 139 wg.Add(desiredLength) 140 141 go func() { 142 wg.Wait() 143 close(res) 144 }() 145 146 for _, v := range shuffleStringSlice(r.fallbacks)[:desiredLength] { 147 go func(url string) { 148 defer wg.Done() 149 r, err := RequestAndParsePlainIPResponse(r.httpClient, url) 150 if err != nil { 151 log.Err(err).Str("url", url).Msg("public ip fallback error") 152 } 153 res <- r 154 }(v) 155 } 156 157 for ip := range res { 158 if ip != "" { 159 return ip, nil 160 } 161 } 162 163 return "", errors.New("out of fallbacks") 164 }