|
|
|
|
@ -34,6 +34,7 @@ import ( |
|
|
|
|
"github.com/kortschak/wol" |
|
|
|
|
"golang.org/x/net/dns/dnsmessage" |
|
|
|
|
"tailscale.com/client/tailscale/apitype" |
|
|
|
|
"tailscale.com/envknob" |
|
|
|
|
"tailscale.com/health" |
|
|
|
|
"tailscale.com/hostinfo" |
|
|
|
|
"tailscale.com/ipn" |
|
|
|
|
@ -572,6 +573,9 @@ func (h *peerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
case "/v0/interfaces": |
|
|
|
|
h.handleServeInterfaces(w, r) |
|
|
|
|
return |
|
|
|
|
case "/v0/ingress": |
|
|
|
|
h.handleServeIngress(w, r) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
who := h.peerUser.DisplayName |
|
|
|
|
fmt.Fprintf(w, `<html> |
|
|
|
|
@ -586,6 +590,63 @@ This is my Tailscale device. Your device is %v. |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (h *peerAPIHandler) handleServeIngress(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
// http.Errors only useful if hitting endpoint manually
|
|
|
|
|
// otherwise rely on log lines when debugging ingress connections
|
|
|
|
|
// as connection is hijacked for bidi and is encrypted tls
|
|
|
|
|
if !h.canIngress() { |
|
|
|
|
h.logf("ingress: denied; no ingress cap from %v", h.remoteAddr) |
|
|
|
|
http.Error(w, "denied; no ingress cap", http.StatusForbidden) |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
logAndError := func(code int, publicMsg string) { |
|
|
|
|
h.logf("ingress: bad request from %v: %s", h.remoteAddr, publicMsg) |
|
|
|
|
http.Error(w, publicMsg, http.StatusMethodNotAllowed) |
|
|
|
|
} |
|
|
|
|
bad := func(publicMsg string) { |
|
|
|
|
logAndError(http.StatusBadRequest, publicMsg) |
|
|
|
|
} |
|
|
|
|
if r.Method != "POST" { |
|
|
|
|
logAndError(http.StatusMethodNotAllowed, "only POST allowed") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
srcAddrStr := r.Header.Get("Tailscale-Ingress-Src") |
|
|
|
|
if srcAddrStr == "" { |
|
|
|
|
bad("Tailscale-Ingress-Src header not set") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
srcAddr, err := netip.ParseAddrPort(srcAddrStr) |
|
|
|
|
if err != nil { |
|
|
|
|
bad("Tailscale-Ingress-Src header invalid; want ip:port") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
target := r.Header.Get("Tailscale-Ingress-Target") |
|
|
|
|
if target == "" { |
|
|
|
|
bad("Tailscale-Target-Target header not set") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
if _, _, err := net.SplitHostPort(target); err != nil { |
|
|
|
|
bad("Tailscale-Target-Target header invalid; want host:port") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
getConn := func() (net.Conn, bool) { |
|
|
|
|
conn, _, err := w.(http.Hijacker).Hijack() |
|
|
|
|
if err != nil { |
|
|
|
|
h.logf("ingress: failed hijacking conn") |
|
|
|
|
http.Error(w, "failed hijacking conn", http.StatusInternalServerError) |
|
|
|
|
return nil, false |
|
|
|
|
} |
|
|
|
|
io.WriteString(conn, "HTTP/1.1 101 Switching Protocols\r\n\r\n") |
|
|
|
|
return conn, true |
|
|
|
|
} |
|
|
|
|
sendRST := func() { |
|
|
|
|
http.Error(w, "denied", http.StatusForbidden) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
h.ps.b.HandleIngressTCPConn(h.peerNode, ipn.HostPort(target), srcAddr, getConn, sendRST) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (h *peerAPIHandler) handleServeInterfaces(w http.ResponseWriter, r *http.Request) { |
|
|
|
|
if !h.canDebug() { |
|
|
|
|
http.Error(w, "denied; no debug access", http.StatusForbidden) |
|
|
|
|
@ -694,6 +755,13 @@ func (h *peerAPIHandler) canWakeOnLAN() bool { |
|
|
|
|
return h.isSelf || h.peerHasCap(tailcfg.CapabilityWakeOnLAN) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var allowSelfIngress = envknob.RegisterBool("TS_ALLOW_SELF_INGRESS") |
|
|
|
|
|
|
|
|
|
// canIngress reports whether h can send ingress requests to this node.
|
|
|
|
|
func (h *peerAPIHandler) canIngress() bool { |
|
|
|
|
return h.peerHasCap(tailcfg.CapabilityIngress) || (allowSelfIngress() && h.isSelf) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (h *peerAPIHandler) peerHasCap(wantCap string) bool { |
|
|
|
|
for _, hasCap := range h.ps.b.PeerCaps(h.remoteAddr.Addr()) { |
|
|
|
|
if hasCap == wantCap { |
|
|
|
|
|