mirror of https://github.com/tailscale/tailscale/
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>pull/2961/head
parent
29a8fb45d3
commit
a7cb241db1
@ -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
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue