progressing to users and remote address whitelists

This commit is contained in:
Jaime Pillora
2015-03-19 01:25:20 +11:00
14 changed files with 608 additions and 342 deletions
+71 -82
View File
@@ -1,33 +1,34 @@
package chclient
import (
"errors"
"fmt"
"io"
"net"
"net/url"
"regexp"
"strings"
"time"
"github.com/hashicorp/yamux"
"github.com/jpillora/backoff"
"github.com/jpillora/chisel/share"
"github.com/jpillora/conncrypt"
"golang.org/x/crypto/ssh"
"golang.org/x/net/websocket"
)
type Client struct {
*chshare.Logger
config *chshare.Config
encconfig []byte
key, server string
sshConfig *ssh.ClientConfig
proxies []*Proxy
session *yamux.Session
sshConn ssh.Conn
fingerprint string
server string
keyPrefix string
running bool
runningc chan error
}
func NewClient(key, server string, remotes ...string) (*Client, error) {
func NewClient(keyPrefix, auth, server string, remotes ...string) (*Client, error) {
//apply default scheme
if !strings.HasPrefix(server, "http") {
@@ -60,20 +61,27 @@ func NewClient(key, server string, remotes ...string) (*Client, error) {
config.Remotes = append(config.Remotes, r)
}
encconfig, err := chshare.EncodeConfig(config)
if err != nil {
return nil, fmt.Errorf("Failed to encode config: %s", err)
}
return &Client{
c := &Client{
Logger: chshare.NewLogger("client"),
config: config,
encconfig: encconfig,
key: key,
server: u.String(),
keyPrefix: keyPrefix,
running: true,
runningc: make(chan error, 1),
}, nil
}
c.sshConfig = &ssh.ClientConfig{
ClientVersion: chshare.ProtocolVersion + "-client",
HostKeyCallback: c.verifyServer,
}
user, pass := chshare.ParseAuth(auth)
if user != "" {
c.sshConfig.User = user
c.sshConfig.Auth = []ssh.AuthMethod{ssh.Password(pass)}
}
return c, nil
}
//Start then Wait
@@ -82,6 +90,15 @@ func (c *Client) Run() error {
return c.Wait()
}
func (c *Client) verifyServer(hostname string, remote net.Addr, key ssh.PublicKey) error {
f := chshare.FingerprintKey(key)
if c.keyPrefix != "" && !strings.HasPrefix(f, c.keyPrefix) {
return fmt.Errorf("Invalid fingerprint (Got %s)", f)
}
c.fingerprint = f
return nil
}
//Starts the client
func (c *Client) Start() {
go c.start()
@@ -90,37 +107,24 @@ func (c *Client) Start() {
func (c *Client) start() {
c.Infof("Connecting to %s\n", c.server)
//proxies all use this function
openStream := func() (net.Conn, error) {
if c.session == nil || c.session.IsClosed() {
return nil, c.Errorf("no session available")
}
stream, err := c.session.Open()
if err != nil {
return nil, err
}
return stream, nil
}
//prepare proxies
for id, r := range c.config.Remotes {
proxy := NewProxy(c, id, r, openStream)
proxy := NewProxy(c, id, r)
go proxy.start()
c.proxies = append(c.proxies, proxy)
}
var connerr error
b := &backoff.Backoff{Max: 15 * time.Second}
//connection loop!
var connerr error
b := &backoff.Backoff{Max: 5 * time.Minute}
for {
if !c.running {
break
}
if connerr != nil {
d := b.Duration()
c.Infof("Connerr: %v", connerr)
c.Infof("Retrying in %s...", d)
c.Infof("Retrying in %s...\n", d)
connerr = nil
time.Sleep(d)
}
@@ -131,58 +135,43 @@ func (c *Client) start() {
continue
}
conn := net.Conn(ws)
if c.key != "" {
conn = conncrypt.New(conn, &conncrypt.Config{Password: c.key})
sshConn, chans, reqs, err := ssh.NewClientConn(ws, "", c.sshConfig)
//NOTE break -> dont retry on handshake failures
if err != nil {
if strings.Contains(err.Error(), "unable to authenticate") {
c.Infof("Authentication failed")
} else {
c.Infof(err.Error())
}
break
}
//write config, read result
chshare.SizeWrite(conn, c.encconfig)
resp := chshare.SizeRead(conn)
if string(resp) != "Handshake Success" {
//no point in retrying
c.runningc <- errors.New("Handshake failed")
conn.Close()
conf, _ := chshare.EncodeConfig(c.config)
_, conerr, err := sshConn.SendRequest("config", true, conf)
if err != nil {
c.Infof("Config verification failed", c.fingerprint)
break
}
if len(conerr) > 0 {
c.Infof(string(conerr))
break
}
// Setup client side of yamux
c.session, err = yamux.Client(conn, nil)
if err != nil {
connerr = err
continue
}
c.Infof("Connected (%s)", c.fingerprint)
//connected
b.Reset()
//check latency
go func() {
d, err := c.session.Ping()
if err == nil {
c.Infof("Connected (Latency: %s)\n", d)
} else {
c.Infof("Connected\n")
}
}()
//signal is connected
connected := make(chan bool)
//poll websocket state
go func() {
for {
if c.session.IsClosed() {
connerr = c.Errorf("disconnected")
c.Infof("Disconnected\n")
close(connected)
break
}
time.Sleep(100 * time.Millisecond)
}
}()
//block!
<-connected
c.sshConn = sshConn
go ssh.DiscardRequests(reqs)
go chshare.RejectStreams(chans)
err = sshConn.Wait()
//disconnected
c.sshConn = nil
if err != nil && err != io.EOF {
connerr = err
c.Infof("Disconnection error: %s", err)
continue
}
c.Infof("Disconnected\n")
}
close(c.runningc)
}
@@ -195,8 +184,8 @@ func (c *Client) Wait() error {
//Close manual stops the client
func (c *Client) Close() error {
c.running = false
if c.session == nil {
if c.sshConn == nil {
return nil
}
return c.session.Close()
return c.sshConn.Close()
}
+23 -22
View File
@@ -1,7 +1,7 @@
package chclient
import (
"encoding/binary"
"io"
"net"
"github.com/jpillora/chisel/share"
@@ -9,18 +9,18 @@ import (
type Proxy struct {
*chshare.Logger
id int
count int
remote *chshare.Remote
openStream func() (net.Conn, error)
client *Client
id int
count int
remote *chshare.Remote
}
func NewProxy(c *Client, id int, remote *chshare.Remote, openStream func() (net.Conn, error)) *Proxy {
func NewProxy(c *Client, id int, remote *chshare.Remote) *Proxy {
return &Proxy{
Logger: c.Logger.Fork("%s:%s#%d", remote.RemoteHost, remote.RemotePort, id+1),
id: id,
remote: remote,
openStream: openStream,
Logger: c.Logger.Fork("%s:%s#%d", remote.RemoteHost, remote.RemotePort, id+1),
client: c,
id: id,
remote: remote,
}
}
@@ -43,27 +43,28 @@ func (p *Proxy) start() {
}
}
func (p *Proxy) accept(src net.Conn) {
func (p *Proxy) accept(src io.ReadWriteCloser) {
p.count++
cid := p.count
clog := p.Fork("conn#%d", cid)
l := p.Fork("conn#%d", cid)
clog.Debugf("Open")
l.Debugf("Open")
dst, err := p.openStream()
if err != nil {
clog.Debugf("Stream error: %s", err)
if p.client.sshConn == nil {
l.Debugf("No server connection")
src.Close()
return
}
//write endpoint id
b := make([]byte, 2)
binary.BigEndian.PutUint16(b, uint16(p.id))
dst.Write(b)
remoteAddr := p.remote.RemoteHost + ":" + p.remote.RemotePort
dst, err := chshare.OpenStream(p.client.sshConn, remoteAddr)
if err != nil {
l.Infof("Stream error: %s", err)
src.Close()
return
}
//then pipe
s, r := chshare.Pipe(src, dst)
clog.Debugf("Close (sent %d received %d)", s, r)
l.Debugf("Close (sent %d received %d)", s, r)
}
+9
View File
@@ -0,0 +1,9 @@
{
"foo:bar": [
"0.0.0.0:3000"
],
"ping:pong": [
"0.0.0.0:[45]000",
"example.com:80"
]
}
+43 -35
View File
@@ -59,13 +59,13 @@ func main() {
}
var commonHelp = `
--key, Enables AES256 encryption and specify the string to
use to derive the key (derivation is performed using PBKDF2
with 2048 iterations of SHA256).
-v, Enable verbose logging
--help, This help text
Read more:
https://github.com/jpillora/chisel
`
var serverHelp = `
@@ -78,22 +78,32 @@ var serverHelp = `
--port, Defines the HTTP listening port (defaults to 8080).
--key, An optional string to seed the generation of a ECC public
and private key pair. All commications will be secured using this
key pair. Share the resulting fingerprint with clients to prevent
man-in-the-middle attacks.
--authfile, An optional path to a users.json file. This file should
be an object with users defined like:
"<user:pass>": ["<addr-regex>","<addr-regex>"]
when <user> connects, their <pass> will be verified and then
each of the remote addresses will be compared against the list
of address regular expressions for a match. Addresses will
always come in the form "<host/ip>:<port>".
--proxy, Specifies the default proxy target to use when chisel
receives a normal HTTP request.
` + commonHelp + `
Read more:
https://github.com/jpillora/chisel
`
` + commonHelp
func server(args []string) {
flags := flag.NewFlagSet("server", flag.ContinueOnError)
hostf := flags.String("host", "", "")
portf := flags.String("port", "", "")
authf := flags.String("key", "", "")
proxyf := flags.String("proxy", "", "")
host := flags.String("host", "", "")
port := flags.String("port", "", "")
key := flags.String("key", "", "")
authfile := flags.String("authfile", "", "")
proxy := flags.String("proxy", "", "")
verbose := flags.Bool("v", false, "")
flags.Usage = func() {
@@ -102,28 +112,21 @@ func server(args []string) {
}
flags.Parse(args)
host := *hostf
if host == "" {
host = os.Getenv("HOST")
if *host == "" {
*host = os.Getenv("HOST")
}
if host == "" {
host = "0.0.0.0"
if *host == "" {
*host = "0.0.0.0"
}
port := *portf
if port == "" {
port = os.Getenv("PORT")
if *port == "" {
*port = os.Getenv("PORT")
}
if port == "" {
port = "8080"
if *port == "" {
*port = "8080"
}
key := *authf
if key == "" {
key = os.Getenv("key")
}
s, err := chserver.NewServer(key, *proxyf)
s, err := chserver.NewServer(*key, *authfile, *proxy)
if err != nil {
log.Fatal(err)
}
@@ -131,7 +134,7 @@ func server(args []string) {
s.Info = true
s.Debug = *verbose
if err = s.Run(host, port); err != nil {
if err = s.Run(*host, *port); err != nil {
log.Fatal(err)
}
}
@@ -159,17 +162,22 @@ var clientHelp = `
192.168.0.5:3000:google.com:80
Options:
` + commonHelp + `
Read more:
https://github.com/jpillora/chisel
`
--key, An optional fingerprint (server authentication) string to
compare against the server's public key. You may provide just a
prefix of the key or the entire string. Fingerprint mismatches
will disallow the connection.
--auth, An optional username and password (client authentication)
in the form: "<user>:<pass>".
` + commonHelp
func client(args []string) {
flags := flag.NewFlagSet("client", flag.ContinueOnError)
key := flags.String("key", "", "")
auth := flags.String("auth", "", "")
verbose := flags.Bool("v", false, "")
flags.Usage = func() {
fmt.Fprintf(os.Stderr, clientHelp)
@@ -185,7 +193,7 @@ func client(args []string) {
server := args[0]
remotes := args[1:]
c, err := chclient.NewClient(*key, server, remotes...)
c, err := chclient.NewClient(*key, *auth, server, remotes...)
if err != nil {
log.Fatal(err)
}
-47
View File
@@ -1,47 +0,0 @@
package chserver
import (
"net"
"github.com/jpillora/chisel/share"
)
type endpoint struct {
*chshare.Logger
id int
count int
addr string
sessions chan net.Conn
}
func newEndpoint(w *webSocket, id int, addr string) *endpoint {
return &endpoint{
Logger: w.Logger.Fork("%s#%d", addr, id+1),
id: id,
addr: addr,
sessions: make(chan net.Conn),
}
}
func (e *endpoint) start() {
e.Debugf("Enabled")
//service incoming streams
for stream := range e.sessions {
go e.pipe(stream)
}
}
func (e *endpoint) pipe(src net.Conn) {
dst, err := net.Dial("tcp", e.addr)
if err != nil {
e.Debugf("%s", err)
src.Close()
return
}
e.count++
eid := e.count
e.Debugf("stream#%d: Open", eid)
s, r := chshare.Pipe(src, dst)
e.Debugf("stream#%d: Close (sent %d received %d)", eid, s, r)
}
+111 -26
View File
@@ -1,34 +1,63 @@
package chserver
import (
"net"
"errors"
"log"
"net/http"
"net/http/httputil"
"net/url"
"github.com/jpillora/chisel/share"
"github.com/jpillora/conncrypt"
"golang.org/x/crypto/ssh"
"golang.org/x/net/websocket"
)
type Server struct {
*chshare.Logger
key string
wsCount int
wsServer websocket.Server
httpServer *chshare.HTTPServer
proxy *httputil.ReverseProxy
Users chshare.Users
fingerprint string
wsCount int
wsServer websocket.Server
httpServer *chshare.HTTPServer
proxy *httputil.ReverseProxy
sshConfig *ssh.ServerConfig
sessions map[string]*chshare.User
}
func NewServer(key, proxy string) (*Server, error) {
func NewServer(keySeed, authfile, proxy string) (*Server, error) {
s := &Server{
Logger: chshare.NewLogger("server"),
key: key,
wsServer: websocket.Server{},
httpServer: chshare.NewHTTPServer(),
sessions: map[string]*chshare.User{},
}
s.wsServer.Handler = websocket.Handler(s.handleWS)
//parse users, if provided
if authfile != "" {
users, err := chshare.ParseUsers(authfile)
if err != nil {
return nil, err
}
s.Users = users
}
//generate private key (optionally using seed)
key, _ := chshare.GenerateKey(keySeed)
//convert into ssh.PrivateKey
private, err := ssh.ParsePrivateKey(key)
if err != nil {
log.Fatal("Failed to parse key")
}
//fingerprint this key
s.fingerprint = chshare.FingerprintKey(private.PublicKey())
//create ssh config
s.sshConfig = &ssh.ServerConfig{
ServerVersion: chshare.ProtocolVersion + "-server",
PasswordCallback: s.authUser,
}
s.sshConfig.AddHostKey(private)
if proxy != "" {
u, err := url.Parse(proxy)
if err != nil {
@@ -57,8 +86,9 @@ func (s *Server) Run(host, port string) error {
}
func (s *Server) Start(host, port string) error {
if s.key != "" {
s.Infof("Authenication enabled")
s.Infof("Fingerprint %s", s.fingerprint)
if len(s.Users) > 0 {
s.Infof("User authenication enabled")
}
if s.proxy != nil {
s.Infof("Default proxy enabled")
@@ -93,25 +123,80 @@ func (s *Server) handleHTTP(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(404)
}
func (s *Server) handleWS(ws *websocket.Conn) {
conn := net.Conn(ws)
if s.key != "" {
conn = conncrypt.New(conn, &conncrypt.Config{Password: s.key})
//
func (s *Server) authUser(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
// no auth
if len(s.Users) == 0 {
return nil, nil
}
// authenticate user
u, ok := s.Users[c.User()]
if !ok || u.Pass != string(pass) {
return nil, errors.New("Invalid auth")
}
//insert session
s.sessions[string(c.SessionID())] = u
return nil, nil
}
configb := chshare.SizeRead(conn)
config, err := chshare.DecodeConfig(configb)
func (s *Server) handleWS(ws *websocket.Conn) {
// Before use, a handshake must be performed on the incoming net.Conn.
sshConn, chans, reqs, err := ssh.NewServerConn(ws, s.sshConfig)
if err != nil {
s.Infof("Handshake failed: %s", err)
chshare.SizeWrite(conn, []byte("Handshake failed"))
s.Debugf("Failed to handshake (%s)", err)
return
}
chshare.SizeWrite(conn, []byte("Handshake Success"))
// s.Infof("success %+v\n", config)
s.wsCount++
newWebSocket(s, config, conn).handle()
//load user
sid := string(sshConn.SessionID())
var user *chshare.User
if len(s.Users) > 0 {
user = s.sessions[sid]
}
//verify configuration
r := <-reqs
reply := func(err error) {
r.Reply(err == nil, []byte(err.Error()))
if err != nil {
sshConn.Close()
}
}
if r.Type != "config" {
reply(s.Errorf("expecting config request"))
return
}
c, err := chshare.DecodeConfig(r.Payload)
if err != nil {
reply(s.Errorf("invalid config"))
return
}
//if user is provided, ensure they have
//access to the desired remote
if user != nil {
for _, r := range c.Remotes {
addr := r.RemoteHost + ":" + r.RemotePort
if !user.HasAccess(addr) {
reply(s.Errorf("access to '%s' denied", addr))
return
}
}
}
//prepare connection logger
s.wsCount++
id := s.wsCount
l := s.Fork("session#%d", id)
l.Debugf("Open")
go ssh.DiscardRequests(reqs)
go chshare.ConnectStreams(l, chans)
sshConn.Wait()
l.Debugf("Close")
if user != nil {
delete(s.sessions, sid)
}
}
-95
View File
@@ -1,95 +0,0 @@
package chserver
import (
"encoding/binary"
"net"
"github.com/hashicorp/yamux"
"github.com/jpillora/chisel/share"
)
type webSocket struct {
*chshare.Logger
id int
config *chshare.Config
conn net.Conn
}
func newWebSocket(s *Server, config *chshare.Config, conn net.Conn) *webSocket {
id := s.wsCount
return &webSocket{
Logger: s.Logger.Fork("conn#%d", id),
id: id,
config: config,
conn: conn,
}
}
func (w *webSocket) handle() {
w.Debugf("Open")
// queue teardown
defer w.teardown()
// Setup server side of yamux
session, err := yamux.Server(w.conn, nil)
if err != nil {
w.Debugf("Yamux server: %s", err)
return
}
endpoints := make([]*endpoint, len(w.config.Remotes))
// Create an endpoint for each required
for id, r := range w.config.Remotes {
addr := r.RemoteHost + ":" + r.RemotePort
e := newEndpoint(w, id, addr)
go e.start()
endpoints[id] = e
}
for {
stream, err := session.Accept()
if err != nil {
if session.IsClosed() {
break
}
w.Debugf("Session accept: %s", err)
continue
}
go w.handleStream(stream, endpoints)
}
}
func (w *webSocket) handleStream(stream net.Conn, endpoints []*endpoint) {
// extract endpoint id
b := make([]byte, 2)
n, err := stream.Read(b)
if err != nil {
stream.Close()
w.Debugf("Stream initial read: %s", err)
return
}
if n != 2 {
stream.Close()
w.Debugf("Should read 2 bytes...")
return
}
id := binary.BigEndian.Uint16(b)
if int(id) >= len(endpoints) {
stream.Close()
w.Debugf("Invalid endpoint index: %d [total endpoints %d]", id, len(endpoints))
return
}
// pass this stream to the
// desired endpoint
e := endpoints[id]
e.sessions <- stream
}
func (w *webSocket) teardown() {
w.Debugf("Closed")
}
+44
View File
@@ -0,0 +1,44 @@
package chshare
// overview: only half the result is used as the output
// next -> sha512(next) -> [output|next] ->
import (
"crypto/sha512"
"io"
)
const DetermRandIter = 1024
func NewDetermRand(seed []byte) io.Reader {
var out []byte
//strengthen seed
var next = seed
for i := 0; i < DetermRandIter; i++ {
next, out = hash(next)
}
return &DetermRand{
next: next,
out: out,
}
}
type DetermRand struct {
next, out []byte
}
func (d *DetermRand) Read(b []byte) (int, error) {
n := 0
l := len(b)
for n < l {
next, out := hash(d.next)
n += copy(b[n:], out)
d.next = next
}
return n, nil
}
func hash(input []byte) ([]byte, []byte) {
nextout := sha512.Sum512(input)
return nextout[:sha512.Size/2], nextout[sha512.Size/2:]
}
-6
View File
@@ -1,6 +0,0 @@
// Chisel tunnels TCP traffic over HTTP
//
// Read more here
// https://github.com/jpillora/chisel
package chshare
+1 -2
View File
@@ -2,11 +2,10 @@ package chshare
import (
"io"
"net"
"sync"
)
func Pipe(src net.Conn, dst net.Conn) (int64, int64) {
func Pipe(src io.ReadWriteCloser, dst io.ReadWriteCloser) (int64, int64) {
var sent, received int64
var c = make(chan bool)
-27
View File
@@ -1,27 +0,0 @@
package chshare
import (
"encoding/binary"
"net"
)
//simple protocol [2 bytes of size, size bytes of data]
//TODO add length and error checks
func SizeRead(c net.Conn) []byte {
sizeb := make([]byte, 2)
c.Read(sizeb)
size := binary.BigEndian.Uint16(sizeb)
datab := make([]byte, size)
c.Read(datab)
return datab
}
func SizeWrite(c net.Conn, data []byte) {
size := len(data)
sizeb := make([]byte, 2)
binary.BigEndian.PutUint16(sizeb, uint16(size))
c.Write(sizeb)
c.Write(data)
return
}
+96
View File
@@ -0,0 +1,96 @@
package chshare
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/md5"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"fmt"
"io"
"net"
"strings"
"golang.org/x/crypto/ssh"
)
func GenerateKey(seed string) ([]byte, error) {
var r io.Reader
if seed == "" {
r = rand.Reader
} else {
r = NewDetermRand([]byte(seed))
}
priv, err := ecdsa.GenerateKey(elliptic.P256(), r)
if err != nil {
return nil, err
}
b, err := x509.MarshalECPrivateKey(priv)
if err != nil {
return nil, fmt.Errorf("Unable to marshal ECDSA private key: %v", err)
}
return pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: b}), nil
}
func FingerprintKey(k ssh.PublicKey) string {
bytes := md5.Sum(k.Marshal())
strbytes := make([]string, len(bytes))
for i, b := range bytes {
strbytes[i] = fmt.Sprintf("%02x", b)
}
return strings.Join(strbytes, ":")
}
func OpenStream(conn ssh.Conn, remote string) (io.ReadWriteCloser, error) {
stream, reqs, err := conn.OpenChannel("chisel", []byte(remote))
if err != nil {
return nil, err
}
go ssh.DiscardRequests(reqs)
return stream, nil
}
func RejectStreams(chans <-chan ssh.NewChannel) {
for ch := range chans {
ch.Reject(ssh.Prohibited, "Tunnels disallowed")
}
}
func ConnectStreams(l *Logger, chans <-chan ssh.NewChannel) {
var streamCount int
for ch := range chans {
addr := string(ch.ExtraData())
stream, reqs, err := ch.Accept()
if err != nil {
l.Debugf("Failed to accept stream: %s", err)
continue
}
streamCount++
id := streamCount
go ssh.DiscardRequests(reqs)
go handleStream(l.Fork("stream#%d", id), stream, addr)
}
}
func handleStream(l *Logger, src io.ReadWriteCloser, remote string) {
dst, err := net.Dial("tcp", remote)
if err != nil {
l.Debugf("%s", err)
src.Close()
return
}
l.Debugf("Open")
s, r := Pipe(src, dst)
l.Debugf("Close (sent %d received %d)", s, r)
}
+68
View File
@@ -0,0 +1,68 @@
package chshare
import (
"encoding/json"
"errors"
"io/ioutil"
"regexp"
"strings"
)
func ParseAuth(auth string) (string, string) {
if strings.Contains(auth, ":") {
pair := strings.SplitN(auth, ":", 2)
return pair[0], pair[1]
}
return "", ""
}
type User struct {
Name string
Pass string
Addrs []*regexp.Regexp
}
func (u *User) HasAccess(addr string) bool {
m := false
for _, r := range u.Addrs {
if r.MatchString(addr) {
m = true
break
}
}
return m
}
type Users map[string]*User
func ParseUsers(authfile string) (Users, error) {
b, err := ioutil.ReadFile(authfile)
if err != nil {
return nil, errors.New("Failed to read auth file")
}
var raw map[string][]string
err = json.Unmarshal(b, &raw)
if err != nil {
return nil, errors.New("Invalid JSON: " + err.Error())
}
users := Users{}
for auth, remotes := range raw {
u := &User{}
u.Name, u.Pass = ParseAuth(auth)
if u.Name == "" {
return nil, errors.New("Invalid user:pass string")
}
for _, r := range remotes {
re, err := regexp.Compile(r)
if err != nil {
return nil, errors.New("Invalid address regex")
}
u.Addrs = append(u.Addrs, re)
}
users[u.Name] = u
}
return users, nil
}
+142
View File
@@ -0,0 +1,142 @@
//chisel tests
//===================
// (direct)
// .--------------->----------------.
// / chisel chisel \
// request--->client:2001--->server:2002---->fileserver:3000
// \ /
// '--> crowbar:4001--->crowbar:4002'
// client server
//
// benchmarks don't use testing.B, instead use
// go test -test.run=Bench
//
// tests use
// go test -test.run=Request
//
// crowbar and chisel binaries should be in your PATH
package test
import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"testing"
"time"
)
const (
B = 1
KB = 1024 * B
MB = 1024 * KB
GB = 1024 * MB
)
//test
func TestRequestChisel(t *testing.T) {
testTunnel("2001", 500, t)
testTunnel("2001", 50000, t)
}
//benchmark
func TestBenchDirect(t *testing.T) {
benchSizes("3000", t)
}
func TestBenchChisel(t *testing.T) {
benchSizes("2001", t)
}
// func TestBenchrowbar(t *testing.T) {
// benchSizes("4001", t)
// }
func benchSizes(port string, t *testing.T) {
for size := 1; size < 100*MB; size *= 10 {
testTunnel(port, size, t)
}
}
func testTunnel(port string, size int, t *testing.T) {
t0 := time.Now()
resp, err := requestFile(port, size)
if err != nil {
t.Fatal(err)
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
t1 := time.Now()
fmt.Printf(":%s => %d bytes in %s\n", port, size, t1.Sub(t0))
if len(b) != size {
t.Fatalf("%d bytes expected, got %d", size, len(b))
}
}
//============================
//global setup
func TestMain(m *testing.M) {
fs := makeFileServer()
go func() {
log.Fatal(fs.Wait())
}()
dir, _ := os.Getwd()
cd := exec.Command("crowbard",
`-listen`, "0.0.0.0:4002",
`-userfile`, path.Join(dir, "userfile"))
if err := cd.Start(); err != nil {
log.Fatal(err)
}
go func() {
log.Fatalf("crowbard: %s", cd.Wait())
}()
time.Sleep(100 * time.Millisecond)
cf := exec.Command("crowbar-forward",
"-local=0.0.0.0:4001",
"-server=http://127.0.0.1:4002",
"-remote=127.0.0.1:3000",
"-username", "foo",
"-password", "bar")
if err := cf.Start(); err != nil {
log.Fatal(err)
}
time.Sleep(100 * time.Millisecond)
hd := exec.Command("chiseld", "--port", "2002", "--auth", "foobar")
// hd.Stdout = os.Stdout
if err := hd.Start(); err != nil {
log.Fatal(err)
}
hf := exec.Command("chisel-forward",
"--auth", "foobar",
"127.0.0.1:2002",
"2001:3000")
if err := hf.Start(); err != nil {
log.Fatal(err)
}
time.Sleep(300 * time.Millisecond)
fmt.Println("Running!")
code := m.Run()
fmt.Println("Done")
cd.Process.Kill()
cf.Process.Kill()
hd.Process.Kill()
hf.Process.Kill()
fs.Close()
os.Exit(code)
}