mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-13 11:11:30 -04:00
This should make the functions easier to use, harder to confuse the return values with the same type.
315 lines
7 KiB
Go
315 lines
7 KiB
Go
//Package for communication with the snowflake broker
|
|
|
|
// import "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/messages"
|
|
package messages
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/nat"
|
|
)
|
|
|
|
const (
|
|
version = "1.3"
|
|
ProxyUnknown = "unknown"
|
|
)
|
|
|
|
var KnownProxyTypes = map[string]bool{
|
|
"standalone": true,
|
|
"webext": true,
|
|
"badge": true,
|
|
"iptproxy": true,
|
|
}
|
|
|
|
/* Version 1.3 specification:
|
|
|
|
== ProxyPollRequest ==
|
|
{
|
|
Sid: [generated session id of proxy],
|
|
Version: 1.3,
|
|
Type: ["badge"|"webext"|"standalone"],
|
|
NAT: ["unknown"|"restricted"|"unrestricted"],
|
|
Clients: [number of current clients, rounded down to multiples of 8],
|
|
AcceptedRelayPattern: [a pattern representing accepted set of relay domains]
|
|
}
|
|
|
|
== ProxyPollResponse ==
|
|
1) If a client is matched:
|
|
HTTP 200 OK
|
|
{
|
|
Status: "client match",
|
|
{
|
|
type: offer,
|
|
sdp: [WebRTC SDP]
|
|
},
|
|
NAT: ["unknown"|"restricted"|"unrestricted"],
|
|
RelayURL: [the WebSocket URL proxy should connect to relay Snowflake traffic]
|
|
}
|
|
|
|
2) If a client is not matched:
|
|
HTTP 200 OK
|
|
|
|
{
|
|
Status: "no match"
|
|
}
|
|
|
|
3) If the request is malformed:
|
|
HTTP 400 BadRequest
|
|
|
|
== ProxyAnswerRequest ==
|
|
{
|
|
Sid: [generated session id of proxy],
|
|
Version: 1.3,
|
|
Answer:
|
|
{
|
|
type: answer,
|
|
sdp: [WebRTC SDP]
|
|
}
|
|
}
|
|
|
|
== ProxyAnswerResponse ==
|
|
1) If the client retrieved the answer:
|
|
HTTP 200 OK
|
|
|
|
{
|
|
Status: "success"
|
|
}
|
|
|
|
2) If the client left:
|
|
HTTP 200 OK
|
|
|
|
{
|
|
Status: "client gone"
|
|
}
|
|
|
|
3) If the request is malformed:
|
|
HTTP 400 BadRequest
|
|
|
|
*/
|
|
|
|
type ProxyPollRequest struct {
|
|
Sid string
|
|
Version string
|
|
Type string
|
|
NAT string
|
|
Clients int
|
|
|
|
AcceptedRelayPattern *string
|
|
}
|
|
|
|
func EncodeProxyPollRequest(sid string, proxyType string, natType string, clients int) ([]byte, error) {
|
|
return EncodeProxyPollRequestWithRelayPrefix(sid, proxyType, natType, clients, "")
|
|
}
|
|
|
|
func EncodeProxyPollRequestWithRelayPrefix(sid string, proxyType string, natType string, clients int, relayPattern string) ([]byte, error) {
|
|
return json.Marshal(ProxyPollRequest{
|
|
Sid: sid,
|
|
Version: version,
|
|
Type: proxyType,
|
|
NAT: natType,
|
|
Clients: clients,
|
|
AcceptedRelayPattern: &relayPattern,
|
|
})
|
|
}
|
|
|
|
func DecodeProxyPollRequest(data []byte) (sid string, proxyType string, natType string, clients int, err error) {
|
|
var relayPrefix string
|
|
sid, proxyType, natType, clients, relayPrefix, _, err = DecodeProxyPollRequestWithRelayPrefix(data)
|
|
if relayPrefix != "" {
|
|
return "", "", "", 0, ErrExtraInfo
|
|
}
|
|
return
|
|
}
|
|
|
|
// Decodes a poll message from a snowflake proxy and returns the
|
|
// sid, proxy type, nat type and clients of the proxy on success
|
|
// and an error if it failed
|
|
func DecodeProxyPollRequestWithRelayPrefix(data []byte) (
|
|
sid string, proxyType string, natType string, clients int, relayPrefix string, relayPrefixAware bool, err error) {
|
|
var message ProxyPollRequest
|
|
|
|
err = json.Unmarshal(data, &message)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
majorVersion := strings.Split(message.Version, ".")[0]
|
|
if majorVersion != "1" {
|
|
err = fmt.Errorf("using unknown version")
|
|
return
|
|
}
|
|
|
|
// Version 1.x requires an Sid
|
|
if message.Sid == "" {
|
|
err = fmt.Errorf("no supplied session id")
|
|
return
|
|
}
|
|
|
|
switch message.NAT {
|
|
case "":
|
|
message.NAT = nat.NATUnknown
|
|
case nat.NATUnknown:
|
|
case nat.NATRestricted:
|
|
case nat.NATUnrestricted:
|
|
default:
|
|
err = fmt.Errorf("invalid NAT type")
|
|
return
|
|
}
|
|
|
|
// we don't reject polls with an unknown proxy type because we encourage
|
|
// projects that embed proxy code to include their own type
|
|
if !KnownProxyTypes[message.Type] {
|
|
message.Type = ProxyUnknown
|
|
}
|
|
var acceptedRelayPattern = ""
|
|
if message.AcceptedRelayPattern != nil {
|
|
acceptedRelayPattern = *message.AcceptedRelayPattern
|
|
}
|
|
return message.Sid, message.Type, message.NAT, message.Clients,
|
|
acceptedRelayPattern, message.AcceptedRelayPattern != nil, nil
|
|
}
|
|
|
|
type ProxyPollResponse struct {
|
|
Status string
|
|
Offer string
|
|
NAT string
|
|
|
|
RelayURL string
|
|
}
|
|
|
|
func EncodePollResponse(offer string, success bool, natType string) ([]byte, error) {
|
|
return EncodePollResponseWithRelayURL(offer, success, natType, "", "no match")
|
|
}
|
|
|
|
func EncodePollResponseWithRelayURL(offer string, success bool, natType, relayURL, failReason string) ([]byte, error) {
|
|
if success {
|
|
return json.Marshal(ProxyPollResponse{
|
|
Status: "client match",
|
|
Offer: offer,
|
|
NAT: natType,
|
|
RelayURL: relayURL,
|
|
})
|
|
|
|
}
|
|
return json.Marshal(ProxyPollResponse{
|
|
Status: failReason,
|
|
})
|
|
}
|
|
func DecodePollResponse(data []byte) (offer string, natType string, err error) {
|
|
offer, natType, relayURL, err := DecodePollResponseWithRelayURL(data)
|
|
if relayURL != "" {
|
|
return "", "", ErrExtraInfo
|
|
}
|
|
return offer, natType, err
|
|
}
|
|
|
|
// Decodes a poll response from the broker and returns an offer and the client's NAT type
|
|
// If there is a client match, the returned offer string will be non-empty
|
|
func DecodePollResponseWithRelayURL(data []byte) (
|
|
offer string,
|
|
natType string,
|
|
relayURL string,
|
|
err_ error,
|
|
) {
|
|
var message ProxyPollResponse
|
|
|
|
err := json.Unmarshal(data, &message)
|
|
if err != nil {
|
|
return "", "", "", err
|
|
}
|
|
if message.Status == "" {
|
|
return "", "", "", fmt.Errorf("received invalid data")
|
|
}
|
|
|
|
err = nil
|
|
if message.Status == "client match" {
|
|
if message.Offer == "" {
|
|
return "", "", "", fmt.Errorf("no supplied offer")
|
|
}
|
|
} else {
|
|
message.Offer = ""
|
|
if message.Status != "no match" {
|
|
err = errors.New(message.Status)
|
|
}
|
|
}
|
|
|
|
natType = message.NAT
|
|
if natType == "" {
|
|
natType = "unknown"
|
|
}
|
|
|
|
return message.Offer, natType, message.RelayURL, err
|
|
}
|
|
|
|
type ProxyAnswerRequest struct {
|
|
Version string
|
|
Sid string
|
|
Answer string
|
|
}
|
|
|
|
func EncodeAnswerRequest(answer string, sid string) ([]byte, error) {
|
|
return json.Marshal(ProxyAnswerRequest{
|
|
Version: version,
|
|
Sid: sid,
|
|
Answer: answer,
|
|
})
|
|
}
|
|
|
|
// Returns the sdp answer and proxy sid
|
|
func DecodeAnswerRequest(data []byte) (answer string, sid string, err error) {
|
|
var message ProxyAnswerRequest
|
|
|
|
err = json.Unmarshal(data, &message)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
majorVersion := strings.Split(message.Version, ".")[0]
|
|
if majorVersion != "1" {
|
|
return "", "", fmt.Errorf("using unknown version")
|
|
}
|
|
|
|
if message.Sid == "" || message.Answer == "" {
|
|
return "", "", fmt.Errorf("no supplied sid or answer")
|
|
}
|
|
|
|
return message.Answer, message.Sid, nil
|
|
}
|
|
|
|
type ProxyAnswerResponse struct {
|
|
Status string
|
|
}
|
|
|
|
func EncodeAnswerResponse(success bool) ([]byte, error) {
|
|
if success {
|
|
return json.Marshal(ProxyAnswerResponse{
|
|
Status: "success",
|
|
})
|
|
|
|
}
|
|
return json.Marshal(ProxyAnswerResponse{
|
|
Status: "client gone",
|
|
})
|
|
}
|
|
|
|
func DecodeAnswerResponse(data []byte) (bool, error) {
|
|
var message ProxyAnswerResponse
|
|
var success bool
|
|
|
|
err := json.Unmarshal(data, &message)
|
|
if err != nil {
|
|
return success, err
|
|
}
|
|
if message.Status == "" {
|
|
return success, fmt.Errorf("received invalid data")
|
|
}
|
|
|
|
if message.Status == "success" {
|
|
success = true
|
|
}
|
|
|
|
return success, nil
|
|
}
|