Skip to content

Commit

Permalink
Merge pull request #676 from mirokuratczyk/discovery
Browse files Browse the repository at this point in the history
Discovery enhancements
  • Loading branch information
rod-hynes committed Apr 29, 2024
2 parents 603cd25 + a0a9302 commit a77d3ca
Show file tree
Hide file tree
Showing 31 changed files with 2,943 additions and 289 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ replace github.com/pion/dtls/v2 => github.com/mingyech/dtls/v2 v2.0.0
require (
github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e
github.com/Psiphon-Labs/bolt v0.0.0-20200624191537-23cedaef7ad7
github.com/Psiphon-Labs/consistent v0.0.0-20240322131436-20aaa4e05737
github.com/Psiphon-Labs/goptlib v0.0.0-20200406165125-c0e32a7a3464
github.com/Psiphon-Labs/psiphon-tls v0.0.0-20240424193802-52b2602ec60c
github.com/Psiphon-Labs/quic-go v0.0.0-20240424181006-45545f5e1536
github.com/armon/go-proxyproto v0.0.0-20180202201750-5b7edb60ff5f
github.com/bifurcation/mint v0.0.0-20180306135233-198357931e61
github.com/cespare/xxhash v1.1.0
github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9
github.com/cognusion/go-cache-lru v0.0.0-20170419142635-f73e2280ecea
github.com/deckarep/golang-set v0.0.0-20171013212420-1d4478f51bed
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ github.com/AndreasBriese/bbloom v0.0.0-20170702084017-28f7e881ca57/go.mod h1:bOv
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e h1:NPfqIbzmijrl0VclX2t8eO5EPBhqe47LLGKpRrcVjXk=
github.com/Psiphon-Inc/rotate-safe-writer v0.0.0-20210303140923-464a7a37606e/go.mod h1:ZdY5pBfat/WVzw3eXbIf7N1nZN0XD5H5+X8ZMDWbCs4=
github.com/Psiphon-Labs/bolt v0.0.0-20200624191537-23cedaef7ad7 h1:Hx/NCZTnvoKZuIBwSmxE58KKoNLXIGG6hBJYN7pj9Ag=
github.com/Psiphon-Labs/bolt v0.0.0-20200624191537-23cedaef7ad7/go.mod h1:alTtZBo3j4AWFvUrAH6F5ZaHcTj4G5Y01nHz8dkU6vU=
github.com/Psiphon-Labs/consistent v0.0.0-20240322131436-20aaa4e05737 h1:QTMy7Uc2Xc7fz6O/Khy1xi0VBND13GqzLUE2mHw6HUU=
github.com/Psiphon-Labs/consistent v0.0.0-20240322131436-20aaa4e05737/go.mod h1:Enj/Gszv2zCbuRbHbabmNvfO9EM+5kmaGj8CyjwNPlY=
github.com/Psiphon-Labs/goptlib v0.0.0-20200406165125-c0e32a7a3464 h1:VmnMMMheFXwLV0noxYhbJbLmkV4iaVW3xNnj6xcCNHo=
github.com/Psiphon-Labs/goptlib v0.0.0-20200406165125-c0e32a7a3464/go.mod h1:Pe5BqN2DdIdChorAXl6bDaQd/wghpCleJfid2NoSli0=
github.com/Psiphon-Labs/psiphon-tls v0.0.0-20240305020009-09f917290799 h1:dHFQz6jeIr2RdtlioyGIdJw2UfKF7G+g7GYnQxhbgrk=
Expand All @@ -29,6 +33,8 @@ github.com/armon/go-proxyproto v0.0.0-20180202201750-5b7edb60ff5f h1:SaJ6yqg936T
github.com/armon/go-proxyproto v0.0.0-20180202201750-5b7edb60ff5f/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU=
github.com/bifurcation/mint v0.0.0-20180306135233-198357931e61 h1:BU+NxuoaYPIvvp8NNkNlLr8aA0utGyuunf4Q3LJ0bh0=
github.com/bifurcation/mint v0.0.0-20180306135233-198357931e61/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9 h1:a1zrFsLFac2xoM6zG1u72DWJwZG3ayttYLfmLbxVETk=
Expand Down Expand Up @@ -201,6 +207,8 @@ github.com/sergeyfrolov/bsbuffer v0.0.0-20180903213811-94e85abb8507 h1:ML7ZNtcln
github.com/sergeyfrolov/bsbuffer v0.0.0-20180903213811-94e85abb8507/go.mod h1:DbI1gxrXI2jRGw7XGEUZQOOMd6PsnKzRrCKabvvMrwM=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
Expand Down
3 changes: 3 additions & 0 deletions psiphon/common/parameters/parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ const (
SteeringIPCacheTTL = "SteeringIPCacheTTL"
SteeringIPCacheMaxEntries = "SteeringIPCacheMaxEntries"
SteeringIPProbability = "SteeringIPProbability"
ServerDiscoveryStrategy = "ServerDiscoveryStrategy"

// Retired parameters

Expand Down Expand Up @@ -782,6 +783,8 @@ var defaultParameters = map[string]struct {
SteeringIPCacheTTL: {value: 1 * time.Hour, minimum: time.Duration(0)},
SteeringIPCacheMaxEntries: {value: 65536, minimum: 0},
SteeringIPProbability: {value: 1.0, minimum: 0.0},

ServerDiscoveryStrategy: {value: "", flags: serverSideOnly},
}

// IsServerSideOnly indicates if the parameter specified by name is used
Expand Down
20 changes: 1 addition & 19 deletions psiphon/server/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
package server

import (
"crypto/hmac"
"crypto/sha256"
"crypto/subtle"
"encoding/base64"
"encoding/json"
Expand Down Expand Up @@ -348,8 +346,7 @@ func handshakeAPIRequestHandler(
return nil, errors.TraceNew("missing client IP")
}

encodedServerList = db.DiscoverServers(
calculateDiscoveryValue(support.Config.DiscoveryValueHMACKey, clientIP))
encodedServerList = support.discovery.DiscoverServers(clientIP)
}

// When the client indicates that it used an out-of-date server entry for
Expand Down Expand Up @@ -413,21 +410,6 @@ func handshakeAPIRequestHandler(
return responsePayload, nil
}

// calculateDiscoveryValue derives a value from the client IP address to be
// used as input in the server discovery algorithm.
// See https://github.com/Psiphon-Inc/psiphon-automation/tree/master/Automation/psi_ops_discovery.py
// for full details.
func calculateDiscoveryValue(discoveryValueHMACKey string, ipAddress net.IP) int {
// From: psi_ops_discovery.calculate_ip_address_strategy_value:
// # Mix bits from all octets of the client IP address to determine the
// # bucket. An HMAC is used to prevent pre-calculation of buckets for IPs.
// return ord(hmac.new(HMAC_KEY, ip_address, hashlib.sha256).digest()[0])
// TODO: use 3-octet algorithm?
hash := hmac.New(sha256.New, []byte(discoveryValueHMACKey))
hash.Write([]byte(ipAddress.String()))
return int(hash.Sum(nil)[0])
}

// uniqueUserParams are the connected request parameters which are logged for
// unique_user events.
var uniqueUserParams = append(
Expand Down
166 changes: 166 additions & 0 deletions psiphon/server/discovery.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* Copyright (c) 2024, Psiphon Inc.
* All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package server

import (
"net"
"sync"

"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/server/discovery"
)

const (
DISCOVERY_STRATEGY_CLASSIC = "classic"
DISCOVERY_STRATEGY_CONSISTENT = "consistent"
)

// Discovery handles the discovery step of the "handshake" API request. It's
// safe for concurrent usage.
type Discovery struct {
support *SupportServices
currentStrategy string
discovery *discovery.Discovery

sync.RWMutex
}

func makeDiscovery(support *SupportServices) *Discovery {
return &Discovery{
support: support,
}
}

// Start starts discovery.
func (d *Discovery) Start() error {

err := d.reload(false)
if err != nil {
return errors.Trace(err)
}

return nil
}

// reload reinitializes the underlying discovery component. If reloadedTactics
// is set and the target discovery strategy has not changed, then the
// underlying discovery component is not reinitialized.
func (d *Discovery) reload(reloadedTactics bool) error {

// Determine which discovery strategy to use. Assumes no GeoIP targeting
// for the ServerDiscoveryStrategy tactic.

p, err := d.support.ServerTacticsParametersCache.Get(NewGeoIPData())
if err != nil {
return errors.Trace(err)
}

strategy := ""
if !p.IsNil() {
strategy = p.String(parameters.ServerDiscoveryStrategy)
}
if strategy == "" {
// No tactics are configured; default to consistent discovery.
strategy = DISCOVERY_STRATEGY_CONSISTENT
}

// Do not reinitialize underlying discovery component if only tactics have
// been reloaded and the discovery strategy has not changed.
if reloadedTactics && d.support.discovery.currentStrategy == strategy {
return nil
}

// Initialize new discovery strategy.
// TODO: do not reinitialize discovery if the discovery strategy and
// discovery servers have not changed.
var discoveryStrategy discovery.DiscoveryStrategy
if strategy == DISCOVERY_STRATEGY_CONSISTENT {
discoveryStrategy, err = discovery.NewConsistentHashingDiscovery()
if err != nil {
return errors.Trace(err)
}
} else if strategy == DISCOVERY_STRATEGY_CLASSIC {
discoveryStrategy, err = discovery.NewClassicDiscovery(
d.support.Config.DiscoveryValueHMACKey)
if err != nil {
return errors.Trace(err)
}
} else {
return errors.Tracef("unknown strategy %s", strategy)
}

// Initialize and set underlying discovery component. Replaces old
// component if discovery is already initialized.

oldDiscovery := d.discovery

discovery := discovery.MakeDiscovery(
d.support.PsinetDatabase.GetDiscoveryServers(),
discoveryStrategy)

discovery.Start()

d.Lock()

d.discovery = discovery
d.currentStrategy = strategy

d.Unlock()

// Ensure resources used by previous underlying discovery component are
// cleaned up.
// Note: a more efficient impementation would not recreate the underlying
// discovery instance if the discovery strategy has not changed, but
// instead would update the underlying set of discovery servers if the set
// of discovery servers has changed.
if oldDiscovery != nil {
oldDiscovery.Stop()
}

log.WithTraceFields(
LogFields{"discovery_strategy": strategy}).Infof("reloaded discovery")

return nil
}

// Stop stops discovery and cleans up underlying resources.
func (d *Discovery) Stop() {
d.discovery.Stop()
}

// DiscoverServers selects new encoded server entries to be "discovered" by
// the client, using the client's IP address as the input into the discovery
// algorithm.
func (d *Discovery) DiscoverServers(clientIP net.IP) []string {

d.RLock()
defer d.RUnlock()

servers := d.discovery.SelectServers(clientIP)

encodedServerEntries := make([]string, 0)

for _, server := range servers {
encodedServerEntries = append(encodedServerEntries, server.EncodedServerEntry)
}

return encodedServerEntries
}

0 comments on commit a77d3ca

Please sign in to comment.