Skip to content
This repository has been archived by the owner on Oct 7, 2023. It is now read-only.

WIP: add experimental socks and http proxying to tailnet #8

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 200 additions & 0 deletions copied.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// The content of this is copied from
// github.com/tailscale/tailscale/commit/7c7f37342fa710bbf223aa29c5631f2d182fa59d
// with the following license.

// BSD 3-Clause License
//
// Copyright (c) 2020 Tailscale & AUTHORS. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package main

import (
"context"
"errors"
"fmt"
"io"
"log"
"net"
"net/http"
"net/http/httputil"
"strings"

"tailscale.com/net/proxymux"
"tailscale.com/net/socks5"
"tailscale.com/tsnet"
"tailscale.com/types/logger"
)

func runProxies(tsnetServer *tsnet.Server, socksAddr, httpProxyAddr string) error {
socksListener, httpProxyListener := mustStartProxyListeners(socksAddr, httpProxyAddr)
if socksListener == nil && httpProxyListener == nil {
return errors.New("proxy listeners are nil")
}
defer socksListener.Close()
defer httpProxyListener.Close()

ec := make(chan error, 1)
if httpProxyListener != nil {
hs := &http.Server{Handler: httpProxyHandler(tsnetServer.Dial)}
go func() {
ec <- fmt.Errorf("http proxy exited with error: %w", hs.Serve(httpProxyListener))
}()
}

if socksListener != nil {
ss := &socks5.Server{
Logf: logger.WithPrefix(log.Printf, "socks5: "),
Dialer: tsnetServer.Dial,
}

go func() {
ec <- fmt.Errorf("socks5 server exited with error: %w", ss.Serve(socksListener))
}()
}

return <-ec
}

// copied from: cmd/tailscaled/tailscaled.go
//
// mustStartProxyListeners creates listeners for local SOCKS and HTTP
// proxies, if the respective addresses are not empty. socksAddr and
// httpAddr can be the same, in which case socksListener will receive
// connections that look like they're speaking SOCKS and httpListener
// will receive everything else.
//
// socksListener and httpListener can be nil, if their respective
// addrs are empty.
func mustStartProxyListeners(socksAddr, httpAddr string) (socksListener, httpListener net.Listener) {
if socksAddr == httpAddr && socksAddr != "" && !strings.HasSuffix(socksAddr, ":0") {
ln, err := net.Listen("tcp", socksAddr)
if err != nil {
log.Fatalf("proxy listener: %v", err)
}
return proxymux.SplitSOCKSAndHTTP(ln)
}

var err error
if socksAddr != "" {
socksListener, err = net.Listen("tcp", socksAddr)
if err != nil {
log.Fatalf("SOCKS5 listener: %v", err)
}
if strings.HasSuffix(socksAddr, ":0") {
// Log kernel-selected port number so integration tests
// can find it portably.
log.Printf("SOCKS5 listening on %v", socksListener.Addr())
}
}
if httpAddr != "" {
httpListener, err = net.Listen("tcp", httpAddr)
if err != nil {
log.Fatalf("HTTP proxy listener: %v", err)
}
if strings.HasSuffix(httpAddr, ":0") {
// Log kernel-selected port number so integration tests
// can find it portably.
log.Printf("HTTP proxy listening on %v", httpListener.Addr())
}
}

return socksListener, httpListener
}

// copied from: cmd/tailscaled/proxy.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

// 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
})
}
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ module github.com/markpash/tailscale-sidecar

go 1.18

require tailscale.com v1.22.2
require (
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
tailscale.com v1.22.2
)

require (
github.com/akutz/memconn v0.1.0 // indirect
Expand Down Expand Up @@ -37,7 +40,6 @@ require (
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 // indirect
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e // indirect
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect
Expand Down
39 changes: 29 additions & 10 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ package main
import (
"crypto/tls"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"log"
"net"
"os"
"sync"

"golang.org/x/sync/errgroup"
"tailscale.com/client/tailscale"
"tailscale.com/tsnet"
)
Expand Down Expand Up @@ -40,9 +41,9 @@ func loadBindings() ([]Binding, error) {
return nil, err
}

if len(bindings) == 0 {
return nil, errors.New("bindings empty")
}
// if len(bindings) == 0 {
// return nil, errors.New("bindings empty")
// }

return bindings, nil
}
Expand Down Expand Up @@ -126,20 +127,38 @@ func main() {
panic(err)
}

proxyToTailnet := flag.Bool("proxy-to-tailnet", false, "EXPERIMENTAL: flag to enable proxying into the tailnet with socks and http proxies")
socksAddr := flag.String("socksproxy", "localhost:1080", "set the address for socks proxy to listen on")
httpProxyAddr := flag.String("httpproxy", "localhost:8080", "set the address for http proxy to listen on")
flag.Parse()

bindings, err := loadBindings()
if err != nil {
panic(err)
}

s := newTsNetServer()
if err := s.Start(); err != nil {
panic(err)
}

eg := errgroup.Group{}

if *proxyToTailnet {
eg.Go(func() error {
return runProxies(&s, *socksAddr, *httpProxyAddr)
})
}

var wg sync.WaitGroup
for _, binding := range bindings {
wg.Add(1)
go func(binding Binding) {
defer wg.Done()
binding := binding
eg.Go(func() error {
proxyBind(&s, &binding)
}(binding)
return nil
})
}

if err := eg.Wait(); err != nil {
panic(err)
}
wg.Wait()
}