Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Format-First transport #167

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions application/transports/wrapping/format_first/http/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package http

import (
"fmt"

"github.com/refraction-networking/conjure/application/transports"
pb "github.com/refraction-networking/gotapdance/protobuf"
"google.golang.org/protobuf/proto"
)

// ClientTransport implements the client side transport interface for the Min transport. The
// significant difference is that there is an instance of this structure per client session, where
// the station side Transport struct has one instance to be re-used for all sessions.
type ClientTransport struct {
// Parameters are fields that will be shared with the station in the registration
Parameters *pb.GenericTransportParams

// // state tracks fields internal to the registrar that survive for the lifetime
// // of the transport session without being shared - i.e. local derived keys.
// state any
}

// Name returns a string identifier for the Transport for logging
func (*ClientTransport) Name() string {
return "min"
}

// String returns a string identifier for the Transport for logging (including string formatters)
func (*ClientTransport) String() string {
return "min"
}

// ID provides an identifier that will be sent to the conjure station during the registration so
// that the station knows what transport to expect connecting to the chosen phantom.
func (*ClientTransport) ID() pb.TransportType {
return pb.TransportType_Min
}

// GetParams returns a generic protobuf with any parameters from both the registration and the
// transport.
func (t *ClientTransport) GetParams() proto.Message {
return t.Parameters
}

// SetParams allows the caller to set parameters associated with the transport, returning an
// error if the provided generic message is not compatible.
func (t *ClientTransport) SetParams(p any) error {
params, ok := p.(*pb.GenericTransportParams)
if !ok {
return fmt.Errorf("unable to parse params")
}
t.Parameters = params

return nil
}

// GetDstPort returns the destination port that the client should open the phantom connection to
func (t *ClientTransport) GetDstPort(seed []byte, params any) (uint16, error) {
if t.Parameters == nil || !t.Parameters.GetRandomizeDstPort() {
return defaultPort, nil
}

return transports.PortSelectorRange(portRangeMin, portRangeMax, seed)
}

// // Connect creates the connection to the phantom address negotiated in the registration phase of
// // Conjure connection establishment.
// func (t *ClientTransport) Connect(ctx context.Context, reg *cj.ConjureReg) (net.Conn, error) {
// // conn, err := reg.getFirstConnection(ctx, reg.TcpDialer, phantoms)
// // if err != nil {
// // return nil, err
// // }

// // // Send hmac(seed, str) bytes to indicate to station (min transport)
// // connectTag := conjureHMAC(reg.keys.SharedSecret, "MinTrasportHMACString")
// // conn.Write(connectTag)
// // return conn, nil
// return nil, nil
// }
158 changes: 158 additions & 0 deletions application/transports/wrapping/format_first/http/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package http

import (
"bufio"
"bytes"
"encoding/base64"
"errors"
"fmt"
"io"
"net"
"net/http"

dd "github.com/refraction-networking/conjure/application/lib"
"github.com/refraction-networking/conjure/application/transports"
pb "github.com/refraction-networking/gotapdance/protobuf"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
)

const (
httpPrefixRegexString = ""
httpPrefixMinLen = 32
hmacString = "HTTPTransportHMACString"
)

const (
// Earliest client library version ID that supports destination port randomization
randomizeDstPortMinVersion uint = 3

// port range boundaries for prefix transport when randomizing
portRangeMin = 1024
portRangeMax = 65535
minTagLength = 32

defaultPort = 80
)

// Transport provides a struct implementing the Transport, WrappingTransport,
// PortRandomizingTransport, and FixedPortTransport interfaces.
type Transport struct{}

// Name returns the human-friendly name of the transport, implementing the
// Transport interface..
func (Transport) Name() string { return "HTTPTransport" }

// LogPrefix returns the prefix used when including this transport in logs,
// implementing the Transport interface.
func (Transport) LogPrefix() string { return "HTTP" }

// GetIdentifier takes in a registration and returns an identifier for it. This
// identifier should be unique for each registration on a given phantom;
// registrations on different phantoms can have the same identifier.
func (Transport) GetIdentifier(d *dd.DecoyRegistration) string {
return string(d.Keys.ConjureHMAC(hmacString))
}

// GetProto returns the next layer protocol that the transport uses. Implements
// the Transport interface.
func (Transport) GetProto() pb.IPProto {
return pb.IPProto_Tcp
}

// ParseParams gives the specific transport an option to parse a generic object
// into parameters provided by the client during registration.
func (Transport) ParseParams(libVersion uint, data *anypb.Any) (any, error) {
if data == nil {
return nil, nil
}

// For backwards compatibility we create a generic transport params object
// for transports that existed before the transportParams fields existed.
if libVersion < randomizeDstPortMinVersion {
f := false
return &pb.GenericTransportParams{
RandomizeDstPort: &f,
}, nil
}

var m = &pb.GenericTransportParams{}
err := anypb.UnmarshalTo(data, m, proto.UnmarshalOptions{})
return m, err
}

// GetDstPort Given the library version, a seed, and a generic object
// containing parameters the transport should be able to return the
// destination port that a clients phantom connection will attempt to reach
func (Transport) GetDstPort(libVersion uint, seed []byte, params any) (uint16, error) {

if libVersion < randomizeDstPortMinVersion {
return 0, transports.ErrTransportNotSupported
}

if params == nil {
return defaultPort, nil
}

parameters, ok := params.(*pb.GenericTransportParams)
if !ok {
return 0, fmt.Errorf("bad parameters provided")
}

if parameters.GetRandomizeDstPort() {
return transports.PortSelectorRange(portRangeMin, portRangeMax, seed)
}

return defaultPort, nil
}

// WrapConnection attempts to wrap the given connection in the transport. It
// takes the information gathered so far on the connection in data, attempts to
// identify itself, and if it positively identifies itself wraps the connection
// in the transport, returning a connection that's ready to be used by others.
//
// If the returned error is nil or non-nil and non-{ transports.ErrTryAgain,
// transports.ErrNotTransport }, the caller may no longer use data or conn.
func (t *Transport) WrapConnection(data *bytes.Buffer, c net.Conn, originalDst net.IP, regManager *dd.RegistrationManager) (*dd.DecoyRegistration, net.Conn, error) {
dataLen := data.Len()

if dataLen == 0 {
return nil, nil, transports.ErrTryAgain
}

req, err := http.ReadRequest(bufio.NewReader(data))
if err != nil {
// fmt.Printf("failed to read request\n%s\n", err)
return nil, nil, transports.ErrNotTransport
}

hmacIDStr := req.Header.Get("X-Ignore")
if hmacIDStr == "" {
return nil, nil, transports.ErrNotTransport
}
hmacID, err := base64.StdEncoding.DecodeString(hmacIDStr)
if err != nil {
return nil, nil, transports.ErrNotTransport
}

reg, ok := regManager.GetRegistrations(originalDst)[string(hmacID)]
if !ok {
return nil, nil, transports.ErrNotTransport
}

if req.ContentLength > 0 {
// buf := make([]byte, req.ContentLength)
// _, err := io.ReadFull(req.Body, buf)
// if err != nil {
// // this would be a very strange case to hit
// return nil, nil, transports.ErrNotTransport
// }
buf, err := io.ReadAll(req.Body)
if err != nil && !errors.Is(err, io.ErrUnexpectedEOF) {
// this would be a very strange case to hit
return nil, nil, fmt.Errorf("%w: failed to buffer http body: %w", transports.ErrNotTransport, err)
}
return reg, transports.PrependToConn(c, bytes.NewBuffer(buf)), nil
}
return reg, c, nil
}
Loading