diff --git a/README.md b/README.md index 48225a7..de6dbbc 100644 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ 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. +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. `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 diff --git a/internal/client/TLS.go b/internal/client/TLS.go index 6c97ab9..178f8fb 100644 --- a/internal/client/TLS.go +++ b/internal/client/TLS.go @@ -1,11 +1,11 @@ package client import ( + "github.com/cbeuw/Cloak/internal/common" utls "github.com/refraction-networking/utls" log "github.com/sirupsen/logrus" "net" - - "github.com/cbeuw/Cloak/internal/common" + "strings" ) const appDataMaxLength = 16401 @@ -30,6 +30,40 @@ 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 @@ -89,6 +123,10 @@ func (tls *DirectTLS) Handshake(rawConn net.Conn, authInfo AuthInfo) (sessionKey serverName: authInfo.MockDomain, } + if strings.EqualFold(fields.serverName, "random") { + fields.serverName = randomServerName() + } + var ch []byte ch, err = buildClientHello(tls.browser, fields) if err != nil { diff --git a/internal/common/crypto.go b/internal/common/crypto.go index 71b3f3f..c46ba89 100644 --- a/internal/common/crypto.go +++ b/internal/common/crypto.go @@ -6,6 +6,7 @@ import ( "crypto/rand" "errors" "io" + "math/big" "time" log "github.com/sirupsen/logrus" @@ -52,8 +53,8 @@ func CryptoRandRead(buf []byte) { RandRead(rand.Reader, buf) } -func RandRead(randSource io.Reader, buf []byte) { - _, err := randSource.Read(buf) +func backoff(f func() error) { + err := f() if err == nil { return } @@ -61,12 +62,36 @@ func RandRead(randSource io.Reader, buf []byte) { 100 * time.Millisecond, 300 * time.Millisecond, 500 * time.Millisecond, 1 * time.Second, 3 * time.Second, 5 * time.Second} for i := 0; i < 10; i++ { - log.Errorf("Failed to get random bytes: %v. Retrying...", err) - _, err = randSource.Read(buf) + log.Errorf("Failed to get random: %v. Retrying...", err) + err = f() if err == nil { return } time.Sleep(waitDur[i]) } - log.Fatal("Cannot get random bytes after 10 retries") + log.Fatal("Cannot get random after 10 retries") +} + +func RandRead(randSource io.Reader, buf []byte) { + backoff(func() error { + _, err := randSource.Read(buf) + return err + }) +} + +func RandItem[T any](list []T) T { + return list[RandInt(len(list))] +} + +func RandInt(n int) int { + s := new(int) + backoff(func() error { + size, err := rand.Int(rand.Reader, big.NewInt(int64(n))) + if err != nil { + return err + } + *s = int(size.Int64()) + return nil + }) + return *s }