mirror of https://github.com/cbeuw/Cloak
Update server random algorithm to use proton vpn latest + add random human strategy.
This commit is contained in:
parent
c3d5470ef7
commit
6f35e356bc
|
|
@ -137,7 +137,9 @@ random-like. **You may only leave it as `plain` if you are certain that your und
|
|||
encryption and authentication (via AEAD or similar techniques).**
|
||||
|
||||
`ServerName` is the domain you want to make your ISP or firewall _think_ you are visiting. Ideally it should
|
||||
match `RedirAddr` in the server's configuration, a major site the censor allows, but it doesn't have to. Use `random` to randomize the server name for every connection made.
|
||||
match `RedirAddr` in the server's configuration, a major site the censor allows, but it doesn't have to.
|
||||
Use `random` to randomize the server name for every connection made. Use `randomTop` to randomize the server name from
|
||||
a list of top accessed domains. Use `randomHuman` to use a randomized but human-readable server name.
|
||||
|
||||
`AlternativeNames` is an array used alongside `ServerName` to shuffle between different ServerNames for every new
|
||||
connection. **This may conflict with `CDN` Transport mode** if the CDN provider prohibits domain fronting and rejects
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cbeuw/Cloak/internal/client/server_name_utils"
|
||||
"github.com/cbeuw/Cloak/internal/common"
|
||||
utls "github.com/refraction-networking/utls"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const appDataMaxLength = 16401
|
||||
|
|
@ -30,40 +33,6 @@ type DirectTLS struct {
|
|||
browser browser
|
||||
}
|
||||
|
||||
var topLevelDomains = []string{"com", "net", "org", "it", "fr", "me", "ru", "cn", "es", "tr", "top", "xyz", "info"}
|
||||
|
||||
func randomServerName() string {
|
||||
/*
|
||||
Copyright: Proton AG
|
||||
https://github.com/ProtonVPN/wireguard-go/commit/bcf344b39b213c1f32147851af0d2a8da9266883
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
charNum := int('z') - int('a') + 1
|
||||
size := 3 + common.RandInt(10)
|
||||
name := make([]byte, size)
|
||||
for i := range name {
|
||||
name[i] = byte(int('a') + common.RandInt(charNum))
|
||||
}
|
||||
return string(name) + "." + common.RandItem(topLevelDomains)
|
||||
}
|
||||
|
||||
func buildClientHello(browser browser, fields clientHelloFields) ([]byte, error) {
|
||||
// We don't use utls to handle connections (as it'll attempt a real TLS negotiation)
|
||||
// We only want it to build the ClientHello locally
|
||||
|
|
@ -79,6 +48,10 @@ func buildClientHello(browser browser, fields clientHelloFields) ([]byte, error)
|
|||
}
|
||||
|
||||
uclient := utls.UClient(&fakeConn, &utls.Config{ServerName: fields.serverName}, helloID)
|
||||
defer func(uclient *utls.UConn) {
|
||||
_ = uclient.Close()
|
||||
}(uclient)
|
||||
|
||||
if err := uclient.BuildHandshakeState(); err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
|
@ -123,8 +96,14 @@ func (tls *DirectTLS) Handshake(rawConn net.Conn, authInfo AuthInfo) (sessionKey
|
|||
serverName: authInfo.MockDomain,
|
||||
}
|
||||
|
||||
randomAddrInput := rawConn.RemoteAddr().String() + "-" + strconv.Itoa(int(authInfo.SessionId))
|
||||
|
||||
if strings.EqualFold(fields.serverName, "random") {
|
||||
fields.serverName = randomServerName()
|
||||
fields.serverName = server_name_utils.ServerNameFor(server_name_utils.ServerNameRandom, randomAddrInput)
|
||||
} else if strings.EqualFold(fields.serverName, "randomTop") {
|
||||
fields.serverName = server_name_utils.ServerNameFor(server_name_utils.ServerNameTop, randomAddrInput)
|
||||
} else if strings.EqualFold(fields.serverName, "randomHuman") {
|
||||
fields.serverName = server_name_utils.ServerNameFor(server_name_utils.ServerNameHuman, randomAddrInput)
|
||||
}
|
||||
|
||||
var ch []byte
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* Copyright (c) 2025. Proton AG
|
||||
*
|
||||
* This file is part of ProtonVPN.
|
||||
*
|
||||
* ProtonVPN 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.
|
||||
*
|
||||
* ProtonVPN 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 ProtonVPN. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package server_name_utils
|
||||
|
||||
import (
|
||||
"hash/crc32"
|
||||
"math"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Set of utilities to implement consistent hashing where map one set of strings (keys) to another (values).
|
||||
// Set of values will change over time but mapping will remain largely stable.
|
||||
// Usage:
|
||||
// hashedValues := sortValuesByHash(values, crc32Hash)
|
||||
// value := findClosestValue(key, hashedValues, crc32Hash)
|
||||
// when set of values changes (e.g. new one is added), only mappings that will change are for keys for which
|
||||
// new value is the closest one (its uint32 hash is closest to key's hash).
|
||||
|
||||
type HashedValue struct {
|
||||
value string
|
||||
hash uint32
|
||||
}
|
||||
|
||||
// Picks value out of sortedValuesWithHashes which hash is closest to hash of value. For distance
|
||||
// calculation, uint32 is forming a ring where 0 is next to math.MaxUint32. Closer of clockwise and
|
||||
// counter-clockwise distance is picked.
|
||||
func findClosestValue(key string, sortedValuesWithHashes []HashedValue, hashFun func(string) uint32) string {
|
||||
n := len(sortedValuesWithHashes)
|
||||
if n == 0 {
|
||||
return ""
|
||||
} else if n == 1 {
|
||||
return sortedValuesWithHashes[0].value
|
||||
}
|
||||
|
||||
keyHash := hashFun(key)
|
||||
i := sort.Search(n, func(i int) bool {
|
||||
return sortedValuesWithHashes[i].hash >= keyHash
|
||||
})
|
||||
|
||||
if i <= 0 || i >= n {
|
||||
// If it's smaller than first or larger than last, return closest
|
||||
// between first and last
|
||||
return closerValue(keyHash, sortedValuesWithHashes[0], sortedValuesWithHashes[n-1])
|
||||
} else {
|
||||
return closerValue(keyHash, sortedValuesWithHashes[i-1], sortedValuesWithHashes[i])
|
||||
}
|
||||
}
|
||||
|
||||
func sortValuesByHash(values []string, hashFun func(string) uint32) []HashedValue {
|
||||
hashedValues := make([]HashedValue, len(values))
|
||||
for i, domain := range values {
|
||||
hashedValues[i] = HashedValue{domain, hashFun(domain)}
|
||||
}
|
||||
sort.Slice(hashedValues, func(i, j int) bool {
|
||||
return hashedValues[i].hash < hashedValues[j].hash
|
||||
})
|
||||
return hashedValues
|
||||
}
|
||||
|
||||
func crc32Hash(s string) uint32 {
|
||||
return crc32.ChecksumIEEE([]byte(s))
|
||||
}
|
||||
|
||||
func ringDistance(a, b uint32) int64 {
|
||||
var fa = int64(a)
|
||||
var fb = int64(b)
|
||||
var large = max(fa, fb)
|
||||
var small = min(fa, fb)
|
||||
// Take smaller of clockwise and counter-clockwise distance
|
||||
return min(large-small, small-large+math.MaxUint32)
|
||||
}
|
||||
|
||||
func closerValue(hash uint32, a HashedValue, b HashedValue) string {
|
||||
var da = ringDistance(hash, a.hash)
|
||||
var db = ringDistance(hash, b.hash)
|
||||
if da < db {
|
||||
return a.value
|
||||
} else {
|
||||
return b.value
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* Copyright (c) 2025. Proton AG
|
||||
*
|
||||
* This file is part of ProtonVPN and modified for Cloak.
|
||||
*
|
||||
* ProtonVPN 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.
|
||||
*
|
||||
* ProtonVPN 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 ProtonVPN. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package server_name_utils
|
||||
|
||||
import (
|
||||
cryptoRand "crypto/rand"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
type ServerNameStrategy int
|
||||
|
||||
const (
|
||||
ServerNameRandom ServerNameStrategy = iota
|
||||
ServerNameTop
|
||||
ServerNameHuman = 2
|
||||
)
|
||||
|
||||
var topLevelDomains = []string{"com", "net", "org", "it", "fr", "me", "ru", "cn", "es", "tr", "top", "xyz", "info"}
|
||||
|
||||
var domains = []string{
|
||||
"accounts.google.com",
|
||||
"activity.windows.com",
|
||||
"analytics.apis.mcafee.com",
|
||||
"android.apis.google.com",
|
||||
"android.googleapis.com",
|
||||
"api.account.samsung.com",
|
||||
"api.accounts.firefox.com",
|
||||
"api.accuweather.com",
|
||||
"api.amazon.com",
|
||||
"api.browser.yandex.net",
|
||||
"api.ipify.org",
|
||||
"api.onedrive.com",
|
||||
"api.reasonsecurity.com",
|
||||
"api.samsungcloud.com",
|
||||
"api.sec.intl.miui.com",
|
||||
"api.vk.com",
|
||||
"api.weather.com",
|
||||
"app-site-association.cdn-apple.com",
|
||||
"apps.mzstatic.com",
|
||||
"assets.msn.com",
|
||||
"backup.googleapis.com",
|
||||
"brave-core-ext.s3.brave.com",
|
||||
"caldav.calendar.yahoo.com",
|
||||
"cc-api-data.adobe.io",
|
||||
"cdn.ampproject.org",
|
||||
"cdn.cookielaw.org",
|
||||
"client.wns.windows.com",
|
||||
"cloudflare.com",
|
||||
"cloudflare-dns.com",
|
||||
"cloudflare-ech.com",
|
||||
"config.extension.grammarly.com",
|
||||
"connectivitycheck.android.com",
|
||||
"connectivitycheck.gstatic.com",
|
||||
"courier.push.apple.com",
|
||||
"crl.globalsign.com",
|
||||
"dc1-file.ksn.kaspersky-labs.com",
|
||||
"dl.google.com",
|
||||
"dns.google",
|
||||
"dns.quad9.net",
|
||||
"doh.cleanbrowsing.org",
|
||||
"doh.dns.apple.com",
|
||||
"doh.opendns.com",
|
||||
"doh.pub",
|
||||
"ds.kaspersky.com",
|
||||
"ecs.office.com",
|
||||
"edge.microsoft.com",
|
||||
"events.gfe.nvidia.com",
|
||||
"excess.duolingo.com",
|
||||
"firefox.settings.services.mozilla.com",
|
||||
"fonts.googleapis.com",
|
||||
"fonts.gstatic.com",
|
||||
"gateway-asset.icloud-content.com",
|
||||
"gateway.icloud.com",
|
||||
"gdmf.apple.com",
|
||||
"github.com",
|
||||
"go.microsoft.com",
|
||||
"go-updater.brave.com",
|
||||
"graph.microsoft.com",
|
||||
"gs-loc.apple.com",
|
||||
"gtglobal.intl.miui.com",
|
||||
"hcaptcha.com",
|
||||
"imap.gmail.com",
|
||||
"imap-mail.outlook.com",
|
||||
"imap.mail.yahoo.com",
|
||||
"in.appcenter.ms",
|
||||
"ipmcdn.avast.com",
|
||||
"itunes.apple.com",
|
||||
"loc.map.baidu.com",
|
||||
"login.live.com",
|
||||
"login.microsoftonline.com",
|
||||
"m.media-amazon.com",
|
||||
"mobile.events.data.microsoft.com",
|
||||
"mozilla.cloudflare-dns.com",
|
||||
"mtalk.google.com",
|
||||
"nimbus.bitdefender.net",
|
||||
"ocsp2.apple.com",
|
||||
"outlook.office365.com",
|
||||
"play-fe.googleapis.com",
|
||||
"play.googleapis.com",
|
||||
"play.samsungcloud.com",
|
||||
"raw.githubusercontent.com",
|
||||
"s3.amazonaws.com",
|
||||
"safebrowsing.googleapis.com",
|
||||
"s.alicdn.com",
|
||||
"self.events.data.microsoft.com",
|
||||
"settings-win.data.microsoft.com",
|
||||
"setup.icloud.com",
|
||||
"sirius.mwbsys.com",
|
||||
"spoc.norton.com",
|
||||
"ssl.gstatic.com",
|
||||
"translate.goo",
|
||||
"unpkg.com",
|
||||
"update.googleapis.com",
|
||||
"weatherapi.intl.xiaomi.com",
|
||||
"weatherkit.apple.com",
|
||||
"westus-0.in.applicationinsights.azure.com",
|
||||
"www.googleapis.com",
|
||||
"www.gstatic.com",
|
||||
"www.msftconnecttest.com",
|
||||
"www.msftncsi.com",
|
||||
"www.ntppool.org",
|
||||
"www.pool.ntp.org",
|
||||
"www.recaptcha.net",
|
||||
}
|
||||
|
||||
// Data pools for linguistic generation
|
||||
var prefixes = []string{
|
||||
"cloud",
|
||||
"global", "fast", "secure", "smart", "net", "data", "prime", "alpha", "edge"}
|
||||
var suffixes = []string{"logic", "stream", "flow", "point", "nexus", "bridge", "lab", "hub", "tech", "base"}
|
||||
var syllables = []string{"ver", "ant", "ix", "cor", "mon", "tel", "al", "is", "ex", "ta", "vi", "ro"}
|
||||
var apiRoots = []string{"assets-delivery", "static-cache", "api-gateway", "edge-compute", "cdn-services"}
|
||||
|
||||
var domainsSortedByHashes = sortValuesByHash(domains, crc32Hash)
|
||||
|
||||
func ServerNameFor(strategy ServerNameStrategy, addr string) string {
|
||||
switch strategy {
|
||||
case ServerNameTop:
|
||||
return serverNameForAddr(addr)
|
||||
case ServerNameRandom:
|
||||
return randomServerName()
|
||||
case ServerNameHuman:
|
||||
return randomHumanReadableServerName()
|
||||
default:
|
||||
return randomServerName()
|
||||
}
|
||||
}
|
||||
|
||||
func serverNameForAddr(addr string) string {
|
||||
return findClosestValue(addr, domainsSortedByHashes, crc32Hash)
|
||||
}
|
||||
|
||||
func randomServerName() string {
|
||||
charNum := int('z') - int('a') + 1
|
||||
size := 3 + randInt(10)
|
||||
name := make([]byte, size)
|
||||
for i := range name {
|
||||
name[i] = byte(int('a') + randInt(charNum))
|
||||
}
|
||||
return string(name) + "." + randItem(topLevelDomains)
|
||||
}
|
||||
|
||||
func randItem(list []string) string {
|
||||
return list[randInt(len(list))]
|
||||
}
|
||||
|
||||
func randInt(n int) int {
|
||||
size, err := cryptoRand.Int(cryptoRand.Reader, big.NewInt(int64(n)))
|
||||
if err == nil {
|
||||
return int(size.Int64())
|
||||
}
|
||||
return rand.Intn(n)
|
||||
}
|
||||
|
||||
func randomHumanReadableServerName() string {
|
||||
// Randomly choose a generation style (0: Dictionary, 1: Phonetic, 2: API Mask)
|
||||
style := rand.Intn(3)
|
||||
var domain string
|
||||
|
||||
switch style {
|
||||
case 0: // Dictionary-based (e.g., "secure-bridge.com")
|
||||
domain = randItem(prefixes) + "-" + randItem(suffixes) + randItem(topLevelDomains)
|
||||
|
||||
case 1: // Phonetic/Brandable (e.g., "verantix.net")
|
||||
// Combine 2 to 3 syllables
|
||||
name := ""
|
||||
for j := 0; j < (rand.Intn(2) + 2); j++ {
|
||||
name += randItem(syllables)
|
||||
}
|
||||
domain = name + randItem(topLevelDomains)
|
||||
|
||||
case 2: // API/CDN Masking (e.g., "v2-node-42.static-cache.net")
|
||||
vNum := rand.Intn(4) + 1
|
||||
nodeNum := rand.Intn(90) + 10
|
||||
root := randItem(apiRoots)
|
||||
tld := ".net"
|
||||
if rand.Intn(2) == 0 {
|
||||
tld = ".com"
|
||||
}
|
||||
domain = fmt.Sprintf("v%d-node-%d.%s%s", vNum, nodeNum, root, tld)
|
||||
}
|
||||
|
||||
return domain
|
||||
}
|
||||
Loading…
Reference in New Issue