Add the ProtonVPN server networks and addresses to the IP lookup (closes #236)
check / check (push) Successful in 1m23s

Adds the VPN lists `protonvpn`, the servers' /24 networks, IPv4 only, and `protonvpn_entry`, the servers' IPv4 and IPv6 addresses. Each is downloaded once a day and shows in `flags.is_vpn` and `vpn_providers` with no other change.

Proton's `/vpn/logicals` answers 401 without a logged-in session, even with a current client's `x-pm-appversion`, so both come from the public ProtonVPN-IPs mirror. No request header is needed.

Judgement call: the mirror's /24 file as well as its exact addresses. Its README says traffic leaves from unpublished addresses in those networks. The cost is that neighbouring addresses are listed too.

Model: opus-5-5
This commit is contained in:
clawbot authored and clawbot committed 2026-11-10 13:03:40 +00:00
1 parent 0cbd9cd943
commit e2707b5e41
6 files changed
+116 -1

No files matched your search

+18
View File
@@ -58,6 +58,8 @@ func parse(r io.Reader, format Format) (*netipx.IPSet, error) {
return parsePIA(r)
case FormatIVPNJSON:
return parseIVPN(r)
case FormatProtonVPN:
return parseProtonVPN(r)
default:
return nil, errUnknownFormat
}
@@ -329,6 +331,22 @@ func parseIVPN(r io.Reader) (*netipx.IPSet, error) {
return addressSet(texts)
}
// parseProtonVPN returns the addresses or networks in a ProtonVPN-IPs list. A
// file with no addresses or networks fails, because the service always has
// servers.
func parseProtonVPN(r io.Reader) (*netipx.IPSet, error) {
addresses, err := parseLines(r, addAddressOrNetwork)
if err != nil {
return nil, err
}
if len(addresses.Ranges()) == 0 {
return nil, errNoAddresses
}
return addresses, nil
}
// readJSON reads r to its end and decodes it, as one JSON document, into v.
func readJSON(r io.Reader, v any) error {
data, err := io.ReadAll(r)