mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-13 11:11:30 -04:00
151 lines
4.1 KiB
Go
151 lines
4.1 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 (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/bridgefingerprint"
|
|
|
|
"gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/nat"
|
|
)
|
|
|
|
const ClientVersion = "1.0"
|
|
|
|
/* Client--Broker protocol v1.x specification:
|
|
|
|
All messages contain the version number
|
|
followed by a new line and then the message body
|
|
<message> := <version>\n<body>
|
|
<version> := <digit>.<digit>
|
|
<body> := <poll request>|<poll response>
|
|
|
|
There are two different types of body messages,
|
|
each encoded in JSON format
|
|
|
|
== ClientPollRequest ==
|
|
<poll request> :=
|
|
{
|
|
offer: <sdp offer>
|
|
[nat: (unknown|restricted|unrestricted)]
|
|
[fingerprint: <fingerprint string>]
|
|
}
|
|
|
|
The NAT field is optional, and if it is missing a
|
|
value of "unknown" will be assumed. The fingerprint
|
|
is also optional and, if absent, will be assigned the
|
|
fingerprint of the default bridge.
|
|
|
|
== ClientPollResponse ==
|
|
<poll response> :=
|
|
{
|
|
[answer: <sdp answer>]
|
|
[error: <error string>]
|
|
}
|
|
|
|
If the broker succeeded in matching the client with a proxy,
|
|
the answer field MUST contain a valid SDP answer, and the
|
|
error field MUST be empty. If the answer field is empty, the
|
|
error field MUST contain a string explaining with a reason
|
|
for the error.
|
|
|
|
*/
|
|
|
|
// The bridge fingerprint to assume, for client poll requests that do not
|
|
// specify a fingerprint. Before #28651, there was only one bridge with one
|
|
// fingerprint, which all clients expected to be connected to implicitly.
|
|
// If a client is old enough that it does not specify a fingerprint, this is
|
|
// the fingerprint it expects. Clients that do set a fingerprint in the
|
|
// SOCKS params will also be assumed to want to connect to the default bridge.
|
|
const defaultBridgeFingerprint = "2B280B23E1107BB62ABFC40DDCC8824814F80A72"
|
|
|
|
type ClientPollRequest struct {
|
|
Offer string `json:"offer"`
|
|
NAT string `json:"nat"`
|
|
Fingerprint string `json:"fingerprint"`
|
|
}
|
|
|
|
// Encodes a poll message from a snowflake client
|
|
func (req *ClientPollRequest) EncodeClientPollRequest() ([]byte, error) {
|
|
if req.Fingerprint == "" {
|
|
req.Fingerprint = defaultBridgeFingerprint
|
|
}
|
|
body, err := json.Marshal(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return append([]byte(ClientVersion+"\n"), body...), nil
|
|
}
|
|
|
|
// Decodes a poll message from a snowflake client
|
|
func DecodeClientPollRequest(data []byte) (*ClientPollRequest, error) {
|
|
parts := bytes.SplitN(data, []byte("\n"), 2)
|
|
|
|
if len(parts) < 2 {
|
|
// no version number found
|
|
return nil, fmt.Errorf("unsupported message version")
|
|
}
|
|
|
|
var message ClientPollRequest
|
|
|
|
if string(parts[0]) != ClientVersion {
|
|
return nil, fmt.Errorf("unsupported message version")
|
|
}
|
|
|
|
err := json.Unmarshal(parts[1], &message)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if message.Offer == "" {
|
|
return nil, fmt.Errorf("no supplied offer")
|
|
}
|
|
|
|
if message.Fingerprint == "" {
|
|
message.Fingerprint = defaultBridgeFingerprint
|
|
}
|
|
|
|
if _, err := bridgefingerprint.FingerprintFromHexString(message.Fingerprint); err != nil {
|
|
return nil, fmt.Errorf("cannot decode fingerprint")
|
|
}
|
|
|
|
switch message.NAT {
|
|
case "":
|
|
message.NAT = nat.NATUnknown
|
|
case nat.NATUnknown:
|
|
case nat.NATRestricted:
|
|
case nat.NATUnrestricted:
|
|
default:
|
|
return nil, fmt.Errorf("invalid NAT type")
|
|
}
|
|
|
|
return &message, nil
|
|
}
|
|
|
|
type ClientPollResponse struct {
|
|
Answer string `json:"answer,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// Encodes a poll response for a snowflake client
|
|
func (resp *ClientPollResponse) EncodePollResponse() ([]byte, error) {
|
|
return json.Marshal(resp)
|
|
}
|
|
|
|
// Decodes a poll response for a snowflake client
|
|
// If the Error field is empty, the Answer should be non-empty
|
|
func DecodeClientPollResponse(data []byte) (*ClientPollResponse, error) {
|
|
var message ClientPollResponse
|
|
|
|
err := json.Unmarshal(data, &message)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if message.Error == "" && message.Answer == "" {
|
|
return nil, fmt.Errorf("received empty broker response")
|
|
}
|
|
|
|
return &message, nil
|
|
}
|