Skip to content

Commit

Permalink
Initial skeleton
Browse files Browse the repository at this point in the history
  • Loading branch information
kelveny committed Nov 14, 2021
0 parents commit 9f01146
Show file tree
Hide file tree
Showing 7 changed files with 859 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.vscode/
tunnel
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Reverse TCP tunnel
For TCP-based services that do not have inbound end point, `Reverse TCP Tunnel` allows us to build a reverse TCP tunnel to open in bound access from a public end point. It virutally extends the TCP listening port to a remote machine in which a `Reverse TCP Tunnel` listener is running.

We use a simple signaling protocol for tunnel establishment and connection multiplexing.

## Tunnel listener
Tunnel listener runs in a public end point, when it receives a `ListenRequest` from service that needs tunnelled inbound access, it opens a dynamic TCP port at public interface and multiplexes traffic between service client and the service provider.

Example command to launch tunnel listener
```bash
./tunnel -l 5555
```

## Tunnel Connector
Tunnel connector runs wihin the private network boundary, it has access to services that requires tunnelled inbound access.

Example command to establish a reverse tunnelling setup

```bash
./tunnel -c localhost:5555 www.myservice.com:80
```

## Build
```
go build
```
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/kelveny/tunnel

go 1.16

require github.com/stretchr/testify v1.7.0 // indirect
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
317 changes: 317 additions & 0 deletions protocol.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
package main

import (
"bytes"
"encoding/binary"
"fmt"
"net"
)

const (
PDU_LISTEN_REQUEST = 1
PDU_LISTEN_RESPONSE = 2
PDU_TUNNEL_CONNECT_REQUEST = 3
PDU_TUNNEL_CONNECT_RESPONSE = 4
PDU_TUNNEL_DATA_INDICATION = 5
PDU_TUNNEL_DISCONNECT_REQUEST = 6
PDU_TUNNEL_DISCONNECT_RESPONSE = 7
)

type Serializable interface {
GetSerialType() int
GetSerialLength() uint32
SerializeTo(w *bytes.Buffer)
SerializeFrom(r *bytes.Buffer)
}

func serializeUInt32To(v uint32, w *bytes.Buffer) {
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, v)
w.Write(b)
}

func serializeUInt32From(r *bytes.Buffer) uint32 {
b := make([]byte, 4)
r.Read(b)
return binary.BigEndian.Uint32(b)
}

func getStringSerialLength(s string) uint32 {
return uint32(4 + len([]byte(s)))
}

func serializeStringTo(s string, w *bytes.Buffer) {
l := uint32(len([]byte(s)))
serializeUInt32To(l, w)
w.Write([]byte(s))
}

func serializeStringFrom(r *bytes.Buffer) string {
l := serializeUInt32From(r)

b := make([]byte, int(l))
r.Read(b)
return string(b)
}

func getPduSerialLength(pdu Serializable) uint32 {
return 1 + pdu.GetSerialLength()
}

func serializePduTo(pdu Serializable, w *bytes.Buffer) {
w.WriteByte(byte(pdu.GetSerialType()))
pdu.SerializeTo(w)
}

func serializePduFrom(r *bytes.Buffer) Serializable {
t, _ := r.ReadByte()
switch int(t) {
case PDU_LISTEN_REQUEST:
pdu := &ListenRequest{}
pdu.SerializeFrom(r)
return pdu

case PDU_LISTEN_RESPONSE:
pdu := &ListenResponse{}
pdu.SerializeFrom(r)
return pdu

case PDU_TUNNEL_CONNECT_REQUEST:
pdu := &TunnelConnectRequest{}
pdu.SerializeFrom(r)
return pdu

case PDU_TUNNEL_CONNECT_RESPONSE:
pdu := &TunnelConnectResponse{}
pdu.SerializeFrom(r)
return pdu

case PDU_TUNNEL_DATA_INDICATION:
pdu := &TunnelDataIndication{}
pdu.SerializeFrom(r)
return pdu

case PDU_TUNNEL_DISCONNECT_REQUEST:
pdu := &TunnelDisconnectRequest{}
pdu.SerializeFrom(r)
return pdu

case PDU_TUNNEL_DISCONNECT_RESPONSE:
pdu := &TunnelDisconnectResponse{}
pdu.SerializeFrom(r)
return pdu
}

fmt.Printf("Invalid protocol data\n")
return nil
}

func sendPdu(conn net.Conn, pdu Serializable) error {
l := getPduSerialLength(pdu)

b := make([]byte, 4)
binary.BigEndian.PutUint32(b, l)
_, err := conn.Write(b)
if err != nil {
return err
}

buf := bytes.NewBuffer(nil)
serializePduTo(pdu, buf)

_, err = conn.Write(buf.Bytes())

return err
}

/////////////////////////////////////////////////////////////////////////////

type ListenRequest struct {
proxyAddress string
proxyPort int
}

func (pdu *ListenRequest) GetSerialType() int {
return PDU_LISTEN_REQUEST
}

func (pdu *ListenRequest) GetSerialLength() uint32 {
return 4 + getStringSerialLength(pdu.proxyAddress)
}

func (pdu *ListenRequest) SerializeTo(w *bytes.Buffer) {
serializeStringTo(pdu.proxyAddress, w)
serializeUInt32To(uint32(pdu.proxyPort), w)
}

func (pdu *ListenRequest) SerializeFrom(r *bytes.Buffer) {
pdu.proxyAddress = serializeStringFrom(r)
pdu.proxyPort = int(serializeUInt32From(r))
}

/////////////////////////////////////////////////////////////////////////////

type ListenResponse struct {
proxyAddress string
proxyPort int
tunnelAddress string
tunnelPort int
}

func (pdu *ListenResponse) GetSerialType() int {
return PDU_LISTEN_RESPONSE
}

func (pdu *ListenResponse) GetSerialLength() uint32 {
return 8 + getStringSerialLength(pdu.proxyAddress) + getStringSerialLength(pdu.tunnelAddress)
}

func (pdu *ListenResponse) SerializeTo(w *bytes.Buffer) {
serializeStringTo(pdu.proxyAddress, w)
serializeUInt32To(uint32(pdu.proxyPort), w)
serializeStringTo(pdu.tunnelAddress, w)
serializeUInt32To(uint32(pdu.tunnelPort), w)
}

func (pdu *ListenResponse) SerializeFrom(r *bytes.Buffer) {
pdu.proxyAddress = serializeStringFrom(r)
pdu.proxyPort = int(serializeUInt32From(r))
pdu.tunnelAddress = serializeStringFrom(r)
pdu.tunnelPort = int(serializeUInt32From(r))
}

/////////////////////////////////////////////////////////////////////////////

// listener -> proxy
type TunnelConnectRequest struct {
dataConnectionHandle uint32
clientAddress string

proxyAddress string
proxyPort int
}

func (pdu *TunnelConnectRequest) GetSerialType() int {
return PDU_TUNNEL_CONNECT_REQUEST
}

func (pdu *TunnelConnectRequest) GetSerialLength() uint32 {
return 4 +
getStringSerialLength(pdu.clientAddress) +
getStringSerialLength(pdu.proxyAddress) +
4
}

func (pdu *TunnelConnectRequest) SerializeTo(w *bytes.Buffer) {
serializeUInt32To(uint32(pdu.dataConnectionHandle), w)
serializeStringTo(pdu.clientAddress, w)
serializeStringTo(pdu.proxyAddress, w)
serializeUInt32To(uint32(pdu.proxyPort), w)
}

func (pdu *TunnelConnectRequest) SerializeFrom(r *bytes.Buffer) {
pdu.dataConnectionHandle = Handle(serializeUInt32From(r))
pdu.clientAddress = serializeStringFrom(r)
pdu.proxyAddress = serializeStringFrom(r)
pdu.proxyPort = int(serializeUInt32From(r))
}

/////////////////////////////////////////////////////////////////////////////

type TunnelConnectResponse struct {
dataConnectionHandle uint32
proxyConnectionHandle uint32
}

func (pdu *TunnelConnectResponse) GetSerialType() int {
return PDU_TUNNEL_CONNECT_RESPONSE
}

func (pdu *TunnelConnectResponse) GetSerialLength() uint32 {
return 8
}

func (pdu *TunnelConnectResponse) SerializeTo(w *bytes.Buffer) {
serializeUInt32To(uint32(pdu.dataConnectionHandle), w)
serializeUInt32To(uint32(pdu.proxyConnectionHandle), w)
}

func (pdu *TunnelConnectResponse) SerializeFrom(r *bytes.Buffer) {
pdu.dataConnectionHandle = serializeUInt32From(r)
pdu.proxyConnectionHandle = serializeUInt32From(r)
}

/////////////////////////////////////////////////////////////////////////////

type TunnelDataIndication struct {
peerConnectionHandle uint32
data []byte
}

func (pdu *TunnelDataIndication) GetSerialType() int {
return PDU_TUNNEL_DATA_INDICATION
}

func (pdu *TunnelDataIndication) GetSerialLength() uint32 {
return uint32(4 + 4 + len(pdu.data))
}

func (pdu *TunnelDataIndication) SerializeTo(w *bytes.Buffer) {
serializeUInt32To(uint32(pdu.peerConnectionHandle), w)
serializeUInt32To(uint32(len(pdu.data)), w)
w.Write(pdu.data)
}

func (pdu *TunnelDataIndication) SerializeFrom(r *bytes.Buffer) {
pdu.peerConnectionHandle = serializeUInt32From(r)

l := serializeUInt32From(r)
pdu.data = make([]byte, int(l))
r.Read(pdu.data)
}

/////////////////////////////////////////////////////////////////////////////

type TunnelDisconnectRequest struct {
peerConnectionHandle uint32
}

func (pdu *TunnelDisconnectRequest) GetSerialType() int {
return PDU_TUNNEL_DISCONNECT_REQUEST
}

func (pdu *TunnelDisconnectRequest) GetSerialLength() uint32 {
return 4
}

func (pdu *TunnelDisconnectRequest) SerializeTo(w *bytes.Buffer) {
serializeUInt32To(uint32(pdu.peerConnectionHandle), w)
}

func (pdu *TunnelDisconnectRequest) SerializeFrom(r *bytes.Buffer) {
pdu.peerConnectionHandle = serializeUInt32From(r)
}

/////////////////////////////////////////////////////////////////////////////

type TunnelDisconnectResponse struct {
peerConnectionHandle uint32
}

func (pdu *TunnelDisconnectResponse) GetSerialType() int {
return PDU_TUNNEL_DISCONNECT_RESPONSE
}

func (pdu *TunnelDisconnectResponse) GetSerialLength() uint32 {
return 4
}

func (pdu *TunnelDisconnectResponse) SerializeTo(w *bytes.Buffer) {
serializeUInt32To(uint32(pdu.peerConnectionHandle), w)
}

func (pdu *TunnelDisconnectResponse) SerializeFrom(r *bytes.Buffer) {
pdu.peerConnectionHandle = serializeUInt32From(r)
}

/////////////////////////////////////////////////////////////////////////////
25 changes: 25 additions & 0 deletions protocol_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package main

import (
"bytes"
"testing"

"github.com/stretchr/testify/require"
)

func TestSerializePdu(t *testing.T) {
assert := require.New(t)

pdu := &ListenRequest{
proxyAddress: "www.google.com",
proxyPort: 443,
}

b := bytes.NewBuffer(nil)
serializePduTo(pdu, b)

pduClone := serializePduFrom(bytes.NewBuffer(b.Bytes()))
assert.True(pduClone != nil)
assert.True(pduClone.(*ListenRequest).proxyAddress == "www.google.com")
assert.True(pduClone.(*ListenRequest).proxyPort == 443)
}
Loading

0 comments on commit 9f01146

Please sign in to comment.