mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-13 11:11:30 -04:00
Add outbound proxy configuration propagation
This commit is contained in:
parent
f43da1d2d2
commit
5df7a06eee
7 changed files with 139 additions and 37 deletions
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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{})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue