package models import ( "errors" "fmt" "net/netip" "reflect" "strings" "github.com/qdm12/gluetun/internal/constants/vpn" ) 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"` Categories []string `json:"categories,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"` // TODO v4 rename to openvpn_tcp UDP bool `json:"udp,omitempty"` // TODO v4 rename to openvpn_udp 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"` // TODO v4 create a SubscriptionTier struct Premium bool `json:"premium,omitempty"` Stream bool `json:"stream,omitempty"` // TODO v4 create a Features struct SecureCore bool `json:"secure_core,omitempty"` Tor bool `json:"tor,omitempty"` PortForward bool `json:"port_forward,omitempty"` Keep bool `json:"keep,omitempty"` IPs []netip.Addr `json:"ips,omitempty"` } func (s *Server) SetVPN(vpnType string) { switch s.VPN { case "": s.VPN = vpnType case vpn.Both: return case vpn.OpenVPN: if vpnType == vpn.Wireguard { s.VPN = vpn.Both } case vpn.Wireguard: if vpnType == vpn.OpenVPN { s.VPN = vpn.Both } default: panic(fmt.Sprintf("VPN type %q not supported", s.VPN)) } } var ( ErrVPNFieldEmpty = errors.New("vpn field is empty") ErrHostnameFieldEmpty = errors.New("hostname field is empty") ErrIPsFieldEmpty = errors.New("ips field is empty") ErrNoNetworkProtocol = errors.New("both TCP and UDP fields are false for OpenVPN") ErrNetworkProtocolSet = errors.New("no network protocol should be set") ErrWireguardPublicKeyEmpty = errors.New("wireguard public key field is empty") ) func (s *Server) HasMinimumInformation() (err error) { isOpenVPN := s.VPN == vpn.OpenVPN || s.VPN == vpn.Both isWireguard := s.VPN == vpn.Wireguard || s.VPN == vpn.Both switch { case s.VPN == "": return fmt.Errorf("%w", ErrVPNFieldEmpty) case len(s.IPs) == 0: return fmt.Errorf("%w", ErrIPsFieldEmpty) case isWireguard && !isOpenVPN && (s.TCP || s.UDP): return fmt.Errorf("%w", ErrNetworkProtocolSet) case isOpenVPN && !s.TCP && !s.UDP: return fmt.Errorf("%w", ErrNoNetworkProtocol) case isWireguard && s.WgPubKey == "": return fmt.Errorf("%w", ErrWireguardPublicKeyEmpty) default: return nil } } func (s *Server) Equal(other Server) (equal bool) { if !ipsAreEqual(s.IPs, other.IPs) { return false } serverCopy := *s serverCopy.IPs = nil other.IPs = nil return reflect.DeepEqual(serverCopy, other) } func ipsAreEqual(a, b []netip.Addr) (equal bool) { if len(a) != len(b) { return false } for i := range a { if a[i].Compare(b[i]) != 0 { return false } } return true } func (s *Server) Key() (key string) { var protocols []string if s.TCP { protocols = append(protocols, "tcp") } if s.UDP { protocols = append(protocols, "udp") } return fmt.Sprintf("%s-%s-%s", s.VPN, strings.Join(protocols, "-"), s.Hostname) }