mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-15 17:03:36 -04:00
Merge branch 'dev-snowflake-udp-rebase-extradata' into 'main'
Draft: Unreliable+unordered WebRTC data channel transport for Snowflake rev2 See merge request tpo/anti-censorship/pluggable-transports/snowflake!315
This commit is contained in:
commit
aae444478d
17 changed files with 506 additions and 91 deletions
|
@ -5,10 +5,13 @@ package messages
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/bridgefingerprint"
|
||||
|
||||
"github.com/fxamacker/cbor"
|
||||
|
||||
"gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/bridgefingerprint"
|
||||
"gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/nat"
|
||||
)
|
||||
|
||||
|
@ -149,3 +152,34 @@ func DecodeClientPollResponse(data []byte) (*ClientPollResponse, error) {
|
|||
|
||||
return &message, nil
|
||||
}
|
||||
|
||||
// ClientConnectionMetadata is a struct that contains metadata about a snowflake connection between client and server
|
||||
// It will be sent from the client to the proxy in WebRTC data channel protocol string
|
||||
// The proxy will then send the metadata to the server in the protocol get parameter of the WebSocket connection
|
||||
type ClientConnectionMetadata struct {
|
||||
ClientID []byte `json:"client_id"`
|
||||
}
|
||||
|
||||
func (meta *ClientConnectionMetadata) EncodeConnectionMetadata() (string, error) {
|
||||
jsonData, err := cbor.Marshal(meta, cbor.CanonicalEncOptions())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return base64.RawURLEncoding.EncodeToString(jsonData), nil
|
||||
}
|
||||
|
||||
func DecodeConnectionMetadata(data string) (*ClientConnectionMetadata, error) {
|
||||
decodedData, err := base64.RawURLEncoding.DecodeString(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var meta ClientConnectionMetadata
|
||||
err = cbor.Unmarshal(decodedData, &meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &meta, nil
|
||||
}
|
||||
|
|
46
common/packetpadding/conn.go
Normal file
46
common/packetpadding/conn.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
package packetpadding
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type ReadWriteCloserPreservesBoundary interface {
|
||||
io.ReadWriteCloser
|
||||
MessageBoundaryPreserved()
|
||||
}
|
||||
|
||||
type PaddableConnection interface {
|
||||
ReadWriteCloserPreservesBoundary
|
||||
}
|
||||
|
||||
func NewPaddableConnection(rwc ReadWriteCloserPreservesBoundary, padding PacketPaddingContainer) PaddableConnection {
|
||||
return &paddableConnection{
|
||||
ReadWriteCloserPreservesBoundary: rwc,
|
||||
padding: padding,
|
||||
}
|
||||
}
|
||||
|
||||
type paddableConnection struct {
|
||||
ReadWriteCloserPreservesBoundary
|
||||
padding PacketPaddingContainer
|
||||
}
|
||||
|
||||
func (c *paddableConnection) Write(p []byte) (n int, err error) {
|
||||
dataLen := len(p)
|
||||
if _, err = c.ReadWriteCloserPreservesBoundary.Write(c.padding.Pack(p, 0)); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return dataLen, nil
|
||||
}
|
||||
|
||||
func (c *paddableConnection) Read(p []byte) (n int, err error) {
|
||||
if n, err = c.ReadWriteCloserPreservesBoundary.Read(p); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
payload, _ := c.padding.Unpack(p[:n])
|
||||
if payload != nil {
|
||||
copy(p, payload)
|
||||
}
|
||||
return len(payload), nil
|
||||
}
|
52
common/packetpadding/container.go
Normal file
52
common/packetpadding/container.go
Normal file
|
@ -0,0 +1,52 @@
|
|||
package packetpadding
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
func New() PacketPaddingContainer {
|
||||
return packetPaddingContainer{}
|
||||
}
|
||||
|
||||
type packetPaddingContainer struct {
|
||||
}
|
||||
|
||||
func (c packetPaddingContainer) Pack(data_OWNERSHIP_RELINQUISHED []byte, paddingLength int) []byte {
|
||||
data := append(data_OWNERSHIP_RELINQUISHED, make([]byte, paddingLength)...)
|
||||
dataLength := len(data_OWNERSHIP_RELINQUISHED)
|
||||
data = binary.BigEndian.AppendUint16(data, uint16(dataLength))
|
||||
return data
|
||||
}
|
||||
|
||||
func (c packetPaddingContainer) Pad(paddingLength int) []byte {
|
||||
if assertPaddingLengthIsNotNegative := paddingLength < 0; assertPaddingLengthIsNotNegative {
|
||||
return nil
|
||||
}
|
||||
switch paddingLength {
|
||||
case 0:
|
||||
return []byte{}
|
||||
case 1:
|
||||
return []byte{0}
|
||||
case 2:
|
||||
return []byte{0, 0}
|
||||
default:
|
||||
return append(make([]byte, paddingLength-2), byte(paddingLength>>8), byte(paddingLength))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (c packetPaddingContainer) Unpack(wrappedData_OWNERSHIP_RELINQUISHED []byte) ([]byte, int) {
|
||||
dataLength := len(wrappedData_OWNERSHIP_RELINQUISHED)
|
||||
if dataLength < 2 {
|
||||
return nil, dataLength
|
||||
}
|
||||
|
||||
dataLen := int(binary.BigEndian.Uint16(wrappedData_OWNERSHIP_RELINQUISHED[dataLength-2:]))
|
||||
if dataLen > 2047 {
|
||||
return nil, 0
|
||||
}
|
||||
paddingLength := dataLength - dataLen - 2
|
||||
if paddingLength < 0 {
|
||||
return nil, paddingLength
|
||||
}
|
||||
|
||||
return wrappedData_OWNERSHIP_RELINQUISHED[:dataLen], paddingLength
|
||||
}
|
34
common/packetpadding/containerIfce.go
Normal file
34
common/packetpadding/containerIfce.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package packetpadding
|
||||
|
||||
// PacketPaddingContainer is an interface that defines methods to pad packets
|
||||
// with a given number of bytes, and to unpack the padding from a padded packet.
|
||||
// The packet format is as follows if the desired output length is greater than
|
||||
// 2 bytes:
|
||||
// | data | padding | data length |
|
||||
// The data length is a 16-bit big-endian integer that represents the length of
|
||||
// the data in bytes.
|
||||
// If the desired output length is 2 bytes or less, the packet format is as
|
||||
// follows:
|
||||
// | padding |
|
||||
// No payload will be included in the packet.
|
||||
type PacketPaddingContainer interface {
|
||||
// Pack pads the given data with the given number of bytes, and appends the
|
||||
// length of the data to the end of the data. The returned byte slice
|
||||
// contains the padded data.
|
||||
// This generates a packet with a length of
|
||||
// len(data_OWNERSHIP_RELINQUISHED) + padding + 2
|
||||
// @param data_OWNERSHIP_RELINQUISHED - The payload, this reference is consumed and should not be used after this call.
|
||||
// @param padding - The number of padding bytes to add to the data.
|
||||
Pack(data_OWNERSHIP_RELINQUISHED []byte, paddingLength int) []byte
|
||||
|
||||
// Unpack extracts the data and padding from the given padded data. It
|
||||
// returns the data and the number of padding bytes.
|
||||
// the data may be nil.
|
||||
// @param wrappedData_OWNERSHIP_RELINQUISHED - The packet, this reference is consumed and should not be used after this call.
|
||||
Unpack(wrappedData_OWNERSHIP_RELINQUISHED []byte) ([]byte, int)
|
||||
|
||||
// Pad returns a padding packet of padding length.
|
||||
// If the padding length is less than 0, nil is returned.
|
||||
// @param padding - The number of padding bytes to add to the data.
|
||||
Pad(paddingLength int) []byte
|
||||
}
|
113
common/packetpadding/container_test.go
Normal file
113
common/packetpadding/container_test.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
package packetpadding_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/v2/common/packetpadding"
|
||||
)
|
||||
|
||||
func TestPacketPaddingContainer(t *testing.T) {
|
||||
Convey("Given a PacketPaddingContainer", t, func() {
|
||||
container := packetpadding.New()
|
||||
|
||||
Convey("When packing data with padding", func() {
|
||||
data := []byte("testdata")
|
||||
paddingLength := 4
|
||||
packedData := container.Pack(data, paddingLength)
|
||||
|
||||
Convey("The packed data should have the correct length", func() {
|
||||
expectedLength := len(data) + paddingLength + 2
|
||||
So(len(packedData), ShouldEqual, expectedLength)
|
||||
})
|
||||
|
||||
Convey("When unpacking the packed data", func() {
|
||||
unpackedData, unpackedPaddingLength := container.Unpack(packedData)
|
||||
|
||||
Convey("The unpacked data should match the original data", func() {
|
||||
So(string(unpackedData), ShouldEqual, string(data))
|
||||
})
|
||||
|
||||
Convey("The unpacked padding length should match the original padding length", func() {
|
||||
So(unpackedPaddingLength, ShouldEqual, paddingLength)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When packing empty data with padding", func() {
|
||||
data := []byte("")
|
||||
paddingLength := 4
|
||||
packedData := container.Pack(data, paddingLength)
|
||||
|
||||
Convey("The packed data should have the correct length", func() {
|
||||
expectedLength := len(data) + paddingLength + 2
|
||||
So(len(packedData), ShouldEqual, expectedLength)
|
||||
})
|
||||
|
||||
Convey("When unpacking the packed data", func() {
|
||||
unpackedData, unpackedPaddingLength := container.Unpack(packedData)
|
||||
|
||||
Convey("The unpacked data should match the original data", func() {
|
||||
So(string(unpackedData), ShouldEqual, string(data))
|
||||
})
|
||||
|
||||
Convey("The unpacked padding length should match the original padding length", func() {
|
||||
So(unpackedPaddingLength, ShouldEqual, paddingLength)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When packing data with zero padding", func() {
|
||||
data := []byte("testdata")
|
||||
paddingLength := 0
|
||||
packedData := container.Pack(data, paddingLength)
|
||||
|
||||
Convey("The packed data should have the correct length", func() {
|
||||
expectedLength := len(data) + paddingLength + 2
|
||||
So(len(packedData), ShouldEqual, expectedLength)
|
||||
})
|
||||
|
||||
Convey("When unpacking the packed data", func() {
|
||||
unpackedData, unpackedPaddingLength := container.Unpack(packedData)
|
||||
|
||||
Convey("The unpacked data should match the original data", func() {
|
||||
So(string(unpackedData), ShouldEqual, string(data))
|
||||
})
|
||||
|
||||
Convey("The unpacked padding length should match the original padding length", func() {
|
||||
So(unpackedPaddingLength, ShouldEqual, paddingLength)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When padding data", func() {
|
||||
Convey("With a positive padding length", func() {
|
||||
padLength := 3
|
||||
padData := container.Pad(padLength)
|
||||
|
||||
Convey("The padded data should have the correct length", func() {
|
||||
So(len(padData), ShouldEqual, padLength)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("With a zero padding length", func() {
|
||||
padLength := 0
|
||||
padData := container.Pad(padLength)
|
||||
|
||||
Convey("The padded data should be empty", func() {
|
||||
So(len(padData), ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("With a negative padding length", func() {
|
||||
padLength := -1
|
||||
padData := container.Pad(padLength)
|
||||
|
||||
Convey("The padded data should be nil", func() {
|
||||
So(padData, ShouldBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
|
@ -41,6 +41,8 @@ func (conn *Conn) SetDeadline(t time.Time) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (conn *Conn) MessageBoundaryPreserved() {}
|
||||
|
||||
func readLoop(w io.Writer, ws *websocket.Conn) error {
|
||||
var buf [2048]byte
|
||||
for {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue