diff --git a/internal/cli/openvpnconfig.go b/internal/cli/openvpnconfig.go index c4883853..6cbd9059 100644 --- a/internal/cli/openvpnconfig.go +++ b/internal/cli/openvpnconfig.go @@ -3,8 +3,8 @@ package cli import ( "context" "fmt" - "net" "net/http" + "net/netip" "strings" "time" @@ -28,11 +28,11 @@ type Unzipper interface { type ParallelResolver interface { Resolve(ctx context.Context, settings resolver.ParallelSettings) ( - hostToIPs map[string][]net.IP, warnings []string, err error) + hostToIPs map[string][]netip.Addr, warnings []string, err error) } type IPFetcher interface { - FetchMultiInfo(ctx context.Context, ips []net.IP) (data []ipinfo.Response, err error) + FetchMultiInfo(ctx context.Context, ips []netip.Addr) (data []ipinfo.Response, err error) } type IPv6Checker interface { diff --git a/internal/configuration/settings/dns.go b/internal/configuration/settings/dns.go index 2d691014..003cf936 100644 --- a/internal/configuration/settings/dns.go +++ b/internal/configuration/settings/dns.go @@ -2,7 +2,7 @@ package settings import ( "fmt" - "net" + "net/netip" "github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gotree" @@ -13,9 +13,9 @@ type DNS struct { // ServerAddress is the DNS server to use inside // the Go program and for the system. // It defaults to '127.0.0.1' to be used with the - // DoT server. It cannot be nil in the internal + // DoT server. It cannot be the zero value in the internal // state. - ServerAddress net.IP + ServerAddress netip.Addr // KeepNameserver is true if the Docker DNS server // found in /etc/resolv.conf should be kept. // Note settings this to true will go around the @@ -39,7 +39,7 @@ func (d DNS) validate() (err error) { func (d *DNS) Copy() (copied DNS) { return DNS{ - ServerAddress: helpers.CopyIP(d.ServerAddress), + ServerAddress: d.ServerAddress, KeepNameserver: helpers.CopyBoolPtr(d.KeepNameserver), DoT: d.DoT.copy(), } @@ -63,7 +63,7 @@ func (d *DNS) overrideWith(other DNS) { } func (d *DNS) setDefaults() { - localhost := net.IPv4(127, 0, 0, 1) //nolint:gomnd + localhost := netip.AddrFrom4([4]byte{127, 0, 0, 1}) d.ServerAddress = helpers.DefaultIP(d.ServerAddress, localhost) d.KeepNameserver = helpers.DefaultBool(d.KeepNameserver, false) d.DoT.setDefaults() diff --git a/internal/configuration/settings/helpers/copy.go b/internal/configuration/settings/helpers/copy.go index 1c6403f1..25a1b140 100644 --- a/internal/configuration/settings/helpers/copy.go +++ b/internal/configuration/settings/helpers/copy.go @@ -1,8 +1,6 @@ package helpers import ( - "fmt" - "net" "net/netip" "time" @@ -81,25 +79,6 @@ func CopyLogLevelPtr(original *log.Level) (copied *log.Level) { return copied } -func CopyIP(original net.IP) (copied net.IP) { - if original == nil { - return nil - } - copied = make(net.IP, len(original)) - copy(copied, original) - return copied -} - -func CopyNetipAddress(original netip.Addr) (copied netip.Addr) { - // AsSlice creates a new byte slice so no need to copy the bytes. - bytes := original.AsSlice() - copied, ok := netip.AddrFromSlice(bytes) - if !ok { - panic(fmt.Sprintf("cannot deep copy address with bytes %#v", bytes)) - } - return copied -} - func CopyStringSlice(original []string) (copied []string) { if original == nil { return nil @@ -136,9 +115,6 @@ func CopyNetipAddressesSlice(original []netip.Addr) (copied []netip.Addr) { } copied = make([]netip.Addr, len(original)) - for i := range original { - copied[i] = CopyNetipAddress(original[i]) - } - + copy(copied, original) return copied } diff --git a/internal/configuration/settings/helpers/default.go b/internal/configuration/settings/helpers/default.go index 9c2fa07c..8ee1abd7 100644 --- a/internal/configuration/settings/helpers/default.go +++ b/internal/configuration/settings/helpers/default.go @@ -1,7 +1,7 @@ package helpers import ( - "net" + "net/netip" "time" "github.com/qdm12/log" @@ -101,9 +101,9 @@ func DefaultLogLevel(existing *log.Level, return result } -func DefaultIP(existing net.IP, defaultValue net.IP) ( - result net.IP) { - if existing != nil { +func DefaultIP(existing netip.Addr, defaultValue netip.Addr) ( + result netip.Addr) { + if existing.IsValid() { return existing } return defaultValue diff --git a/internal/configuration/settings/helpers/merge.go b/internal/configuration/settings/helpers/merge.go index 3d5cad5c..663a41f7 100644 --- a/internal/configuration/settings/helpers/merge.go +++ b/internal/configuration/settings/helpers/merge.go @@ -1,7 +1,7 @@ package helpers import ( - "net" + "fmt" "net/http" "net/netip" "time" @@ -96,14 +96,17 @@ func MergeWithUint32(existing, other *uint32) (result *uint32) { return result } -func MergeWithIP(existing, other net.IP) (result net.IP) { - if existing != nil { +func MergeWithIP(existing, other netip.Addr) (result netip.Addr) { + if existing.IsValid() { + return existing + } else if !other.IsValid() { return existing - } else if other == nil { - return nil } - result = make(net.IP, len(other)) - copy(result, other) + + result, ok := netip.AddrFromSlice(other.AsSlice()) + if !ok { + panic(fmt.Sprintf("failed copying other address: %s", other)) + } return result } diff --git a/internal/configuration/settings/helpers/override.go b/internal/configuration/settings/helpers/override.go index e75d86e1..86d7ba08 100644 --- a/internal/configuration/settings/helpers/override.go +++ b/internal/configuration/settings/helpers/override.go @@ -1,7 +1,7 @@ package helpers import ( - "net" + "fmt" "net/http" "net/netip" "time" @@ -84,12 +84,14 @@ func OverrideWithUint32(existing, other *uint32) (result *uint32) { return result } -func OverrideWithIP(existing, other net.IP) (result net.IP) { - if other == nil { +func OverrideWithIP(existing, other netip.Addr) (result netip.Addr) { + if !other.IsValid() { return existing } - result = make(net.IP, len(other)) - copy(result, other) + result, ok := netip.AddrFromSlice(other.AsSlice()) + if !ok { + panic(fmt.Sprintf("failed copying other address: %s", other)) + } return result } diff --git a/internal/configuration/settings/serverselection.go b/internal/configuration/settings/serverselection.go index 81d94772..2bcc2765 100644 --- a/internal/configuration/settings/serverselection.go +++ b/internal/configuration/settings/serverselection.go @@ -3,7 +3,7 @@ package settings import ( "errors" "fmt" - "net" + "net/netip" "strings" "github.com/qdm12/gluetun/internal/configuration/settings/helpers" @@ -21,10 +21,10 @@ type ServerSelection struct { //nolint:maligned VPN string // TargetIP is the server endpoint IP address to use. // It will override any IP address from the picked - // built-in server. It cannot be nil in the internal - // state, and can be set to an empty net.IP{} to indicate + // built-in server. It cannot be the empty value in the internal + // state, and can be set to the unspecified address to indicate // there is not target IP address to use. - TargetIP net.IP + TargetIP netip.Addr // Counties is the list of countries to filter VPN servers with. Countries []string // Regions is the list of regions to filter VPN servers with. @@ -202,7 +202,7 @@ func validateServerFilters(settings ServerSelection, filterChoices models.Filter func (ss *ServerSelection) copy() (copied ServerSelection) { return ServerSelection{ VPN: ss.VPN, - TargetIP: helpers.CopyIP(ss.TargetIP), + TargetIP: ss.TargetIP, Countries: helpers.CopyStringSlice(ss.Countries), Regions: helpers.CopyStringSlice(ss.Regions), Cities: helpers.CopyStringSlice(ss.Cities), @@ -261,7 +261,7 @@ func (ss *ServerSelection) overrideWith(other ServerSelection) { func (ss *ServerSelection) setDefaults(vpnProvider string) { ss.VPN = helpers.DefaultString(ss.VPN, vpn.OpenVPN) - ss.TargetIP = helpers.DefaultIP(ss.TargetIP, net.IP{}) + ss.TargetIP = helpers.DefaultIP(ss.TargetIP, netip.IPv4Unspecified()) ss.OwnedOnly = helpers.DefaultBool(ss.OwnedOnly, false) ss.FreeOnly = helpers.DefaultBool(ss.FreeOnly, false) ss.PremiumOnly = helpers.DefaultBool(ss.PremiumOnly, false) @@ -278,7 +278,7 @@ func (ss ServerSelection) String() string { func (ss ServerSelection) toLinesNode() (node *gotree.Node) { node = gotree.New("Server selection settings:") node.Appendf("VPN type: %s", ss.VPN) - if len(ss.TargetIP) > 0 { + if !ss.TargetIP.IsUnspecified() { node.Appendf("Target IP address: %s", ss.TargetIP) } diff --git a/internal/configuration/settings/unbound.go b/internal/configuration/settings/unbound.go index 4df8b2b3..717a2f88 100644 --- a/internal/configuration/settings/unbound.go +++ b/internal/configuration/settings/unbound.go @@ -3,7 +3,6 @@ package settings import ( "errors" "fmt" - "net" "net/netip" "github.com/qdm12/dns/pkg/provider" @@ -155,14 +154,24 @@ func (u Unbound) ToUnboundFormat() (settings unbound.Settings, err error) { }, nil } -func (u Unbound) GetFirstPlaintextIPv4() (ipv4 net.IP, err error) { +var ( + ErrConvertingNetip = errors.New("converting net.IP to netip.Addr failed") +) + +func (u Unbound) GetFirstPlaintextIPv4() (ipv4 netip.Addr, err error) { s := u.Providers[0] provider, err := provider.Parse(s) if err != nil { - return nil, err + return ipv4, err } - return provider.DNS().IPv4[0], nil + ip := provider.DNS().IPv4[0] + ipv4, ok := netip.AddrFromSlice(ip) + if !ok { + return ipv4, fmt.Errorf("%w: for ip %s (%#v)", + ErrConvertingNetip, ip, ip) + } + return ipv4.Unmap(), nil } func (u Unbound) String() string { diff --git a/internal/configuration/settings/wireguardselection.go b/internal/configuration/settings/wireguardselection.go index 16d926d6..43716375 100644 --- a/internal/configuration/settings/wireguardselection.go +++ b/internal/configuration/settings/wireguardselection.go @@ -2,7 +2,7 @@ package settings import ( "fmt" - "net" + "net/netip" "github.com/qdm12/gluetun/internal/configuration/settings/helpers" "github.com/qdm12/gluetun/internal/constants/providers" @@ -15,9 +15,9 @@ type WireguardSelection struct { // It is only used with VPN providers generating Wireguard // configurations specific to each server and user. // To indicate it should not be used, it should be set - // to the empty net.IP{} slice. It can never be nil + // to netaddr.IPv4Unspecified(). It can never be the zero value // in the internal state. - EndpointIP net.IP + EndpointIP netip.Addr // EndpointPort is a the server port to use for the VPN server. // It is optional for VPN providers IVPN, Mullvad, Surfshark // and Windscribe, and compulsory for the others. @@ -40,7 +40,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) { providers.Surfshark, providers.Windscribe: // endpoint IP addresses are baked in case providers.Custom: - if len(w.EndpointIP) == 0 { + if !w.EndpointIP.IsValid() || w.EndpointIP.IsUnspecified() { return fmt.Errorf("%w", ErrWireguardEndpointIPNotSet) } default: // Providers not supporting Wireguard @@ -109,7 +109,7 @@ func (w WireguardSelection) validate(vpnProvider string) (err error) { func (w *WireguardSelection) copy() (copied WireguardSelection) { return WireguardSelection{ - EndpointIP: helpers.CopyIP(w.EndpointIP), + EndpointIP: w.EndpointIP, EndpointPort: helpers.CopyUint16Ptr(w.EndpointPort), PublicKey: w.PublicKey, } @@ -128,7 +128,7 @@ func (w *WireguardSelection) overrideWith(other WireguardSelection) { } func (w *WireguardSelection) setDefaults() { - w.EndpointIP = helpers.DefaultIP(w.EndpointIP, net.IP{}) + w.EndpointIP = helpers.DefaultIP(w.EndpointIP, netip.IPv4Unspecified()) w.EndpointPort = helpers.DefaultUint16(w.EndpointPort, 0) } @@ -139,7 +139,7 @@ func (w WireguardSelection) String() string { func (w WireguardSelection) toLinesNode() (node *gotree.Node) { node = gotree.New("Wireguard selection settings:") - if len(w.EndpointIP) > 0 { + if !w.EndpointIP.IsUnspecified() { node.Appendf("Endpoint IP address: %s", w.EndpointIP) } diff --git a/internal/configuration/sources/env/dns.go b/internal/configuration/sources/env/dns.go index bb48e891..745d6814 100644 --- a/internal/configuration/sources/env/dns.go +++ b/internal/configuration/sources/env/dns.go @@ -2,7 +2,7 @@ package env import ( "fmt" - "net" + "net/netip" "github.com/qdm12/gluetun/internal/configuration/settings" ) @@ -26,19 +26,19 @@ func (s *Source) readDNS() (dns settings.DNS, err error) { return dns, nil } -func (s *Source) readDNSServerAddress() (address net.IP, err error) { +func (s *Source) readDNSServerAddress() (address netip.Addr, err error) { key, value := s.getEnvWithRetro("DNS_ADDRESS", "DNS_PLAINTEXT_ADDRESS") if value == "" { - return nil, nil + return address, nil } - address = net.ParseIP(value) - if address == nil { - return nil, fmt.Errorf("environment variable %s: %w: %s", key, ErrIPAddressParse, value) + address, err = netip.ParseAddr(value) + if err != nil { + return address, fmt.Errorf("environment variable %s: %w", key, err) } // TODO remove in v4 - if !address.Equal(net.IPv4(127, 0, 0, 1)) { //nolint:gomnd + if address.Unmap().Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 { s.warner.Warn(key + " is set to " + value + " so the DNS over TLS (DoT) server will not be used." + " The default value changed to 127.0.0.1 so it uses the internal DoT serves." + diff --git a/internal/configuration/sources/env/serverselection.go b/internal/configuration/sources/env/serverselection.go index a70708b1..148b4b49 100644 --- a/internal/configuration/sources/env/serverselection.go +++ b/internal/configuration/sources/env/serverselection.go @@ -3,7 +3,7 @@ package env import ( "errors" "fmt" - "net" + "net/netip" "strconv" "strings" @@ -113,16 +113,15 @@ var ( ErrInvalidIP = errors.New("invalid IP address") ) -func (s *Source) readOpenVPNTargetIP() (ip net.IP, err error) { +func (s *Source) readOpenVPNTargetIP() (ip netip.Addr, err error) { envKey, value := s.getEnvWithRetro("VPN_ENDPOINT_IP", "OPENVPN_TARGET_IP") if value == "" { - return nil, nil + return ip, nil } - ip = net.ParseIP(value) - if ip == nil { - return nil, fmt.Errorf("environment variable %s: %w: %s", - envKey, ErrInvalidIP, value) + ip, err = netip.ParseAddr(value) + if err != nil { + return ip, fmt.Errorf("environment variable %s: %w", envKey, err) } return ip, nil diff --git a/internal/configuration/sources/env/wireguardselection.go b/internal/configuration/sources/env/wireguardselection.go index e68b15f0..b9078f81 100644 --- a/internal/configuration/sources/env/wireguardselection.go +++ b/internal/configuration/sources/env/wireguardselection.go @@ -1,9 +1,8 @@ package env import ( - "errors" "fmt" - "net" + "net/netip" "github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/govalid/port" @@ -26,18 +25,15 @@ func (s *Source) readWireguardSelection() ( return selection, nil } -var ErrIPAddressParse = errors.New("cannot parse IP address") - -func (s *Source) readWireguardEndpointIP() (endpointIP net.IP, err error) { +func (s *Source) readWireguardEndpointIP() (endpointIP netip.Addr, err error) { key, value := s.getEnvWithRetro("VPN_ENDPOINT_IP", "WIREGUARD_ENDPOINT_IP") if value == "" { - return nil, nil + return endpointIP, nil } - endpointIP = net.ParseIP(value) - if endpointIP == nil { - return nil, fmt.Errorf("environment variable %s: %w: %s", - key, ErrIPAddressParse, value) + endpointIP, err = netip.ParseAddr(value) + if err != nil { + return endpointIP, fmt.Errorf("environment variable %s: %w", key, err) } return endpointIP, nil diff --git a/internal/dns/plaintext.go b/internal/dns/plaintext.go index c0703d87..57165b57 100644 --- a/internal/dns/plaintext.go +++ b/internal/dns/plaintext.go @@ -1,7 +1,7 @@ package dns import ( - "net" + "net/netip" "github.com/qdm12/dns/pkg/nameserver" ) @@ -12,14 +12,14 @@ func (l *Loop) useUnencryptedDNS(fallback bool) { // Try with user provided plaintext ip address // if it's not 127.0.0.1 (default for DoT) targetIP := settings.ServerAddress - if targetIP != nil && !targetIP.Equal(net.IPv4(127, 0, 0, 1)) { //nolint:gomnd + if targetIP.Compare(netip.AddrFrom4([4]byte{127, 0, 0, 1})) != 0 { if fallback { l.logger.Info("falling back on plaintext DNS at address " + targetIP.String()) } else { l.logger.Info("using plaintext DNS at address " + targetIP.String()) } - nameserver.UseDNSInternally(targetIP) - err := nameserver.UseDNSSystemWide(l.resolvConf, targetIP, *settings.KeepNameserver) + nameserver.UseDNSInternally(targetIP.AsSlice()) + err := nameserver.UseDNSSystemWide(l.resolvConf, targetIP.AsSlice(), *settings.KeepNameserver) if err != nil { l.logger.Error(err.Error()) } @@ -38,8 +38,8 @@ func (l *Loop) useUnencryptedDNS(fallback bool) { } else { l.logger.Info("using plaintext DNS at address " + targetIP.String()) } - nameserver.UseDNSInternally(targetIP) - err = nameserver.UseDNSSystemWide(l.resolvConf, targetIP, *settings.KeepNameserver) + nameserver.UseDNSInternally(targetIP.AsSlice()) + err = nameserver.UseDNSSystemWide(l.resolvConf, targetIP.AsSlice(), *settings.KeepNameserver) if err != nil { l.logger.Error(err.Error()) } diff --git a/internal/dns/setup.go b/internal/dns/setup.go index 3200cf4e..0443e08b 100644 --- a/internal/dns/setup.go +++ b/internal/dns/setup.go @@ -43,8 +43,8 @@ func (l *Loop) setupUnbound(ctx context.Context) ( } // use Unbound - nameserver.UseDNSInternally(settings.ServerAddress) - err = nameserver.UseDNSSystemWide(l.resolvConf, settings.ServerAddress, + nameserver.UseDNSInternally(settings.ServerAddress.AsSlice()) + err = nameserver.UseDNSSystemWide(l.resolvConf, settings.ServerAddress.AsSlice(), *settings.KeepNameserver) if err != nil { l.logger.Error(err.Error()) diff --git a/internal/firewall/enable.go b/internal/firewall/enable.go index 47305bd0..f4f30101 100644 --- a/internal/firewall/enable.go +++ b/internal/firewall/enable.go @@ -130,7 +130,7 @@ func (c *Config) enable(ctx context.Context) (err error) { } func (c *Config) allowVPNIP(ctx context.Context) (err error) { - if c.vpnConnection.IP == nil { + if !c.vpnConnection.IP.IsValid() { return nil } diff --git a/internal/firewall/iptables.go b/internal/firewall/iptables.go index 4e9b56a4..495ef5d1 100644 --- a/internal/firewall/iptables.go +++ b/internal/firewall/iptables.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io" - "net" "net/netip" "os" "os/exec" @@ -146,8 +145,7 @@ func (c *Config) acceptOutputTrafficToVPN(ctx context.Context, instruction := fmt.Sprintf("%s OUTPUT -d %s -o %s -p %s -m %s --dport %d -j ACCEPT", appendOrDelete(remove), connection.IP, defaultInterface, connection.Protocol, connection.Protocol, connection.Port) - isIPv4 := connection.IP.To4() != nil - if isIPv4 { + if connection.IP.Is4() { return c.runIptablesInstruction(ctx, instruction) } else if c.ip6Tables == "" { return fmt.Errorf("accept output to VPN server: %w", ErrNeedIP6Tables) @@ -157,8 +155,8 @@ func (c *Config) acceptOutputTrafficToVPN(ctx context.Context, // Thanks to @npawelek. func (c *Config) acceptOutputFromIPToSubnet(ctx context.Context, - intf string, sourceIP net.IP, destinationSubnet netip.Prefix, remove bool) error { - doIPv4 := sourceIP.To4() != nil && destinationSubnet.Addr().Is4() + intf string, sourceIP netip.Addr, destinationSubnet netip.Prefix, remove bool) error { + doIPv4 := sourceIP.Is4() && destinationSubnet.Addr().Is4() interfaceFlag := "-o " + intf if intf == "*" { // all interfaces diff --git a/internal/firewall/vpn.go b/internal/firewall/vpn.go index 7df6687a..a585ce1d 100644 --- a/internal/firewall/vpn.go +++ b/internal/firewall/vpn.go @@ -25,7 +25,7 @@ func (c *Config) SetVPNConnection(ctx context.Context, } remove := true - if c.vpnConnection.IP != nil { + if c.vpnConnection.IP.IsValid() { for _, defaultRoute := range c.defaultRoutes { if err := c.acceptOutputTrafficToVPN(ctx, defaultRoute.NetInterface, c.vpnConnection, remove); err != nil { c.logger.Error("cannot remove outdated VPN connection rule: " + err.Error()) diff --git a/internal/models/connection.go b/internal/models/connection.go index 654f75ae..60689af6 100644 --- a/internal/models/connection.go +++ b/internal/models/connection.go @@ -1,14 +1,14 @@ package models import ( - "net" + "net/netip" ) type Connection struct { // Type is the connection type and can be "openvpn" or "wireguard" Type string `json:"type"` // IP is the VPN server IP address. - IP net.IP `json:"ip"` + IP netip.Addr `json:"ip"` // Port is the VPN server port. Port uint16 `json:"port"` // Protocol can be "tcp" or "udp". @@ -24,15 +24,15 @@ type Connection struct { } func (c *Connection) Equal(other Connection) bool { - return c.IP.Equal(other.IP) && c.Port == other.Port && + return c.IP.Compare(other.IP) == 0 && c.Port == other.Port && c.Protocol == other.Protocol && c.Hostname == other.Hostname && c.ServerName == other.ServerName && c.PubKey == other.PubKey } // UpdateEmptyWith updates each field of the connection where the // value is not set using the value given as arguments. -func (c *Connection) UpdateEmptyWith(ip net.IP, port uint16, protocol string) { - if c.IP == nil { +func (c *Connection) UpdateEmptyWith(ip netip.Addr, port uint16, protocol string) { + if !c.IP.IsValid() { c.IP = ip } if c.Port == 0 { diff --git a/internal/models/publicip.go b/internal/models/publicip.go index 998a412d..0978f4d9 100644 --- a/internal/models/publicip.go +++ b/internal/models/publicip.go @@ -1,22 +1,24 @@ package models -import "net" +import ( + "net/netip" +) type PublicIP struct { - IP net.IP `json:"public_ip,omitempty"` - Region string `json:"region,omitempty"` - Country string `json:"country,omitempty"` - City string `json:"city,omitempty"` - Hostname string `json:"hostname,omitempty"` - Location string `json:"location,omitempty"` - Organization string `json:"organization,omitempty"` - PostalCode string `json:"postal_code,omitempty"` - Timezone string `json:"timezone,omitempty"` + IP netip.Addr `json:"public_ip,omitempty"` + Region string `json:"region,omitempty"` + Country string `json:"country,omitempty"` + City string `json:"city,omitempty"` + Hostname string `json:"hostname,omitempty"` + Location string `json:"location,omitempty"` + Organization string `json:"organization,omitempty"` + PostalCode string `json:"postal_code,omitempty"` + Timezone string `json:"timezone,omitempty"` } func (p *PublicIP) Copy() (publicIPCopy PublicIP) { publicIPCopy = PublicIP{ - IP: make(net.IP, len(p.IP)), + IP: p.IP, Region: p.Region, Country: p.Country, City: p.City, @@ -26,6 +28,5 @@ func (p *PublicIP) Copy() (publicIPCopy PublicIP) { PostalCode: p.PostalCode, Timezone: p.Timezone, } - copy(publicIPCopy.IP, p.IP) return publicIPCopy } diff --git a/internal/models/server.go b/internal/models/server.go index f7dad571..5446bc7f 100644 --- a/internal/models/server.go +++ b/internal/models/server.go @@ -3,7 +3,7 @@ package models import ( "errors" "fmt" - "net" + "net/netip" "reflect" "strings" @@ -13,26 +13,26 @@ import ( type Server struct { VPN string `json:"vpn,omitempty"` // Surfshark: country is also used for multi-hop - Country string `json:"country,omitempty"` - Region string `json:"region,omitempty"` - City string `json:"city,omitempty"` - ISP string `json:"isp,omitempty"` - Owned bool `json:"owned,omitempty"` - Number uint16 `json:"number,omitempty"` - ServerName string `json:"server_name,omitempty"` - Hostname string `json:"hostname,omitempty"` - TCP bool `json:"tcp,omitempty"` - UDP bool `json:"udp,omitempty"` - OvpnX509 string `json:"x509,omitempty"` - RetroLoc string `json:"retroloc,omitempty"` // TODO remove in v4 - MultiHop bool `json:"multihop,omitempty"` - WgPubKey string `json:"wgpubkey,omitempty"` - Free bool `json:"free,omitempty"` - Stream bool `json:"stream,omitempty"` - Premium bool `json:"premium,omitempty"` - PortForward bool `json:"port_forward,omitempty"` - Keep bool `json:"keep,omitempty"` - IPs []net.IP `json:"ips,omitempty"` + Country string `json:"country,omitempty"` + Region string `json:"region,omitempty"` + City string `json:"city,omitempty"` + ISP string `json:"isp,omitempty"` + Owned bool `json:"owned,omitempty"` + Number uint16 `json:"number,omitempty"` + ServerName string `json:"server_name,omitempty"` + Hostname string `json:"hostname,omitempty"` + TCP bool `json:"tcp,omitempty"` + UDP bool `json:"udp,omitempty"` + OvpnX509 string `json:"x509,omitempty"` + RetroLoc string `json:"retroloc,omitempty"` // TODO remove in v4 + MultiHop bool `json:"multihop,omitempty"` + WgPubKey string `json:"wgpubkey,omitempty"` + Free bool `json:"free,omitempty"` + Stream bool `json:"stream,omitempty"` + Premium bool `json:"premium,omitempty"` + PortForward bool `json:"port_forward,omitempty"` + Keep bool `json:"keep,omitempty"` + IPs []netip.Addr `json:"ips,omitempty"` } var ( @@ -72,13 +72,13 @@ func (s *Server) Equal(other Server) (equal bool) { return reflect.DeepEqual(serverCopy, other) } -func ipsAreEqual(a, b []net.IP) (equal bool) { +func ipsAreEqual(a, b []netip.Addr) (equal bool) { if len(a) != len(b) { return false } for i := range a { - if !a[i].Equal(b[i]) { + if a[i].Compare(b[i]) != 0 { return false } } diff --git a/internal/models/server_test.go b/internal/models/server_test.go index 4fa9e7c9..94321fd3 100644 --- a/internal/models/server_test.go +++ b/internal/models/server_test.go @@ -1,7 +1,7 @@ package models import ( - "net" + "net/netip" "testing" "github.com/stretchr/testify/assert" @@ -17,28 +17,28 @@ func Test_Server_Equal(t *testing.T) { }{ "same IPs": { a: &Server{ - IPs: []net.IP{net.IPv4(1, 2, 3, 4)}, + IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4})}, }, b: Server{ - IPs: []net.IP{net.IPv4(1, 2, 3, 4)}, + IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4})}, }, equal: true, }, "same IP strings": { a: &Server{ - IPs: []net.IP{net.IPv4(1, 2, 3, 4)}, + IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4})}, }, b: Server{ - IPs: []net.IP{{1, 2, 3, 4}}, + IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4})}, }, equal: true, }, "different IPs": { a: &Server{ - IPs: []net.IP{{1, 2, 3, 4}, {2, 3, 4, 5}}, + IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4}), netip.AddrFrom4([4]byte{2, 3, 4, 5})}, }, b: Server{ - IPs: []net.IP{{1, 2, 3, 4}, {1, 2, 3, 4}}, + IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4}), netip.AddrFrom4([4]byte{1, 2, 3, 4})}, }, }, "all fields equal": { @@ -61,7 +61,7 @@ func Test_Server_Equal(t *testing.T) { Free: true, Stream: true, PortForward: true, - IPs: []net.IP{net.IPv4(1, 2, 3, 4)}, + IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4})}, Keep: true, }, b: Server{ @@ -83,7 +83,7 @@ func Test_Server_Equal(t *testing.T) { Free: true, Stream: true, PortForward: true, - IPs: []net.IP{net.IPv4(1, 2, 3, 4)}, + IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4})}, Keep: true, }, equal: true, diff --git a/internal/openvpn/extract/extract.go b/internal/openvpn/extract/extract.go index 9d70a528..c3ef47bd 100644 --- a/internal/openvpn/extract/extract.go +++ b/internal/openvpn/extract/extract.go @@ -3,7 +3,7 @@ package extract import ( "errors" "fmt" - "net" + "net/netip" "strconv" "strings" @@ -25,12 +25,12 @@ func extractDataFromLines(lines []string) ( connection.UpdateEmptyWith(ip, port, protocol) - if connection.Protocol != "" && connection.IP != nil { + if connection.Protocol != "" && connection.IP.IsValid() { break } } - if connection.IP == nil { + if !connection.IP.IsValid() { return connection, errRemoteLineNotFound } @@ -49,24 +49,24 @@ func extractDataFromLines(lines []string) ( } func extractDataFromLine(line string) ( - ip net.IP, port uint16, protocol string, err error) { + ip netip.Addr, port uint16, protocol string, err error) { switch { case strings.HasPrefix(line, "proto "): protocol, err = extractProto(line) if err != nil { - return nil, 0, "", fmt.Errorf("extracting protocol from proto line: %w", err) + return ip, 0, "", fmt.Errorf("extracting protocol from proto line: %w", err) } - return nil, 0, protocol, nil + return ip, 0, protocol, nil case strings.HasPrefix(line, "remote "): ip, port, protocol, err = extractRemote(line) if err != nil { - return nil, 0, "", fmt.Errorf("extracting from remote line: %w", err) + return ip, 0, "", fmt.Errorf("extracting from remote line: %w", err) } return ip, port, protocol, nil } - return nil, 0, "", nil + return ip, 0, "", nil } var ( @@ -95,19 +95,19 @@ var ( errPortNotValid = errors.New("port is not valid") ) -func extractRemote(line string) (ip net.IP, port uint16, +func extractRemote(line string) (ip netip.Addr, port uint16, protocol string, err error) { fields := strings.Fields(line) n := len(fields) if n < 2 || n > 4 { - return nil, 0, "", fmt.Errorf("%w: %s", errRemoteLineFieldsCount, line) + return netip.Addr{}, 0, "", fmt.Errorf("%w: %s", errRemoteLineFieldsCount, line) } host := fields[1] - ip = net.ParseIP(host) - if ip == nil { - return nil, 0, "", fmt.Errorf("%w: %s", errHostNotIP, host) + ip, err = netip.ParseAddr(host) + if err != nil { + return netip.Addr{}, 0, "", fmt.Errorf("%w: %s", errHostNotIP, host) // TODO resolve hostname once there is an option to allow it through // the firewall before the VPN is up. } @@ -115,9 +115,9 @@ func extractRemote(line string) (ip net.IP, port uint16, if n > 2 { //nolint:gomnd portInt, err := strconv.Atoi(fields[2]) if err != nil { - return nil, 0, "", fmt.Errorf("%w: %s", errPortNotValid, line) + return netip.Addr{}, 0, "", fmt.Errorf("%w: %s", errPortNotValid, line) } else if portInt < 1 || portInt > 65535 { - return nil, 0, "", fmt.Errorf("%w: %d must be between 1 and 65535", errPortNotValid, portInt) + return netip.Addr{}, 0, "", fmt.Errorf("%w: %d must be between 1 and 65535", errPortNotValid, portInt) } port = uint16(portInt) } @@ -127,7 +127,7 @@ func extractRemote(line string) (ip net.IP, port uint16, case "tcp", "udp": protocol = fields[3] default: - return nil, 0, "", fmt.Errorf("%w: %s", errProtocolNotSupported, fields[3]) + return netip.Addr{}, 0, "", fmt.Errorf("%w: %s", errProtocolNotSupported, fields[3]) } } diff --git a/internal/openvpn/extract/extract_test.go b/internal/openvpn/extract/extract_test.go index 6f678b37..a38d6a6e 100644 --- a/internal/openvpn/extract/extract_test.go +++ b/internal/openvpn/extract/extract_test.go @@ -2,7 +2,7 @@ package extract import ( "errors" - "net" + "net/netip" "testing" "github.com/qdm12/gluetun/internal/constants" @@ -22,7 +22,7 @@ func Test_extractDataFromLines(t *testing.T) { "success": { lines: []string{"bla bla", "proto tcp", "remote 1.2.3.4 1194 tcp", "dev tun6"}, connection: models.Connection{ - IP: net.IPv4(1, 2, 3, 4), + IP: netip.AddrFrom4([4]byte{1, 2, 3, 4}), Port: 1194, Protocol: constants.TCP, }, @@ -34,7 +34,7 @@ func Test_extractDataFromLines(t *testing.T) { "only use first values found": { lines: []string{"proto udp", "proto tcp", "remote 1.2.3.4 443 tcp", "remote 5.2.3.4 1194 udp"}, connection: models.Connection{ - IP: net.IPv4(1, 2, 3, 4), + IP: netip.AddrFrom4([4]byte{1, 2, 3, 4}), Port: 443, Protocol: constants.UDP, }, @@ -49,7 +49,7 @@ func Test_extractDataFromLines(t *testing.T) { "default TCP port": { lines: []string{"remote 1.2.3.4", "proto tcp"}, connection: models.Connection{ - IP: net.IPv4(1, 2, 3, 4), + IP: netip.AddrFrom4([4]byte{1, 2, 3, 4}), Port: 443, Protocol: constants.TCP, }, @@ -57,7 +57,7 @@ func Test_extractDataFromLines(t *testing.T) { "default UDP port": { lines: []string{"remote 1.2.3.4", "proto udp"}, connection: models.Connection{ - IP: net.IPv4(1, 2, 3, 4), + IP: netip.AddrFrom4([4]byte{1, 2, 3, 4}), Port: 1194, Protocol: constants.UDP, }, @@ -88,7 +88,7 @@ func Test_extractDataFromLine(t *testing.T) { testCases := map[string]struct { line string - ip net.IP + ip netip.Addr port uint16 protocol string isErr error @@ -110,7 +110,7 @@ func Test_extractDataFromLine(t *testing.T) { }, "extract remote success": { line: "remote 1.2.3.4 1194 udp", - ip: net.IPv4(1, 2, 3, 4), + ip: netip.AddrFrom4([4]byte{1, 2, 3, 4}), port: 1194, protocol: constants.UDP, }, @@ -186,7 +186,7 @@ func Test_extractRemote(t *testing.T) { testCases := map[string]struct { line string - ip net.IP + ip netip.Addr port uint16 protocol string err error @@ -205,7 +205,7 @@ func Test_extractRemote(t *testing.T) { }, "only IP host": { line: "remote 1.2.3.4", - ip: net.IPv4(1, 2, 3, 4), + ip: netip.AddrFrom4([4]byte{1, 2, 3, 4}), }, "port not an integer": { line: "remote 1.2.3.4 bad", @@ -225,7 +225,7 @@ func Test_extractRemote(t *testing.T) { }, "IP host and port": { line: "remote 1.2.3.4 8000", - ip: net.IPv4(1, 2, 3, 4), + ip: netip.AddrFrom4([4]byte{1, 2, 3, 4}), port: 8000, }, "invalid protocol": { @@ -234,7 +234,7 @@ func Test_extractRemote(t *testing.T) { }, "IP host and port and protocol": { line: "remote 1.2.3.4 8000 udp", - ip: net.IPv4(1, 2, 3, 4), + ip: netip.AddrFrom4([4]byte{1, 2, 3, 4}), port: 8000, protocol: constants.UDP, }, diff --git a/internal/portforward/state/startdata.go b/internal/portforward/state/startdata.go index e7511362..dc3c779e 100644 --- a/internal/portforward/state/startdata.go +++ b/internal/portforward/state/startdata.go @@ -1,16 +1,16 @@ package state import ( - "net" + "net/netip" "github.com/qdm12/gluetun/internal/provider" ) type StartData struct { PortForwarder provider.PortForwarder - Gateway net.IP // needed for PIA - ServerName string // needed for PIA - Interface string // tun0 for example + Gateway netip.Addr // needed for PIA + ServerName string // needed for PIA + Interface string // tun0 for example } func (s *State) GetStartData() (startData StartData) { diff --git a/internal/provider/airvpn/updater/api.go b/internal/provider/airvpn/updater/api.go index 83e009ac..9639f793 100644 --- a/internal/provider/airvpn/updater/api.go +++ b/internal/provider/airvpn/updater/api.go @@ -4,8 +4,8 @@ import ( "context" "encoding/json" "fmt" - "net" "net/http" + "net/netip" "github.com/qdm12/gluetun/internal/provider/common" ) @@ -15,20 +15,20 @@ type apiData struct { } type apiServer struct { - PublicName string `json:"public_name"` - CountryName string `json:"country_name"` - CountryCode string `json:"country_code"` - Location string `json:"location"` - Continent string `json:"continent"` - IPv4In1 net.IP `json:"ip_v4_in1"` - IPv4In2 net.IP `json:"ip_v4_in2"` - IPv4In3 net.IP `json:"ip_v4_in3"` - IPv4In4 net.IP `json:"ip_v4_in4"` - IPv6In1 net.IP `json:"ip_v6_in1"` - IPv6In2 net.IP `json:"ip_v6_in2"` - IPv6In3 net.IP `json:"ip_v6_in3"` - IPv6In4 net.IP `json:"ip_v6_in4"` - Health string `json:"health"` + PublicName string `json:"public_name"` + CountryName string `json:"country_name"` + CountryCode string `json:"country_code"` + Location string `json:"location"` + Continent string `json:"continent"` + IPv4In1 netip.Addr `json:"ip_v4_in1"` + IPv4In2 netip.Addr `json:"ip_v4_in2"` + IPv4In3 netip.Addr `json:"ip_v4_in3"` + IPv4In4 netip.Addr `json:"ip_v4_in4"` + IPv6In1 netip.Addr `json:"ip_v6_in1"` + IPv6In2 netip.Addr `json:"ip_v6_in2"` + IPv6In3 netip.Addr `json:"ip_v6_in3"` + IPv6In4 netip.Addr `json:"ip_v6_in4"` + Health string `json:"health"` } func fetchAPI(ctx context.Context, client *http.Client) ( diff --git a/internal/provider/airvpn/updater/servers.go b/internal/provider/airvpn/updater/servers.go index 8a576268..ff2e2122 100644 --- a/internal/provider/airvpn/updater/servers.go +++ b/internal/provider/airvpn/updater/servers.go @@ -3,7 +3,7 @@ package updater import ( "context" "fmt" - "net" + "net/netip" "sort" "strings" @@ -57,12 +57,12 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) ( baseWireguardServer.WgPubKey = "PyLCXAQT8KkM4T+dUsOQfn+Ub3pGxfGlxkIApuig+hk=" ipv4WireguadServer := baseWireguardServer - ipv4WireguadServer.IPs = []net.IP{apiServer.IPv4In1} + ipv4WireguadServer.IPs = []netip.Addr{apiServer.IPv4In1} ipv4WireguadServer.Hostname = apiServer.CountryCode + ".vpn.airdns.org" servers = append(servers, ipv4WireguadServer) ipv6WireguadServer := baseWireguardServer - ipv6WireguadServer.IPs = []net.IP{apiServer.IPv6In1} + ipv6WireguadServer.IPs = []netip.Addr{apiServer.IPv6In1} ipv6WireguadServer.Hostname = apiServer.CountryCode + ".ipv6.vpn.airdns.org" servers = append(servers, ipv6WireguadServer) @@ -74,22 +74,22 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) ( // Ignore IPs 1 and 2 since tls-crypt is superior to tls-auth really. ipv4In3OpenVPNServer := baseOpenVPNServer - ipv4In3OpenVPNServer.IPs = []net.IP{apiServer.IPv4In3} + ipv4In3OpenVPNServer.IPs = []netip.Addr{apiServer.IPv4In3} ipv4In3OpenVPNServer.Hostname = apiServer.CountryCode + "3.vpn.airdns.org" servers = append(servers, ipv4In3OpenVPNServer) ipv6In3OpenVPNServer := baseOpenVPNServer - ipv6In3OpenVPNServer.IPs = []net.IP{apiServer.IPv6In3} + ipv6In3OpenVPNServer.IPs = []netip.Addr{apiServer.IPv6In3} ipv6In3OpenVPNServer.Hostname = apiServer.CountryCode + "3.ipv6.vpn.airdns.org" servers = append(servers, ipv6In3OpenVPNServer) ipv4In4OpenVPNServer := baseOpenVPNServer - ipv4In4OpenVPNServer.IPs = []net.IP{apiServer.IPv4In4} + ipv4In4OpenVPNServer.IPs = []netip.Addr{apiServer.IPv4In4} ipv4In4OpenVPNServer.Hostname = apiServer.CountryCode + "4.vpn.airdns.org" servers = append(servers, ipv4In4OpenVPNServer) ipv6In4OpenVPNServer := baseOpenVPNServer - ipv6In4OpenVPNServer.IPs = []net.IP{apiServer.IPv6In4} + ipv6In4OpenVPNServer.IPs = []netip.Addr{apiServer.IPv6In4} ipv6In4OpenVPNServer.Hostname = apiServer.CountryCode + "4.ipv6.vpn.airdns.org" servers = append(servers, ipv6In4OpenVPNServer) } diff --git a/internal/provider/common/mocks.go b/internal/provider/common/mocks.go index 5c4f565d..affeb7b1 100644 --- a/internal/provider/common/mocks.go +++ b/internal/provider/common/mocks.go @@ -6,7 +6,7 @@ package common import ( context "context" - net "net" + netip "net/netip" reflect "reflect" gomock "github.com/golang/mock/gomock" @@ -39,10 +39,10 @@ func (m *MockParallelResolver) EXPECT() *MockParallelResolverMockRecorder { } // Resolve mocks base method. -func (m *MockParallelResolver) Resolve(arg0 context.Context, arg1 resolver.ParallelSettings) (map[string][]net.IP, []string, error) { +func (m *MockParallelResolver) Resolve(arg0 context.Context, arg1 resolver.ParallelSettings) (map[string][]netip.Addr, []string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Resolve", arg0, arg1) - ret0, _ := ret[0].(map[string][]net.IP) + ret0, _ := ret[0].(map[string][]netip.Addr) ret1, _ := ret[1].([]string) ret2, _ := ret[2].(error) return ret0, ret1, ret2 diff --git a/internal/provider/common/mocks_generate_test.go b/internal/provider/common/mocks_generate_test.go index e94fd840..4732dd7f 100644 --- a/internal/provider/common/mocks_generate_test.go +++ b/internal/provider/common/mocks_generate_test.go @@ -2,4 +2,5 @@ package common // Exceptionally, these mocks are exported since they are used by all // provider subpackages tests, and it reduces test code duplication a lot. +// Note mocks.go might need to be removed before re-generating it. //go:generate mockgen -destination=mocks.go -package $GOPACKAGE . ParallelResolver,Storage,Unzipper,Warner diff --git a/internal/provider/common/updater.go b/internal/provider/common/updater.go index 4938edfb..10225d19 100644 --- a/internal/provider/common/updater.go +++ b/internal/provider/common/updater.go @@ -3,7 +3,7 @@ package common import ( "context" "errors" - "net" + "net/netip" "github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/publicip/ipinfo" @@ -21,7 +21,7 @@ type Fetcher interface { type ParallelResolver interface { Resolve(ctx context.Context, settings resolver.ParallelSettings) ( - hostToIPs map[string][]net.IP, warnings []string, err error) + hostToIPs map[string][]netip.Addr, warnings []string, err error) } type Unzipper interface { @@ -34,5 +34,5 @@ type Warner interface { } type IPFetcher interface { - FetchMultiInfo(ctx context.Context, ips []net.IP) (data []ipinfo.Response, err error) + FetchMultiInfo(ctx context.Context, ips []netip.Addr) (data []ipinfo.Response, err error) } diff --git a/internal/provider/custom/openvpnconf_test.go b/internal/provider/custom/openvpnconf_test.go index bf7d1303..796219e6 100644 --- a/internal/provider/custom/openvpnconf_test.go +++ b/internal/provider/custom/openvpnconf_test.go @@ -1,7 +1,7 @@ package custom import ( - "net" + "net/netip" "testing" "github.com/qdm12/gluetun/internal/configuration/settings" @@ -46,7 +46,7 @@ func Test_modifyConfig(t *testing.T) { Verbosity: intPtr(0), }.WithDefaults(providers.Custom), connection: models.Connection{ - IP: net.IPv4(1, 2, 3, 4), + IP: netip.AddrFrom4([4]byte{1, 2, 3, 4}), Port: 1194, Protocol: constants.UDP, }, diff --git a/internal/provider/cyberghost/updater/hosttoserver.go b/internal/provider/cyberghost/updater/hosttoserver.go index c35c09f8..32b6ad3f 100644 --- a/internal/provider/cyberghost/updater/hosttoserver.go +++ b/internal/provider/cyberghost/updater/hosttoserver.go @@ -1,7 +1,7 @@ package updater import ( - "net" + "net/netip" "github.com/qdm12/gluetun/internal/constants" "github.com/qdm12/gluetun/internal/constants/vpn" @@ -47,7 +47,7 @@ func (hts hostToServer) hostsSlice() (hosts []string) { return hosts } -func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]net.IP) { +func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) { for host, IPs := range hostToIPs { server := hts[host] server.IPs = IPs diff --git a/internal/provider/expressvpn/connection_test.go b/internal/provider/expressvpn/connection_test.go index ba07619b..372179e6 100644 --- a/internal/provider/expressvpn/connection_test.go +++ b/internal/provider/expressvpn/connection_test.go @@ -3,7 +3,7 @@ package expressvpn import ( "errors" "math/rand" - "net" + "net/netip" "testing" "github.com/golang/mock/gomock" @@ -41,7 +41,7 @@ func Test_Provider_GetConnection(t *testing.T) { }, "default OpenVPN TCP port": { filteredServers: []models.Server{ - {IPs: []net.IP{net.IPv4(1, 1, 1, 1)}}, + {IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}}, }, selection: settings.ServerSelection{ OpenVPN: settings.OpenVPNSelection{ @@ -52,7 +52,7 @@ func Test_Provider_GetConnection(t *testing.T) { }, "default OpenVPN UDP port": { filteredServers: []models.Server{ - {IPs: []net.IP{net.IPv4(1, 1, 1, 1)}}, + {IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}}, }, selection: settings.ServerSelection{ OpenVPN: settings.OpenVPNSelection{ @@ -61,14 +61,14 @@ func Test_Provider_GetConnection(t *testing.T) { }.WithDefaults(provider), connection: models.Connection{ Type: vpn.OpenVPN, - IP: net.IPv4(1, 1, 1, 1), + IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}), Port: 1195, Protocol: constants.UDP, }, }, "default Wireguard port": { filteredServers: []models.Server{ - {IPs: []net.IP{net.IPv4(1, 1, 1, 1)}}, + {IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}}, }, selection: settings.ServerSelection{ VPN: vpn.Wireguard, diff --git a/internal/provider/fastestvpn/updater/hosttoserver.go b/internal/provider/fastestvpn/updater/hosttoserver.go index b169294c..6deaf3bd 100644 --- a/internal/provider/fastestvpn/updater/hosttoserver.go +++ b/internal/provider/fastestvpn/updater/hosttoserver.go @@ -1,7 +1,7 @@ package updater import ( - "net" + "net/netip" "github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/models" @@ -33,7 +33,7 @@ func (hts hostToServer) toHostsSlice() (hosts []string) { return hosts } -func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]net.IP) { +func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) { for host, IPs := range hostToIPs { server := hts[host] server.IPs = IPs diff --git a/internal/provider/ipvanish/updater/hosttoserver.go b/internal/provider/ipvanish/updater/hosttoserver.go index 92f6e261..9295ef60 100644 --- a/internal/provider/ipvanish/updater/hosttoserver.go +++ b/internal/provider/ipvanish/updater/hosttoserver.go @@ -1,7 +1,7 @@ package updater import ( - "net" + "net/netip" "sort" "github.com/qdm12/gluetun/internal/constants/vpn" @@ -38,7 +38,7 @@ func (hts hostToServer) toHostsSlice() (hosts []string) { return hosts } -func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]net.IP) { +func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) { for host, IPs := range hostToIPs { server := hts[host] server.IPs = IPs diff --git a/internal/provider/ipvanish/updater/hosttoserver_test.go b/internal/provider/ipvanish/updater/hosttoserver_test.go index 8c400a15..01438397 100644 --- a/internal/provider/ipvanish/updater/hosttoserver_test.go +++ b/internal/provider/ipvanish/updater/hosttoserver_test.go @@ -1,7 +1,7 @@ package updater import ( - "net" + "net/netip" "testing" "github.com/qdm12/gluetun/internal/constants/vpn" @@ -134,17 +134,17 @@ func Test_hostToServer_adaptWithIPs(t *testing.T) { t.Parallel() testCases := map[string]struct { initialHTS hostToServer - hostToIPs map[string][]net.IP + hostToIPs map[string][]netip.Addr expectedHTS hostToServer }{ "create server": { initialHTS: hostToServer{}, - hostToIPs: map[string][]net.IP{ - "A": {{1, 2, 3, 4}}, + hostToIPs: map[string][]netip.Addr{ + "A": {netip.AddrFrom4([4]byte{1, 2, 3, 4})}, }, expectedHTS: hostToServer{ "A": models.Server{ - IPs: []net.IP{{1, 2, 3, 4}}, + IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4})}, }, }, }, @@ -154,13 +154,13 @@ func Test_hostToServer_adaptWithIPs(t *testing.T) { Country: "country", }, }, - hostToIPs: map[string][]net.IP{ - "A": {{1, 2, 3, 4}}, + hostToIPs: map[string][]netip.Addr{ + "A": {netip.AddrFrom4([4]byte{1, 2, 3, 4})}, }, expectedHTS: hostToServer{ "A": models.Server{ Country: "country", - IPs: []net.IP{{1, 2, 3, 4}}, + IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4})}, }, }, }, @@ -170,7 +170,7 @@ func Test_hostToServer_adaptWithIPs(t *testing.T) { Country: "country", }, }, - hostToIPs: map[string][]net.IP{}, + hostToIPs: map[string][]netip.Addr{}, expectedHTS: hostToServer{}, }, } diff --git a/internal/provider/ipvanish/updater/servers_test.go b/internal/provider/ipvanish/updater/servers_test.go index 7a64c4fb..a12d5dd7 100644 --- a/internal/provider/ipvanish/updater/servers_test.go +++ b/internal/provider/ipvanish/updater/servers_test.go @@ -3,7 +3,7 @@ package updater import ( "context" "errors" - "net" + "net/netip" "testing" "time" @@ -32,7 +32,7 @@ func Test_Updater_GetServers(t *testing.T) { // Resolution expectResolve bool resolverSettings resolver.ParallelSettings - hostToIPs map[string][]net.IP + hostToIPs map[string][]netip.Addr resolveWarnings []string resolveErr error @@ -161,9 +161,9 @@ func Test_Updater_GetServers(t *testing.T) { SortIPs: true, }, }, - hostToIPs: map[string][]net.IP{ - "hosta": {{1, 1, 1, 1}, {2, 2, 2, 2}}, - "hostb": {{3, 3, 3, 3}, {4, 4, 4, 4}}, + hostToIPs: map[string][]netip.Addr{ + "hosta": {netip.AddrFrom4([4]byte{1, 1, 1, 1}), netip.AddrFrom4([4]byte{2, 2, 2, 2})}, + "hostb": {netip.AddrFrom4([4]byte{3, 3, 3, 3}), netip.AddrFrom4([4]byte{4, 4, 4, 4})}, }, resolveWarnings: []string{"resolve warning"}, servers: []models.Server{ @@ -173,7 +173,7 @@ func Test_Updater_GetServers(t *testing.T) { City: "City A", Hostname: "hosta", UDP: true, - IPs: []net.IP{{1, 1, 1, 1}, {2, 2, 2, 2}}, + IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1}), netip.AddrFrom4([4]byte{2, 2, 2, 2})}, }, { VPN: vpn.OpenVPN, @@ -181,7 +181,7 @@ func Test_Updater_GetServers(t *testing.T) { City: "City B", Hostname: "hostb", UDP: true, - IPs: []net.IP{{3, 3, 3, 3}, {4, 4, 4, 4}}, + IPs: []netip.Addr{netip.AddrFrom4([4]byte{3, 3, 3, 3}), netip.AddrFrom4([4]byte{4, 4, 4, 4})}, }, }, }, diff --git a/internal/provider/ivpn/connection_test.go b/internal/provider/ivpn/connection_test.go index 7792139e..9a10ad2a 100644 --- a/internal/provider/ivpn/connection_test.go +++ b/internal/provider/ivpn/connection_test.go @@ -3,8 +3,8 @@ package ivpn import ( "errors" "math/rand" - "net" "net/http" + "net/netip" "testing" "github.com/golang/mock/gomock" @@ -41,7 +41,7 @@ func Test_Provider_GetConnection(t *testing.T) { }, "default OpenVPN TCP port": { filteredServers: []models.Server{ - {IPs: []net.IP{net.IPv4(1, 1, 1, 1)}}, + {IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}}, }, selection: settings.ServerSelection{ OpenVPN: settings.OpenVPNSelection{ @@ -50,14 +50,14 @@ func Test_Provider_GetConnection(t *testing.T) { }.WithDefaults(provider), connection: models.Connection{ Type: vpn.OpenVPN, - IP: net.IPv4(1, 1, 1, 1), + IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}), Port: 443, Protocol: constants.TCP, }, }, "default OpenVPN UDP port": { filteredServers: []models.Server{ - {IPs: []net.IP{net.IPv4(1, 1, 1, 1)}}, + {IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}}, }, selection: settings.ServerSelection{ OpenVPN: settings.OpenVPNSelection{ @@ -66,21 +66,21 @@ func Test_Provider_GetConnection(t *testing.T) { }.WithDefaults(provider), connection: models.Connection{ Type: vpn.OpenVPN, - IP: net.IPv4(1, 1, 1, 1), + IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}), Port: 1194, Protocol: constants.UDP, }, }, "default Wireguard port": { filteredServers: []models.Server{ - {IPs: []net.IP{net.IPv4(1, 1, 1, 1)}, WgPubKey: "x"}, + {IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}, WgPubKey: "x"}, }, selection: settings.ServerSelection{ VPN: vpn.Wireguard, }.WithDefaults(provider), connection: models.Connection{ Type: vpn.Wireguard, - IP: net.IPv4(1, 1, 1, 1), + IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}), Port: 58237, Protocol: constants.UDP, PubKey: "x", diff --git a/internal/provider/ivpn/updater/servers_test.go b/internal/provider/ivpn/updater/servers_test.go index b4b0c6b3..6bab0365 100644 --- a/internal/provider/ivpn/updater/servers_test.go +++ b/internal/provider/ivpn/updater/servers_test.go @@ -4,8 +4,8 @@ import ( "context" "errors" "io" - "net" "net/http" + "net/netip" "strings" "testing" "time" @@ -36,7 +36,7 @@ func Test_Updater_GetServers(t *testing.T) { // Resolution expectResolve bool resolveSettings resolver.ParallelSettings - hostToIPs map[string][]net.IP + hostToIPs map[string][]netip.Addr resolveWarnings []string resolveErr error @@ -109,24 +109,24 @@ func Test_Updater_GetServers(t *testing.T) { SortIPs: true, }, }, - hostToIPs: map[string][]net.IP{ - "hosta": {{1, 1, 1, 1}, {2, 2, 2, 2}}, - "hostb": {{3, 3, 3, 3}, {4, 4, 4, 4}}, - "hostc": {{5, 5, 5, 5}, {6, 6, 6, 6}}, + hostToIPs: map[string][]netip.Addr{ + "hosta": {netip.AddrFrom4([4]byte{1, 1, 1, 1}), netip.AddrFrom4([4]byte{2, 2, 2, 2})}, + "hostb": {netip.AddrFrom4([4]byte{3, 3, 3, 3}), netip.AddrFrom4([4]byte{4, 4, 4, 4})}, + "hostc": {netip.AddrFrom4([4]byte{5, 5, 5, 5}), netip.AddrFrom4([4]byte{6, 6, 6, 6})}, }, resolveWarnings: []string{"resolve warning"}, servers: []models.Server{ {VPN: vpn.OpenVPN, Country: "Country1", City: "City A", Hostname: "hosta", TCP: true, UDP: true, - IPs: []net.IP{{1, 1, 1, 1}, {2, 2, 2, 2}}}, + IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1}), netip.AddrFrom4([4]byte{2, 2, 2, 2})}}, {VPN: vpn.OpenVPN, Country: "Country2", City: "City B", Hostname: "hostb", TCP: true, UDP: true, - IPs: []net.IP{{3, 3, 3, 3}, {4, 4, 4, 4}}}, + IPs: []netip.Addr{netip.AddrFrom4([4]byte{3, 3, 3, 3}), netip.AddrFrom4([4]byte{4, 4, 4, 4})}}, {VPN: vpn.Wireguard, Country: "Country3", City: "City C", Hostname: "hostc", WgPubKey: "xyz", - IPs: []net.IP{{5, 5, 5, 5}, {6, 6, 6, 6}}}, + IPs: []netip.Addr{netip.AddrFrom4([4]byte{5, 5, 5, 5}), netip.AddrFrom4([4]byte{6, 6, 6, 6})}}, }, }, } diff --git a/internal/provider/mullvad/connection_test.go b/internal/provider/mullvad/connection_test.go index 53225d17..8e13f290 100644 --- a/internal/provider/mullvad/connection_test.go +++ b/internal/provider/mullvad/connection_test.go @@ -3,8 +3,8 @@ package mullvad import ( "errors" "math/rand" - "net" "net/http" + "net/netip" "testing" "github.com/golang/mock/gomock" @@ -41,7 +41,7 @@ func Test_Provider_GetConnection(t *testing.T) { }, "default OpenVPN TCP port": { filteredServers: []models.Server{ - {IPs: []net.IP{net.IPv4(1, 1, 1, 1)}}, + {IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}}, }, selection: settings.ServerSelection{ OpenVPN: settings.OpenVPNSelection{ @@ -50,14 +50,14 @@ func Test_Provider_GetConnection(t *testing.T) { }.WithDefaults(provider), connection: models.Connection{ Type: vpn.OpenVPN, - IP: net.IPv4(1, 1, 1, 1), + IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}), Port: 443, Protocol: constants.TCP, }, }, "default OpenVPN UDP port": { filteredServers: []models.Server{ - {IPs: []net.IP{net.IPv4(1, 1, 1, 1)}}, + {IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}}, }, selection: settings.ServerSelection{ OpenVPN: settings.OpenVPNSelection{ @@ -66,21 +66,21 @@ func Test_Provider_GetConnection(t *testing.T) { }.WithDefaults(provider), connection: models.Connection{ Type: vpn.OpenVPN, - IP: net.IPv4(1, 1, 1, 1), + IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}), Port: 1194, Protocol: constants.UDP, }, }, "default Wireguard port": { filteredServers: []models.Server{ - {IPs: []net.IP{net.IPv4(1, 1, 1, 1)}, WgPubKey: "x"}, + {IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}, WgPubKey: "x"}, }, selection: settings.ServerSelection{ VPN: vpn.Wireguard, }.WithDefaults(provider), connection: models.Connection{ Type: vpn.Wireguard, - IP: net.IPv4(1, 1, 1, 1), + IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}), Port: 51820, Protocol: constants.UDP, PubKey: "x", diff --git a/internal/provider/mullvad/updater/hosttoserver.go b/internal/provider/mullvad/updater/hosttoserver.go index 981cff0b..621d3192 100644 --- a/internal/provider/mullvad/updater/hosttoserver.go +++ b/internal/provider/mullvad/updater/hosttoserver.go @@ -3,7 +3,7 @@ package updater import ( "errors" "fmt" - "net" + "net/netip" "strings" "github.com/qdm12/gluetun/internal/constants/vpn" @@ -14,8 +14,8 @@ type hostToServer map[string]models.Server var ( ErrNoIP = errors.New("no IP address for VPN server") - ErrParseIPv4 = errors.New("cannot parse IPv4 address") - ErrParseIPv6 = errors.New("cannot parse IPv6 address") + ErrIPIsNotV4 = errors.New("IP address is not IPv4") + ErrIPIsNotV6 = errors.New("IP address is not IPv6") ErrVPNTypeNotSupported = errors.New("VPN type not supported") ) @@ -48,17 +48,21 @@ func (hts hostToServer) add(data serverData) (err error) { } if data.IPv4 != "" { - ipv4 := net.ParseIP(data.IPv4) - if ipv4 == nil || ipv4.To4() == nil { - return fmt.Errorf("%w: %s", ErrParseIPv4, data.IPv4) + ipv4, err := netip.ParseAddr(data.IPv4) + if err != nil { + return fmt.Errorf("parsing IPv4 address: %w", err) + } else if !ipv4.Is4() { + return fmt.Errorf("%w: %s", ErrIPIsNotV4, data.IPv4) } server.IPs = append(server.IPs, ipv4) } if data.IPv6 != "" { - ipv6 := net.ParseIP(data.IPv6) - if ipv6 == nil || ipv6.To4() != nil { - return fmt.Errorf("%w: %s", ErrParseIPv6, data.IPv6) + ipv6, err := netip.ParseAddr(data.IPv6) + if err != nil { + return fmt.Errorf("parsing IPv6 address: %w", err) + } else if !ipv6.Is6() { + return fmt.Errorf("%w: %s", ErrIPIsNotV6, data.IPv6) } server.IPs = append(server.IPs, ipv6) } diff --git a/internal/provider/mullvad/updater/ips.go b/internal/provider/mullvad/updater/ips.go index f591e17a..14fb730e 100644 --- a/internal/provider/mullvad/updater/ips.go +++ b/internal/provider/mullvad/updater/ips.go @@ -1,29 +1,31 @@ package updater import ( - "bytes" - "net" + "net/netip" "sort" ) -func uniqueSortedIPs(ips []net.IP) []net.IP { +func uniqueSortedIPs(ips []netip.Addr) []netip.Addr { uniqueIPs := make(map[string]struct{}, len(ips)) for _, ip := range ips { key := ip.String() uniqueIPs[key] = struct{}{} } - ips = make([]net.IP, 0, len(uniqueIPs)) + ips = make([]netip.Addr, 0, len(uniqueIPs)) for key := range uniqueIPs { - ip := net.ParseIP(key) - if ipv4 := ip.To4(); ipv4 != nil { - ip = ipv4 + ip, err := netip.ParseAddr(key) + if err != nil { + panic(err) + } + if ip.Is4In6() { + ip = netip.AddrFrom4(ip.As4()) } ips = append(ips, ip) } sort.Slice(ips, func(i, j int) bool { - return bytes.Compare(ips[i], ips[j]) < 0 + return ips[i].Compare(ips[j]) < 0 }) return ips diff --git a/internal/provider/mullvad/updater/ips_test.go b/internal/provider/mullvad/updater/ips_test.go index b2ea1051..ae8ec6ab 100644 --- a/internal/provider/mullvad/updater/ips_test.go +++ b/internal/provider/mullvad/updater/ips_test.go @@ -1,7 +1,7 @@ package updater import ( - "net" + "net/netip" "testing" "github.com/stretchr/testify/assert" @@ -10,24 +10,24 @@ import ( func Test_uniqueSortedIPs(t *testing.T) { t.Parallel() testCases := map[string]struct { - inputIPs []net.IP - outputIPs []net.IP + inputIPs []netip.Addr + outputIPs []netip.Addr }{ "nil": { inputIPs: nil, - outputIPs: []net.IP{}, + outputIPs: []netip.Addr{}, }, "empty": { - inputIPs: []net.IP{}, - outputIPs: []net.IP{}, + inputIPs: []netip.Addr{}, + outputIPs: []netip.Addr{}, }, "single IPv4": { - inputIPs: []net.IP{{1, 1, 1, 1}}, - outputIPs: []net.IP{{1, 1, 1, 1}}, + inputIPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}, + outputIPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}, }, "two IPv4s": { - inputIPs: []net.IP{{1, 1, 2, 1}, {1, 1, 1, 1}}, - outputIPs: []net.IP{{1, 1, 1, 1}, {1, 1, 2, 1}}, + inputIPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 2, 1}), netip.AddrFrom4([4]byte{1, 1, 1, 1})}, + outputIPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1}), netip.AddrFrom4([4]byte{1, 1, 2, 1})}, }, } for name, testCase := range testCases { diff --git a/internal/provider/nordvpn/updater/ip.go b/internal/provider/nordvpn/updater/ip.go index 76a404ae..fcde29e9 100644 --- a/internal/provider/nordvpn/updater/ip.go +++ b/internal/provider/nordvpn/updater/ip.go @@ -2,15 +2,16 @@ package updater import ( "fmt" - "net" + "net/netip" ) -func parseIPv4(s string) (ipv4 net.IP, err error) { - ip := net.ParseIP(s) - if ip == nil { - return nil, fmt.Errorf("%w: %q", ErrParseIP, s) - } else if ip.To4() == nil { - return nil, fmt.Errorf("%w: %s", ErrNotIPv4, ip) +func parseIPv4(s string) (ipv4 netip.Addr, err error) { + ipv4, err = netip.ParseAddr(s) + if err != nil { + return ipv4, err } - return ip, nil + if !ipv4.Is4() { + return ipv4, fmt.Errorf("%w: %s", ErrNotIPv4, ipv4) + } + return ipv4, nil } diff --git a/internal/provider/nordvpn/updater/servers.go b/internal/provider/nordvpn/updater/servers.go index 69b42fd6..ad5f7ddd 100644 --- a/internal/provider/nordvpn/updater/servers.go +++ b/internal/provider/nordvpn/updater/servers.go @@ -4,7 +4,7 @@ import ( "context" "errors" "fmt" - "net" + "net/netip" "sort" "github.com/qdm12/gluetun/internal/constants/vpn" @@ -13,7 +13,6 @@ import ( ) var ( - ErrParseIP = errors.New("cannot parse IP address") ErrNotIPv4 = errors.New("IP address is not IPv4") ) @@ -47,7 +46,7 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) ( Region: jsonServer.Country, Hostname: jsonServer.Domain, Number: number, - IPs: []net.IP{ip}, + IPs: []netip.Addr{ip}, TCP: jsonServer.Features.TCP, UDP: jsonServer.Features.UDP, } diff --git a/internal/provider/perfectprivacy/updater/citytoserver.go b/internal/provider/perfectprivacy/updater/citytoserver.go index 5605d55c..39b2c8f1 100644 --- a/internal/provider/perfectprivacy/updater/citytoserver.go +++ b/internal/provider/perfectprivacy/updater/citytoserver.go @@ -1,7 +1,7 @@ package updater import ( - "net" + "net/netip" "github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/models" @@ -9,7 +9,7 @@ import ( type cityToServer map[string]models.Server -func (cts cityToServer) add(city string, ips []net.IP) { +func (cts cityToServer) add(city string, ips []netip.Addr) { server, ok := cts[city] if !ok { server.VPN = vpn.OpenVPN diff --git a/internal/provider/privado/updater/hosttoserver.go b/internal/provider/privado/updater/hosttoserver.go index 9c3a87d7..ce2a38ac 100644 --- a/internal/provider/privado/updater/hosttoserver.go +++ b/internal/provider/privado/updater/hosttoserver.go @@ -1,7 +1,7 @@ package updater import ( - "net" + "net/netip" "github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/models" @@ -28,7 +28,7 @@ func (hts hostToServer) toHostsSlice() (hosts []string) { return hosts } -func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]net.IP) { +func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) { for host, IPs := range hostToIPs { server := hts[host] server.IPs = IPs diff --git a/internal/provider/privado/updater/location.go b/internal/provider/privado/updater/location.go index 2e11743b..07c25b59 100644 --- a/internal/provider/privado/updater/location.go +++ b/internal/provider/privado/updater/location.go @@ -2,7 +2,7 @@ package updater import ( "context" - "net" + "net/netip" "github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/provider/common" @@ -10,7 +10,7 @@ import ( func setLocationInfo(ctx context.Context, fetcher common.IPFetcher, servers []models.Server) (err error) { // Get public IP address information - ipsToGetInfo := make([]net.IP, 0, len(servers)) + ipsToGetInfo := make([]netip.Addr, 0, len(servers)) for _, server := range servers { ipsToGetInfo = append(ipsToGetInfo, server.IPs...) } diff --git a/internal/provider/privateinternetaccess/portforward.go b/internal/provider/privateinternetaccess/portforward.go index b6868ae9..a6cf844a 100644 --- a/internal/provider/privateinternetaccess/portforward.go +++ b/internal/provider/privateinternetaccess/portforward.go @@ -9,6 +9,7 @@ import ( "io" "net" "net/http" + "net/netip" "net/url" "os" "strconv" @@ -21,14 +22,14 @@ import ( ) var ( - ErrServerNameNotFound = errors.New("server name not found in servers") - ErrGatewayIPIsNil = errors.New("gateway IP address is nil") - ErrServerNameEmpty = errors.New("server name is empty") + ErrServerNameNotFound = errors.New("server name not found in servers") + ErrGatewayIPIsNotValid = errors.New("gateway IP address is not valid") + ErrServerNameEmpty = errors.New("server name is empty") ) // PortForward obtains a VPN server side port forwarded from PIA. func (p *Provider) PortForward(ctx context.Context, client *http.Client, - logger utils.Logger, gateway net.IP, serverName string) ( + logger utils.Logger, gateway netip.Addr, serverName string) ( port uint16, err error) { server, ok := p.storage.GetServerByName(providers.PrivateInternetAccess, serverName) if !ok { @@ -40,8 +41,8 @@ func (p *Provider) PortForward(ctx context.Context, client *http.Client, " (region " + server.Region + ") does not support port forwarding") return 0, nil } - if gateway == nil { - return 0, ErrGatewayIPIsNil + if !gateway.IsValid() { + return 0, fmt.Errorf("%w: %s", ErrGatewayIPIsNotValid, gateway) } else if serverName == "" { return 0, ErrServerNameEmpty } @@ -91,7 +92,7 @@ var ( ) func (p *Provider) KeepPortForward(ctx context.Context, - gateway net.IP, serverName string) (err error) { + gateway netip.Addr, serverName string) (err error) { privateIPClient, err := newHTTPClient(serverName) if err != nil { return fmt.Errorf("creating custom HTTP client: %w", err) @@ -132,7 +133,7 @@ func (p *Provider) KeepPortForward(ctx context.Context, } func refreshPIAPortForwardData(ctx context.Context, client, privateIPClient *http.Client, - gateway net.IP, portForwardPath, authFilePath string) (data piaPortForwardData, err error) { + gateway netip.Addr, portForwardPath, authFilePath string) (data piaPortForwardData, err error) { data.Token, err = fetchToken(ctx, client, authFilePath) if err != nil { return data, fmt.Errorf("fetching token: %w", err) @@ -314,7 +315,7 @@ func getOpenvpnCredentials(authFilePath string) ( return username, password, nil } -func fetchPortForwardData(ctx context.Context, client *http.Client, gateway net.IP, token string) ( +func fetchPortForwardData(ctx context.Context, client *http.Client, gateway netip.Addr, token string) ( port uint16, signature string, expiration time.Time, err error) { errSubstitutions := map[string]string{url.QueryEscape(token): ""} @@ -368,7 +369,7 @@ var ( ErrBadResponse = errors.New("bad response received") ) -func bindPort(ctx context.Context, client *http.Client, gateway net.IP, data piaPortForwardData) (err error) { +func bindPort(ctx context.Context, client *http.Client, gateway netip.Addr, data piaPortForwardData) (err error) { payload, err := packPayload(data.Port, data.Token, data.Expiration) if err != nil { return fmt.Errorf("serializing payload: %w", err) diff --git a/internal/provider/privateinternetaccess/updater/api.go b/internal/provider/privateinternetaccess/updater/api.go index 9048f432..fc17d2aa 100644 --- a/internal/provider/privateinternetaccess/updater/api.go +++ b/internal/provider/privateinternetaccess/updater/api.go @@ -7,8 +7,8 @@ import ( "errors" "fmt" "io" - "net" "net/http" + "net/netip" ) var ( @@ -31,8 +31,8 @@ type regionData struct { } type serverData struct { - IP net.IP `json:"ip"` - CN string `json:"cn"` + IP netip.Addr `json:"ip"` + CN string `json:"cn"` } func fetchAPI(ctx context.Context, client *http.Client) ( diff --git a/internal/provider/privateinternetaccess/updater/hosttoserver.go b/internal/provider/privateinternetaccess/updater/hosttoserver.go index c17f2a54..76cd6efc 100644 --- a/internal/provider/privateinternetaccess/updater/hosttoserver.go +++ b/internal/provider/privateinternetaccess/updater/hosttoserver.go @@ -1,7 +1,7 @@ package updater import ( - "net" + "net/netip" "github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/models" @@ -10,7 +10,7 @@ import ( type nameToServer map[string]models.Server func (nts nameToServer) add(name, hostname, region string, - tcp, udp, portForward bool, ip net.IP) (change bool) { + tcp, udp, portForward bool, ip netip.Addr) (change bool) { server, ok := nts[name] if !ok { change = true @@ -32,7 +32,7 @@ func (nts nameToServer) add(name, hostname, region string, ipFound := false for _, existingIP := range server.IPs { - if ip.Equal(existingIP) { + if ip == existingIP { ipFound = true break } diff --git a/internal/provider/privatevpn/updater/hosttoserver.go b/internal/provider/privatevpn/updater/hosttoserver.go index 77168061..bd015721 100644 --- a/internal/provider/privatevpn/updater/hosttoserver.go +++ b/internal/provider/privatevpn/updater/hosttoserver.go @@ -1,7 +1,7 @@ package updater import ( - "net" + "net/netip" "github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/models" @@ -32,7 +32,7 @@ func (hts hostToServer) toHostsSlice() (hosts []string) { return hosts } -func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]net.IP) { +func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) { for host, IPs := range hostToIPs { server := hts[host] server.IPs = IPs diff --git a/internal/provider/protonvpn/updater/api.go b/internal/provider/protonvpn/updater/api.go index 54d33e97..fc9bf97c 100644 --- a/internal/provider/protonvpn/updater/api.go +++ b/internal/provider/protonvpn/updater/api.go @@ -5,8 +5,8 @@ import ( "encoding/json" "errors" "fmt" - "net" "net/http" + "net/netip" ) var ( @@ -26,8 +26,8 @@ type logicalServer struct { } type physicalServer struct { - EntryIP net.IP - ExitIP net.IP + EntryIP netip.Addr + ExitIP netip.Addr Domain string Status uint8 } diff --git a/internal/provider/protonvpn/updater/iptoserver.go b/internal/provider/protonvpn/updater/iptoserver.go index 0b1e49a7..f95fd1f6 100644 --- a/internal/provider/protonvpn/updater/iptoserver.go +++ b/internal/provider/protonvpn/updater/iptoserver.go @@ -1,7 +1,7 @@ package updater import ( - "net" + "net/netip" "github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/models" @@ -10,7 +10,7 @@ import ( type ipToServer map[string]models.Server func (its ipToServer) add(country, region, city, name, hostname string, - free bool, entryIP net.IP) { + free bool, entryIP netip.Addr) { key := entryIP.String() server, ok := its[key] @@ -27,7 +27,7 @@ func (its ipToServer) add(country, region, city, name, hostname string, server.Free = free server.UDP = true server.TCP = true - server.IPs = []net.IP{entryIP} + server.IPs = []netip.Addr{entryIP} its[key] = server } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 02e213db..3fe70cce 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -2,8 +2,8 @@ package provider import ( "context" - "net" "net/http" + "net/netip" "github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/models" @@ -22,8 +22,8 @@ type Provider interface { type PortForwarder interface { PortForward(ctx context.Context, client *http.Client, - logger utils.Logger, gateway net.IP, serverName string) ( + logger utils.Logger, gateway netip.Addr, serverName string) ( port uint16, err error) - KeepPortForward(ctx context.Context, gateway net.IP, + KeepPortForward(ctx context.Context, gateway netip.Addr, serverName string) (err error) } diff --git a/internal/provider/purevpn/updater/hosttoserver.go b/internal/provider/purevpn/updater/hosttoserver.go index c9ab8e83..3b6af538 100644 --- a/internal/provider/purevpn/updater/hosttoserver.go +++ b/internal/provider/purevpn/updater/hosttoserver.go @@ -1,7 +1,7 @@ package updater import ( - "net" + "net/netip" "github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/models" @@ -32,7 +32,7 @@ func (hts hostToServer) toHostsSlice() (hosts []string) { return hosts } -func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]net.IP) { +func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) { for host, IPs := range hostToIPs { server := hts[host] server.IPs = IPs diff --git a/internal/provider/purevpn/updater/servers.go b/internal/provider/purevpn/updater/servers.go index df727728..8bc6e8f5 100644 --- a/internal/provider/purevpn/updater/servers.go +++ b/internal/provider/purevpn/updater/servers.go @@ -3,7 +3,7 @@ package updater import ( "context" "fmt" - "net" + "net/netip" "sort" "strings" @@ -76,7 +76,7 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) ( servers = hts.toServersSlice() // Get public IP address information - ipsToGetInfo := make([]net.IP, len(servers)) + ipsToGetInfo := make([]netip.Addr, len(servers)) for i := range servers { ipsToGetInfo[i] = servers[i].IPs[0] } diff --git a/internal/provider/surfshark/updater/hosttoserver.go b/internal/provider/surfshark/updater/hosttoserver.go index 29c685eb..6c762a67 100644 --- a/internal/provider/surfshark/updater/hosttoserver.go +++ b/internal/provider/surfshark/updater/hosttoserver.go @@ -1,7 +1,7 @@ package updater import ( - "net" + "net/netip" "github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/models" @@ -75,7 +75,7 @@ func (hts hostToServers) toHostsSlice() (hosts []string) { return hosts } -func (hts hostToServers) adaptWithIPs(hostToIPs map[string][]net.IP) { +func (hts hostToServers) adaptWithIPs(hostToIPs map[string][]netip.Addr) { for host, IPs := range hostToIPs { servers := hts[host] for i := range servers { diff --git a/internal/provider/torguard/updater/hosttoserver.go b/internal/provider/torguard/updater/hosttoserver.go index cb21f7e2..7755414a 100644 --- a/internal/provider/torguard/updater/hosttoserver.go +++ b/internal/provider/torguard/updater/hosttoserver.go @@ -1,7 +1,7 @@ package updater import ( - "net" + "net/netip" "github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/models" @@ -10,7 +10,7 @@ import ( type hostToServer map[string]models.Server func (hts hostToServer) add(host, country, city string, - tcp, udp bool, ips []net.IP) { + tcp, udp bool, ips []netip.Addr) { server, ok := hts[host] if !ok { server.VPN = vpn.OpenVPN @@ -39,7 +39,7 @@ func (hts hostToServer) toHostsSlice() (hosts []string) { return hosts } -func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]net.IP) { +func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) { for host, IPs := range hostToIPs { server := hts[host] server.IPs = IPs diff --git a/internal/provider/utils/connection.go b/internal/provider/utils/connection.go index 74670cc2..c3aeae1b 100644 --- a/internal/provider/utils/connection.go +++ b/internal/provider/utils/connection.go @@ -48,7 +48,7 @@ func GetConnection(provider string, connections := make([]models.Connection, 0, len(servers)) for _, server := range servers { for _, ip := range server.IPs { - if !ipv6Supported && ip.To4() == nil { + if !ipv6Supported && ip.Is6() { continue } diff --git a/internal/provider/utils/connection_test.go b/internal/provider/utils/connection_test.go index 9a287242..cefe17b3 100644 --- a/internal/provider/utils/connection_test.go +++ b/internal/provider/utils/connection_test.go @@ -3,7 +3,7 @@ package utils import ( "errors" "math/rand" - "net" + "net/netip" "testing" "github.com/golang/mock/gomock" @@ -58,7 +58,7 @@ func Test_GetConnection(t *testing.T) { { VPN: vpn.OpenVPN, UDP: true, - IPs: []net.IP{net.IPv4(1, 1, 1, 1)}, + IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}, Hostname: "name", }, }, @@ -68,7 +68,7 @@ func Test_GetConnection(t *testing.T) { randSource: rand.NewSource(0), connection: models.Connection{ Type: vpn.OpenVPN, - IP: net.IPv4(1, 1, 1, 1), + IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}), Protocol: constants.UDP, Port: 1194, Hostname: "name", @@ -79,7 +79,7 @@ func Test_GetConnection(t *testing.T) { { VPN: vpn.OpenVPN, UDP: true, - IPs: []net.IP{net.IPv4(1, 1, 1, 1)}, + IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}, Hostname: "hostname", OvpnX509: "x509", }, @@ -90,7 +90,7 @@ func Test_GetConnection(t *testing.T) { randSource: rand.NewSource(0), connection: models.Connection{ Type: vpn.OpenVPN, - IP: net.IPv4(1, 1, 1, 1), + IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}), Protocol: constants.UDP, Port: 1194, Hostname: "x509", @@ -101,14 +101,14 @@ func Test_GetConnection(t *testing.T) { { VPN: vpn.OpenVPN, UDP: true, - IPs: []net.IP{ - net.IPv4(1, 1, 1, 1), + IPs: []netip.Addr{ + netip.AddrFrom4([4]byte{1, 1, 1, 1}), // All IPv6 is ignored - net.IPv6zero, - net.IPv6zero, - net.IPv6zero, - net.IPv6zero, - net.IPv6zero, + netip.IPv6Unspecified(), + netip.IPv6Unspecified(), + netip.IPv6Unspecified(), + netip.IPv6Unspecified(), + netip.IPv6Unspecified(), }, }, }, @@ -118,7 +118,7 @@ func Test_GetConnection(t *testing.T) { randSource: rand.NewSource(0), connection: models.Connection{ Type: vpn.OpenVPN, - IP: net.IPv4(1, 1, 1, 1), + IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}), Protocol: constants.UDP, Port: 1194, }, @@ -128,9 +128,9 @@ func Test_GetConnection(t *testing.T) { { VPN: vpn.OpenVPN, UDP: true, - IPs: []net.IP{ - net.IPv6zero, - net.IPv4(1, 1, 1, 1), + IPs: []netip.Addr{ + netip.IPv6Unspecified(), + netip.AddrFrom4([4]byte{1, 1, 1, 1}), }, }, }, @@ -141,7 +141,7 @@ func Test_GetConnection(t *testing.T) { randSource: rand.NewSource(0), connection: models.Connection{ Type: vpn.OpenVPN, - IP: net.IPv6zero, + IP: netip.IPv6Unspecified(), Protocol: constants.UDP, Port: 1194, }, @@ -151,21 +151,21 @@ func Test_GetConnection(t *testing.T) { { VPN: vpn.OpenVPN, UDP: true, - IPs: []net.IP{net.IPv4(1, 1, 1, 1)}, + IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}, OvpnX509: "ovpnx509", }, { VPN: vpn.Wireguard, UDP: true, - IPs: []net.IP{net.IPv4(2, 2, 2, 2)}, + IPs: []netip.Addr{netip.AddrFrom4([4]byte{2, 2, 2, 2})}, OvpnX509: "ovpnx509", }, { VPN: vpn.OpenVPN, UDP: true, - IPs: []net.IP{ - net.IPv4(3, 3, 3, 3), - {1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, // ipv6 ignored + IPs: []netip.Addr{ + netip.AddrFrom4([4]byte{3, 3, 3, 3}), + netip.AddrFrom16([16]byte{1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}), // ipv6 ignored }, Hostname: "hostname", }, @@ -176,7 +176,7 @@ func Test_GetConnection(t *testing.T) { randSource: rand.NewSource(0), connection: models.Connection{ Type: vpn.OpenVPN, - IP: net.IPv4(1, 1, 1, 1), + IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}), Protocol: constants.UDP, Port: 1194, Hostname: "ovpnx509", diff --git a/internal/provider/utils/noportforward.go b/internal/provider/utils/noportforward.go index 1c204d57..d70bc21a 100644 --- a/internal/provider/utils/noportforward.go +++ b/internal/provider/utils/noportforward.go @@ -4,15 +4,15 @@ import ( "context" "errors" "fmt" - "net" "net/http" + "net/netip" ) type NoPortForwarder interface { PortForward(ctx context.Context, client *http.Client, - logger Logger, gateway net.IP, serverName string) ( + logger Logger, gateway netip.Addr, serverName string) ( port uint16, err error) - KeepPortForward(ctx context.Context, gateway net.IP, + KeepPortForward(ctx context.Context, gateway netip.Addr, serverName string) (err error) } @@ -29,10 +29,10 @@ func NewNoPortForwarding(providerName string) *NoPortForwarding { var ErrPortForwardingNotSupported = errors.New("custom port forwarding obtention is not supported") func (n *NoPortForwarding) PortForward(context.Context, *http.Client, - Logger, net.IP, string) (port uint16, err error) { + Logger, netip.Addr, string) (port uint16, err error) { return 0, fmt.Errorf("%w: for %s", ErrPortForwardingNotSupported, n.providerName) } -func (n *NoPortForwarding) KeepPortForward(context.Context, net.IP, string) (err error) { +func (n *NoPortForwarding) KeepPortForward(context.Context, netip.Addr, string) (err error) { return fmt.Errorf("%w: for %s", ErrPortForwardingNotSupported, n.providerName) } diff --git a/internal/provider/utils/pick.go b/internal/provider/utils/pick.go index 94dae872..1ce7b1f5 100644 --- a/internal/provider/utils/pick.go +++ b/internal/provider/utils/pick.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" "math/rand" - "net" + "net/netip" "github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/constants/vpn" @@ -25,13 +25,15 @@ func pickConnection(connections []models.Connection, return connection, ErrNoConnectionToPickFrom } - if len(selection.TargetIP) > 0 && selection.VPN == vpn.Wireguard { + targetIPSet := selection.TargetIP.IsValid() && !selection.TargetIP.IsUnspecified() + + if targetIPSet && selection.VPN == vpn.Wireguard { // we need the right public key return getTargetIPConnection(connections, selection.TargetIP) } connection = pickRandomConnection(connections, randSource) - if len(selection.TargetIP) > 0 { + if targetIPSet { connection.IP = selection.TargetIP } @@ -46,9 +48,9 @@ func pickRandomConnection(connections []models.Connection, var errTargetIPNotFound = errors.New("target IP address not found") func getTargetIPConnection(connections []models.Connection, - targetIP net.IP) (connection models.Connection, err error) { + targetIP netip.Addr) (connection models.Connection, err error) { for _, connection := range connections { - if targetIP.Equal(connection.IP) { + if targetIP == connection.IP { return connection, nil } } diff --git a/internal/provider/utils/wireguard.go b/internal/provider/utils/wireguard.go index d1afa948..bbe93eef 100644 --- a/internal/provider/utils/wireguard.go +++ b/internal/provider/utils/wireguard.go @@ -22,8 +22,7 @@ func BuildWireguardSettings(connection models.Connection, settings.RulePriority = rulePriority settings.Endpoint = new(net.UDPAddr) - settings.Endpoint.IP = make(net.IP, len(connection.IP)) - copy(settings.Endpoint.IP, connection.IP) + settings.Endpoint.IP = connection.IP.AsSlice() settings.Endpoint.Port = int(connection.Port) settings.Addresses = make([]netip.Prefix, 0, len(userSettings.Addresses)) diff --git a/internal/provider/utils/wireguard_test.go b/internal/provider/utils/wireguard_test.go index 6ec8026f..f28f9ae8 100644 --- a/internal/provider/utils/wireguard_test.go +++ b/internal/provider/utils/wireguard_test.go @@ -24,7 +24,7 @@ func Test_BuildWireguardSettings(t *testing.T) { }{ "some settings": { connection: models.Connection{ - IP: net.IPv4(1, 2, 3, 4), + IP: netip.AddrFrom4([4]byte{1, 2, 3, 4}), Port: 51821, PubKey: "public", }, @@ -44,7 +44,7 @@ func Test_BuildWireguardSettings(t *testing.T) { PublicKey: "public", PreSharedKey: "pre-shared", Endpoint: &net.UDPAddr{ - IP: net.IPv4(1, 2, 3, 4), + IP: net.IP{1, 2, 3, 4}, Port: 51821, }, Addresses: []netip.Prefix{ diff --git a/internal/provider/vpnsecure/updater/hosttoserver.go b/internal/provider/vpnsecure/updater/hosttoserver.go index 54a03072..605775b3 100644 --- a/internal/provider/vpnsecure/updater/hosttoserver.go +++ b/internal/provider/vpnsecure/updater/hosttoserver.go @@ -1,7 +1,7 @@ package updater import ( - "net" + "net/netip" "github.com/qdm12/gluetun/internal/models" ) @@ -16,7 +16,7 @@ func (hts hostToServer) toHostsSlice() (hosts []string) { return hosts } -func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]net.IP) { +func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) { for host, IPs := range hostToIPs { server := hts[host] server.IPs = IPs diff --git a/internal/provider/vpnunlimited/updater/hosttoserver.go b/internal/provider/vpnunlimited/updater/hosttoserver.go index 54a03072..605775b3 100644 --- a/internal/provider/vpnunlimited/updater/hosttoserver.go +++ b/internal/provider/vpnunlimited/updater/hosttoserver.go @@ -1,7 +1,7 @@ package updater import ( - "net" + "net/netip" "github.com/qdm12/gluetun/internal/models" ) @@ -16,7 +16,7 @@ func (hts hostToServer) toHostsSlice() (hosts []string) { return hosts } -func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]net.IP) { +func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) { for host, IPs := range hostToIPs { server := hts[host] server.IPs = IPs diff --git a/internal/provider/vyprvpn/updater/hosttoserver.go b/internal/provider/vyprvpn/updater/hosttoserver.go index 29912f51..df9e9369 100644 --- a/internal/provider/vyprvpn/updater/hosttoserver.go +++ b/internal/provider/vyprvpn/updater/hosttoserver.go @@ -1,7 +1,7 @@ package updater import ( - "net" + "net/netip" "github.com/qdm12/gluetun/internal/constants/vpn" "github.com/qdm12/gluetun/internal/models" @@ -33,7 +33,7 @@ func (hts hostToServer) toHostsSlice() (hosts []string) { return hosts } -func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]net.IP) { +func (hts hostToServer) adaptWithIPs(hostToIPs map[string][]netip.Addr) { for host, IPs := range hostToIPs { server := hts[host] server.IPs = IPs diff --git a/internal/provider/wevpn/connection_test.go b/internal/provider/wevpn/connection_test.go index ae260007..01184fe1 100644 --- a/internal/provider/wevpn/connection_test.go +++ b/internal/provider/wevpn/connection_test.go @@ -3,7 +3,7 @@ package wevpn import ( "errors" "math/rand" - "net" + "net/netip" "testing" "github.com/golang/mock/gomock" @@ -41,7 +41,7 @@ func Test_Provider_GetConnection(t *testing.T) { }, "default OpenVPN TCP port": { filteredServers: []models.Server{ - {IPs: []net.IP{net.IPv4(1, 1, 1, 1)}}, + {IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}}, }, selection: settings.ServerSelection{ OpenVPN: settings.OpenVPNSelection{ @@ -50,14 +50,14 @@ func Test_Provider_GetConnection(t *testing.T) { }.WithDefaults(provider), connection: models.Connection{ Type: vpn.OpenVPN, - IP: net.IPv4(1, 1, 1, 1), + IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}), Port: 1195, Protocol: constants.TCP, }, }, "default OpenVPN UDP port": { filteredServers: []models.Server{ - {IPs: []net.IP{net.IPv4(1, 1, 1, 1)}}, + {IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}}, }, selection: settings.ServerSelection{ OpenVPN: settings.OpenVPNSelection{ @@ -66,14 +66,14 @@ func Test_Provider_GetConnection(t *testing.T) { }.WithDefaults(provider), connection: models.Connection{ Type: vpn.OpenVPN, - IP: net.IPv4(1, 1, 1, 1), + IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}), Port: 1194, Protocol: constants.UDP, }, }, "default Wireguard port": { filteredServers: []models.Server{ - {IPs: []net.IP{net.IPv4(1, 1, 1, 1)}, WgPubKey: "x"}, + {IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}, WgPubKey: "x"}, }, selection: settings.ServerSelection{ VPN: vpn.Wireguard, diff --git a/internal/provider/windscribe/connection_test.go b/internal/provider/windscribe/connection_test.go index 25f1a09c..9198f432 100644 --- a/internal/provider/windscribe/connection_test.go +++ b/internal/provider/windscribe/connection_test.go @@ -3,8 +3,8 @@ package windscribe import ( "errors" "math/rand" - "net" "net/http" + "net/netip" "testing" "github.com/golang/mock/gomock" @@ -42,7 +42,7 @@ func Test_Provider_GetConnection(t *testing.T) { }, "default OpenVPN TCP port": { filteredServers: []models.Server{ - {IPs: []net.IP{net.IPv4(1, 1, 1, 1)}}, + {IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}}, }, selection: settings.ServerSelection{ OpenVPN: settings.OpenVPNSelection{ @@ -51,14 +51,14 @@ func Test_Provider_GetConnection(t *testing.T) { }.WithDefaults(provider), connection: models.Connection{ Type: vpn.OpenVPN, - IP: net.IPv4(1, 1, 1, 1), + IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}), Port: 443, Protocol: constants.TCP, }, }, "default OpenVPN UDP port": { filteredServers: []models.Server{ - {IPs: []net.IP{net.IPv4(1, 1, 1, 1)}}, + {IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}}, }, selection: settings.ServerSelection{ OpenVPN: settings.OpenVPNSelection{ @@ -67,21 +67,21 @@ func Test_Provider_GetConnection(t *testing.T) { }.WithDefaults(provider), connection: models.Connection{ Type: vpn.OpenVPN, - IP: net.IPv4(1, 1, 1, 1), + IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}), Port: 1194, Protocol: constants.UDP, }, }, "default Wireguard port": { filteredServers: []models.Server{ - {IPs: []net.IP{net.IPv4(1, 1, 1, 1)}, WgPubKey: "x"}, + {IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}, WgPubKey: "x"}, }, selection: settings.ServerSelection{ VPN: vpn.Wireguard, }.WithDefaults(provider), connection: models.Connection{ Type: vpn.Wireguard, - IP: net.IPv4(1, 1, 1, 1), + IP: netip.AddrFrom4([4]byte{1, 1, 1, 1}), Port: 1194, Protocol: constants.UDP, PubKey: "x", diff --git a/internal/provider/windscribe/updater/api.go b/internal/provider/windscribe/updater/api.go index b0920005..0a70a46f 100644 --- a/internal/provider/windscribe/updater/api.go +++ b/internal/provider/windscribe/updater/api.go @@ -5,8 +5,8 @@ import ( "encoding/json" "errors" "fmt" - "net" "net/http" + "net/netip" "strconv" "time" ) @@ -32,10 +32,10 @@ type groupData struct { } type serverData struct { - Hostname string `json:"hostname"` - IP net.IP `json:"ip"` - IP2 net.IP `json:"ip2"` - IP3 net.IP `json:"ip3"` + Hostname string `json:"hostname"` + IP netip.Addr `json:"ip"` + IP2 netip.Addr `json:"ip2"` + IP3 netip.Addr `json:"ip3"` } func fetchAPI(ctx context.Context, client *http.Client) ( diff --git a/internal/provider/windscribe/updater/servers.go b/internal/provider/windscribe/updater/servers.go index 7e1b05f3..0cd39926 100644 --- a/internal/provider/windscribe/updater/servers.go +++ b/internal/provider/windscribe/updater/servers.go @@ -4,7 +4,7 @@ import ( "context" "errors" "fmt" - "net" + "net/netip" "sort" "github.com/qdm12/gluetun/internal/constants/vpn" @@ -30,11 +30,11 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) ( x5090Name := group.OvpnX509 wgPubKey := group.WgPubKey for _, node := range group.Nodes { - ips := make([]net.IP, 0, 2) //nolint:gomnd - if node.IP != nil { + ips := make([]netip.Addr, 0, 2) //nolint:gomnd + if node.IP.IsValid() { ips = append(ips, node.IP) } - if node.IP2 != nil { + if node.IP2.IsValid() { ips = append(ips, node.IP2) } server := models.Server{ @@ -49,7 +49,7 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) ( } servers = append(servers, server) - if node.IP3 == nil { // Wireguard + Stealth + if !node.IP3.IsValid() { // Wireguard + Stealth continue } else if wgPubKey == "" { return nil, fmt.Errorf("%w: for node %s", ErrNoWireguardKey, node.Hostname) @@ -60,7 +60,7 @@ func (u *Updater) FetchServers(ctx context.Context, minServers int) ( server.TCP = false server.OvpnX509 = "" server.WgPubKey = wgPubKey - server.IPs = []net.IP{node.IP3} + server.IPs = []netip.Addr{node.IP3} servers = append(servers, server) } } diff --git a/internal/publicip/interfaces.go b/internal/publicip/interfaces.go index 490d19c2..4bf8f278 100644 --- a/internal/publicip/interfaces.go +++ b/internal/publicip/interfaces.go @@ -2,12 +2,12 @@ package publicip import ( "context" - "net" + "net/netip" "github.com/qdm12/gluetun/internal/publicip/ipinfo" ) type Fetcher interface { - FetchInfo(ctx context.Context, ip net.IP) ( + FetchInfo(ctx context.Context, ip netip.Addr) ( result ipinfo.Response, err error) } diff --git a/internal/publicip/ipinfo/fetch.go b/internal/publicip/ipinfo/fetch.go index 119c4d8e..306c7125 100644 --- a/internal/publicip/ipinfo/fetch.go +++ b/internal/publicip/ipinfo/fetch.go @@ -5,8 +5,8 @@ import ( "encoding/json" "errors" "fmt" - "net" "net/http" + "net/netip" "strings" "github.com/qdm12/gluetun/internal/constants" @@ -28,13 +28,13 @@ var ( ) // FetchInfo obtains information on the ip address provided -// using the ipinfo.io API. If the ip is nil, the public IP address +// using the ipinfo.io API. If the ip is the zero value, the public IP address // of the machine is used as the IP. -func (f *Fetch) FetchInfo(ctx context.Context, ip net.IP) ( +func (f *Fetch) FetchInfo(ctx context.Context, ip netip.Addr) ( result Response, err error) { const baseURL = "https://ipinfo.io/" url := baseURL - if ip != nil { + if ip.IsValid() { url += ip.String() } diff --git a/internal/publicip/ipinfo/model.go b/internal/publicip/ipinfo/model.go index d8384786..3c5c2080 100644 --- a/internal/publicip/ipinfo/model.go +++ b/internal/publicip/ipinfo/model.go @@ -1,26 +1,26 @@ package ipinfo import ( - "net" + "net/netip" "github.com/qdm12/gluetun/internal/models" ) type Response struct { - IP net.IP `json:"ip,omitempty"` - Region string `json:"region,omitempty"` - Country string `json:"country,omitempty"` - City string `json:"city,omitempty"` - Hostname string `json:"hostname,omitempty"` - Loc string `json:"loc,omitempty"` - Org string `json:"org,omitempty"` - Postal string `json:"postal,omitempty"` - Timezone string `json:"timezone,omitempty"` + IP netip.Addr `json:"ip,omitempty"` + Region string `json:"region,omitempty"` + Country string `json:"country,omitempty"` + City string `json:"city,omitempty"` + Hostname string `json:"hostname,omitempty"` + Loc string `json:"loc,omitempty"` + Org string `json:"org,omitempty"` + Postal string `json:"postal,omitempty"` + Timezone string `json:"timezone,omitempty"` } func (r *Response) ToPublicIPModel() (model models.PublicIP) { - model = models.PublicIP{ - IP: make(net.IP, len(r.IP)), + return models.PublicIP{ + IP: r.IP, Region: r.Region, Country: r.Country, City: r.City, @@ -30,6 +30,4 @@ func (r *Response) ToPublicIPModel() (model models.PublicIP) { PostalCode: r.Postal, Timezone: r.Timezone, } - copy(model.IP, r.IP) - return model } diff --git a/internal/publicip/ipinfo/multi.go b/internal/publicip/ipinfo/multi.go index b60e49f0..fe83708c 100644 --- a/internal/publicip/ipinfo/multi.go +++ b/internal/publicip/ipinfo/multi.go @@ -2,7 +2,7 @@ package ipinfo import ( "context" - "net" + "net/netip" ) // FetchMultiInfo obtains the public IP address information for every IP @@ -11,7 +11,7 @@ import ( // If an error is encountered, all the operations are canceled and // an error is returned, so the results returned should be considered // incomplete in this case. -func (f *Fetch) FetchMultiInfo(ctx context.Context, ips []net.IP) ( +func (f *Fetch) FetchMultiInfo(ctx context.Context, ips []netip.Addr) ( results []Response, err error) { ctx, cancel := context.WithCancel(ctx) @@ -23,7 +23,7 @@ func (f *Fetch) FetchMultiInfo(ctx context.Context, ips []net.IP) ( resultsCh := make(chan asyncResult) for i, ip := range ips { - go func(index int, ip net.IP) { + go func(index int, ip netip.Addr) { aResult := asyncResult{ index: index, } diff --git a/internal/publicip/runner.go b/internal/publicip/runner.go index 5ba1c84a..181e80e9 100644 --- a/internal/publicip/runner.go +++ b/internal/publicip/runner.go @@ -3,6 +3,7 @@ package publicip import ( "context" "errors" + "net/netip" "os" "github.com/qdm12/gluetun/internal/constants" @@ -26,7 +27,7 @@ func (l *Loop) Run(ctx context.Context, done chan<- struct{}) { resultCh := make(chan models.PublicIP) errorCh := make(chan error) go func() { - result, err := l.fetcher.FetchInfo(getCtx, nil) + result, err := l.fetcher.FetchInfo(getCtx, netip.Addr{}) if err != nil { if getCtx.Err() == nil { errorCh <- err diff --git a/internal/routing/default.go b/internal/routing/default.go index 7696da67..60e0746e 100644 --- a/internal/routing/default.go +++ b/internal/routing/default.go @@ -3,7 +3,7 @@ package routing import ( "errors" "fmt" - "net" + "net/netip" "github.com/qdm12/gluetun/internal/netlink" ) @@ -14,8 +14,8 @@ var ( type DefaultRoute struct { NetInterface string - Gateway net.IP - AssignedIP net.IP + Gateway netip.Addr + AssignedIP netip.Addr Family int } @@ -35,7 +35,7 @@ func (r *Routing) DefaultRoutes() (defaultRoutes []DefaultRoute, err error) { continue } defaultRoute := DefaultRoute{ - Gateway: route.Gw, + Gateway: netIPToNetipAddress(route.Gw), Family: route.Family, } linkIndex := route.LinkIndex diff --git a/internal/routing/inbound.go b/internal/routing/inbound.go index 3814f858..6ab1b4a2 100644 --- a/internal/routing/inbound.go +++ b/internal/routing/inbound.go @@ -62,7 +62,7 @@ func (r *Routing) unrouteInboundFromDefault(defaultRoutes []DefaultRoute) (err e func (r *Routing) addRuleInboundFromDefault(table int, defaultRoutes []DefaultRoute) (err error) { for _, defaultRoute := range defaultRoutes { - assignedIP := netIPToNetipAddress(defaultRoute.AssignedIP) + assignedIP := defaultRoute.AssignedIP bits := 32 if assignedIP.Is6() { bits = 128 @@ -82,7 +82,7 @@ func (r *Routing) addRuleInboundFromDefault(table int, defaultRoutes []DefaultRo func (r *Routing) delRuleInboundFromDefault(table int, defaultRoutes []DefaultRoute) (err error) { for _, defaultRoute := range defaultRoutes { - assignedIP := netIPToNetipAddress(defaultRoute.AssignedIP) + assignedIP := defaultRoute.AssignedIP bits := 32 if assignedIP.Is6() { bits = 128 diff --git a/internal/routing/ip.go b/internal/routing/ip.go index 4b7415ec..52232c5c 100644 --- a/internal/routing/ip.go +++ b/internal/routing/ip.go @@ -4,11 +4,12 @@ import ( "errors" "fmt" "net" + "net/netip" "github.com/qdm12/gluetun/internal/netlink" ) -func IPIsPrivate(ip net.IP) bool { +func IPIsPrivate(ip netip.Addr) bool { return ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() } @@ -17,38 +18,26 @@ var ( errInterfaceIPNotFound = errors.New("IP address not found for interface") ) -func ipMatchesFamily(ip net.IP, family int) bool { - return (family == netlink.FAMILY_V6 && ip.To4() == nil) || - (family == netlink.FAMILY_V4 && ip.To4() != nil) +func ipMatchesFamily(ip netip.Addr, family int) bool { + return (family == netlink.FAMILY_V6 && ip.Is6()) || + (family == netlink.FAMILY_V4 && (ip.Is4() || ip.Is4In6())) } -func ensureNoIPv6WrappedIPv4(candidateIP net.IP) (resultIP net.IP) { - const ipv4Size = 4 - if candidateIP.To4() == nil || len(candidateIP) == ipv4Size { // ipv6 or ipv4 - return candidateIP - } - - // ipv6-wrapped ipv4 - resultIP = make(net.IP, ipv4Size) - copy(resultIP, candidateIP[12:16]) - return resultIP -} - -func (r *Routing) assignedIP(interfaceName string, family int) (ip net.IP, err error) { +func (r *Routing) assignedIP(interfaceName string, family int) (ip netip.Addr, err error) { iface, err := net.InterfaceByName(interfaceName) if err != nil { - return nil, fmt.Errorf("network interface %s not found: %w", interfaceName, err) + return ip, fmt.Errorf("network interface %s not found: %w", interfaceName, err) } addresses, err := iface.Addrs() if err != nil { - return nil, fmt.Errorf("listing interface %s addresses: %w", interfaceName, err) + return ip, fmt.Errorf("listing interface %s addresses: %w", interfaceName, err) } for _, address := range addresses { switch value := address.(type) { case *net.IPAddr: - ip = value.IP + ip = netIPToNetipAddress(value.IP) case *net.IPNet: - ip = value.IP + ip = netIPToNetipAddress(value.IP) default: continue } @@ -60,9 +49,8 @@ func (r *Routing) assignedIP(interfaceName string, family int) (ip net.IP, err e // Ensure we don't return an IPv6-wrapped IPv4 address // since netip.Address String method works differently than // net.IP String method for this kind of addresses. - ip = ensureNoIPv6WrappedIPv4(ip) - return ip, nil + return ip.Unmap(), nil } - return nil, fmt.Errorf("%w: interface %s in %d addresses", + return ip, fmt.Errorf("%w: interface %s in %d addresses", errInterfaceIPNotFound, interfaceName, len(addresses)) } diff --git a/internal/routing/ip_test.go b/internal/routing/ip_test.go index ed2783ee..1e34d895 100644 --- a/internal/routing/ip_test.go +++ b/internal/routing/ip_test.go @@ -1,7 +1,7 @@ package routing import ( - "net" + "net/netip" "testing" "github.com/stretchr/testify/assert" @@ -87,8 +87,8 @@ func Test_IPIsPrivate(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - ip := net.ParseIP(testCase.ipString) - require.NotNil(t, ip) + ip, err := netip.ParseAddr(testCase.ipString) + require.NoError(t, err) isPrivate := IPIsPrivate(ip) @@ -96,35 +96,3 @@ func Test_IPIsPrivate(t *testing.T) { }) } } - -func Test_ensureNoIPv6WrappedIPv4(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - candidateIP net.IP - resultIP net.IP - }{ - "nil": {}, - "ipv6": { - candidateIP: net.IPv6loopback, - resultIP: net.IPv6loopback, - }, - "ipv4": { - candidateIP: net.IP{1, 2, 3, 4}, - resultIP: net.IP{1, 2, 3, 4}, - }, - "ipv6_wrapped_ipv4": { - candidateIP: net.IPv4(1, 2, 3, 4), - resultIP: net.IP{1, 2, 3, 4}, - }, - } - for name, testCase := range testCases { - testCase := testCase - t.Run(name, func(t *testing.T) { - t.Parallel() - - resultIP := ensureNoIPv6WrappedIPv4(testCase.candidateIP) - assert.Equal(t, testCase.resultIP, resultIP) - }) - } -} diff --git a/internal/routing/local.go b/internal/routing/local.go index 14072f68..59c7e2c9 100644 --- a/internal/routing/local.go +++ b/internal/routing/local.go @@ -3,7 +3,6 @@ package routing import ( "errors" "fmt" - "net" "net/netip" "github.com/qdm12/gluetun/internal/netlink" @@ -18,7 +17,7 @@ var ( type LocalNetwork struct { IPNet netip.Prefix InterfaceName string - IP net.IP + IP netip.Addr } func (r *Routing) LocalNetworks() (localNetworks []LocalNetwork, err error) { diff --git a/internal/routing/routes.go b/internal/routing/routes.go index 127ac856..c9422ba3 100644 --- a/internal/routing/routes.go +++ b/internal/routing/routes.go @@ -2,14 +2,13 @@ package routing import ( "fmt" - "net" "net/netip" "strconv" "github.com/qdm12/gluetun/internal/netlink" ) -func (r *Routing) addRouteVia(destination netip.Prefix, gateway net.IP, +func (r *Routing) addRouteVia(destination netip.Prefix, gateway netip.Addr, iface string, table int) error { destinationStr := destination.String() r.logger.Info("adding route for " + destinationStr) @@ -25,7 +24,7 @@ func (r *Routing) addRouteVia(destination netip.Prefix, gateway net.IP, route := netlink.Route{ Dst: NetipPrefixToIPNet(&destination), - Gw: gateway, + Gw: gateway.AsSlice(), LinkIndex: link.Attrs().Index, Table: table, } @@ -37,7 +36,7 @@ func (r *Routing) addRouteVia(destination netip.Prefix, gateway net.IP, return nil } -func (r *Routing) deleteRouteVia(destination netip.Prefix, gateway net.IP, +func (r *Routing) deleteRouteVia(destination netip.Prefix, gateway netip.Addr, iface string, table int) (err error) { destinationStr := destination.String() r.logger.Info("deleting route for " + destinationStr) @@ -53,7 +52,7 @@ func (r *Routing) deleteRouteVia(destination netip.Prefix, gateway net.IP, route := netlink.Route{ Dst: NetipPrefixToIPNet(&destination), - Gw: gateway, + Gw: gateway.AsSlice(), LinkIndex: link.Attrs().Index, Table: table, } diff --git a/internal/routing/vpn.go b/internal/routing/vpn.go index a32006d7..4615ed98 100644 --- a/internal/routing/vpn.go +++ b/internal/routing/vpn.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net" + "net/netip" "github.com/qdm12/gluetun/internal/netlink" ) @@ -14,10 +15,10 @@ var ( ErrVPNLocalGatewayIPNotFound = errors.New("VPN local gateway IP address not found") ) -func (r *Routing) VPNDestinationIP() (ip net.IP, err error) { +func (r *Routing) VPNDestinationIP() (ip netip.Addr, err error) { routes, err := r.netLinker.RouteList(nil, netlink.FAMILY_ALL) if err != nil { - return nil, fmt.Errorf("listing routes: %w", err) + return ip, fmt.Errorf("listing routes: %w", err) } defaultLinkIndex := -1 @@ -28,36 +29,36 @@ func (r *Routing) VPNDestinationIP() (ip net.IP, err error) { } } if defaultLinkIndex == -1 { - return nil, fmt.Errorf("%w: in %d route(s)", ErrLinkDefaultNotFound, len(routes)) + return ip, fmt.Errorf("%w: in %d route(s)", ErrLinkDefaultNotFound, len(routes)) } for _, route := range routes { if route.LinkIndex == defaultLinkIndex && route.Dst != nil && - !IPIsPrivate(route.Dst.IP) && + !IPIsPrivate(netIPToNetipAddress(route.Dst.IP)) && bytes.Equal(route.Dst.Mask, net.IPMask{255, 255, 255, 255}) { - return route.Dst.IP, nil + return netIPToNetipAddress(route.Dst.IP), nil } } - return nil, fmt.Errorf("%w: in %d routes", ErrVPNDestinationIPNotFound, len(routes)) + return ip, fmt.Errorf("%w: in %d routes", ErrVPNDestinationIPNotFound, len(routes)) } -func (r *Routing) VPNLocalGatewayIP(vpnIntf string) (ip net.IP, err error) { +func (r *Routing) VPNLocalGatewayIP(vpnIntf string) (ip netip.Addr, err error) { routes, err := r.netLinker.RouteList(nil, netlink.FAMILY_ALL) if err != nil { - return nil, fmt.Errorf("listing routes: %w", err) + return ip, fmt.Errorf("listing routes: %w", err) } for _, route := range routes { link, err := r.netLinker.LinkByIndex(route.LinkIndex) if err != nil { - return nil, fmt.Errorf("finding link at index %d: %w", route.LinkIndex, err) + return ip, fmt.Errorf("finding link at index %d: %w", route.LinkIndex, err) } interfaceName := link.Attrs().Name if interfaceName == vpnIntf && route.Dst != nil && route.Dst.IP.Equal(net.IP{0, 0, 0, 0}) { - return route.Gw, nil + return netIPToNetipAddress(route.Gw), nil } } - return nil, fmt.Errorf("%w: in %d routes", ErrVPNLocalGatewayIPNotFound, len(routes)) + return ip, fmt.Errorf("%w: in %d routes", ErrVPNLocalGatewayIPNotFound, len(routes)) } diff --git a/internal/storage/copy.go b/internal/storage/copy.go index 5f0b8ed6..113478f9 100644 --- a/internal/storage/copy.go +++ b/internal/storage/copy.go @@ -1,7 +1,7 @@ package storage import ( - "net" + "net/netip" "github.com/qdm12/gluetun/internal/models" ) @@ -12,21 +12,12 @@ func copyServer(server models.Server) (serverCopy models.Server) { return serverCopy } -func copyIPs(toCopy []net.IP) (copied []net.IP) { +func copyIPs(toCopy []netip.Addr) (copied []netip.Addr) { if toCopy == nil { return nil } - copied = make([]net.IP, len(toCopy)) - for i := range toCopy { - copied[i] = copyIP(toCopy[i]) - } - - return copied -} - -func copyIP(toCopy net.IP) (copied net.IP) { - copied = make(net.IP, len(toCopy)) + copied = make([]netip.Addr, len(toCopy)) copy(copied, toCopy) return copied } diff --git a/internal/storage/copy_test.go b/internal/storage/copy_test.go index bad83d59..0b5004a9 100644 --- a/internal/storage/copy_test.go +++ b/internal/storage/copy_test.go @@ -1,12 +1,11 @@ package storage import ( - "net" + "net/netip" "testing" "github.com/qdm12/gluetun/internal/models" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func Test_copyServer(t *testing.T) { @@ -14,14 +13,14 @@ func Test_copyServer(t *testing.T) { server := models.Server{ Country: "a", - IPs: []net.IP{{1, 2, 3, 4}}, + IPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 2, 3, 4})}, } serverCopy := copyServer(server) assert.Equal(t, server, serverCopy) // Check for mutation - serverCopy.IPs[0][0] = 9 + serverCopy.IPs[0] = netip.AddrFrom4([4]byte{9, 9, 9, 9}) assert.NotEqual(t, server, serverCopy) } @@ -29,21 +28,21 @@ func Test_copyIPs(t *testing.T) { t.Parallel() testCases := map[string]struct { - toCopy []net.IP - copied []net.IP + toCopy []netip.Addr + copied []netip.Addr }{ "nil": {}, "empty": { - toCopy: []net.IP{}, - copied: []net.IP{}, + toCopy: []netip.Addr{}, + copied: []netip.Addr{}, }, "single IP": { - toCopy: []net.IP{{1, 1, 1, 1}}, - copied: []net.IP{{1, 1, 1, 1}}, + toCopy: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}, + copied: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}, }, "two IPs": { - toCopy: []net.IP{{1, 1, 1, 1}, {2, 2, 2, 2}}, - copied: []net.IP{{1, 1, 1, 1}, {2, 2, 2, 2}}, + toCopy: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1}), netip.AddrFrom4([4]byte{2, 2, 2, 2})}, + copied: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1}), netip.AddrFrom4([4]byte{2, 2, 2, 2})}, }, } @@ -52,23 +51,13 @@ func Test_copyIPs(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - // Reserver leading 9 for copy modifications below - for _, ipToCopy := range testCase.toCopy { - require.NotEqual(t, 9, ipToCopy[0]) - } - copied := copyIPs(testCase.toCopy) assert.Equal(t, testCase.copied, copied) if len(copied) > 0 { - original := testCase.toCopy[0][0] - testCase.toCopy[0][0] = 9 - assert.NotEqual(t, 9, copied[0][0]) - testCase.toCopy[0][0] = original - - copied[0][0] = 9 - assert.NotEqual(t, 9, testCase.toCopy[0][0]) + testCase.toCopy[0] = netip.AddrFrom4([4]byte{9, 9, 9, 9}) + assert.NotEqual(t, testCase.toCopy[0], testCase.copied[0]) } }) } diff --git a/internal/updater/openvpn/extract.go b/internal/updater/openvpn/extract.go index 014bc18c..37f6b14d 100644 --- a/internal/updater/openvpn/extract.go +++ b/internal/updater/openvpn/extract.go @@ -3,7 +3,7 @@ package openvpn import ( "errors" "fmt" - "net" + "net/netip" "sort" "strings" ) @@ -54,7 +54,7 @@ func ExtractHost(b []byte) (host, warning string, err error) { return hosts[0], warning, nil } -func ExtractIPs(b []byte) (ips []net.IP, err error) { +func ExtractIPs(b []byte) (ips []netip.Addr, err error) { const rejectIP, rejectDomain = false, true ipStrings := extractRemoteHosts(b, rejectIP, rejectDomain) if len(ipStrings) == 0 { @@ -65,9 +65,12 @@ func ExtractIPs(b []byte) (ips []net.IP, err error) { return ipStrings[i] < ipStrings[j] }) - ips = make([]net.IP, len(ipStrings)) + ips = make([]netip.Addr, len(ipStrings)) for i := range ipStrings { - ips[i] = net.ParseIP(ipStrings[i]) + ips[i], err = netip.ParseAddr(ipStrings[i]) + if err != nil { + return nil, fmt.Errorf("parsing IP address: %w", err) + } } return ips, nil @@ -85,9 +88,9 @@ func extractRemoteHosts(content []byte, rejectIP, rejectDomain bool) (hosts []st continue } host := fields[1] - parsedIP := net.ParseIP(host) - if (rejectIP && parsedIP != nil) || - (rejectDomain && parsedIP == nil) { + _, err := netip.ParseAddr(host) + if (rejectIP && err == nil) || + (rejectDomain && err != nil) { continue } hosts = append(hosts, host) diff --git a/internal/updater/resolver/ips.go b/internal/updater/resolver/ips.go index 2cf5cf53..8acb5077 100644 --- a/internal/updater/resolver/ips.go +++ b/internal/updater/resolver/ips.go @@ -1,15 +1,20 @@ package resolver -import "net" +import ( + "net/netip" +) -func uniqueIPsToSlice(uniqueIPs map[string]struct{}) (ips []net.IP) { - ips = make([]net.IP, 0, len(uniqueIPs)) +func uniqueIPsToSlice(uniqueIPs map[string]struct{}) (ips []netip.Addr) { + ips = make([]netip.Addr, 0, len(uniqueIPs)) for key := range uniqueIPs { - IP := net.ParseIP(key) - if IPv4 := IP.To4(); IPv4 != nil { - IP = IPv4 + ip, err := netip.ParseAddr(key) + if err != nil { + panic(err) } - ips = append(ips, IP) + if ip.Is4In6() { + ip = netip.AddrFrom4(ip.As4()) + } + ips = append(ips, ip) } return ips } diff --git a/internal/updater/resolver/ips_test.go b/internal/updater/resolver/ips_test.go index bd34bd91..b2832af7 100644 --- a/internal/updater/resolver/ips_test.go +++ b/internal/updater/resolver/ips_test.go @@ -1,7 +1,7 @@ package resolver import ( - "net" + "net/netip" "testing" "github.com/stretchr/testify/assert" @@ -11,23 +11,23 @@ func Test_uniqueIPsToSlice(t *testing.T) { t.Parallel() testCases := map[string]struct { inputIPs map[string]struct{} - outputIPs []net.IP + outputIPs []netip.Addr }{ "nil": { inputIPs: nil, - outputIPs: []net.IP{}, + outputIPs: []netip.Addr{}, }, "empty": { inputIPs: map[string]struct{}{}, - outputIPs: []net.IP{}, + outputIPs: []netip.Addr{}, }, "single IPv4": { inputIPs: map[string]struct{}{"1.1.1.1": {}}, - outputIPs: []net.IP{{1, 1, 1, 1}}, + outputIPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1})}, }, "two IPv4s": { inputIPs: map[string]struct{}{"1.1.1.1": {}, "1.1.2.1": {}}, - outputIPs: []net.IP{{1, 1, 1, 1}, {1, 1, 2, 1}}, + outputIPs: []netip.Addr{netip.AddrFrom4([4]byte{1, 1, 1, 1}), netip.AddrFrom4([4]byte{1, 1, 2, 1})}, }, } for name, testCase := range testCases { diff --git a/internal/updater/resolver/parallel.go b/internal/updater/resolver/parallel.go index 6f3506e4..cbca1cac 100644 --- a/internal/updater/resolver/parallel.go +++ b/internal/updater/resolver/parallel.go @@ -4,7 +4,7 @@ import ( "context" "errors" "fmt" - "net" + "net/netip" ) type Parallel struct { @@ -31,7 +31,7 @@ type ParallelSettings struct { type parallelResult struct { host string - IPs []net.IP + IPs []netip.Addr } var ( @@ -40,7 +40,7 @@ var ( ) func (pr *Parallel) Resolve(ctx context.Context, settings ParallelSettings) ( - hostToIPs map[string][]net.IP, warnings []string, err error) { + hostToIPs map[string][]netip.Addr, warnings []string, err error) { ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -53,7 +53,7 @@ func (pr *Parallel) Resolve(ctx context.Context, settings ParallelSettings) ( go pr.resolveAsync(ctx, host, settings.Repeat, results, errors) } - hostToIPs = make(map[string][]net.IP, len(settings.Hosts)) + hostToIPs = make(map[string][]netip.Addr, len(settings.Hosts)) maxFails := int(settings.MaxFailRatio * float64(len(settings.Hosts))) for range settings.Hosts { diff --git a/internal/updater/resolver/repeat.go b/internal/updater/resolver/repeat.go index 94822f8b..85200eee 100644 --- a/internal/updater/resolver/repeat.go +++ b/internal/updater/resolver/repeat.go @@ -1,11 +1,11 @@ package resolver import ( - "bytes" "context" "errors" "fmt" "net" + "net/netip" "sort" "time" ) @@ -31,7 +31,7 @@ type RepeatSettings struct { } func (r *Repeat) Resolve(ctx context.Context, host string, settings RepeatSettings) ( - ips []net.IP, err error) { + ips []netip.Addr, err error) { timedCtx, cancel := context.WithTimeout(ctx, settings.MaxDuration) defer cancel() @@ -54,7 +54,7 @@ func (r *Repeat) Resolve(ctx context.Context, host string, settings RepeatSettin if settings.SortIPs { sort.Slice(ips, func(i, j int) bool { - return bytes.Compare(ips[i], ips[j]) < 1 + return ips[i].Compare(ips[j]) < 1 }) } @@ -121,15 +121,15 @@ func (r *Repeat) resolveOnce(ctx, timedCtx context.Context, host string, } } -func (r *Repeat) lookupIPs(ctx context.Context, host string) (ips []net.IP, err error) { +func (r *Repeat) lookupIPs(ctx context.Context, host string) (ips []netip.Addr, err error) { addresses, err := r.resolver.LookupIPAddr(ctx, host) if err != nil { return nil, err } - ips = make([]net.IP, 0, len(addresses)) + ips = make([]netip.Addr, 0, len(addresses)) for i := range addresses { - ip := addresses[i].IP - if ip == nil { + ip, ok := netip.AddrFromSlice(addresses[i].IP) + if !ok { continue } ips = append(ips, ip) diff --git a/internal/vpn/interfaces.go b/internal/vpn/interfaces.go index 54df30bf..26c9239a 100644 --- a/internal/vpn/interfaces.go +++ b/internal/vpn/interfaces.go @@ -2,7 +2,7 @@ package vpn import ( "context" - "net" + "net/netip" "github.com/qdm12/gluetun/internal/configuration/settings" "github.com/qdm12/gluetun/internal/models" @@ -18,7 +18,7 @@ type Firewall interface { } type Routing interface { - VPNLocalGatewayIP(vpnInterface string) (gateway net.IP, err error) + VPNLocalGatewayIP(vpnInterface string) (gateway netip.Addr, err error) } type PortForward interface {