mirror of
https://github.com/tailscale/tailscale.git
synced 2024-12-01 14:05:39 +00:00
cmd/tailscaled: add support for running an HTTP proxy
This adds support for tailscaled to be an HTTP proxy server. It shares the same backend dialing code as the SOCK5 server, but the client protocol is HTTP (including CONNECT), rather than SOCKS. Fixes #2289 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
29a8fb45d3
commit
a7cb241db1
@ -277,7 +277,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
net from crypto/tls+
|
||||
net/http from expvar+
|
||||
net/http/httptrace from github.com/tcnksm/go-httpstat+
|
||||
net/http/httputil from tailscale.com/ipn/localapi
|
||||
net/http/httputil from tailscale.com/ipn/localapi+
|
||||
net/http/internal from net/http+
|
||||
net/http/pprof from tailscale.com/cmd/tailscaled+
|
||||
net/textproto from golang.org/x/net/http/httpguts+
|
||||
|
79
cmd/tailscaled/proxy.go
Normal file
79
cmd/tailscaled/proxy.go
Normal file
@ -0,0 +1,79 @@
|
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// HTTP proxy code
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// httpProxyHandler returns an HTTP proxy http.Handler using the
|
||||
// provided backend dialer.
|
||||
func httpProxyHandler(dialer func(ctx context.Context, netw, addr string) (net.Conn, error)) http.Handler {
|
||||
rp := &httputil.ReverseProxy{
|
||||
Director: func(r *http.Request) {}, // no change
|
||||
Transport: &http.Transport{
|
||||
DialContext: dialer,
|
||||
},
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "CONNECT" {
|
||||
backURL := r.RequestURI
|
||||
if strings.HasPrefix(backURL, "/") || backURL == "*" {
|
||||
http.Error(w, "bogus RequestURI; must be absolute URL or CONNECT", 400)
|
||||
return
|
||||
}
|
||||
rp.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// CONNECT support:
|
||||
|
||||
dst := r.RequestURI
|
||||
c, err := dialer(r.Context(), "tcp", dst)
|
||||
if err != nil {
|
||||
w.Header().Set("Tailscale-Connect-Error", err.Error())
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
cc, ccbuf, err := w.(http.Hijacker).Hijack()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
defer cc.Close()
|
||||
|
||||
io.WriteString(cc, "HTTP/1.1 200 OK\r\n\r\n")
|
||||
|
||||
var clientSrc io.Reader = ccbuf
|
||||
if ccbuf.Reader.Buffered() == 0 {
|
||||
// In the common case (with no
|
||||
// buffered data), read directly from
|
||||
// the underlying client connection to
|
||||
// save some memory, letting the
|
||||
// bufio.Reader/Writer get GC'ed.
|
||||
clientSrc = cc
|
||||
}
|
||||
|
||||
errc := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := io.Copy(cc, c)
|
||||
errc <- err
|
||||
}()
|
||||
go func() {
|
||||
_, err := io.Copy(c, clientSrc)
|
||||
errc <- err
|
||||
}()
|
||||
<-errc
|
||||
})
|
||||
}
|
@ -82,6 +82,7 @@ func defaultTunName() string {
|
||||
birdSocketPath string
|
||||
verbose int
|
||||
socksAddr string // listen address for SOCKS5 server
|
||||
httpProxyAddr string // listen address for HTTP proxy server
|
||||
}
|
||||
|
||||
var (
|
||||
@ -110,6 +111,7 @@ func main() {
|
||||
flag.BoolVar(&args.cleanup, "cleanup", false, "clean up system state and exit")
|
||||
flag.StringVar(&args.debug, "debug", "", "listen address ([ip]:port) of optional debug server")
|
||||
flag.StringVar(&args.socksAddr, "socks5-server", "", `optional [ip]:port to run a SOCK5 server (e.g. "localhost:1080")`)
|
||||
flag.StringVar(&args.httpProxyAddr, "http-proxy-server", "", `optional [ip]:port to run an HTTP proxy (e.g. "localhost:8080")`)
|
||||
flag.StringVar(&args.tunname, "tun", defaultTunName(), `tunnel interface name; use "userspace-networking" (beta) to not use TUN`)
|
||||
flag.Var(flagtype.PortValue(&args.port, 0), "port", "UDP port to listen on for WireGuard and peer-to-peer traffic; 0 means automatically select")
|
||||
flag.StringVar(&args.statepath, "state", paths.DefaultTailscaledStateFile(), "path of state file; use 'kube:<secret-name>' to use Kubernetes secrets")
|
||||
@ -278,19 +280,8 @@ func run() error {
|
||||
}
|
||||
pol.Logtail.SetLinkMonitor(linkMon)
|
||||
|
||||
var socksListener net.Listener
|
||||
if args.socksAddr != "" {
|
||||
var err error
|
||||
socksListener, err = net.Listen("tcp", args.socksAddr)
|
||||
if err != nil {
|
||||
log.Fatalf("SOCKS5 listener: %v", err)
|
||||
}
|
||||
if strings.HasSuffix(args.socksAddr, ":0") {
|
||||
// Log kernel-selected port number so integration tests
|
||||
// can find it portably.
|
||||
log.Printf("SOCKS5 listening on %v", socksListener.Addr())
|
||||
}
|
||||
}
|
||||
socksListener := mustStartTCPListener("SOCKS5", args.socksAddr)
|
||||
httpProxyListener := mustStartTCPListener("HTTP proxy", args.httpProxyAddr)
|
||||
|
||||
e, useNetstack, err := createEngine(logf, linkMon)
|
||||
if err != nil {
|
||||
@ -304,11 +295,19 @@ func run() error {
|
||||
ns = mustStartNetstack(logf, e, onlySubnets)
|
||||
}
|
||||
|
||||
if socksListener != nil {
|
||||
if socksListener != nil || httpProxyListener != nil {
|
||||
srv := tssocks.NewServer(logger.WithPrefix(logf, "socks5: "), e, ns)
|
||||
go func() {
|
||||
log.Fatalf("SOCKS5 server exited: %v", srv.Serve(socksListener))
|
||||
}()
|
||||
if httpProxyListener != nil {
|
||||
hs := &http.Server{Handler: httpProxyHandler(srv.Dialer)}
|
||||
go func() {
|
||||
log.Fatalf("HTTP proxy exited: %v", hs.Serve(httpProxyListener))
|
||||
}()
|
||||
}
|
||||
if socksListener != nil {
|
||||
go func() {
|
||||
log.Fatalf("SOCKS5 server exited: %v", srv.Serve(socksListener))
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
e = wgengine.NewWatchdog(e)
|
||||
@ -468,3 +467,19 @@ func mustStartNetstack(logf logger.Logf, e wgengine.Engine, onlySubnets bool) *n
|
||||
}
|
||||
return ns
|
||||
}
|
||||
|
||||
func mustStartTCPListener(name, addr string) net.Listener {
|
||||
if addr == "" {
|
||||
return nil
|
||||
}
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
log.Fatalf("%v listener: %v", name, err)
|
||||
}
|
||||
if strings.HasSuffix(addr, ":0") {
|
||||
// Log kernel-selected port number so integration tests
|
||||
// can find it portably.
|
||||
log.Printf("%v listening on %v", name, ln.Addr())
|
||||
}
|
||||
return ln
|
||||
}
|
||||
|
@ -25,6 +25,7 @@
|
||||
_ "net"
|
||||
_ "net/http"
|
||||
_ "net/http/httptrace"
|
||||
_ "net/http/httputil"
|
||||
_ "net/http/pprof"
|
||||
_ "net/url"
|
||||
_ "os"
|
||||
|
@ -25,6 +25,7 @@
|
||||
_ "net"
|
||||
_ "net/http"
|
||||
_ "net/http/httptrace"
|
||||
_ "net/http/httputil"
|
||||
_ "net/http/pprof"
|
||||
_ "net/url"
|
||||
_ "os"
|
||||
|
@ -25,6 +25,7 @@
|
||||
_ "net"
|
||||
_ "net/http"
|
||||
_ "net/http/httptrace"
|
||||
_ "net/http/httputil"
|
||||
_ "net/http/pprof"
|
||||
_ "net/url"
|
||||
_ "os"
|
||||
|
@ -25,6 +25,7 @@
|
||||
_ "net"
|
||||
_ "net/http"
|
||||
_ "net/http/httptrace"
|
||||
_ "net/http/httputil"
|
||||
_ "net/http/pprof"
|
||||
_ "net/url"
|
||||
_ "os"
|
||||
|
@ -29,6 +29,7 @@
|
||||
_ "net"
|
||||
_ "net/http"
|
||||
_ "net/http/httptrace"
|
||||
_ "net/http/httputil"
|
||||
_ "net/http/pprof"
|
||||
_ "net/url"
|
||||
_ "os"
|
||||
|
Loading…
Reference in New Issue
Block a user