├── .gitignore ├── protoplex ├── protocols │ ├── ssh.go │ ├── tls.go │ ├── socks4.go │ ├── socks5.go │ ├── http.go │ ├── syncthing-relay.go │ ├── openvpn.go │ └── protocol.go ├── proxy.go └── multiplexer.go ├── go.mod ├── Dockerfile ├── .drone.yml ├── LICENSE ├── README.md ├── go.sum └── cmd └── protoplex └── protoplex.go /.gitignore: -------------------------------------------------------------------------------- 1 | # IDEA 2 | *.iml 3 | .idea/ 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # project-specific 11 | config.yml 12 | venv/ 13 | -------------------------------------------------------------------------------- /protoplex/protocols/ssh.go: -------------------------------------------------------------------------------- 1 | package protocols 2 | 3 | // NewSSHProtocol initializes a Protocol with a SSH signature. 4 | func NewSSHProtocol(targetAddress string) *Protocol { 5 | return &Protocol{ 6 | Name: "SSH", 7 | Target: targetAddress, 8 | MatchStartBytes: [][]byte{{'S', 'S', 'H', '-'}}, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /protoplex/protocols/tls.go: -------------------------------------------------------------------------------- 1 | package protocols 2 | 3 | // NewTLSProtocol initializes a Protocol with a TLS signature. 4 | func NewTLSProtocol(targetAddress string) *Protocol { 5 | return &Protocol{ 6 | Name: "TLS", 7 | Target: targetAddress, 8 | MatchStartBytes: [][]byte{{0x16, 0x03, 0x01}}, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /protoplex/protocols/socks4.go: -------------------------------------------------------------------------------- 1 | package protocols 2 | 3 | // NewSOCKS4Protocol initializes a Protocol with a SOCKS4 signature. 4 | func NewSOCKS4Protocol(targetAddress string) *Protocol { 5 | return &Protocol{ 6 | Name: "SOCKS4", 7 | Target: targetAddress, 8 | MatchStartBytes: [][]byte{{0x04}}, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /protoplex/protocols/socks5.go: -------------------------------------------------------------------------------- 1 | package protocols 2 | 3 | // NewSOCKS5Protocol initializes a Protocol with a SOCKS5 signature. 4 | func NewSOCKS5Protocol(targetAddress string) *Protocol { 5 | return &Protocol{ 6 | Name: "SOCKS5", 7 | Target: targetAddress, 8 | MatchStartBytes: [][]byte{{0x05}}, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Pandentia/protoplex 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect 7 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 // indirect 8 | github.com/rs/zerolog v1.15.0 9 | github.com/stretchr/testify v1.4.0 // indirect 10 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 11 | ) 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # build 2 | FROM golang:1-alpine AS build 3 | 4 | RUN apk add git 5 | RUN mkdir -p /go/src/github.com/Pandentia 6 | COPY ./ /go/src/github.com/Pandentia/protoplex 7 | RUN go get github.com/Pandentia/protoplex/cmd/protoplex 8 | 9 | # deploy 10 | FROM alpine:latest 11 | COPY --from=build /go/bin/protoplex /protoplex 12 | 13 | USER 999 14 | ENTRYPOINT ["/protoplex"] 15 | EXPOSE 8443/tcp 16 | STOPSIGNAL SIGINT 17 | -------------------------------------------------------------------------------- /protoplex/proxy.go: -------------------------------------------------------------------------------- 1 | package protoplex 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | func proxy(from net.Conn, to net.Conn, closed chan bool) { 8 | data := make([]byte, 4096) // 4KiB buffer 9 | 10 | for { 11 | n, err := from.Read(data) 12 | if err != nil { 13 | closed <- true 14 | return 15 | } 16 | _, err = to.Write(data[:n]) 17 | if err != nil { 18 | closed <- true 19 | return 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /protoplex/protocols/http.go: -------------------------------------------------------------------------------- 1 | package protocols 2 | 3 | import "regexp" 4 | 5 | // NewHTTPProtocol initializes a Protocol with a HTTP signature. 6 | func NewHTTPProtocol(targetAddress string) *Protocol { 7 | regexes := []*regexp.Regexp{ 8 | regexp.MustCompile("^[A-Z]+ .+ HTTP/"), 9 | } 10 | 11 | return &Protocol{ 12 | Name: "HTTP", 13 | Target: targetAddress, 14 | MatchRegexes: regexes, 15 | NoComparisonBeforeBytes: 11, // GET / HTTP/ 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /protoplex/protocols/syncthing-relay.go: -------------------------------------------------------------------------------- 1 | package protocols 2 | 3 | // NewSTRelayProtocol initializes a Protocol with a Syncthing Relay signature. 4 | // 5 | // Deprecated: This signature does not function properly and unless further developed, will not establish a working 6 | // connection. 7 | func NewSTRelayProtocol(targetAddress string) *Protocol { 8 | return &Protocol{ 9 | Name: "STRelay", 10 | Target: targetAddress, 11 | MatchBytes: [][]byte{ 12 | {'b', 'e', 'p', '-', 'r', 'e', 'l', 'a', 'y'}, 13 | {'b', 'e', 'p', '/'}, 14 | }, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /protoplex/protocols/openvpn.go: -------------------------------------------------------------------------------- 1 | package protocols 2 | 3 | import "regexp" 4 | 5 | // NewOpenVPNProtocol initializes a Protocol with an OpenVPN signature. 6 | func NewOpenVPNProtocol(targetAddress string) *Protocol { 7 | return &Protocol{ 8 | Name: "OpenVPN", 9 | Target: targetAddress, 10 | // MatchStartBytes: [][]byte{{0x00, 0x0e, 0x38}}, 11 | MatchRegexes: []*regexp.Regexp{ 12 | regexp.MustCompile(`^\x00[\x0d-\xff]\x38`), // asumming this variant is more common in newer clients 13 | regexp.MustCompile(`^\x00[\x0d-\xff]$`), 14 | }, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /protoplex/protocols/protocol.go: -------------------------------------------------------------------------------- 1 | package protocols 2 | 3 | import "regexp" 4 | 5 | // Protocol is the implementation of a protocol signature. 6 | type Protocol struct { 7 | Name string // the protocol name for auditing 8 | Target string // the proxy target 9 | MatchStartBytes [][]byte // the bytestrings by which to match this protocol (prefixes) 10 | MatchBytes [][]byte // the bytestrings by which to match this protocol (contains) 11 | MatchRegexes []*regexp.Regexp // the regexes by which to match this protocol 12 | NoComparisonBeforeBytes int // we know we won't match before this many bytes, set to 0 to ignore 13 | NoComparisonAfterBytes int // we know we won't match after this many bytes, set to 0 to ignore 14 | } 15 | -------------------------------------------------------------------------------- /.drone.yml: -------------------------------------------------------------------------------- 1 | kind: pipeline 2 | type: docker 3 | name: build 4 | 5 | steps: 6 | - name: lint 7 | image: golang:1.14-alpine 8 | commands: 9 | - go get golang.org/x/lint/golint github.com/securego/gosec/cmd/gosec 10 | - test -z $(gofmt -l .) 11 | - golint -set_exit_status ./... 12 | - gosec -quiet ./... 13 | 14 | - name: prepare and build 15 | image: golang:1.14-alpine 16 | commands: 17 | - go get github.com/mitchellh/gox 18 | - go get -v -d ./... 19 | - mkdir -p dist 20 | - arch=$(go tool dist list | 21 | tr "\n" " " | 22 | sed -r "s~((android|nacl)/\w+|darwin/arm(64)?)~~g" | 23 | xargs) 24 | - version=$(if [ -z "$DRONE_TAG" ]; then echo $DRONE_COMMIT; else echo $DRONE_TAG; fi) 25 | - gox -parallel=16 -osarch="$arch" -output="dist/{{.Dir}}_{{.OS}}_{{.Arch}}" -ldflags "-X main.version=$version" ./... 26 | environment: 27 | CGO_ENABLED: 0 28 | 29 | - name: release binaries 30 | image: plugins/github-release 31 | settings: 32 | api_key: 33 | from_secret: github_token 34 | files: dist/* 35 | when: 36 | event: tag 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © 2018-2019 Pandentia and contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # protoplex 2 | 3 | *An application protocol multiplexer* 4 | 5 | [![Build Status](https://cloud.drone.io/api/badges/Pandentia/protoplex/status.svg?ref=refs/heads/mistress)](https://cloud.drone.io/Pandentia/protoplex) 6 | 7 | ## What is this? 8 | 9 | In a nutshell, this application lets you run multiple kinds of applications 10 | on a single port. This is useful for, for instance, running an OpenVPN server 11 | and a TLS/HTTPS server on port 443, which in turn is useful for evading 12 | firewalls that block all other outbound ports. 13 | 14 | ## Running 15 | 16 | ### Native 17 | 18 | Assuming you have a properly configured Go setup, get and compile the multiplexer with 19 | 20 | ```bash 21 | go get github.com/Pandentia/protoplex/cmd/protoplex 22 | ``` 23 | 24 | and then run it with (for example, to run SSH and HTTPS) 25 | 26 | ```bash 27 | protoplex --ssh your_ssh_host:22 --tls your_webserver:443 28 | ``` 29 | 30 | Protoplex is now running on port `8443` and ready to accept connections. 31 | 32 | For more extensive configuration, please see the output of `--help`. 33 | 34 | ### Docker 35 | 36 | [A docker image may be used](https://hub.docker.com/r/pandentia/protoplex) 37 | for ease of use and deployment. 38 | 39 | ## Goals 40 | 41 | The concepts for this multiplexer were as follows: 42 | 43 | - Resource usage about on par with `sslh` 44 | - Easily extensible 45 | - Highly dynamic 46 | 47 | To this end, protoplex supports multiple matching methods for protocols: 48 | 49 | - Bytestring comparison 50 | - Regex matching 51 | 52 | These can both be implemented for a protocol, with bytestrings taking 53 | priority (due to efficiency). In addition, protocols support matching limits, 54 | reducing the amount of protocols evaluated for a given handshake. 55 | 56 | ## Protocol support 57 | 58 | Currently supported protocols are: 59 | 60 | - SSH 61 | - HTTP 62 | - TLS (/ HTTPS) 63 | - OpenVPN 64 | - SOCKS4 / SOCKS5 65 | 66 | Feel free to [file an issue](https://github.com/Pandentia/protoplex/issues/new) 67 | on the GitHub repository if you want a protocol to be supported. Please include 68 | steps to accurately reproduce your client setup. 69 | 70 | Alternatively, you may submit a pull request. 71 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= 2 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 3 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= 4 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 5 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 6 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 9 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 10 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 11 | github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= 12 | github.com/rs/zerolog v1.15.0 h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY= 13 | github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= 14 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 15 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 16 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 17 | github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= 18 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 19 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 20 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 21 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 22 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 23 | golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 24 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= 25 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 26 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 27 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 28 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 29 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 30 | -------------------------------------------------------------------------------- /cmd/protoplex/protoplex.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/Pandentia/protoplex/protoplex" 8 | "github.com/Pandentia/protoplex/protoplex/protocols" 9 | "github.com/rs/zerolog" 10 | "gopkg.in/alecthomas/kingpin.v2" 11 | ) 12 | 13 | var version string 14 | 15 | func printVersion() { 16 | if version == "" { 17 | fmt.Println("Version has not been set.") 18 | os.Exit(1) 19 | return 20 | } 21 | fmt.Println(version) 22 | os.Exit(0) 23 | } 24 | 25 | func main() { 26 | app := kingpin.New("protoplex", "A fast and simple protocol multiplexer.") 27 | logger := zerolog.New(os.Stdout).With().Timestamp().Logger() 28 | 29 | version := app.Flag("version", "Prints the current program version").Short('V').Bool() 30 | 31 | bind := app.Flag("bind", "The address to bind to").Short('b').Default("0.0.0.0:8443").String() 32 | verbose := app.Flag("verbose", "Enables debug logging").Short('v').Bool() 33 | pretty := app.Flag("pretty", "Enables pretty logging").Short('p').Bool() 34 | 35 | ssh := app.Flag("ssh", "The SSH server address").String() 36 | tls := app.Flag("tls", "The TLS/HTTPS server address").String() 37 | openvpn := app.Flag("ovpn", "The OpenVPN server address").String() 38 | http := app.Flag("http", "The HTTP server address").String() 39 | socks5 := app.Flag("socks5", "The SOCKS5 server address").String() 40 | socks4 := app.Flag("socks4", "The SOCKS4 server address").String() 41 | // stRelay := flag.String("strelay", "", "The Syncthing Relay server address") 42 | 43 | _, _ = app.Parse(os.Args[1:]) 44 | 45 | if *version { 46 | printVersion() 47 | } 48 | 49 | if *pretty { 50 | logger = logger.Output(zerolog.ConsoleWriter{Out: os.Stderr}) 51 | } 52 | 53 | if *verbose { 54 | logger = logger.Level(zerolog.DebugLevel) 55 | } else { 56 | logger = logger.Level(zerolog.InfoLevel) 57 | } 58 | 59 | p := make([]*protocols.Protocol, 0, 7) 60 | // contain-bytes-matched protocols (usually ALPNs) take priority 61 | // (due to start-bytes-matching overriding some of them) 62 | // if *stRelay != "" { 63 | // logger.Warningf("Syncthing Relay support is deprecated.\n") 64 | // p = append(p, protocols.NewSTRelayProtocol(*stRelay)) 65 | // } 66 | // start-bytes-matched protocols are the next most efficient approach 67 | if *tls != "" { 68 | p = append(p, protocols.NewTLSProtocol(*tls)) 69 | } 70 | if *ssh != "" { 71 | p = append(p, protocols.NewSSHProtocol(*ssh)) 72 | } 73 | if *socks5 != "" { 74 | p = append(p, protocols.NewSOCKS5Protocol(*socks5)) 75 | } 76 | if *socks4 != "" { 77 | p = append(p, protocols.NewSOCKS4Protocol(*socks4)) 78 | } 79 | // regex protocols come at the end of the chain as they'll be expensive anyway if used 80 | if *openvpn != "" { 81 | p = append(p, protocols.NewOpenVPNProtocol(*openvpn)) 82 | } 83 | if *http != "" { 84 | p = append(p, protocols.NewHTTPProtocol(*http)) 85 | } 86 | 87 | protoplex.RunServer(*bind, p, logger) 88 | } 89 | -------------------------------------------------------------------------------- /protoplex/multiplexer.go: -------------------------------------------------------------------------------- 1 | package protoplex 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | "os" 7 | "time" 8 | 9 | "github.com/rs/zerolog" 10 | 11 | "github.com/Pandentia/protoplex/protoplex/protocols" 12 | ) 13 | 14 | // RunServer runs protoplex 15 | func RunServer(bind string, p []*protocols.Protocol, logger zerolog.Logger) { 16 | logger = logger.With().Str("module", "listener").Logger() 17 | 18 | if len(p) == 0 { 19 | logger.Warn().Msg("No protocols defined.") 20 | } else { 21 | logger.Info().Msg("Protocol chain:") 22 | for _, proto := range p { 23 | logger.Info().Str("protocol", proto.Name).Str("target", proto.Target).Msgf("- %s @ %s", proto.Name, proto.Target) 24 | } 25 | } 26 | 27 | listener, err := net.Listen("tcp", bind) 28 | if err != nil { 29 | logger.Fatal().Str("bind", bind).Err(err).Msg("Unable to create listener.") 30 | os.Exit(1) 31 | } 32 | defer listener.Close() 33 | logger.Info().Str("bind", listener.Addr().String()).Msg("Listening...") 34 | for { 35 | conn, err := listener.Accept() 36 | if err != nil { 37 | logger.Debug().Err(err).Msg("Error while accepting connection.") 38 | } 39 | go ConnectionHandler(conn, p, 40 | logger.With().Str("module", "handler").Str("ip", conn.RemoteAddr().String()).Logger()) 41 | } 42 | } 43 | 44 | // ConnectionHandler connects a net.Conn with a proxy target given a list of protocols 45 | func ConnectionHandler(conn net.Conn, p []*protocols.Protocol, logger zerolog.Logger) { 46 | defer conn.Close() // the connection must close after this goroutine exits 47 | 48 | identifyBuffer := make([]byte, 1024) // at max 1KB buffer to identify payload 49 | 50 | // read the handshake into our buffer 51 | _ = conn.SetReadDeadline(time.Now().Add(15 * time.Second)) // 15-second timeout to identify 52 | n, err := conn.Read(identifyBuffer) 53 | if err != nil { 54 | logger.Debug().Err(err).Msg("Identify read error. Connection closed.") 55 | return 56 | } 57 | _ = conn.SetReadDeadline(time.Time{}) // reset our timeout 58 | 59 | // determine the protocol 60 | protocol := DetermineProtocol(identifyBuffer[:n], p) 61 | if protocol == nil { // unsuccessful protocol identify, close and forget 62 | logger.Debug().Msg("Protocol unrecognized. Connection closed.") 63 | return 64 | } 65 | logger = logger.With().Str("protocol", protocol.Name).Str("target", protocol.Target).Logger() 66 | logger.Debug().Msg("Protocol recognized.") 67 | 68 | // establish our connection with the target 69 | targetConn, err := net.Dial("tcp", protocol.Target) 70 | if err != nil { 71 | logger.Debug().Err(err).Msg("Remote connection unsuccessful.") 72 | return // we were unable to establish the connection with the proxy target 73 | } 74 | defer targetConn.Close() 75 | _, err = targetConn.Write(identifyBuffer[:n]) // tell them everything they just told us 76 | if err != nil { 77 | logger.Debug().Err(err).Msg("Remote disconnected us during identify.") 78 | return // remote rejected us?? okay. 79 | } 80 | 81 | // run the proxy readers 82 | closed := make(chan bool, 2) 83 | go proxy(conn, targetConn, closed) 84 | go proxy(targetConn, conn, closed) 85 | 86 | // wait for any connection to close 87 | <-closed 88 | logger.Debug().Msg("Connection closed.") 89 | } 90 | 91 | // DetermineProtocol determines a Protocol based on a given handshake 92 | func DetermineProtocol(data []byte, p []*protocols.Protocol) *protocols.Protocol { 93 | dataLength := len(data) 94 | for _, protocol := range p { 95 | // since every protocol is different, let's limit the way we match things 96 | if (protocol.NoComparisonBeforeBytes != 0 && dataLength < protocol.NoComparisonBeforeBytes) || 97 | (protocol.NoComparisonAfterBytes != 0 && dataLength > protocol.NoComparisonAfterBytes) { 98 | continue // avoids unnecessary comparisons 99 | } 100 | 101 | // compare against bytestrings first for efficiency 102 | // first "contains" (due to ALPNs we can't match against TLS start bytes first) 103 | for _, byteSlice := range protocol.MatchBytes { 104 | byteSliceLength := len(byteSlice) 105 | if dataLength < byteSliceLength { 106 | continue 107 | } 108 | if bytes.Contains(data, byteSlice) { 109 | return protocol 110 | } 111 | } 112 | // then against prefixes 113 | for _, byteSlice := range protocol.MatchStartBytes { 114 | byteSliceLength := len(byteSlice) 115 | if dataLength < byteSliceLength { 116 | continue 117 | } 118 | if bytes.Equal(byteSlice, data[:byteSliceLength]) { 119 | return protocol 120 | } 121 | } 122 | 123 | // let's use regex matching as a last resort 124 | for _, regex := range protocol.MatchRegexes { 125 | if regex.Match(data) { 126 | return protocol 127 | } 128 | } 129 | } 130 | return nil 131 | } 132 | --------------------------------------------------------------------------------