mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-26 05:09:41 +00:00
tsnet: add ListenFunnel
This lets a tsnet binary share a server out over Tailscale Funnel. Signed-off-by: David Crawshaw <crawshaw@tailscale.com> Signed-off-by: Maisem Ali <maisem@tailscale.com> Signed-off-by: Shayne Sweeney <shayne@tailscale.com>
This commit is contained in:
@@ -2,130 +2,40 @@
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// The tsnet-funnel server demonstrates how to use tsnet with Funnel.
|
||||
//
|
||||
// To use it, generate an auth key from the Tailscale admin panel and
|
||||
// run the demo with the key:
|
||||
//
|
||||
// TS_AUTHKEY=<yourkey> go run tsnet-funnel.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/tsnet"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
addr = flag.String("addr", ":443", "address to listen on")
|
||||
)
|
||||
|
||||
func enableFunnel(ctx context.Context, s *tsnet.Server) error {
|
||||
st, err := s.Up(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(st.CertDomains) == 0 {
|
||||
return errors.New("tsnet: you must enable HTTPS in the admin panel to proceed")
|
||||
}
|
||||
domain := st.CertDomains[0]
|
||||
|
||||
hp := ipn.HostPort(net.JoinHostPort(domain, "443"))
|
||||
|
||||
srvConfig := &ipn.ServeConfig{
|
||||
AllowFunnel: map[ipn.HostPort]bool{
|
||||
hp: true,
|
||||
},
|
||||
}
|
||||
lc, err := s.LocalClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return lc.SetServeConfig(ctx, srvConfig)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
s := new(tsnet.Server)
|
||||
defer s.Close()
|
||||
ctx := context.Background()
|
||||
if err := enableFunnel(ctx, s); err != nil {
|
||||
log.Fatal(err)
|
||||
s := &tsnet.Server{
|
||||
Dir: "./funnel-demo-config.state",
|
||||
Logf: logger.Discard,
|
||||
Hostname: "fun",
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ln, err := s.Listen("tcp", *addr)
|
||||
ln, err := s.ListenFunnel("tcp", ":443")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer ln.Close()
|
||||
|
||||
lc, err := s.LocalClient()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
ln = tls.NewListener(ln, &tls.Config{
|
||||
GetCertificate: lc.GetCertificate,
|
||||
})
|
||||
httpServer := &http.Server{
|
||||
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
|
||||
if tc, ok := c.(*tls.Conn); ok {
|
||||
// First unwrap the TLS connection to get the underlying
|
||||
// net.Conn.
|
||||
c = tc.NetConn()
|
||||
}
|
||||
// Then check if the underlying net.Conn is a FunnelConn.
|
||||
if fc, ok := c.(*ipn.FunnelConn); ok {
|
||||
ctx = context.WithValue(ctx, funnelKey{}, true)
|
||||
ctx = context.WithValue(ctx, funnelSrcKey{}, fc.Src)
|
||||
}
|
||||
return ctx
|
||||
},
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if isFunnel(r.Context()) {
|
||||
fmt.Fprintln(w, "<html><body><h1>Hello, internet!</h1>")
|
||||
fmt.Fprintln(w, "<p>You are connected over the internet!</p>")
|
||||
fmt.Fprintf(w, "<p>You are coming from %v</p></html>\n", funnelSrc(r.Context()))
|
||||
} else {
|
||||
fmt.Fprintln(w, "<html><body><h1>Hello, tailnet!</h1>")
|
||||
fmt.Fprintln(w, "<p>You are connected over the tailnet!</p>")
|
||||
who, err := lc.WhoIs(r.Context(), r.RemoteAddr)
|
||||
if err != nil {
|
||||
log.Printf("WhoIs(%v): %v", r.RemoteAddr, err)
|
||||
fmt.Fprintf(w, "<p>I do not know who you are</p>")
|
||||
} else if len(who.Node.Tags) > 0 {
|
||||
fmt.Fprintf(w, "<p>You are using a tagged node: %v</p>\n", who.Node.Tags)
|
||||
} else {
|
||||
fmt.Fprintf(w, "<p>You are %v</p>\n", who.UserProfile.DisplayName)
|
||||
}
|
||||
fmt.Fprintf(w, "<p>You are coming from %v</p></html>\n", r.RemoteAddr)
|
||||
}
|
||||
}),
|
||||
}
|
||||
log.Fatal(httpServer.Serve(ln))
|
||||
}
|
||||
|
||||
// funnelKey is a context key used to indicate that a request is coming
|
||||
// over the internet.
|
||||
// It is not used by tsnet, but is used by this example to demonstrate
|
||||
// how to detect when a request is coming over the internet rather than
|
||||
// over the tailnet.
|
||||
type funnelKey struct{}
|
||||
|
||||
// funnelSrcKey is a context key used to indicate the source of a
|
||||
// request.
|
||||
type funnelSrcKey struct{}
|
||||
|
||||
// isFunnel reports whether the request is coming over the internet.
|
||||
func isFunnel(ctx context.Context) bool {
|
||||
v, _ := ctx.Value(funnelKey{}).(bool)
|
||||
return v
|
||||
}
|
||||
|
||||
func funnelSrc(ctx context.Context) netip.AddrPort {
|
||||
v, _ := ctx.Value(funnelSrcKey{}).(netip.AddrPort)
|
||||
return v
|
||||
err = http.Serve(ln, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "<html><body><h1>Hello, internet!</h1>")
|
||||
}))
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
Reference in New Issue
Block a user