mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 21:15:39 +00:00
132 lines
3.3 KiB
Go
132 lines
3.3 KiB
Go
|
// 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
|
||
|
}
|