mirror of https://github.com/tailscale/tailscale/
ipn/ipnlocal: add support for funnel in tsnet
Previously the part that handled Funnel connections was not aware of any listeners that tsnet.Servers might have had open so it would check against the ServeConfig and fail. Adding a ServeConfig for a TCP proxy was also not suitable in this scenario as that would mean creating two different listeners and have one forward to the other, which really meant that you could not have funnel and tailnet-only listeners on the same port. This also introduces the ipn.FunnelConn as a way for users to identify whether the call is coming over funnel or not. Currently it only holds the underlying conn and the target as presented in the "Tailscale-Ingress-Target" header. Signed-off-by: Maisem Ali <maisem@tailscale.com>pull/7523/head
parent
dad78f31f3
commit
b797f773c7
@ -0,0 +1,131 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// The tsnet-funnel server demonstrates how to use tsnet with Funnel.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/tsnet"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
ln, err := s.Listen("tcp", *addr)
|
||||
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
|
||||
}
|
Loading…
Reference in New Issue