This is an automated email from the git hooks/post-receive script.
itchyonion pushed a change to branch main in repository pluggable-transports/snowflake.
from 5cc849e fix: up/down traffic stats being mixed up new 39d906b Add utls-imitate, utls-nosni doc to README new 473cc45 Add utls-imitate, utls-nosni doc to README: fix style new b63d227 Test for data race with QueuePacketConn.WriteTo and kcp-go. new d2858ae Revert "Take ownership of buffer in QueuePacketConn QueueIncoming/WriteTo." new ef51f20 Merge branch '40260-revert-queuepacketconn-ownership' into 'main' new 36d5d2d Fix comment typo on NewRedialPacketConn. new fb35e80 Proxy: add outbound-address config new 5dd0a31 Add comments and improve logging
The 8 revisions listed above as "new" are entirely new to this repository and will be described in separate emails. The revisions listed as "add" were already present in the repository and have only been added to this reference.
Summary of changes: README.md | 10 +++ common/turbotunnel/queuepacketconn.go | 22 +++-- common/turbotunnel/queuepacketconn_test.go | 129 +++++++++++++++++++++++++++++ common/turbotunnel/redialpacketconn.go | 2 +- proxy/README.md | 2 + proxy/lib/snowflake.go | 114 ++++++++++++++++--------- proxy/main.go | 6 ++ 7 files changed, 237 insertions(+), 48 deletions(-)
This is an automated email from the git hooks/post-receive script.
itchyonion pushed a commit to branch main in repository pluggable-transports/snowflake.
commit 39d906b3803e804ddb27ea6fb3f65653f4431352 Author: Shelikhoo xiaokangwang@outlook.com AuthorDate: Fri Mar 10 15:25:15 2023 +0000
Add utls-imitate, utls-nosni doc to README --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+)
diff --git a/README.md b/README.md index 9a1c958..bc61107 100644 --- a/README.md +++ b/README.md @@ -97,3 +97,13 @@ runs in GitLab CI. It is also possible to run this setup in a Virtual Machine using [vagrant](https://www.vagrantup.com/). Just run `vagrant up` and it will create and provision the VM. `vagrant ssh` to get into the VM to use it as a development environment. + +##### uTLS Settings + +Snowflake communicate with broker that serves as signaling server with TLS based domain fronting connection, which may be identified by its usage of Go language TLS stack. + +uTLS is a software library designed to initiate the TLS Client Hello fingerprint of browsers or other popular software's TLS stack to evade censorship based on TLS client hello fingerprint with `-utls-imitate` . You can use `-version` to see a list of supported values. + +Depending on client and server configuration, it may not always work as expected as not all extensions are correctly implemented. + +You can also remove SNI(Server Name Indication) from client hello to evade censorship with `-utls-nosni`, not all servers supports this.
This is an automated email from the git hooks/post-receive script.
itchyonion pushed a commit to branch main in repository pluggable-transports/snowflake.
commit 473cc4598702c48ae8562df54115d54a7d9ec71c Author: Shelikhoo xiaokangwang@outlook.com AuthorDate: Mon Mar 13 14:13:50 2023 +0000
Add utls-imitate, utls-nosni doc to README: fix style --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md index bc61107..c242e5c 100644 --- a/README.md +++ b/README.md @@ -106,4 +106,4 @@ uTLS is a software library designed to initiate the TLS Client Hello fingerprint
Depending on client and server configuration, it may not always work as expected as not all extensions are correctly implemented.
-You can also remove SNI(Server Name Indication) from client hello to evade censorship with `-utls-nosni`, not all servers supports this. +You can also remove SNI (Server Name Indication) from client hello to evade censorship with `-utls-nosni`, not all servers supports this.
This is an automated email from the git hooks/post-receive script.
itchyonion pushed a commit to branch main in repository pluggable-transports/snowflake.
commit ef51f2063e8747f4256e756a3f572875700b0190 Merge: b63d227 d2858ae Author: David Fifield dcf@torproject.org AuthorDate: Mon Mar 13 19:36:09 2023 +0000
Merge branch '40260-revert-queuepacketconn-ownership' into 'main'
Revert "Take ownership of buffer in QueuePacketConn QueueIncoming/WriteTo"
See merge request tpo/anti-censorship/pluggable-transports/snowflake!140
common/turbotunnel/queuepacketconn.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-)
This is an automated email from the git hooks/post-receive script.
itchyonion pushed a commit to branch main in repository pluggable-transports/snowflake.
commit fb35e80b0a9c2b85cbd8fbee502519df396fea74 Author: itchyonion itchyonion@torproject.org AuthorDate: Mon Dec 12 13:02:25 2022 -0800
Proxy: add outbound-address config --- proxy/README.md | 2 ++ proxy/lib/snowflake.go | 9 ++++++++- proxy/main.go | 6 ++++++ 3 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/proxy/README.md b/proxy/README.md index f7efaec..e26ef62 100644 --- a/proxy/README.md +++ b/proxy/README.md @@ -47,6 +47,8 @@ Usage of ./proxy: the time interval in second before NAT type is retested, 0s disables retest. Valid time units are "s", "m", "h". (default 24h0m0s) -relay string websocket relay URL (default "wss://snowflake.bamsoftware.com/") + -outbound-address string + bind a specific outbound address. Replace all host candidates with this address without validation. -stun string stun URL (default "stun:stun.l.google.com:19302") -summary-interval duration diff --git a/proxy/lib/snowflake.go b/proxy/lib/snowflake.go index fd9df88..9616132 100644 --- a/proxy/lib/snowflake.go +++ b/proxy/lib/snowflake.go @@ -114,6 +114,8 @@ type SnowflakeProxy struct { KeepLocalAddresses bool // RelayURL is the URL of the Snowflake server that all traffic will be relayed to RelayURL string + // OutboundAddress specify an IP address to use as SDP host candidate + OutboundAddress string // Ephemeral*Port limits the pool of ports that ICE UDP connections can allocate from EphemeralMinPort uint16 EphemeralMaxPort uint16 @@ -360,6 +362,11 @@ func (sf *SnowflakeProxy) makeWebRTCAPI() *webrtc.API { } }
+ if sf.OutboundAddress != "" { + // replace SDP host candidates with the given IP without validation + // still have server reflexive candidates to fall back on + settingsEngine.SetNAT1To1IPs([]string{sf.OutboundAddress}, webrtc.ICECandidateTypeHost) + } settingsEngine.SetICEMulticastDNSMode(ice.MulticastDNSModeDisabled)
return webrtc.NewAPI(webrtc.WithSettingEngine(settingsEngine)) @@ -672,8 +679,8 @@ func (sf *SnowflakeProxy) checkNATType(config webrtc.Configuration, probeURL str }
offer := pc.LocalDescription() + log.Printf("Offer: \n\t%s", strings.ReplaceAll(offer.SDP, "\n", "\n\t")) sdp, err := util.SerializeSessionDescription(offer) - log.Printf("Offer: %s", sdp) if err != nil { log.Printf("Error encoding probe message: %s", err.Error()) return diff --git a/proxy/main.go b/proxy/main.go index d470f38..500b36a 100644 --- a/proxy/main.go +++ b/proxy/main.go @@ -25,6 +25,7 @@ func main() { unsafeLogging := flag.Bool("unsafe-logging", false, "prevent logs from being scrubbed") keepLocalAddresses := flag.Bool("keep-local-addresses", false, "keep local LAN address ICE candidates") relayURL := flag.String("relay", sf.DefaultRelayURL, "websocket relay URL") + outboundAddress := flag.String("outbound-address", "", "prefer the given address as outbound address") allowedRelayHostNamePattern := flag.String("allowed-relay-hostname-pattern", "snowflake.torproject.net$", "a pattern to specify allowed hostname pattern for relay URL.") allowNonTLSRelay := flag.Bool("allow-non-tls-relay", false, "allow relay without tls encryption") NATTypeMeasurementInterval := flag.Duration("nat-retest-interval", time.Hour*24, @@ -44,6 +45,10 @@ func main() { os.Exit(0) }
+ if *outboundAddress != "" && *keepLocalAddresses { + log.Fatal("Cannot keep local address candidates when outbound address is specified") + } + eventLogger := event.NewSnowflakeEventDispatcher()
if *ephemeralPortsRangeFlag != "" { @@ -78,6 +83,7 @@ func main() { BrokerURL: *rawBrokerURL, KeepLocalAddresses: *keepLocalAddresses, RelayURL: *relayURL, + OutboundAddress: *outboundAddress, EphemeralMinPort: ephemeralPortsRange[0], EphemeralMaxPort: ephemeralPortsRange[1],
This is an automated email from the git hooks/post-receive script.
itchyonion pushed a commit to branch main in repository pluggable-transports/snowflake.
commit 5dd0a31d95052eca7e89cef8ccc527b74a1c6be5 Author: itchyonion itchyonion@torproject.org AuthorDate: Fri Feb 24 01:56:22 2023 -0800
Add comments and improve logging --- proxy/lib/snowflake.go | 107 +++++++++++++++++++++++++++++++------------------ 1 file changed, 69 insertions(+), 38 deletions(-)
diff --git a/proxy/lib/snowflake.go b/proxy/lib/snowflake.go index 9616132..9b25405 100644 --- a/proxy/lib/snowflake.go +++ b/proxy/lib/snowflake.go @@ -51,30 +51,37 @@ import ( "github.com/pion/webrtc/v3" )
-const DefaultBrokerURL = "https://snowflake-broker.torproject.net/" - -const DefaultNATProbeURL = "https://snowflake-broker.torproject.net:8443/probe" - -const DefaultRelayURL = "wss://snowflake.bamsoftware.com/" - -const DefaultSTUNURL = "stun:stun.l.google.com:19302" -const DefaultProxyType = "standalone" -const pollInterval = 5 * time.Second +const ( + DefaultBrokerURL = "https://snowflake-broker.torproject.net/" + DefaultNATProbeURL = "https://snowflake-broker.torproject.net:8443/probe" + DefaultRelayURL = "wss://snowflake.bamsoftware.com/" + DefaultSTUNURL = "stun:stun.l.google.com:19302" + DefaultProxyType = "standalone" +)
const ( // NATUnknown represents a NAT type which is unknown. NATUnknown = "unknown" + // NATRestricted represents a restricted NAT. NATRestricted = "restricted" + // NATUnrestricted represents an unrestricted NAT. NATUnrestricted = "unrestricted" )
-// amount of time after sending an SDP answer before the proxy assumes the -// client is not going to connect -const dataChannelTimeout = 20 * time.Second +const ( + pollInterval = 5 * time.Second + + // Amount of time after sending an SDP answer before the proxy assumes the + // client is not going to connect + dataChannelTimeout = 20 * time.Second + + //Maximum number of bytes to be read from an HTTP request + readLimit = 100000
-const readLimit = 100000 //Maximum number of bytes to be read from an HTTP request + sessionIDLength = 16 +)
var broker *SignalingServer
@@ -90,10 +97,6 @@ func getCurrentNATType() string { return currentNATType }
-const ( - sessionIDLength = 16 -) - var ( tokens *tokens_t config webrtc.Configuration @@ -184,16 +187,15 @@ func newSignalingServer(rawURL string, keepLocalAddresses bool) (*SignalingServe
// Post sends a POST request to the SignalingServer func (s *SignalingServer) Post(path string, payload io.Reader) ([]byte, error) { - req, err := http.NewRequest("POST", path, payload) if err != nil { return nil, err } + resp, err := s.transport.RoundTrip(req) if err != nil { return nil, err } - if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("remote returned status code %d", resp.StatusCode) } @@ -202,6 +204,8 @@ func (s *SignalingServer) Post(path string, payload io.Reader) ([]byte, error) { return limitedRead(resp.Body, readLimit) }
+// pollOffer communicates the proxy's capabilities with broker +// and retrieves a compatible SDP offer func (s *SignalingServer) pollOffer(sid string, proxyType string, acceptedRelayPattern string, shutdown chan struct{}) (*webrtc.SessionDescription, string) { brokerPath := s.url.ResolveReference(&url.URL{Path: "proxy"})
@@ -221,6 +225,7 @@ func (s *SignalingServer) pollOffer(sid string, proxyType string, acceptedRelayP log.Printf("Error encoding poll message: %s", err.Error()) return nil, "" } + resp, err := s.Post(brokerPath.String(), bytes.NewBuffer(body)) if err != nil { log.Printf("error polling broker: %s", err.Error()) @@ -239,7 +244,6 @@ func (s *SignalingServer) pollOffer(sid string, proxyType string, acceptedRelayP return nil, "" } return offer, relayURL - } } } @@ -247,7 +251,6 @@ func (s *SignalingServer) pollOffer(sid string, proxyType string, acceptedRelayP }
func (s *SignalingServer) sendAnswer(sid string, pc *webrtc.PeerConnection) error { - brokerPath := s.url.ResolveReference(&url.URL{Path: "answer"}) ld := pc.LocalDescription() if !s.keepLocalAddresses { ld = &webrtc.SessionDescription{ @@ -255,14 +258,18 @@ func (s *SignalingServer) sendAnswer(sid string, pc *webrtc.PeerConnection) erro SDP: util.StripLocalAddresses(ld.SDP), } } + answer, err := util.SerializeSessionDescription(ld) if err != nil { return err } + body, err := messages.EncodeAnswerRequest(answer, sid) if err != nil { return err } + + brokerPath := s.url.ResolveReference(&url.URL{Path: "answer"}) resp, err := s.Post(brokerPath.String(), bytes.NewBuffer(body)) if err != nil { return fmt.Errorf("error sending answer to broker: %s", err.Error()) @@ -307,8 +314,8 @@ func copyLoop(c1 io.ReadWriteCloser, c2 io.ReadWriteCloser, shutdown chan struct
// We pass conn.RemoteAddr() as an additional parameter, rather than calling // conn.RemoteAddr() inside this function, as a workaround for a hang that -// otherwise occurs inside of conn.pc.RemoteDescription() (called by -// RemoteAddr). https://bugs.torproject.org/18628#comment:8 +// otherwise occurs inside conn.pc.RemoteDescription() (called by RemoteAddr). +// https://bugs.torproject.org/18628#comment:8 func (sf *SnowflakeProxy) datachannelHandler(conn *webRTCConn, remoteAddr net.Addr, relayURL string) { defer conn.Close() defer tokens.ret() @@ -316,6 +323,7 @@ func (sf *SnowflakeProxy) datachannelHandler(conn *webRTCConn, remoteAddr net.Ad if relayURL == "" { relayURL = sf.RelayURL } + u, err := url.Parse(relayURL) if err != nil { log.Fatalf("invalid relay url: %s", err) @@ -336,8 +344,9 @@ func (sf *SnowflakeProxy) datachannelHandler(conn *webRTCConn, remoteAddr net.Ad log.Printf("error dialing relay: %s = %s", u.String(), err) return } + wsConn := websocketconn.New(ws) - log.Printf("connected to relay: %v", relayURL) + log.Printf("Connected to relay: %v", relayURL) defer wsConn.Close() copyLoop(conn, wsConn, sf.shutdown) log.Printf("datachannelHandler ends") @@ -367,6 +376,7 @@ func (sf *SnowflakeProxy) makeWebRTCAPI() *webrtc.API { // still have server reflexive candidates to fall back on settingsEngine.SetNAT1To1IPs([]string{sf.OutboundAddress}, webrtc.ICECandidateTypeHost) } + settingsEngine.SetICEMulticastDNSMode(ice.MulticastDNSModeDisabled)
return webrtc.NewAPI(webrtc.WithSettingEngine(settingsEngine)) @@ -386,20 +396,33 @@ func (sf *SnowflakeProxy) makePeerConnectionFromOffer(sdp *webrtc.SessionDescrip if err != nil { return nil, fmt.Errorf("accept: NewPeerConnection: %s", err) } + pc.OnDataChannel(func(dc *webrtc.DataChannel) { - log.Println("OnDataChannel") + log.Printf("New Data Channel %s-%d\n", dc.Label(), dc.ID()) close(dataChan)
pr, pw := io.Pipe() conn := newWebRTCConn(pc, dc, pr, sf.EventDispatcher)
dc.OnOpen(func() { - log.Println("OnOpen channel") + log.Printf("Data Channel %s-%d open\n", dc.Label(), dc.ID()) + + if sf.OutboundAddress != "" { + selectedCandidatePair, err := pc.SCTP().Transport().ICETransport().GetSelectedCandidatePair() + if err != nil { + log.Printf("Warning: couldn't get the selected candidate pair") + } + + log.Printf("Selected Local Candidate: %s:%d", selectedCandidatePair.Local.Address, selectedCandidatePair.Local.Port) + if sf.OutboundAddress != selectedCandidatePair.Local.Address { + log.Printf("Warning: the IP address provided by --outbound-address is not used for establishing peerconnection") + } + } }) dc.OnClose(func() { conn.lock.Lock() defer conn.lock.Unlock() - log.Println("OnClose channel") + log.Printf("Data Channel %s-%d close\n", dc.Label(), dc.ID()) log.Println(conn.bytesLogger.ThroughputSummary()) in, out := conn.bytesLogger.GetStat() conn.eventLogger.OnNewSnowflakeEvent(event.EventOnProxyConnectionOver{ @@ -437,7 +460,6 @@ func (sf *SnowflakeProxy) makePeerConnectionFromOffer(sdp *webrtc.SessionDescrip } return nil, fmt.Errorf("accept: SetRemoteDescription: %s", err) } - log.Println("sdp offer successfully received.")
log.Println("Generating answer...") answer, err := pc.CreateAnswer(nil) @@ -458,8 +480,11 @@ func (sf *SnowflakeProxy) makePeerConnectionFromOffer(sdp *webrtc.SessionDescrip } return nil, err } + // Wait for ICE candidate gathering to complete <-done + log.Printf("Answer: \n\t%s", strings.ReplaceAll(pc.LocalDescription().SDP, "\n", "\n\t")) + return pc, nil }
@@ -497,7 +522,7 @@ func (sf *SnowflakeProxy) makeNewPeerConnection(config webrtc.Configuration, pc.Close() return nil, err } - log.Println("WebRTC: Created offer") + log.Println("Probetest: Creating offer")
// As of v3.0.0, pion-webrtc uses trickle ICE by default. // We have to wait for candidate gathering to complete @@ -509,7 +534,7 @@ func (sf *SnowflakeProxy) makeNewPeerConnection(config webrtc.Configuration, pc.Close() return nil, err } - log.Println("WebRTC: Set local description") + log.Println("Probetest: Set local description")
// Wait for ICE candidate gathering to complete <-done @@ -523,6 +548,8 @@ func (sf *SnowflakeProxy) runSession(sid string) { tokens.ret() return } + log.Printf("Received Offer From Broker: \n\t%s", strings.ReplaceAll(offer.SDP, "\n", "\n\t")) + matcher := namematcher.NewNameMatcher(sf.RelayDomainNamePattern) parsedRelayURL, err := url.Parse(relayURL) if err != nil { @@ -530,11 +557,13 @@ func (sf *SnowflakeProxy) runSession(sid string) { tokens.ret() return } + if relayURL != "" && (!matcher.IsMember(parsedRelayURL.Hostname()) || (!sf.AllowNonTLSRelay && parsedRelayURL.Scheme != "wss")) { log.Printf("bad offer from broker: rejected Relay URL") tokens.ret() return } + dataChan := make(chan struct{}) dataChannelAdaptor := dataChannelHandlerWithRelayURL{RelayURL: relayURL, sf: sf} pc, err := sf.makePeerConnectionFromOffer(offer, config, dataChan, dataChannelAdaptor.datachannelHandler) @@ -543,6 +572,7 @@ func (sf *SnowflakeProxy) runSession(sid string) { tokens.ret() return } + err = broker.sendAnswer(sid, pc) if err != nil { log.Printf("error sending answer to client through broker: %s", err) @@ -557,7 +587,7 @@ func (sf *SnowflakeProxy) runSession(sid string) { // destroy the peer connection and return the token. select { case <-dataChan: - log.Println("Connection successful.") + log.Println("Connection successful") case <-time.After(dataChannelTimeout): log.Println("Timed out waiting for client to open data channel.") if err := pc.Close(); err != nil { @@ -622,11 +652,8 @@ func (sf *SnowflakeProxy) Start() error { } tokens = newTokens(sf.Capacity)
- // use probetest to determine NAT compatability sf.checkNATType(config, sf.NATProbeURL) - currentNATTypeLoaded := getCurrentNATType() - sf.EventDispatcher.OnNewSnowflakeEvent(&event.EventOnCurrentNATTypeDetermined{CurNATType: currentNATTypeLoaded})
NatRetestTask := task.Periodic{ @@ -663,14 +690,16 @@ func (sf *SnowflakeProxy) Stop() { close(sf.shutdown) }
+// checkNATType use probetest to determine NAT compatability by +// attempting to connect with a known symmetric NAT. If success, +// it is considered "unrestricted". If timeout it is considered "restricted" func (sf *SnowflakeProxy) checkNATType(config webrtc.Configuration, probeURL string) { - probe, err := newSignalingServer(probeURL, false) if err != nil { log.Printf("Error parsing url: %s", err.Error()) }
- // create offer + // create offer used for probetest dataChan := make(chan struct{}) pc, err := sf.makeNewPeerConnection(config, dataChan) if err != nil { @@ -679,7 +708,7 @@ func (sf *SnowflakeProxy) checkNATType(config webrtc.Configuration, probeURL str }
offer := pc.LocalDescription() - log.Printf("Offer: \n\t%s", strings.ReplaceAll(offer.SDP, "\n", "\n\t")) + log.Printf("Probetest offer: \n\t%s", strings.ReplaceAll(offer.SDP, "\n", "\n\t")) sdp, err := util.SerializeSessionDescription(offer) if err != nil { log.Printf("Error encoding probe message: %s", err.Error()) @@ -692,6 +721,7 @@ func (sf *SnowflakeProxy) checkNATType(config webrtc.Configuration, probeURL str log.Printf("Error encoding probe message: %s", err.Error()) return } + resp, err := probe.Post(probe.url.String(), bytes.NewBuffer(body)) if err != nil { log.Printf("error polling probe: %s", err.Error()) @@ -703,11 +733,13 @@ func (sf *SnowflakeProxy) checkNATType(config webrtc.Configuration, probeURL str log.Printf("Error reading probe response: %s", err.Error()) return } + answer, err := util.DeserializeSessionDescription(sdp) if err != nil { log.Printf("Error setting answer: %s", err.Error()) return } + err = pc.SetRemoteDescription(*answer) if err != nil { log.Printf("Error setting answer: %s", err.Error()) @@ -745,5 +777,4 @@ func (sf *SnowflakeProxy) checkNATType(config webrtc.Configuration, probeURL str if err := pc.Close(); err != nil { log.Printf("error calling pc.Close: %v", err) } - }
tor-commits@lists.torproject.org