diff options
| -rw-r--r-- | TODO | 3 | ||||
| -rw-r--r-- | pkg/http/http.go | 166 | ||||
| -rw-r--r-- | pkg/server/env/env.go | 8 | ||||
| -rw-r--r-- | pkg/server/hook/aes.go | 2 | ||||
| -rw-r--r-- | pkg/server/hook/auth.go | 2 | ||||
| -rw-r--r-- | pkg/server/hook/hook.go | 8 | ||||
| -rw-r--r-- | pkg/server/hook/look-http.go | 9 | ||||
| -rw-r--r-- | pkg/server/hook/proxy.go | 89 | ||||
| -rw-r--r-- | pkg/server/hook/tee.go | 2 | ||||
| -rw-r--r-- | pkg/server/socket/defer.go | 49 | ||||
| -rw-r--r-- | pkg/server/socket/dial.go | 2 | ||||
| -rw-r--r-- | pkg/server/socket/listen.go | 2 | ||||
| -rw-r--r-- | pkg/server/socket/loop.go | 14 | ||||
| -rw-r--r-- | pkg/server/socket/proxy.go | 134 | ||||
| -rw-r--r-- | pkg/server/socket/socket.go | 19 | ||||
| -rw-r--r-- | pkg/server/socket/tun.go | 14 | ||||
| -rw-r--r-- | pkg/server/tunnel.go | 11 | ||||
| -rwxr-xr-x | test/auth.sh | 1 | ||||
| -rwxr-xr-x | test/proxy.sh | 15 |
19 files changed, 406 insertions, 144 deletions
@@ -2,7 +2,8 @@ - tunnel enable/disable - stream.run check errors on Open - per tunnel, per stream statistics -- http connect server proxy module - systemd socket activation - tun: separate data and control packets (route) - tun: auto created interface and auto ip address from pool +- go tests +- docs diff --git a/pkg/http/http.go b/pkg/http/http.go new file mode 100644 index 0000000..eefe348 --- /dev/null +++ b/pkg/http/http.go @@ -0,0 +1,166 @@ +package http + +import ( + "bufio" + "encoding/base64" + "errors" + "fmt" + "io" + "regexp" + "strconv" + "strings" +) + +type Header map[string]string + +type Request struct { + Method string + URI string + Proto string + Header Header +} + +type Response struct { + Code int + Desc string + Proto string + Header Header +} + +var version = "HTTP/[0-9]+\\.[0-9]+" +var requestLine = regexp.MustCompile("^([A-Z]+) +([^ ]+) +(" + version + ")$") +var statusLine = regexp.MustCompile("^(" + version + ") +([0-9]+) +(.*)$") +var header = regexp.MustCompile("^([^ ]+) *: *(.*)$") + +const OK = 200 + +func parseHeader(s string) (key, value string, ok bool) { + if m := header.FindStringSubmatch(s); m == nil { + return + } else { + return m[1], m[2], true + } +} + +func addHeader(m Header, k, v string) Header { + if m == nil { + m = make(Header) + } + + m[strings.Title(k)] = v + + return m +} + +func parse(r io.Reader, init func(string) error, f func(string) error) error { + scanner := bufio.NewScanner(r) + ok := false + + if scanner.Scan() { + if err := init(scanner.Text()); err != nil { + return err + } + } + + for scanner.Scan() { + if scanner.Text() == "" { + ok = true + break + } + + if err := f(scanner.Text()); err != nil { + return err + } + } + + if err := scanner.Err(); err != nil { + return err + } else if !ok { + return io.ErrUnexpectedEOF + } + + return nil +} + +func ParseRequestLine(s string) (method, uri, proto string, ok bool) { + if m := requestLine.FindStringSubmatch(s); m == nil { + return + } else { + return m[1], m[2], m[3], true + } +} + +func (r *Request) parseRequestLine(s string) error { + if method, uri, proto, ok := ParseRequestLine(s); !ok { + return errors.New("request line parse failed") + } else { + r.Method, r.URI, r.Proto = method, uri, proto + return nil + } +} + +func (r *Request) parseHeader(s string) error { + if k, v, ok := parseHeader(s); !ok { + return errors.New("header parse failed") + } else { + r.Header = addHeader(r.Header, k, v) + return nil + } +} + +func ParseRequest(r io.Reader) (*Request, error) { + req := new(Request) + + if err := parse(r, req.parseRequestLine, req.parseHeader); err != nil { + return nil, fmt.Errorf("http: bad request: %w", err) + } + + return req, nil +} + +func ParseStatusLine(s string) (proto string, code int, desc string, ok bool) { + if m := statusLine.FindStringSubmatch(s); m == nil { + return + } else if n, err := strconv.Atoi(m[2]); err != nil { + return + } else { + return m[1], n, m[3], true + } +} + +func (r *Response) parseStatusLine(s string) error { + if proto, code, desc, ok := ParseStatusLine(s); !ok { + return errors.New("status line parse failed") + } else { + r.Code, r.Desc, r.Proto = code, desc, proto + return nil + } +} + +func (r *Response) parseHeader(s string) error { + if k, v, ok := parseHeader(s); !ok { + return errors.New("header parse failed") + } else { + r.Header = addHeader(r.Header, k, v) + return nil + } +} + +func ParseResponse(r io.Reader) (*Response, error) { + resp := new(Response) + + if err := parse(r, resp.parseStatusLine, resp.parseHeader); err != nil { + return nil, fmt.Errorf("http: bad response: %w", err) + } + + return resp, nil +} + +func BasicAuthEncode(s string) string { + encoded := base64.StdEncoding.EncodeToString([]byte(s)) + return "Basic " + encoded +} + +func BasicAuthCheck(origin string, encoded string) bool { + return BasicAuthEncode(origin) == encoded +} diff --git a/pkg/server/env/env.go b/pkg/server/env/env.go index 5a49532..45650ab 100644 --- a/pkg/server/env/env.go +++ b/pkg/server/env/env.go @@ -148,3 +148,11 @@ func (e Env) Eval(s string) string { return s } + +func (e Env) GetLocal(key string) string { + if v := e.Eval("@{tunnel.@{tunnel}." + key + "}"); v != "" { + return v + } + + return e.Get(key) +} diff --git a/pkg/server/hook/aes.go b/pkg/server/hook/aes.go index dc9605a..8ae47a3 100644 --- a/pkg/server/hook/aes.go +++ b/pkg/server/hook/aes.go @@ -65,7 +65,7 @@ func (a *aesPipe) Recv(rq, wq queue.Q) error { } func newAes(env env.Env) *aesPipe { - s := getHookVar(env, "secret") + s := env.GetLocal("secret") h := md5.Sum([]byte(s)) a := &aesPipe{key: make([]byte, 16)} diff --git a/pkg/server/hook/auth.go b/pkg/server/hook/auth.go index b31855a..14ad114 100644 --- a/pkg/server/hook/auth.go +++ b/pkg/server/hook/auth.go @@ -151,7 +151,7 @@ func (a *auth) Recv(rq, wq queue.Q) error { func (h *authHook) Open(env env.Env) (interface{}, error) { a := &auth{ h: h, - secret: getHookVar(env, "secret"), + secret: env.GetLocal("secret"), recvChallenge: make(chan struct{}), recvHash: make(chan struct{}), fail: make(chan struct{}), diff --git a/pkg/server/hook/hook.go b/pkg/server/hook/hook.go index 1702afd..6ac51a1 100644 --- a/pkg/server/hook/hook.go +++ b/pkg/server/hook/hook.go @@ -113,14 +113,6 @@ func New(desc string, env env.Env) (H, error) { } } -func getHookVar(env env.Env, s string) string { - if v := env.Eval("@{tunnel.@{tunnel}." + s + "}"); v != "" { - return v - } - - return env.Get(s) -} - func register(name string, f hookInitFunc) { if _, ok := hooks[name]; ok { log.Panicf("duplicate hook name '%s'", name) diff --git a/pkg/server/hook/look-http.go b/pkg/server/hook/look-http.go index d467563..50ec663 100644 --- a/pkg/server/hook/look-http.go +++ b/pkg/server/hook/look-http.go @@ -2,7 +2,7 @@ package hook import ( "bufio" - "strings" + "tunnel/pkg/http" "tunnel/pkg/server/env" "tunnel/pkg/server/opts" "tunnel/pkg/server/queue" @@ -28,11 +28,8 @@ func (look *lookHttp) Send(rq, wq queue.Q) error { }() if s.Scan() { - if w := strings.Split(s.Text(), " "); len(w) > 1 { - switch m := strings.ToUpper(w[0]); m { - case "CONNECT", "GET", "POST": - look.env.Set("look.info", m+" "+w[1]) - } + if method, uri, _, ok := http.ParseRequestLine(s.Text()); ok { + look.env.Set("look.info", method+" "+uri) } } diff --git a/pkg/server/hook/proxy.go b/pkg/server/hook/proxy.go index 64db784..26be2d0 100644 --- a/pkg/server/hook/proxy.go +++ b/pkg/server/hook/proxy.go @@ -1,23 +1,17 @@ package hook import ( - "bufio" "bytes" - "encoding/base64" "errors" "fmt" - "io" "regexp" - "strconv" + "tunnel/pkg/http" "tunnel/pkg/server/env" "tunnel/pkg/server/opts" "tunnel/pkg/server/queue" ) var addrRe = regexp.MustCompile("^[0-9a-zA-Z-.]+:[0-9]+$") -var respRe = regexp.MustCompile("^([^ ]+) +([0-9]+) +(.*)$") - -var errBadHttpResponse = errors.New("bad HTTP response") type proxyHook struct { addr string @@ -27,93 +21,41 @@ type proxyHook struct { type proxy struct { addr string auth string - ok chan struct{} - fail chan struct{} + c chan bool } func (p *proxy) Send(rq, wq queue.Q) error { var out bytes.Buffer - fmt.Fprintf(&out, "CONNECT %s HTTP/1.0\r\n", p.addr) + fmt.Fprintf(&out, "CONNECT %s HTTP/1.1\r\n", p.addr) if p.auth != "" { - encoded := base64.StdEncoding.EncodeToString([]byte(p.auth)) - fmt.Fprintf(&out, "Proxy-Authorization: Basic %s\r\n", encoded) + encoded := http.BasicAuthEncode(p.auth) + fmt.Fprintf(&out, "Proxy-Authorization: %s\r\n", encoded) } fmt.Fprintf(&out, "\r\n") wq <- out.Bytes() - select { - case <-p.fail: + if !<-p.c { return nil - case <-p.ok: } return queue.Copy(rq, wq) } -func parseProxyResponse(s string) error { - var version string - var code int - var desc string - - if m := respRe.FindStringSubmatch(s); m == nil { - return errBadHttpResponse - } else { - version = m[1] - if c, err := strconv.Atoi(m[2]); err != nil { - return errBadHttpResponse - } else { - code = c - } - desc = m[3] - } - - if version != "HTTP/1.0" && version != "HTTP/1.1" { - return errBadHttpResponse - } - - if code != 200 { - return fmt.Errorf("connect failed: %d %s", code, desc) - } - - return nil -} - -func (p *proxy) Recv(rq, wq queue.Q) (err error) { - defer func() { - if err != nil { - close(p.fail) - } - }() - - s := bufio.NewScanner(rq.Reader()) - - var resp bool - - for s.Scan() { - line := s.Text() - - if !resp { - if err := parseProxyResponse(line); err != nil { - return err - } - resp = true - continue - } +func (p *proxy) Recv(rq, wq queue.Q) error { + resp, err := http.ParseResponse(rq.Reader()) - if line == "" { - break - } + if err == nil && resp.Code != http.OK { + err = fmt.Errorf("connect failed: %d %s", resp.Code, resp.Desc) } - if err := s.Err(); err != nil { + if err != nil { + p.c <- false return err - } else if !resp { - return io.ErrUnexpectedEOF } - close(p.ok) + p.c <- true return queue.Copy(rq, wq) } @@ -122,12 +64,11 @@ func (h *proxyHook) Open(env env.Env) (interface{}, error) { p := &proxy{ addr: h.addr, auth: h.auth, - ok: make(chan struct{}), - fail: make(chan struct{}), + c: make(chan bool), } if p.auth == "" { - p.auth = getHookVar(env, "proxy.auth") + p.auth = env.GetLocal("proxy.auth") } return p, nil diff --git a/pkg/server/hook/tee.go b/pkg/server/hook/tee.go index 521164b..2d13fcb 100644 --- a/pkg/server/hook/tee.go +++ b/pkg/server/hook/tee.go @@ -64,7 +64,7 @@ func (h *teeHook) where(env env.Env) string { return h.file } - if v := getHookVar(env, "tee.file"); v != "" { + if v := env.GetLocal("tee.file"); v != "" { return v } diff --git a/pkg/server/socket/defer.go b/pkg/server/socket/defer.go index bfb2157..7ed303d 100644 --- a/pkg/server/socket/defer.go +++ b/pkg/server/socket/defer.go @@ -9,10 +9,11 @@ type deferSocket struct { S } -type deferChannel struct { - s *deferSocket - c chan Channel - e env.Env +type deferConn struct { + sock *deferSocket + wait chan bool + env env.Env + conn Conn } func newDeferSocket(proto, addr string) (S, error) { @@ -24,42 +25,43 @@ func newDeferSocket(proto, addr string) (S, error) { return &deferSocket{s}, nil } -func (s *deferSocket) Open(env env.Env) (Channel, error) { - c := &deferChannel{ - s: s, - c: make(chan Channel), - e: env, +func (s *deferSocket) Open(env env.Env) (Conn, error) { + c := &deferConn{ + sock: s, + wait: make(chan bool), + env: env, } return c, nil } -func (c *deferChannel) String() string { +func (c *deferConn) String() string { return "defer" } -func (c *deferChannel) Send(wq queue.Q) error { - if x := <-c.c; x == nil { +func (c *deferConn) Send(wq queue.Q) error { + if !<-c.wait { return nil } else { - return x.Send(wq) + return c.conn.Send(wq) } } -func (c *deferChannel) Recv(rq queue.Q) error { +func (c *deferConn) Recv(rq queue.Q) error { b := <-rq if b == nil { - close(c.c) + c.wait <- false return nil } - x, err := c.s.S.Open(c.e) + conn, err := c.sock.S.Open(c.env) if err != nil { - close(c.c) + c.wait <- false return err } - c.c <- x + c.conn = conn + c.wait <- true q := queue.New() @@ -71,10 +73,13 @@ func (c *deferChannel) Recv(rq queue.Q) error { defer q.Dry() - return x.Recv(q) + return c.conn.Recv(q) } -/* TODO */ -func (c *deferChannel) Close() error { - return nil +func (c *deferConn) Close() (err error) { + if c.conn != nil { + err = c.conn.Close() + } + + return } diff --git a/pkg/server/socket/dial.go b/pkg/server/socket/dial.go index d4c20e1..d7b232c 100644 --- a/pkg/server/socket/dial.go +++ b/pkg/server/socket/dial.go @@ -26,7 +26,7 @@ func (s *dialSocket) String() string { return fmt.Sprintf("%s/%s", s.proto, s.addr) } -func (s *dialSocket) Open(e env.Env) (Channel, error) { +func (s *dialSocket) Open(e env.Env) (Conn, error) { conn, err := net.Dial(s.proto, e.Eval(s.addr)) if err != nil { return nil, err diff --git a/pkg/server/socket/listen.go b/pkg/server/socket/listen.go index c328945..caf5fcf 100644 --- a/pkg/server/socket/listen.go +++ b/pkg/server/socket/listen.go @@ -28,7 +28,7 @@ func newListenSocket(proto, addr string) (S, error) { return s, nil } -func (s *listenSocket) Open(env.Env) (Channel, error) { +func (s *listenSocket) Open(env.Env) (Conn, error) { conn, err := s.listen.Accept() if err != nil { return nil, err diff --git a/pkg/server/socket/loop.go b/pkg/server/socket/loop.go index 88e9491..a06448a 100644 --- a/pkg/server/socket/loop.go +++ b/pkg/server/socket/loop.go @@ -7,31 +7,31 @@ import ( type loopSocket struct{} -type loopChannel struct { +type loopConn struct { c chan queue.Q q chan error } -func (c *loopChannel) Send(wq queue.Q) error { +func (c *loopConn) Send(wq queue.Q) error { c.c <- wq return <-c.q } -func (c *loopChannel) Recv(rq queue.Q) error { +func (c *loopConn) Recv(rq queue.Q) error { defer close(c.q) return queue.Copy(rq, <-c.c) } -func (c *loopChannel) String() string { +func (c *loopConn) String() string { return "loop" } -func (c *loopChannel) Close() error { +func (c *loopConn) Close() error { return nil } -func (s *loopSocket) Open(env.Env) (Channel, error) { - return &loopChannel{make(chan queue.Q), make(chan error)}, nil +func (s *loopSocket) Open(env.Env) (Conn, error) { + return &loopConn{make(chan queue.Q), make(chan error)}, nil } func (s *loopSocket) String() string { diff --git a/pkg/server/socket/proxy.go b/pkg/server/socket/proxy.go new file mode 100644 index 0000000..ef14f48 --- /dev/null +++ b/pkg/server/socket/proxy.go @@ -0,0 +1,134 @@ +package socket + +import ( + "bytes" + "errors" + "fmt" + "tunnel/pkg/http" + "tunnel/pkg/server/env" + "tunnel/pkg/server/queue" +) + +type status struct { + code int + desc string +} + +type proxySocket struct { + proto string +} + +type proxyServer struct { + sock *proxySocket + addr string + auth string + wait chan status + env env.Env + conn Conn +} + +func newProxySocket(proto string) (S, error) { + return &proxySocket{proto}, nil +} + +func (sock *proxySocket) Open(env env.Env) (Conn, error) { + s := &proxyServer{ + sock: sock, + auth: env.GetLocal("proxy.auth"), + wait: make(chan status), + env: env, + } + + return s, nil +} + +func (sock *proxySocket) Close() { +} + +func (s *proxyServer) String() string { + return "proxy" +} + +func (s *proxyServer) Send(wq queue.Q) error { + var out bytes.Buffer + + status := <-s.wait + + fmt.Fprintf(&out, "HTTP/1.0 %d %s\r\n", status.code, status.desc) + switch status.code { + case http.OK: + fmt.Fprintf(&out, "Proxy-Agent: tunnel\r\n") + case 407: + fmt.Fprintf(&out, "Proxy-Authenticate: Basic realm=\"tunnel\"\r\n") + fallthrough + default: + fmt.Fprintf(&out, "Server: tunnel\r\n") + fmt.Fprintf(&out, "Connection: close\r\n") + } + fmt.Fprintf(&out, "\r\n") + + wq <- out.Bytes() + + if status.code != http.OK { + return nil + } + + return s.conn.Send(wq) +} + +func (s *proxyServer) initConn(addr string) error { + dial, err := newDialSocket(s.sock.proto, addr) + if err != nil { + return err + } + + conn, err := dial.Open(s.env) + if err != nil { + dial.Close() + return err + } + + s.conn = conn + + return nil +} + +func (s *proxyServer) Recv(rq queue.Q) error { + req, err := http.ParseRequest(rq.Reader()) + if err != nil { + s.wait <- status{400, "Bad Request"} + return err + } + + if req.Method != "CONNECT" { + s.wait <- status{400, "Bad Request"} + return errors.New("bad method") + } + + if s.auth != "" { + if auth, ok := req.Header["Proxy-Authorization"]; !ok { + s.wait <- status{407, "Proxy Authentication Required"} + return errors.New("auth required") + } else if !http.BasicAuthCheck(s.auth, auth) { + s.wait <- status{401, "Unauthorized"} + return errors.New("auth failed") + } + } + + if err := s.initConn(req.URI); err != nil { + s.wait <- status{500, "Unable to connect"} + return err + } + + s.wait <- status{200, "Connection established"} + + return s.conn.Recv(rq) +} + +func (s *proxyServer) Close() (err error) { + if s.conn != nil { + err = s.conn.Close() + } + + return +} diff --git a/pkg/server/socket/socket.go b/pkg/server/socket/socket.go index 1d447f8..48c06b4 100644 --- a/pkg/server/socket/socket.go +++ b/pkg/server/socket/socket.go @@ -16,17 +16,17 @@ var ErrAlreadyClosed = errors.New("already closed") type exported struct { info string - Channel + Conn } -type Channel interface { +type Conn interface { Send(wq queue.Q) error Recv(rq queue.Q) error Close() error } type S interface { - Open(env env.Env) (Channel, error) + Open(env env.Env) (Conn, error) Close() } @@ -44,7 +44,7 @@ func (c exported) String() string { return c.info } -func newConn(cn net.Conn) Channel { +func newConn(cn net.Conn) Conn { c := &conn{Conn: cn} log.Println("open", c) return c @@ -87,15 +87,16 @@ func New(desc string, env env.Env) (S, error) { proto, addr = args[0], args[1] } - if addr == "loop" { - return newLoopSocket() - } - if proto == "" { proto = "tcp" } - if addr == "" { + switch addr { + case "loop": + return newLoopSocket() + case "proxy": + return newProxySocket(proto) + case "": return nil, fmt.Errorf("bad socket '%s'", desc) } diff --git a/pkg/server/socket/tun.go b/pkg/server/socket/tun.go index 78bdfd4..c14125b 100644 --- a/pkg/server/socket/tun.go +++ b/pkg/server/socket/tun.go @@ -27,7 +27,7 @@ type tunSocket struct { name string } -type tunChannel struct { +type tunConn struct { name string s *tunSocket fp *os.File @@ -52,7 +52,7 @@ func (s *tunSocket) String() string { return fmt.Sprintf("tun/%s", s.name) } -func (s *tunSocket) Open(env.Env) (Channel, error) { +func (s *tunSocket) Open(env.Env) (Conn, error) { fd, err := unix.Open("/dev/net/tun", unix.O_RDWR, 0) if err != nil { return nil, err @@ -72,7 +72,7 @@ func (s *tunSocket) Open(env.Env) (Channel, error) { return nil, fmt.Errorf("set nonblock %s: %w", s.name, err) } - c := &tunChannel{ + c := &tunConn{ name: strings.Trim(string(ifr.name[:]), "\x00"), fp: os.NewFile(uintptr(fd), "tun"), } @@ -83,7 +83,7 @@ func (s *tunSocket) Open(env.Env) (Channel, error) { func (s *tunSocket) Close() { } -func (c *tunChannel) Send(wq queue.Q) error { +func (c *tunConn) Send(wq queue.Q) error { buf := make([]byte, maxTunBufSize) enc := pack.NewEncoder(wq.Writer()) @@ -97,7 +97,7 @@ func (c *tunChannel) Send(wq queue.Q) error { } } -func (c *tunChannel) Recv(rq queue.Q) error { +func (c *tunConn) Recv(rq queue.Q) error { dec := pack.NewDecoder(rq.Reader()) for { @@ -117,11 +117,11 @@ func (c *tunChannel) Recv(rq queue.Q) error { } } -func (c *tunChannel) String() string { +func (c *tunConn) String() string { return "tun/" + c.name } -func (c *tunChannel) Close() error { +func (c *tunConn) Close() error { err := ErrAlreadyClosed c.once.Do(func() { diff --git a/pkg/server/tunnel.go b/pkg/server/tunnel.go index 4543714..e47215e 100644 --- a/pkg/server/tunnel.go +++ b/pkg/server/tunnel.go @@ -33,7 +33,7 @@ type stream struct { since time.Time until time.Time wg sync.WaitGroup - in, out socket.Channel + in, out socket.Conn pipes []*hook.Pipe m struct { @@ -135,6 +135,8 @@ func (t *tunnel) serve() { env := t.env.Fork() + env.Set("tunnel", t.id) + if in, err := t.in.Open(env); err == nil { if out, err := t.out.Open(env); err == nil { s := t.newStream(env, in, out) @@ -157,7 +159,7 @@ func (t *tunnel) serve() { close(t.done) } -func (t *tunnel) newStream(env env.Env, in, out socket.Channel) *stream { +func (t *tunnel) newStream(env env.Env, in, out socket.Conn) *stream { s := &stream{ t: t, in: in, @@ -167,7 +169,6 @@ func (t *tunnel) newStream(env env.Env, in, out socket.Channel) *stream { since: time.Now(), } - s.env.Set("tunnel", t.id) s.env.Set("stream", strconv.Itoa(s.id)) s.run() @@ -230,7 +231,7 @@ func (s *stream) waitAndClose() { log.Println(s.t, s, "done", s.info()) } -func (s *stream) channel(c socket.Channel, m *metric, rq, wq queue.Q) { +func (s *stream) channel(c socket.Conn, m *metric, rq, wq queue.Q) { watch := func(q queue.Q, f func(q queue.Q) error) { defer s.wg.Done() @@ -249,7 +250,7 @@ func (s *stream) channel(c socket.Channel, m *metric, rq, wq queue.Q) { } if err != nil { - log.Println(s.t, s, err) + log.Println(s.t, s, c, err) } } diff --git a/test/auth.sh b/test/auth.sh index 58b1180..66855e2 100755 --- a/test/auth.sh +++ b/test/auth.sh @@ -9,6 +9,7 @@ tunnel set tunnel.T.secret secret tunnel set tunnel.X.secret secret nc -l 4000 & echo "Hello, World!" | nc -N localhost 2000 +wait tunnel clear tunnel del T tunnel del X diff --git a/test/proxy.sh b/test/proxy.sh new file mode 100755 index 0000000..ec513fe --- /dev/null +++ b/test/proxy.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +ROOT=$(dirname $0)/.. +PATH=$ROOT/cmd/tunnel:$PATH + +tunnel add name C 2000,listen proxy,addr=localhost:4000 3000 +tunnel add name S 3000,listen proxy +tunnel set tunnel.C.proxy.auth user:password +tunnel set tunnel.S.proxy.auth user:password +nc -l 4000 & +echo "Hello, World!" | nc -N localhost 2000 +wait +tunnel clear +tunnel del C +tunnel del S |
