diff options
author | Shelikhoo <xiaokangwang@outlook.com> | 2023-10-19 11:40:57 +0100 |
---|---|---|
committer | Shelikhoo <xiaokangwang@outlook.com> | 2023-10-24 17:47:25 +0100 |
commit | 5df7a06eeecf18f7c998b576e68cdea7a4c2fdeb (patch) | |
tree | 027817a523ca3e0758b6a190dc72c455ba774d20 | |
parent | f43da1d2d2cd35fcd2900f2da103c8acdbf3b69b (diff) | |
download | snowflake-5df7a06eeecf18f7c998b576e68cdea7a4c2fdeb.tar.gz snowflake-5df7a06eeecf18f7c998b576e68cdea7a4c2fdeb.zip |
Add outbound proxy configuration propagation
-rw-r--r-- | client/lib/rendezvous.go | 26 | ||||
-rw-r--r-- | client/lib/snowflake.go | 10 | ||||
-rw-r--r-- | client/lib/webrtc.go | 32 | ||||
-rw-r--r-- | client/snowflake.go | 17 | ||||
-rw-r--r-- | common/nat/nat.go | 57 | ||||
-rw-r--r-- | common/proxy/client.go | 4 | ||||
-rw-r--r-- | common/utls/roundtripper.go | 30 |
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 |