package ip import ( "bytes" "context" "fmt" "io" "net" "net/http" "time" ) const ifconfigURL = "https://ifconfig.me" const httpMaxRead = 100 const httpTimeout = 10 * time.Second type IPGetter struct { httpClt *http.Client remoteAddress string timeout time.Duration } func (c *IPGetter) GetCurrentPublicIP(ctx context.Context) (net.IP, error) { reqCtx, cancel := context.WithTimeout(ctx, c.timeout) defer cancel() req, err := http.NewRequestWithContext(reqCtx, http.MethodGet, c.remoteAddress, nil) if err != nil { return nil, fmt.Errorf("failed to prepare public IP request: %w", err) } resp, err := c.httpClt.Do(req) if err != nil { return nil, fmt.Errorf("failed to get current IP from ifconfig: %w", err) } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("invalid returncode %d", resp.StatusCode) } if resp.ContentLength > httpMaxRead { return nil, fmt.Errorf("response too big: %d/%d", resp.ContentLength, httpMaxRead) } buf := bytes.NewBuffer([]byte{}) if _, err := io.CopyN(buf, resp.Body, resp.ContentLength); err != nil { return nil, fmt.Errorf("error parsing body: %w", err) } content := string(buf.Bytes()) res := net.ParseIP(content) if res == nil { return nil, fmt.Errorf("got an invalid public IP %q", content) } return res, nil } func New() *IPGetter { return &IPGetter{ httpClt: http.DefaultClient, remoteAddress: ifconfigURL, timeout: httpTimeout, } }