Author SHA1 Message Date
sneak 368fca9955 Make the tests pass on a busy host (closes #240)
check / check (push) Successful in 3m21s
Tests that waited a few seconds for something to happen now wait 15 seconds.
The wait ends as soon as the condition holds, so a passing run takes no longer.

TestStreamNotDroppedWhileLinesArrive, the reconnect tests, the tests whose
requests pass a 2- or 4-second limit in the server, and
TestStreamDurationsCountSeconds run on a fake clock, so a stall on the host
cannot change their result. The reconnect tests also use a fake transport, so
a stall cannot reorder their requests.

The checkpoint tests give the writing connection a 30-second busy timeout, so
their limit can be 15 seconds and a checkpoint that waits for a read still
fails them.

Model: opus-5-5
2026-11-10 11:25:19 +00:00
6 changed files with 1 additions and 114 deletions

No files matched your search

-8
View File
@@ -434,14 +434,6 @@ The lists:
- `pia`: VPN servers run by Private Internet Access. From - `pia`: VPN servers run by Private Internet Access. From
[Private Internet Access](https://serverlist.piaservers.net/vpninfo/servers/v6), [Private Internet Access](https://serverlist.piaservers.net/vpninfo/servers/v6),
downloaded once a day. downloaded once a day.
- `protonvpn`: the /24 networks that hold ProtonVPN servers. The whole /24 is
listed because the ProtonVPN-IPs README says traffic leaves from unpublished
addresses in it. From the public
[ProtonVPN-IPs](https://github.com/tn3w/ProtonVPN-IPs) mirror of Proton's
server list, downloaded once a day.
- `protonvpn_entry`: the IPv4 and IPv6 addresses of ProtonVPN servers. From the
same [ProtonVPN-IPs](https://github.com/tn3w/ProtonVPN-IPs) mirror, downloaded
once a day.
- `spamhaus_drop` and `spamhaus_dropv6`: IPv4 and IPv6 networks hijacked or - `spamhaus_drop` and `spamhaus_dropv6`: IPv4 and IPv6 networks hijacked or
leased by spammers or cyber criminals, according to Spamhaus. From leased by spammers or cyber criminals, according to Spamhaus. From
[Spamhaus DROP](https://www.spamhaus.org/blocklists/do-not-route-or-peer/), [Spamhaus DROP](https://www.spamhaus.org/blocklists/do-not-route-or-peer/),
-3
View File
@@ -53,9 +53,6 @@ problem the run finds.
# Completed Steps # Completed Steps
- 2026-11-10: the IP lookup checks the `protonvpn` list of ProtonVPN's /24
networks, from a public mirror of Proton's server list (closes
https://git.eeqj.de/sneak/routewatch/issues/236).
- 2026-11-10: the tests no longer fail when a busy host stalls for several - 2026-11-10: the tests no longer fail when a busy host stalls for several
seconds. Most such waits are now 15 seconds. The test of the stream's idle seconds. Most such waits are now 15 seconds. The test of the stream's idle
timeout runs on a fake clock (closes #240) timeout runs on a fake clock (closes #240)
-5
View File
@@ -51,11 +51,6 @@ const (
// FormatIVPNJSON is IVPN's JSON server list. The hosts of its wireguard // FormatIVPNJSON is IVPN's JSON server list. The hosts of its wireguard
// and openvpn gateways have their addresses in host and v2ray. // and openvpn gateways have their addresses in host and v2ray.
FormatIVPNJSON FormatIVPNJSON
// FormatProtonVPN has one address or network per line, with comment lines
// that start with #. Unlike FormatLines, a file with no addresses or
// networks fails.
FormatProtonVPN
) )
// List is one public IP list. // List is one public IP list.
+1 -56
View File
@@ -70,19 +70,6 @@ const (
"config":{"api":{"ips":["192.0.2.73"]}}}` "config":{"api":{"ips":["192.0.2.73"]}}}`
) )
// protonVPNBody is a small file in the format of the ProtonVPN-IPs list.
const protonVPNBody = "#\n# protonvpn_ip_ranges.txt\n" +
"# https://github.com/tn3w/ProtonVPN-IPs\n#\n" +
"# The /24 block of every advertised IPv4.\n#\n" +
"192.0.2.0/24\n198.51.100.0/24\n"
// protonVPNEntryBody is a small file in the format of the ProtonVPN-IPs list
// of server addresses.
const protonVPNEntryBody = "#\n# protonvpn_entry_ips.txt\n" +
"# https://github.com/tn3w/ProtonVPN-IPs\n#\n" +
"# Entry IPs clients connect to, resolved from ProtonVPN hostnames.\n#\n" +
"192.0.2.80\n2001:db8::80\n"
// errorPage is a file that is no list, as a publisher's error page is. // errorPage is a file that is no list, as a publisher's error page is.
const errorPage = "<!DOCTYPE html>\n<html></html>\n" const errorPage = "<!DOCTYPE html>\n<html></html>\n"
@@ -280,20 +267,6 @@ func TestLookupFindsVPNServers(t *testing.T) {
listed: []string{"192.0.2.70", "192.0.2.71", "192.0.2.72"}, listed: []string{"192.0.2.70", "192.0.2.71", "192.0.2.72"},
unlisted: []string{"192.0.2.73", "2001:db8::70"}, unlisted: []string{"192.0.2.73", "2001:db8::70"},
}, },
{
name: "ProtonVPN networks",
format: FormatProtonVPN,
body: protonVPNBody,
listed: []string{"192.0.2.0", "192.0.2.255", "198.51.100.7"},
unlisted: []string{"192.0.3.1", "2001:db8::90"},
},
{
name: "ProtonVPN server addresses",
format: FormatProtonVPN,
body: protonVPNEntryBody,
listed: []string{"192.0.2.80", "2001:db8::80"},
unlisted: []string{"192.0.2.81", "2001:db8::81"},
},
}) })
} }
@@ -506,31 +479,6 @@ func TestWrongVPNDocumentFailsToParse(t *testing.T) {
} }
} }
// TestWrongProtonVPNDocumentFailsToParse parses documents that are not a
// ProtonVPN-IPs list. Each fails, so none can replace a good copy.
func TestWrongProtonVPNDocumentFailsToParse(t *testing.T) {
t.Parallel()
bodies := map[string]string{
"error page": errorPage,
"JSON": `{"status":"ok"}`,
"comments only": "#\n# protonvpn_ip_ranges.txt\n#\n",
"bad network": "192.0.2.0/24\nexample\n",
"an empty file": "",
}
for name, body := range bodies {
t.Run(name, func(t *testing.T) {
t.Parallel()
addresses, err := parse(strings.NewReader(body), FormatProtonVPN)
if err == nil {
t.Errorf("parse returned %v and no error", addresses.Ranges())
}
})
}
}
// TestLookupMarksVPNLists looks up an address on a VPN list and on another // TestLookupMarksVPNLists looks up an address on a VPN list and on another
// list. Only the VPN list's listing says it is one. // list. Only the VPN list's listing says it is one.
func TestLookupMarksVPNLists(t *testing.T) { func TestLookupMarksVPNLists(t *testing.T) {
@@ -575,10 +523,7 @@ func TestPublicListsMarkTheVPNLists(t *testing.T) {
} }
} }
want := []string{ want := []string{"ivpn", "mullvad", "nordvpn", "pia", "x4bnet", "x4bnet_v6"}
"ivpn", "mullvad", "nordvpn", "pia", "protonvpn", "protonvpn_entry",
"x4bnet", "x4bnet_v6",
}
if !slices.Equal(got, want) { if !slices.Equal(got, want) {
t.Errorf("VPN lists %v, want %v", got, want) t.Errorf("VPN lists %v, want %v", got, want)
} }
-24
View File
@@ -146,30 +146,6 @@ func vpnLists() []List {
Format: FormatPIAJSON, Format: FormatPIAJSON,
VPN: true, VPN: true,
}, },
{
// Proton's own server list needs a logged-in session, so this is
// the public ProtonVPN-IPs mirror of it. Proton publishes only
// the addresses clients connect to. The mirror's README says
// traffic leaves from others in the same /24, so this file of
// /24 networks is the one it says to use for detecting exits.
Name: "protonvpn",
Description: "In a /24 network that holds a server of the " +
"ProtonVPN service",
URL: "https://raw.githubusercontent.com/tn3w/ProtonVPN-IPs/master/" +
"protonvpn_ip_ranges.txt",
Interval: vpnListInterval,
Format: FormatProtonVPN,
VPN: true,
},
{
Name: "protonvpn_entry",
Description: "A server of the ProtonVPN service",
URL: "https://raw.githubusercontent.com/tn3w/ProtonVPN-IPs/master/" +
"protonvpn_entry_ips.txt",
Interval: vpnListInterval,
Format: FormatProtonVPN,
VPN: true,
},
{ {
Name: "x4bnet", Name: "x4bnet",
Description: "In an IPv4 network of a VPN service, according to " + Description: "In an IPv4 network of a VPN service, according to " +
-18
View File
@@ -58,8 +58,6 @@ func parse(r io.Reader, format Format) (*netipx.IPSet, error) {
return parsePIA(r) return parsePIA(r)
case FormatIVPNJSON: case FormatIVPNJSON:
return parseIVPN(r) return parseIVPN(r)
case FormatProtonVPN:
return parseProtonVPN(r)
default: default:
return nil, errUnknownFormat return nil, errUnknownFormat
} }
@@ -331,22 +329,6 @@ func parseIVPN(r io.Reader) (*netipx.IPSet, error) {
return addressSet(texts) 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. // readJSON reads r to its end and decodes it, as one JSON document, into v.
func readJSON(r io.Reader, v any) error { func readJSON(r io.Reader, v any) error {
data, err := io.ReadAll(r) data, err := io.ReadAll(r)