Files
3x-ui/internal/pia/catalog.go
T
Masterain bd6a6aba43 feat(pia): add PIA login-and-add WireGuard outbounds (#6272)
* feat(pia): add login-and-add WireGuard outbounds (#2)

* fix(pia): keep PIA outbounds identifiable after the editor strips hostname

The outbound editor drops piaHostname, so last-segment matching failed for hyphenated servers. Identify rows by the computed tag, re-encrypt stored tokens onto the active key, skip unusable catalog rows, and always release the catalog refresh latch.
2026-08-22 23:11:06 +02:00

87 lines
1.9 KiB
Go

package pia
import (
"context"
"sync"
"time"
)
type Catalog struct {
Source ServerListSource
CacheTTL time.Duration
Now func() time.Time
mu sync.Mutex
cached []Region
schema string
verified bool
fetchedAt time.Time
refreshing chan struct{}
}
func NewCatalog(source ServerListSource) *Catalog {
return &Catalog{Source: source, CacheTTL: DefaultCatalogFreshTTL, Now: time.Now}
}
func (c *Catalog) ListRegions(ctx context.Context) ([]Region, string, error) {
for {
c.mu.Lock()
age := c.Now().Sub(c.fetchedAt)
if len(c.cached) > 0 && c.verified && c.CacheTTL > 0 && age >= 0 && age < c.CacheTTL {
regions, schema := cloneRegions(c.cached), c.schema
c.mu.Unlock()
return regions, schema, nil
}
if wait := c.refreshing; wait != nil {
c.mu.Unlock()
select {
case <-ctx.Done():
return nil, "", ctx.Err()
case <-wait:
continue
}
}
done := make(chan struct{})
c.refreshing = done
c.mu.Unlock()
return c.fetchAndPublish(ctx, done)
}
}
func (c *Catalog) fetchAndPublish(ctx context.Context, done chan struct{}) ([]Region, string, error) {
defer func() {
c.mu.Lock()
c.refreshing = nil
close(done)
c.mu.Unlock()
}()
snapshot, err := c.Source.Fetch(ctx)
var regions []Region
var schema string
if err == nil && !snapshot.SignatureVerified {
err = NewError(CodeCatalogSignatureInvalid, "The PIA region list was not signature-verified.")
}
if err == nil {
regions, schema, err = ParseServerList(snapshot.Payload, snapshot.SchemaHint)
}
if err != nil {
return nil, "", err
}
c.mu.Lock()
c.cached = cloneRegions(regions)
c.schema = schema
c.verified = true
c.fetchedAt = c.Now()
c.mu.Unlock()
return cloneRegions(regions), schema, nil
}
func cloneRegions(regions []Region) []Region {
result := make([]Region, len(regions))
for i, region := range regions {
result[i] = region
result[i].WireGuard = append([]WireGuardServer(nil), region.WireGuard...)
}
return result
}