Skip to content

Commit beb3b6e

Browse files
committed
rpn: impl exclude country codes
1 parent 17c0fc1 commit beb3b6e

3 files changed

Lines changed: 145 additions & 23 deletions

File tree

intra/backend/ipn_proxies.go

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66

77
package backend
88

9-
import "fmt"
9+
import (
10+
"fmt"
11+
"slices"
12+
"strings"
13+
)
1014

1115
const ( // see ipn/proxies.go
1216
// IDs for default proxies
@@ -90,15 +94,16 @@ type RpnOps struct {
9094
newPort uint16 // fixed WireGuard port; 0 = random per wsRandomPort()
9195
dnsConfig string // csv of DNS filter presets: "family", "security", "social", "privacy", "all", "none", "default"
9296
forceInit bool // when false, skips expensive ops unless absolutely required.
97+
excludeCCs string // csv of (sorted) country codes to exclude from selection.
9398
}
9499

95100
func NewRpnOps() *RpnOps {
96101
return &RpnOps{}
97102
}
98103

99104
func (o *RpnOps) String() string {
100-
return fmt.Sprintf("rotate: %t; perma: %t; forceFetchServers: %t; port: %d; dns: %s; forceInit: %t",
101-
o.rotateCreds, o.permaCreds, o.forceFetchServers, o.newPort, o.dnsConfig, o.forceInit)
105+
return fmt.Sprintf("rotate: %t; perma: %t; forceFetchServers: %t; port: %d; dns: %s; forceInit: %t; excludeCCs: %v",
106+
o.rotateCreds, o.permaCreds, o.forceFetchServers, o.newPort, o.dnsConfig, o.forceInit, o.excludeCCs)
102107
}
103108

104109
// SetRotateCreds forces generation of a new WireGuard keypair on the next Update.
@@ -130,6 +135,23 @@ func (o *RpnOps) SetDNSConfig(v string) {
130135
// expensive ops are skipped if called within some threshold of the previous call.
131136
func (o *RpnOps) SetForceInit(v bool) { o.forceInit = v }
132137

138+
// SetExcludeCCs sets a CSV of country codes to exclude from CC selection.
139+
// Empty or whitespace entries are ignored; codes are normalised to upper-case.
140+
func (o *RpnOps) SetExcludeCCs(v string) {
141+
parts := slices.Sorted(strings.SplitSeq(v, ","))
142+
out := make([]string, 0, len(parts))
143+
for _, p := range parts {
144+
p = strings.ToUpper(strings.TrimSpace(p))
145+
if len(p) > 0 {
146+
out = append(out, p)
147+
}
148+
}
149+
o.excludeCCs = strings.Join(out, ",")
150+
}
151+
152+
// ExcludeCCs returns the CSV of country codes excluded from CC selection.
153+
func (o RpnOps) ExcludeCCs() (csv string) { return o.excludeCCs }
154+
133155
// Rotate reports whether a new WG keypair should be generated.
134156
func (o RpnOps) Rotate() bool { return o.rotateCreds }
135157

@@ -152,12 +174,19 @@ func (o RpnOps) DNSConfig() string {
152174
// ForceInit returns false if expensive update ops may be skipped if approp.
153175
func (o RpnOps) ForceInit() bool { return o.forceInit }
154176

177+
// ExcludeCCs returns csv of (to be) sorted excluded country codes.
178+
func (o RpnOps) GetExcludeCCs() string { return o.excludeCCs }
179+
155180
// ChangesConfig reports whether this RpnOps would cause a change in wg config
156181
// if applied to override "other".
157182
func (o RpnOps) ChangesConfig(other RpnOps) bool {
158183
return o.rotateCreds != other.rotateCreds ||
159184
o.permaCreds != other.permaCreds ||
160185
o.newPort != other.newPort
186+
// TODO: asses if excludeccs would cause change in wg config (RegionalWgConfs)
187+
// a empty excludeCCs means existing ones, if any, are retained.
188+
// excludedccs is a sorted csv and so string equality should work.
189+
// len(o.excludeCCs) > 0 && o.excludeCCs != other.excludeCCs
161190
}
162191

163192
type Rpn interface {
@@ -412,8 +441,10 @@ type RpnServer struct {
412441
Link int32
413442
// Number of active servers in this CC+City.
414443
Count int32
415-
// Premium
444+
// Premium server
416445
Premium bool
446+
// Excluded by end-user
447+
Excluded bool
417448
}
418449

419450
type IPMetadata struct {

intra/ipn/rpn.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -364,21 +364,45 @@ func (r *rpnp) forkMain() error {
364364
return err
365365
}
366366

367+
// ccCsvAsSet mods a comma-separated list of country codes into a lookup set.
368+
func ccCsvAsSet(csv string) map[string]struct{} {
369+
if len(csv) <= 0 {
370+
return nil
371+
}
372+
parts := strings.Split(csv, ",")
373+
out := make(map[string]struct{}, len(parts))
374+
for _, p := range parts {
375+
p = strings.ToUpper(strings.TrimSpace(p))
376+
if len(p) > 0 {
377+
out[p] = struct{}{}
378+
}
379+
}
380+
return out
381+
}
382+
367383
func (r *rpnp) forkAll() error {
368384
provider := r.RpnAcc.ProviderID()
369385
kids := r.flattenKids()
370-
log.I("proxy: rpn: forkAll: %s[%v]", provider, kids)
371386

372387
errs := make([]error, 0) // may contain nil errors
373388

374-
e := r.forkMain()
389+
ops := r.RpnAcc.Ops()
390+
excludedSet := ccCsvAsSet(ops.ExcludeCCs())
375391

392+
log.I("proxy: rpn: forkAll: %s [%v] incl: %d / excl: %d", provider, kids, len(kids), len(excludedSet))
393+
394+
e := r.forkMain()
376395
errs = append(errs, e)
377396

378397
for _, cc := range kids {
379-
_, e := r.fork(cc)
380-
loged(e)("proxy: rpn: forkAll: forked %s[%s]; err? %v", provider, cc, e)
381-
errs = append(errs, e)
398+
if _, excluded := excludedSet[cc]; excluded {
399+
r.purge(cc) // remove excluded kid
400+
log.I("proxy: rpn: forkAll: %s[%s] excluded; purged", provider, cc)
401+
} else {
402+
_, e := r.fork(cc)
403+
loged(e)("proxy: rpn: forkAll: forked %s[%s]; err? %v", provider, cc, e)
404+
errs = append(errs, e)
405+
}
382406
}
383407
return core.JoinErr(errs...)
384408
}

intra/ipn/rpn/yegor.go

Lines changed: 81 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ var (
178178
errWsRetryUpdate = errors.New("ws: retry update")
179179
errWsNoCcConfig = errors.New("ws: not available in that location")
180180
errWsNoFilters = errors.New("ws: no filter list")
181+
errWsCCExcluded = errors.New("ws: cc excluded")
181182
)
182183

183184
/*
@@ -1054,6 +1055,7 @@ func (a *WsClient) Locations() (x.RpnServers, error) {
10541055
if len(c.Configs) <= 0 {
10551056
return nil, errWsNoCcConfig
10561057
}
1058+
excl := ccCsvAsSet(a.Ops().ExcludeCCs())
10571059
visited := make(map[string]bool, len(c.Configs))
10581060
s := make([]x.RpnServer, 0, len(c.Configs)/maxPerRegionWgConfs)
10591061
for i, rc := range c.Configs {
@@ -1070,14 +1072,16 @@ func (a *WsClient) Locations() (x.RpnServers, error) {
10701072
continue
10711073
}
10721074
if !visited[rc.Name] {
1075+
_, isExcluded := excl[rc.CC]
10731076
s = append(s, x.RpnServer{
1074-
CC: rc.CC,
1075-
City: rc.City,
1076-
Name: rc.Name,
1077-
Load: rc.Load,
1078-
Link: rc.Link,
1079-
Count: rc.Count,
1080-
Premium: rc.Premium,
1077+
CC: rc.CC,
1078+
City: rc.City,
1079+
Name: rc.Name,
1080+
Load: rc.Load,
1081+
Link: rc.Link,
1082+
Count: rc.Count,
1083+
Premium: rc.Premium,
1084+
Excluded: isExcluded,
10811085
// cc is always suffixed; see proxy.go:proxifier.postAddRpnProxy
10821086
Key: strings.Join([]string{rc.City, rc.CC}, confKeySep),
10831087
Addrs: strings.Join([]string{rc.ServerDomainPort, rc.addrCsv()}, ","),
@@ -1100,9 +1104,15 @@ func (a *WsClient) Update(ops *x.RpnOps) (newstate []byte, err error) {
11001104
curops := a.Ops()
11011105
if ops == nil {
11021106
ops = curops
1103-
} else if len(ops.DNSConfig()) <= 0 {
1104-
// retain existing dns config
1105-
ops.SetDNSConfig(curops.DNSConfig())
1107+
} else {
1108+
if len(ops.DNSConfig()) <= 0 {
1109+
// retain existing dns config
1110+
ops.SetDNSConfig(curops.DNSConfig())
1111+
}
1112+
// retain existing excludeCCs if not set by incoming ops
1113+
if len(ops.ExcludeCCs()) <= 0 {
1114+
ops.SetExcludeCCs(curops.ExcludeCCs())
1115+
}
11061116
}
11071117
start := time.Now()
11081118
b, refreshed, needsRedo, err := makeWsWgFrom(a.http, c, *ops, true /*updating*/, ops.ChangesConfig(*curops))
@@ -1159,12 +1169,28 @@ func (a *WsClient) Conf(cc string) (string, error) {
11591169
city = cccsv[0]
11601170
cc = cccsv[1]
11611171
}
1162-
visited := make(map[string]struct{}, 0)
11631172
// in sync with anyCountryCode / noCountryForOldMen vars in proxy.go
11641173
chooseAny := cc == "**" || len(cc) <= 0
11651174
hasCity := len(city) > 0
1166-
tot := 0
1167-
c := 0
1175+
cc = strings.ToUpper(cc)
1176+
1177+
excl := ccCsvAsSet(a.Ops().ExcludeCCs())
1178+
// if a specific (non-wildcard) CC is explicitly excluded, bail early
1179+
if !chooseAny && len(excl) > 0 {
1180+
if _, excluded := excl[cc]; excluded {
1181+
log.W("ws: conf: cc %s is excluded...", cc)
1182+
return "", errWsCCExcluded
1183+
}
1184+
}
1185+
1186+
retried := false
1187+
reconf:
1188+
tot := 0 // total seen
1189+
c := 0 // good cc conf
1190+
badc := 0 // bad cc conf
1191+
x := 0 // total excluded
1192+
v := 0 // total visited
1193+
visited := make(map[string]struct{}, len(cfg.Configs))
11681194
out := make([]string, 0, maxPerRegionWgConfs)
11691195
ids := make([]string, 0, maxPerRegionWgConfs)
11701196
for _, rc := range cfg.Configs {
@@ -1175,8 +1201,14 @@ func (a *WsClient) Conf(cc string) (string, error) {
11751201
continue
11761202
}
11771203
visited[rc.CC] = struct{}{}
1204+
v++
1205+
// skip CCs the user has excluded
1206+
if _, excluded := excl[rc.CC]; excluded {
1207+
x++
1208+
continue
1209+
}
11781210
if c > 2 {
1179-
// choose only low load and high link speed servers
1211+
// after a couple random servers, prefer low load and high link speed servers
11801212
gbps10 := rc.Link >= 10000
11811213
healthy50 := rc.Load <= 50
11821214
gbps1 := rc.Link >= 1000
@@ -1211,6 +1243,8 @@ func (a *WsClient) Conf(cc string) (string, error) {
12111243
out = append(out, confstr)
12121244
ids = append(ids, strings.Join([]string{rc.CC, rc.City, rc.Name}, "/"))
12131245
c++
1246+
} else {
1247+
badc++
12141248
}
12151249
}
12161250
tot++
@@ -1220,6 +1254,16 @@ func (a *WsClient) Conf(cc string) (string, error) {
12201254
log.I("ws: conf: cc %s(%s): %d/%d => chosen (any? %t): %d[%s] (port: %s)", cc, city, c, len(out), chooseAny, r, ids[r], portstr)
12211255
return out[r], nil
12221256
}
1257+
if tot == 0 || v <= x { // fail open if all CCs excluded
1258+
logew(retried)("ws: conf: cc %s(%s): all visited(%d) / excluded(%d) / bad(%d); tot: %d / excl: %d; retry?",
1259+
cc, city, v, x, badc, tot, len(excl), !retried)
1260+
if !retried {
1261+
clear(excl) // fail open; excluded none
1262+
clear(visited)
1263+
retried = true
1264+
goto reconf
1265+
}
1266+
}
12231267
log.E("ws: conf: cc %s(%s) not found (tot: %d)", cc, city, tot)
12241268
return "", errWsNoCcConfig
12251269
}
@@ -2555,6 +2599,22 @@ func createPermaCreds(h *http.Client, ent *WsEntitlement, bearer, pubkey string)
25552599
return &cfg, nil
25562600
}
25572601

2602+
// ccCsvAsSet mods a csv of country codes into a set.
2603+
func ccCsvAsSet(csv string) map[string]struct{} {
2604+
if len(csv) <= 0 {
2605+
return nil
2606+
}
2607+
parts := strings.Split(csv, ",")
2608+
out := make(map[string]struct{}, len(parts))
2609+
for _, p := range parts {
2610+
p = strings.ToUpper(strings.TrimSpace(p))
2611+
if len(p) > 0 {
2612+
out[p] = struct{}{}
2613+
}
2614+
}
2615+
return out
2616+
}
2617+
25582618
func wsBriefPauseBeforeRetry() {
25592619
time.Sleep(2200 * time.Millisecond)
25602620
}
@@ -2566,6 +2626,13 @@ func loge(err error) log.LogFn {
25662626
return log.E
25672627
}
25682628

2629+
func logew(cond bool) log.LogFn {
2630+
if cond {
2631+
return log.E
2632+
}
2633+
return log.W
2634+
}
2635+
25692636
func sha(p string) []byte {
25702637
return shab([]byte(p))
25712638
}

0 commit comments

Comments
 (0)