aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShelikhoo <xiaokangwang@outlook.com>2023-10-19 11:40:57 +0100
committerShelikhoo <xiaokangwang@outlook.com>2023-10-24 17:47:25 +0100
commit5df7a06eeecf18f7c998b576e68cdea7a4c2fdeb (patch)
tree027817a523ca3e0758b6a190dc72c455ba774d20
parentf43da1d2d2cd35fcd2900f2da103c8acdbf3b69b (diff)
downloadsnowflake-5df7a06eeecf18f7c998b576e68cdea7a4c2fdeb.tar.gz
snowflake-5df7a06eeecf18f7c998b576e68cdea7a4c2fdeb.zip
Add outbound proxy configuration propagation
-rw-r--r--client/lib/rendezvous.go26
-rw-r--r--client/lib/snowflake.go10
-rw-r--r--client/lib/webrtc.go32
-rw-r--r--client/snowflake.go17
-rw-r--r--common/nat/nat.go57
-rw-r--r--common/proxy/client.go4
-rw-r--r--common/utls/roundtripper.go30
7 files changed, 139 insertions, 37 deletions
diff --git a/client/lib/rendezvous.go b/client/lib/rendezvous.go
index ea6ab39..a9d810e 100644
--- a/client/lib/rendezvous.go
+++ b/client/lib/rendezvous.go
@@ -7,6 +7,7 @@ import (
"crypto/tls"
"errors"
"fmt"
+ "net/url"
"log"
"net/http"
@@ -51,12 +52,15 @@ type BrokerChannel struct {
// We make a copy of DefaultTransport because we want the default Dial
// and TLSHandshakeTimeout settings. But we want to disable the default
// ProxyFromEnvironment setting.
-func createBrokerTransport() http.RoundTripper {
+func createBrokerTransport(proxy *url.URL) http.RoundTripper {
tlsConfig := &tls.Config{
RootCAs: certs.GetRootCAs(),
}
transport := &http.Transport{TLSClientConfig: tlsConfig}
transport.Proxy = nil
+ if proxy != nil {
+ transport.Proxy = http.ProxyURL(proxy)
+ }
transport.ResponseHeaderTimeout = 15 * time.Second
return transport
}
@@ -68,7 +72,7 @@ func newBrokerChannelFromConfig(config ClientConfig) (*BrokerChannel, error) {
log.Printf("Domain fronting using a randomly selected domain from: %v", config.FrontDomains)
}
- brokerTransport := createBrokerTransport()
+ brokerTransport := createBrokerTransport(config.CommunicationProxy)
if config.UTLSClientID != "" {
utlsClientHelloID, err := utlsutil.NameToUTLSID(config.UTLSClientID)
@@ -78,7 +82,8 @@ func newBrokerChannelFromConfig(config ClientConfig) (*BrokerChannel, error) {
utlsConfig := &utls.Config{
RootCAs: certs.GetRootCAs(),
}
- brokerTransport = utlsutil.NewUTLSHTTPRoundTripper(utlsClientHelloID, utlsConfig, brokerTransport, config.UTLSRemoveSNI)
+ brokerTransport = utlsutil.NewUTLSHTTPRoundTripperWithProxy(utlsClientHelloID, utlsConfig, brokerTransport,
+ config.UTLSRemoveSNI, config.CommunicationProxy)
}
var rendezvous RendezvousMethod
@@ -168,14 +173,22 @@ type WebRTCDialer struct {
max int
eventLogger event.SnowflakeEventReceiver
+ proxy *url.URL
}
+// Deprecated: Use NewWebRTCDialerWithEventsAndProxy instead
func NewWebRTCDialer(broker *BrokerChannel, iceServers []webrtc.ICEServer, max int) *WebRTCDialer {
- return NewWebRTCDialerWithEvents(broker, iceServers, max, nil)
+ return NewWebRTCDialerWithEventsAndProxy(broker, iceServers, max, nil, nil)
}
-// NewWebRTCDialerWithEvents constructs a new WebRTCDialer.
+// Deprecated: Use NewWebRTCDialerWithEventsAndProxy instead
func NewWebRTCDialerWithEvents(broker *BrokerChannel, iceServers []webrtc.ICEServer, max int, eventLogger event.SnowflakeEventReceiver) *WebRTCDialer {
+ return NewWebRTCDialerWithEventsAndProxy(broker, iceServers, max, eventLogger, nil)
+}
+
+// NewWebRTCDialerWithEventsAndProxy constructs a new WebRTCDialer.
+func NewWebRTCDialerWithEventsAndProxy(broker *BrokerChannel, iceServers []webrtc.ICEServer, max int,
+ eventLogger event.SnowflakeEventReceiver, proxy *url.URL) *WebRTCDialer {
config := webrtc.Configuration{
ICEServers: iceServers,
}
@@ -186,6 +199,7 @@ func NewWebRTCDialerWithEvents(broker *BrokerChannel, iceServers []webrtc.ICESer
max: max,
eventLogger: eventLogger,
+ proxy: proxy,
}
}
@@ -193,7 +207,7 @@ func NewWebRTCDialerWithEvents(broker *BrokerChannel, iceServers []webrtc.ICESer
func (w WebRTCDialer) Catch() (*WebRTCPeer, error) {
// TODO: [#25591] Fetch ICE server information from Broker.
// TODO: [#25596] Consider TURN servers here too.
- return NewWebRTCPeerWithEvents(w.webrtcConfig, w.BrokerChannel, w.eventLogger)
+ return NewWebRTCPeerWithEventsAndProxy(w.webrtcConfig, w.BrokerChannel, w.eventLogger, w.proxy)
}
// GetMax returns the maximum number of snowflakes to collect.
diff --git a/client/lib/snowflake.go b/client/lib/snowflake.go
index 453b42b..daa8b4a 100644
--- a/client/lib/snowflake.go
+++ b/client/lib/snowflake.go
@@ -110,6 +110,8 @@ type ClientConfig struct {
// BridgeFingerprint is the fingerprint of the bridge that the client will eventually
// connect to, as specified in the Bridge line of the torrc.
BridgeFingerprint string
+ // CommunicationProxy is the proxy address for network communication
+ CommunicationProxy *url.URL
}
// NewSnowflakeClient creates a new Snowflake transport client that can spawn multiple
@@ -147,14 +149,14 @@ func NewSnowflakeClient(config ClientConfig) (*Transport, error) {
if err != nil {
return nil, err
}
- go updateNATType(iceServers, broker)
+ go updateNATType(iceServers, broker, config.CommunicationProxy)
max := 1
if config.Max > max {
max = config.Max
}
eventsLogger := event.NewSnowflakeEventDispatcher()
- transport := &Transport{dialer: NewWebRTCDialerWithEvents(broker, iceServers, max, eventsLogger), eventDispatcher: eventsLogger}
+ transport := &Transport{dialer: NewWebRTCDialerWithEventsAndProxy(broker, iceServers, max, eventsLogger, config.CommunicationProxy), eventDispatcher: eventsLogger}
return transport, nil
}
@@ -247,13 +249,13 @@ func (conn *SnowflakeConn) Close() error {
// loop through all provided STUN servers until we exhaust the list or find
// one that is compatible with RFC 5780
-func updateNATType(servers []webrtc.ICEServer, broker *BrokerChannel) {
+func updateNATType(servers []webrtc.ICEServer, broker *BrokerChannel, proxy *url.URL) {
var restrictedNAT bool
var err error
for _, server := range servers {
addr := strings.TrimPrefix(server.URLs[0], "stun:")
- restrictedNAT, err = nat.CheckIfRestrictedNAT(addr)
+ restrictedNAT, err = nat.CheckIfRestrictedNATWithProxy(addr, proxy)
if err != nil {
log.Printf("Warning: NAT checking failed for server at %s: %s", addr, err)
diff --git a/client/lib/webrtc.go b/client/lib/webrtc.go
index fedf299..2b46ef6 100644
--- a/client/lib/webrtc.go
+++ b/client/lib/webrtc.go
@@ -5,8 +5,11 @@ import (
"encoding/hex"
"errors"
"fmt"
+ "github.com/pion/transport/v2"
+ "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/proxy"
"io"
"log"
+ "net/url"
"strings"
"sync"
"time"
@@ -38,20 +41,28 @@ type WebRTCPeer struct {
bytesLogger bytesLogger
eventsLogger event.SnowflakeEventReceiver
+ proxy *url.URL
}
+// Deprecated: Use NewWebRTCPeerWithEventsAndProxy Instead.
func NewWebRTCPeer(config *webrtc.Configuration,
broker *BrokerChannel) (*WebRTCPeer, error) {
- return NewWebRTCPeerWithEvents(config, broker, nil)
+ return NewWebRTCPeerWithEventsAndProxy(config, broker, nil, nil)
}
-// NewWebRTCPeerWithEvents constructs a WebRTC PeerConnection to a snowflake proxy.
+// Deprecated: Use NewWebRTCPeerWithEventsAndProxy Instead.
+func NewWebRTCPeerWithEvents(config *webrtc.Configuration,
+ broker *BrokerChannel, eventsLogger event.SnowflakeEventReceiver) (*WebRTCPeer, error) {
+ return NewWebRTCPeerWithEventsAndProxy(config, broker, eventsLogger, nil)
+}
+
+// NewWebRTCPeerWithEventsAndProxy constructs a WebRTC PeerConnection to a snowflake proxy.
//
// The creation of the peer handles the signaling to the Snowflake broker, including
// the exchange of SDP information, the creation of a PeerConnection, and the establishment
// of a DataChannel to the Snowflake proxy.
-func NewWebRTCPeerWithEvents(config *webrtc.Configuration,
- broker *BrokerChannel, eventsLogger event.SnowflakeEventReceiver) (*WebRTCPeer, error) {
+func NewWebRTCPeerWithEventsAndProxy(config *webrtc.Configuration,
+ broker *BrokerChannel, eventsLogger event.SnowflakeEventReceiver, proxy *url.URL) (*WebRTCPeer, error) {
if eventsLogger == nil {
eventsLogger = event.NewSnowflakeEventDispatcher()
}
@@ -73,6 +84,7 @@ func NewWebRTCPeerWithEvents(config *webrtc.Configuration,
connection.recvPipe, connection.writePipe = io.Pipe()
connection.eventsLogger = eventsLogger
+ connection.proxy = proxy
err := connection.connect(config, broker)
if err != nil {
@@ -199,7 +211,17 @@ func (c *WebRTCPeer) preparePeerConnection(config *webrtc.Configuration) error {
// to get snowflake working in shadow (where the AF_NETLINK family is not implemented).
// These two lines of code functionally revert a new change in pion by silently ignoring
// when net.Interfaces() fails, rather than throwing an error
- vnet, _ := stdnet.NewNet()
+ var vnet transport.Net
+ vnet, _ = stdnet.NewNet()
+
+ if c.proxy != nil {
+ if err = proxy.CheckProxyProtocolSupport(c.proxy); err != nil {
+ return err
+ }
+ socksClient := proxy.NewSocks5UDPClient(c.proxy)
+ vnet = proxy.NewTransportWrapper(&socksClient, vnet)
+ }
+
s.SetNet(vnet)
api := webrtc.NewAPI(webrtc.WithSettingEngine(s))
c.pc, err = api.NewPeerConnection(*config)
diff --git a/client/snowflake.go b/client/snowflake.go
index f4d9c5f..6d4b05c 100644
--- a/client/snowflake.go
+++ b/client/snowflake.go
@@ -4,6 +4,7 @@ package main
import (
"flag"
"fmt"
+ "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/proxy"
"io"
"io/ioutil"
"log"
@@ -231,8 +232,20 @@ func main() {
log.Fatal(err)
}
if ptInfo.ProxyURL != nil {
- pt.ProxyError("proxy is not supported")
- os.Exit(1)
+ if err := proxy.CheckProxyProtocolSupport(ptInfo.ProxyURL); err != nil {
+ pt.ProxyError("proxy is not supported:" + err.Error())
+ os.Exit(1)
+ } else {
+ config.CommunicationProxy = ptInfo.ProxyURL
+ client := proxy.NewSocks5UDPClient(config.CommunicationProxy)
+ conn, err := client.ListenPacket("udp", nil)
+ if err != nil {
+ pt.ProxyError("proxy test failure:" + err.Error())
+ os.Exit(1)
+ }
+ conn.Close()
+ pt.ProxyDone()
+ }
}
listeners := make([]net.Listener, 0)
shutdown := make(chan struct{})
diff --git a/common/nat/nat.go b/common/nat/nat.go
index 81a82bc..cfadcdb 100644
--- a/common/nat/nat.go
+++ b/common/nat/nat.go
@@ -16,8 +16,10 @@ package nat
import (
"errors"
"fmt"
+ "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/proxy"
"log"
"net"
+ "net/url"
"time"
"github.com/pion/stun"
@@ -31,23 +33,28 @@ const (
NATUnrestricted = "unrestricted"
)
-// This function checks the NAT mapping and filtering
+// Deprecated: Use CheckIfRestrictedNATWithProxy Instead.
+func CheckIfRestrictedNAT(server string) (bool, error) {
+ return CheckIfRestrictedNATWithProxy(server, nil)
+}
+
+// CheckIfRestrictedNATWithProxy checks the NAT mapping and filtering
// behaviour and returns true if the NAT is restrictive
// (address-dependent mapping and/or port-dependent filtering)
// and false if the NAT is unrestrictive (meaning it
// will work with most other NATs),
-func CheckIfRestrictedNAT(server string) (bool, error) {
- return isRestrictedMapping(server)
+func CheckIfRestrictedNATWithProxy(server string, proxy *url.URL) (bool, error) {
+ return isRestrictedMapping(server, proxy)
}
// Performs two tests from RFC 5780 to determine whether the mapping type
// of the client's NAT is address-independent or address-dependent
// Returns true if the mapping is address-dependent and false otherwise
-func isRestrictedMapping(addrStr string) (bool, error) {
+func isRestrictedMapping(addrStr string, proxy *url.URL) (bool, error) {
var xorAddr1 stun.XORMappedAddress
var xorAddr2 stun.XORMappedAddress
- mapTestConn, err := connect(addrStr)
+ mapTestConn, err := connect(addrStr, proxy)
if err != nil {
return false, fmt.Errorf("Error creating STUN connection: %w", err)
}
@@ -98,10 +105,10 @@ func isRestrictedMapping(addrStr string) (bool, error) {
// Note: This function is no longer used because a client's NAT type is
// determined only by their mapping type, but the functionality might
// be useful in the future and remains here.
-func isRestrictedFiltering(addrStr string) (bool, error) {
+func isRestrictedFiltering(addrStr string, proxy *url.URL) (bool, error) {
var xorAddr stun.XORMappedAddress
- mapTestConn, err := connect(addrStr)
+ mapTestConn, err := connect(addrStr, proxy)
if err != nil {
log.Printf("Error creating STUN connection: %s", err.Error())
return false, err
@@ -142,23 +149,41 @@ func isRestrictedFiltering(addrStr string) (bool, error) {
}
// Given an address string, returns a StunServerConn
-func connect(addrStr string) (*StunServerConn, error) {
+func connect(addrStr string, proxyAddr *url.URL) (*StunServerConn, error) {
// Creating a "connection" to STUN server.
- addr, err := net.ResolveUDPAddr("udp4", addrStr)
+ var conn net.PacketConn
+
+ ResolveUDPAddr := net.ResolveUDPAddr
+ if proxyAddr != nil {
+ socksClient := proxy.NewSocks5UDPClient(proxyAddr)
+ ResolveUDPAddr = socksClient.ResolveUDPAddr
+ }
+
+ addr, err := ResolveUDPAddr("udp4", addrStr)
if err != nil {
log.Printf("Error resolving address: %s\n", err.Error())
return nil, err
}
- c, err := net.ListenUDP("udp4", nil)
- if err != nil {
- return nil, err
+ if proxyAddr == nil {
+ c, err := net.ListenUDP("udp4", nil)
+ if err != nil {
+ return nil, err
+ }
+ conn = c
+ } else {
+ socksClient := proxy.NewSocks5UDPClient(proxyAddr)
+ c, err := socksClient.ListenPacket("udp", nil)
+ if err != nil {
+ return nil, err
+ }
+ conn = c
}
- mChan := listen(c)
+ mChan := listen(conn)
return &StunServerConn{
- conn: c,
+ conn: conn,
PrimaryAddr: addr,
messageChan: mChan,
}, nil
@@ -203,13 +228,13 @@ func (c *StunServerConn) AddOtherAddr(addrStr string) error {
}
// taken from https://github.com/pion/stun/blob/master/cmd/stun-traversal/main.go
-func listen(conn *net.UDPConn) chan *stun.Message {
+func listen(conn net.PacketConn) chan *stun.Message {
messages := make(chan *stun.Message)
go func() {
for {
buf := make([]byte, 1024)
- n, _, err := conn.ReadFromUDP(buf)
+ n, _, err := conn.ReadFrom(buf)
if err != nil {
close(messages)
return
diff --git a/common/proxy/client.go b/common/proxy/client.go
index eb062f8..689bf59 100644
--- a/common/proxy/client.go
+++ b/common/proxy/client.go
@@ -261,6 +261,10 @@ type transportWrapper struct {
sc *SocksClient
}
+func (t *transportWrapper) ListenUDP(network string, locAddr *net.UDPAddr) (transport.UDPConn, error) {
+ return t.sc.ListenPacket(network, nil)
+}
+
func (t *transportWrapper) ListenPacket(network string, address string) (net.PacketConn, error) {
return t.sc.ListenPacket(network, nil)
}
diff --git a/common/utls/roundtripper.go b/common/utls/roundtripper.go
index 5b74aec..10c9155 100644
--- a/common/utls/roundtripper.go
+++ b/common/utls/roundtripper.go
@@ -5,8 +5,10 @@ import (
"crypto/tls"
"errors"
"fmt"
+ "golang.org/x/net/proxy"
"net"
"net/http"
+ "net/url"
"sync"
"time"
@@ -14,7 +16,13 @@ import (
"golang.org/x/net/http2"
)
-// NewUTLSHTTPRoundTripper creates an instance of RoundTripper that dial to remote HTTPS endpoint with
+// Deprecated: use NewUTLSHTTPRoundTripperWithProxy instead
+func NewUTLSHTTPRoundTripper(clientHelloID utls.ClientHelloID, uTlsConfig *utls.Config,
+ backdropTransport http.RoundTripper, removeSNI bool) http.RoundTripper {
+ return NewUTLSHTTPRoundTripperWithProxy(clientHelloID, uTlsConfig, backdropTransport, removeSNI, nil)
+}
+
+// NewUTLSHTTPRoundTripperWithProxy creates an instance of RoundTripper that dial to remote HTTPS endpoint with
// an alternative version of TLS implementation that attempts to imitate browsers' fingerprint.
// clientHelloID is the clientHello that uTLS attempts to imitate
// uTlsConfig is the TLS Configuration template
@@ -22,8 +30,8 @@ import (
// removeSNI indicates not to send Server Name Indication Extension
// returns a RoundTripper: its behaviour is documented at
// https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/merge_requests/76#note_2777161
-func NewUTLSHTTPRoundTripper(clientHelloID utls.ClientHelloID, uTlsConfig *utls.Config,
- backdropTransport http.RoundTripper, removeSNI bool) http.RoundTripper {
+func NewUTLSHTTPRoundTripperWithProxy(clientHelloID utls.ClientHelloID, uTlsConfig *utls.Config,
+ backdropTransport http.RoundTripper, removeSNI bool, proxy *url.URL) http.RoundTripper {
rtImpl := &uTLSHTTPRoundTripperImpl{
clientHelloID: clientHelloID,
config: uTlsConfig,
@@ -31,6 +39,7 @@ func NewUTLSHTTPRoundTripper(clientHelloID utls.ClientHelloID, uTlsConfig *utls.
backdropTransport: backdropTransport,
pendingConn: map[pendingConnKey]*unclaimedConnection{},
removeSNI: removeSNI,
+ proxyAddr: proxy,
}
rtImpl.init()
return rtImpl
@@ -51,6 +60,7 @@ type uTLSHTTPRoundTripperImpl struct {
pendingConn map[pendingConnKey]*unclaimedConnection
removeSNI bool
+ proxyAddr *url.URL
}
type pendingConnKey struct {
@@ -174,7 +184,19 @@ func (r *uTLSHTTPRoundTripperImpl) dialTLS(ctx context.Context, addr string) (*u
}
config.ServerName = host
- dialer := &net.Dialer{}
+ systemDialer := &net.Dialer{}
+
+ var dialer proxy.ContextDialer
+ dialer = systemDialer
+
+ if r.proxyAddr != nil {
+ proxyDialer, err := proxy.FromURL(r.proxyAddr, systemDialer)
+ if err != nil {
+ return nil, err
+ }
+ dialer = proxyDialer.(proxy.ContextDialer)
+ }
+
conn, err := dialer.DialContext(ctx, "tcp", addr)
if err != nil {
return nil, err