package ip import ( "bytes" "context" "fmt" "io" "net" "net/http" ) const ifconfigURL = "https://ifconfig.me" const httpMaxRead = 100 // TODO: use a struct, but a baseHTTPClient inside it to perform unit tests type Getter interface { GetCurrentPublicIP(ctx context.Context) (net.IP, error) } type concreteIPGetter struct { } func (c *concreteIPGetter) GetCurrentPublicIP(ctx context.Context) (net.IP, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, ifconfigURL, nil) if err != nil { return nil, fmt.Errorf("failed to prepare public IP request: %w", err) } resp, err := http.DefaultClient.Do(req) if err != nil { return nil, fmt.Errorf("failed to get current IP from ifconfig: %w", err) } 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() *concreteIPGetter { return &concreteIPGetter{} }