mirror of
https://github.com/mmatczuk/go-http-tunnel.git
synced 2026-05-15 14:16:17 -06:00
proto: ControlMessage improvements
This commit is contained in:
parent
9a7c36ff5a
commit
2857955688
9 changed files with 67 additions and 95 deletions
1
TODO.md
1
TODO.md
|
|
@ -1,4 +1,3 @@
|
|||
1. `ControlMessage` `String()` function for better logging
|
||||
1. Dynamic `AllowedClient` management
|
||||
1. Client driven configuration, on connect client sends it's configuration, server just needs to know the certificate id
|
||||
1. Cli and file configuration https://ngrok.com/docs#config
|
||||
|
|
|
|||
16
client.go
16
client.go
|
|
@ -131,19 +131,27 @@ func (c *Client) dial(network, addr string, config *tls.Config) (net.Conn, error
|
|||
|
||||
func (c *Client) serveHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodConnect {
|
||||
c.log.Info("Handshake: hello from server")
|
||||
c.log.Info("Connected to server: %s", r.RemoteAddr)
|
||||
http.Error(w, "Nice to see you", http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
msg, err := proto.ParseControlMessage(r.Header)
|
||||
if err != nil {
|
||||
c.log.Warning("Parsing control message failed: %s", err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
c.log.Debug("Start proxying %v", msg)
|
||||
c.config.Proxy(w, r.Body, msg)
|
||||
c.log.Debug("Done proxying %v", msg)
|
||||
|
||||
c.log.Debug("Start %s", msg)
|
||||
switch msg.Action {
|
||||
case proto.Proxy:
|
||||
c.config.Proxy(w, r.Body, msg)
|
||||
default:
|
||||
c.log.Warning("Unknown action: %s", msg)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
c.log.Debug("Done %s", msg)
|
||||
}
|
||||
|
||||
// Stop closes the connection between client and server. After stopping client
|
||||
|
|
|
|||
|
|
@ -81,7 +81,6 @@ func (p *HTTPProxy) Proxy(w io.Writer, r io.ReadCloser, msg *proto.ControlMessag
|
|||
return
|
||||
}
|
||||
req.URL.Host = msg.ForwardedBy
|
||||
req.URL.Path = msg.URLPath
|
||||
|
||||
p.ServeHTTP(rw, req)
|
||||
}
|
||||
|
|
|
|||
16
proto/action_string.go
Normal file
16
proto/action_string.go
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Code generated by "stringer -type=Action"; DO NOT EDIT
|
||||
|
||||
package proto
|
||||
|
||||
import "fmt"
|
||||
|
||||
const _Action_name = "Proxy"
|
||||
|
||||
var _Action_index = [...]uint8{0, 5}
|
||||
|
||||
func (i Action) String() string {
|
||||
if i < 0 || i >= Action(len(_Action_index)-1) {
|
||||
return fmt.Sprintf("Action(%d)", i)
|
||||
}
|
||||
return _Action_name[_Action_index[i]:_Action_index[i+1]]
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ type Action int
|
|||
|
||||
// ControlMessage actions.
|
||||
const (
|
||||
RequestClientSession Action = iota
|
||||
Proxy Action = iota
|
||||
)
|
||||
|
||||
// ControlMessage headers
|
||||
|
|
@ -21,8 +21,8 @@ const (
|
|||
)
|
||||
|
||||
// Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only), "udp",
|
||||
// "udp4" (IPv4-only), "udp6" (IPv6-only), "ip", "ip4" (IPv4-only), "ip6" (IPv6-only),
|
||||
// "unix", "unixgram" and "unixpacket".
|
||||
// "udp4" (IPv4-only), "udp6" (IPv6-only), "ip", "ip4" (IPv4-only),
|
||||
// "ip6" (IPv6-only), "unix", "unixgram" and "unixpacket".
|
||||
const (
|
||||
HTTP = "http"
|
||||
TCP = "tcp"
|
||||
|
|
@ -31,18 +31,23 @@ const (
|
|||
UNIX = "unix"
|
||||
)
|
||||
|
||||
// ControlMessage is sent from server to client to establish tunneled connection.
|
||||
// ControlMessage is sent from server to client to establish tunneled
|
||||
// connection.
|
||||
type ControlMessage struct {
|
||||
Action Action
|
||||
Protocol string
|
||||
ForwardedFor string
|
||||
ForwardedBy string
|
||||
URLPath string
|
||||
}
|
||||
|
||||
var xffRegexp = regexp.MustCompile("(for|by|proto|path)=([^;$]+)")
|
||||
func (c *ControlMessage) String() string {
|
||||
return fmt.Sprintf("%s %s: %s<-%s", c.Action, c.Protocol, c.ForwardedBy, c.ForwardedFor)
|
||||
}
|
||||
|
||||
// ParseControlMessage creates new ControlMessage based on "Forwarded" http header.
|
||||
var xffRegexp = regexp.MustCompile("(for|proto|by)=([^;$]+)")
|
||||
|
||||
// ParseControlMessage creates new ControlMessage based on "Forwarded" http
|
||||
// header.
|
||||
func ParseControlMessage(h http.Header) (*ControlMessage, error) {
|
||||
v := h.Get(ForwardedHeader)
|
||||
if v == "" {
|
||||
|
|
@ -59,22 +64,17 @@ func ParseControlMessage(h http.Header) (*ControlMessage, error) {
|
|||
msg.ForwardedBy = i[2]
|
||||
case "proto":
|
||||
msg.Protocol = i[2]
|
||||
case "path":
|
||||
msg.URLPath = i[2]
|
||||
}
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// WriteTo writes ControlMessage to "Forwarded" http header, "by" and "for" parameters
|
||||
// take form of full IP and port.
|
||||
// Update writes ControlMessage to "Forwarded" http header, "by" and "for"
|
||||
// parameters take form of full IP and port.
|
||||
//
|
||||
// If the server receiving proxied requests requires some address-based functionality,
|
||||
// this parameter MAY instead contain an IP address (and, potentially, a port number)
|
||||
//
|
||||
// See https://tools.ietf.org/html/rfc7239.
|
||||
func (c *ControlMessage) WriteTo(h http.Header) {
|
||||
h.Set(ForwardedHeader, fmt.Sprintf("for=%s; by=%s; proto=%s; path=%s",
|
||||
c.ForwardedFor, c.ForwardedBy, c.Protocol, c.URLPath))
|
||||
// See Forwarded header specification https://tools.ietf.org/html/rfc7239.
|
||||
func (c *ControlMessage) Update(h http.Header) {
|
||||
v := fmt.Sprintf("for=%s; proto=%s; by=%s", c.ForwardedFor, c.Protocol, c.ForwardedBy)
|
||||
h.Set(ForwardedHeader, v)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,11 +11,10 @@ func TestControlMessage_WriteParse(t *testing.T) {
|
|||
Protocol: "tcp",
|
||||
ForwardedFor: "127.0.0.1:58104",
|
||||
ForwardedBy: "127.0.0.1:7777",
|
||||
URLPath: "/some/path",
|
||||
}
|
||||
|
||||
h := make(http.Header)
|
||||
msg.WriteTo(h)
|
||||
var h = http.Header{}
|
||||
msg.Update(h)
|
||||
actual, err := ParseControlMessage(h)
|
||||
if err != nil {
|
||||
t.Errorf("Parse error %s", err)
|
||||
|
|
|
|||
19
server.go
19
server.go
|
|
@ -160,7 +160,7 @@ func (s *Server) handleClient(conn net.Conn) {
|
|||
goto reject
|
||||
}
|
||||
|
||||
s.log.Info("Client %q connected from %q", client.ID, conn.RemoteAddr().String())
|
||||
s.log.Info("Connected to client %s (%q)", conn.RemoteAddr(), client.ID)
|
||||
|
||||
return
|
||||
|
||||
|
|
@ -207,7 +207,7 @@ func (s *Server) listen(l net.Listener, client *AllowedClient) {
|
|||
l.Addr().Network(), conn.RemoteAddr(), l.Addr().String())
|
||||
|
||||
msg := &proto.ControlMessage{
|
||||
Action: proto.RequestClientSession,
|
||||
Action: proto.Proxy,
|
||||
Protocol: l.Addr().Network(),
|
||||
ForwardedFor: conn.RemoteAddr().String(),
|
||||
ForwardedBy: l.Addr().String(),
|
||||
|
|
@ -227,11 +227,7 @@ func (s *Server) proxyConn(host string, conn net.Conn, msg *proto.ControlMessage
|
|||
defer pr.Close()
|
||||
defer pw.Close()
|
||||
|
||||
req, err := http.NewRequest(http.MethodPut, clientURL(host), pr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("request creation error: %s", err)
|
||||
}
|
||||
msg.WriteTo(req.Header)
|
||||
req := proxyRequest(host, msg, pr)
|
||||
|
||||
go transfer("local to remote", pw, conn)
|
||||
|
||||
|
|
@ -262,11 +258,10 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
// RoundTrip is http.RoundTriper implementation.
|
||||
func (s *Server) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
msg := &proto.ControlMessage{
|
||||
Action: proto.RequestClientSession,
|
||||
Action: proto.Proxy,
|
||||
Protocol: proto.HTTP,
|
||||
ForwardedFor: r.RemoteAddr,
|
||||
ForwardedBy: r.Host,
|
||||
URLPath: r.URL.Path,
|
||||
}
|
||||
return s.proxyHTTP(trimPort(r.Host), r, msg)
|
||||
}
|
||||
|
|
@ -276,11 +271,7 @@ func (s *Server) proxyHTTP(host string, r *http.Request, msg *proto.ControlMessa
|
|||
defer pr.Close()
|
||||
defer pw.Close()
|
||||
|
||||
req, err := http.NewRequest(http.MethodPut, clientURL(host), pr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("request creation error: %s", err)
|
||||
}
|
||||
msg.WriteTo(req.Header)
|
||||
req := proxyRequest(host, msg, pr)
|
||||
|
||||
go func() {
|
||||
cw := &countWriter{pw, 0}
|
||||
|
|
|
|||
|
|
@ -12,12 +12,9 @@ import (
|
|||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/koding/logging"
|
||||
"github.com/mmatczuk/tunnel"
|
||||
"github.com/mmatczuk/tunnel/proto"
|
||||
)
|
||||
|
||||
// EchoHTTP starts serving HTTP requests on listener l, it accepts connections,
|
||||
|
|
@ -48,57 +45,6 @@ func EchoTCP(l net.Listener) {
|
|||
}
|
||||
}
|
||||
|
||||
// InMemoryFileServer scans directory dir, loads all files to memory and returns
|
||||
// a http ProxyFunc that maps URL paths to relative filesystem paths i.e. file
|
||||
// ./data/foo/bar.zip will be available under URL host:port/data/foo/bar.zip.
|
||||
func InMemoryFileServer(dir string) (tunnel.ProxyFunc, error) {
|
||||
dir, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get directory absoute path %q: %s", dir, err)
|
||||
}
|
||||
prefix, _ := path.Split(dir)
|
||||
|
||||
mux := make(map[string][]byte)
|
||||
|
||||
visit := func(path string, f os.FileInfo, err error) error {
|
||||
if f.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := ResponseBytes(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mux[path[len(prefix)-1:]] = b
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := filepath.Walk(dir, visit); err != nil {
|
||||
return nil, fmt.Errorf("failed to read directory %q: %s", dir, err)
|
||||
}
|
||||
|
||||
return func(w io.Writer, r io.ReadCloser, msg *proto.ControlMessage) {
|
||||
b, ok := mux[msg.URLPath]
|
||||
if !ok {
|
||||
logging.Warning("Resource not found for %v", msg)
|
||||
resp := &http.Response{
|
||||
Status: "404 Not found",
|
||||
StatusCode: 404,
|
||||
Proto: "HTTP/1.0",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 0,
|
||||
Header: make(http.Header),
|
||||
}
|
||||
resp.Write(w)
|
||||
}
|
||||
w.Write(b)
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ResponseBytes returns http response containing file as body.
|
||||
func ResponseBytes(file string) ([]byte, error) {
|
||||
resp := &http.Response{
|
||||
|
|
|
|||
14
utils.go
14
utils.go
|
|
@ -7,8 +7,22 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/koding/logging"
|
||||
"github.com/mmatczuk/tunnel/proto"
|
||||
)
|
||||
|
||||
func proxyRequest(host string, msg *proto.ControlMessage, r io.Reader) *http.Request {
|
||||
if msg.Action != proto.Proxy {
|
||||
panic("Invalid action")
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodPut, clientURL(host), r)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Could not create request: %s", err))
|
||||
}
|
||||
msg.Update(req.Header)
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
func clientURL(host string) string {
|
||||
return fmt.Sprint("https://", host)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue