Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
darvid committed Jan 14, 2017
0 parents commit 1d0b894
Show file tree
Hide file tree
Showing 13 changed files with 566 additions and 0 deletions.
13 changes: 13 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2

[*.yml]
indent_style = space
indent_size = 2

[{Makefile,*.go}]
indent_style = tab
indent_size = 4
36 changes: 36 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
*.sublime-workspace

dist/
coverage.html

# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so

# Folders
_obj
_test

# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out

*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*

_testmain.go

*.exe
*.test
*.prof

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# External packages folder
vendor/
0
17 changes: 17 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
language: go
sudo: false
go:
- tip
install:
- go get -v github.com/golang/lint/golint
- go get -v github.com/Masterminds/glide
- go get -v github.com/mattn/goveralls
- go get -v github.com/mitchellh/gox
- go get -v github.com/tcnksm/ghr
script:
- glide install
- make test
- $HOME/gopath/bin/goveralls -service=travis-ci
after_success:
- make build
- ghr --username darvid --token $GITHUB_TOKEN --replace $TRAVIS_TAG dist/
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2017 David Gidwani

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
21 changes: 21 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
COVERPROFILE=coverage.out
TAG_NAME=$(shell git describe --tags --long --dirty 2>/dev/null || echo "unknown")
GOX_LDFLAGS="-X main.version=$(TAG_NAME) -X main.buildTime=$(shell date -u +%Y-%m-%dT%H:%M:%S%z)"

.PHONY: clean install test

default: build

build: test
gox -output "dist/{{.OS}}_{{.Arch}}_{{.Dir}}" -ldflags $(GOX_LDFLAGS)

install:
go install -ldflags $(GOX_LDFLAGS)

clean:
go clean

test:
go test -v -coverprofile=$(COVERPROFILE)
go tool cover -html=$(COVERPROFILE)
rm $(COVERPROFILE)
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# emissary: A TCP service multiplexer

[![Coveralls](https://img.shields.io/coveralls/darvid/emissary.svg)](https://coveralls.io/github/darvid/emissary) [![Go Report Card](https://goreportcard.com/badge/github.com/darvid/emissary)](https://goreportcard.com/report/github.com/darvid/emissary) [![Travis](https://img.shields.io/travis/darvid/emissary.svg)](https://travis-ci.org/darvid/emissary)

[![asciicast](https://asciinema.org/a/99252.png)](https://asciinema.org/a/99252)

**emissary** provides a command to multiplex TCP services on the same port,
and route connections to different upstream addresses based on their starting
bytes.

Upstreams are configured through *upstream rules*, which are a simple
regexp/remote address pair.

## Examples

```shell
# Forward all HTTP GET requests to httpbin.org
$ emissary -bind localhost:1080 -upstream '/^GET/:httpbin.org:80'

# Forward SOCKS5 traffic to a local SOCKS5 server
$ emissary -bind localhost:1080 -upstream '/^\x05/:localhost:1081'
```

Any number of upstreams may be chained together.

## Usage

Usage of emissary:
-alsologtostderr
log to standard error as well as files
-bind string
bind address (default "localhost:1080")
-buffersize int
buffer size for first read (default 4096)
-log_backtrace_at value
when logging hits line file:N, emit a stack trace (default :0)
-log_dir string
If non-empty, write log files in this directory
-logtostderr
log to standard error instead of files
-stderrthreshold value
logs at or above this threshold go to stderr
-upstream value
list of upstream rules (default [])
-v value
log level for V logs
-version
show version
-vmodule value
comma-separated list of pattern=N settings for file-filtered logging

# Similar projects

A few projects exist that provide TCP service muxing. The ones mentioned below
are libraries which require writing custom applications or scripts, which may be
preferential to some, depending on the use case.

* [node-port-mux](https://github.com/robertklep/node-port-mux)
* [cmux](https://github.com/soheilhy/cmux)
8 changes: 8 additions & 0 deletions emissary.sublime-project
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"folders":
[
{
"path": "."
}
]
}
6 changes: 6 additions & 0 deletions glide.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions glide.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package: github.com/darvid/emissary
import:
- package: github.com/golang/glog
55 changes: 55 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package main

import (
"flag"
"fmt"
"net"

"github.com/golang/glog"
)

var (
version = "unknown"
buildTime = "unknown"

bindAddr string
bufferSize int
upstreamRules UpstreamRuleList
showVersion bool
)

func init() {
flag.IntVar(&bufferSize, "buffersize", 4096, "buffer size for first read")
flag.StringVar(&bindAddr, "bind", "localhost:1080", "bind address")
flag.Var(&upstreamRules, "upstream", "list of upstream rules")
flag.BoolVar(&showVersion, "version", false, "show version")
}

func main() {
flag.Parse()
if showVersion {
fmt.Printf("emissary version: %s\n", version)
fmt.Printf("build time: %s\n", buildTime)
} else {
if len(upstreamRules) == 0 {
flag.PrintDefaults()
} else {
listener, err := net.Listen("tcp", bindAddr)
if err != nil {
glog.Fatalln(err)
}
defer listener.Close()
glog.Infof("listening on %s", listener.Addr().String())

for {
conn, err := listener.Accept()
defer conn.Close()
if err != nil {
panic(err)
}
go upstreamRules.HandleConn(conn, bufferSize)
}
}
}
return
}
104 changes: 104 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package main

import (
"bufio"
"bytes"
"fmt"
"net"
"os"
"regexp"
"testing"
)

var listenAddrPattern = regexp.MustCompile(`listening on (.+?:\d+)`)

func TestMain(t *testing.T) {
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
os.Args = []string{"emissary"}

_, stderrWriter, _ := os.Pipe()
oldStderr := os.Stderr
os.Stderr = stderrWriter
defer func() { os.Stderr = oldStderr }()

t.Log("Testing main without args...")
main()

t.Log("Testing main with single upstream...")
listener, err := net.Listen("tcp", "localhost:0")
if err != nil {
t.Errorf("Failed to start listener: %s", err)
}

stderrReader, stderrWriter, _ := os.Pipe()
go func(f *os.File) {
oldStderr := os.Stderr
os.Stderr = f
defer func() { os.Stderr = oldStderr }()

os.Args = []string{
"emissary",
"-alsologtostderr",
"-bind",
"localhost:0",
"-upstream",
fmt.Sprintf("/^GET/:%s", listener.Addr().String()),
}
main()
}(stderrWriter)
reader := bufio.NewReader(stderrReader)
line, _, err := reader.ReadLine()
if err != nil {
t.Errorf("Failed to read output from cli: %s", err)
}
matchAddr := listenAddrPattern.FindStringSubmatch(string(line))
if len(matchAddr) != 2 {
t.Errorf("Unexpected output line from cli: %s", line)
}
server, err := net.Dial("tcp", matchAddr[1])
defer server.Close()
if err != nil {
t.Errorf("Failed to connect to emissary: %s", err)
}
server.Write([]byte("GET /\n"))
client, err := listener.Accept()
defer client.Close()
if err != nil {
t.Errorf("Failed to accept connection: %s", err)
}
// buf := make([]byte, 6)
// client.Read(buf)
response := []byte("HTTP/1.0 740 Computer says no")
client.Write(response)
buf := make([]byte, len(response))
server.Read(buf)
if bytes.Compare(buf, response) != 0 {
t.Error("Expected responses to match")
t.Errorf("`%s' != `%s'", response, buf)
}
}

func TestMainVersion(t *testing.T) {
oldArgs := os.Args
defer func() { os.Args = oldArgs }()
os.Args = []string{"emissary", "-version"}

stdoutReader, stdoutWriter, _ := os.Pipe()
oldStdout := os.Stdout
os.Stdout = stdoutWriter
defer func() { os.Stdout = oldStdout }()

t.Log("Testing main version flag...")
main()

buf := make([]byte, 46)
stdoutReader.Read(buf)

expectedOutput := []byte(`emissary version: unknown
build time: unknown
`)
if bytes.Compare(expectedOutput, buf) != 0 {
t.Errorf("Unexpected output from `emissary -version'")
}
}
Loading

0 comments on commit 1d0b894

Please sign in to comment.