mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-26 03:25:35 +00:00
a7cb241db1
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>
80 lines
1.8 KiB
Go
80 lines
1.8 KiB
Go
// 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
|
|
})
|
|
}
|