cmd/tailscale/cli: add a risk message about rp_filter

We already present a health warning about this, but it is easy to miss
on a server when blackholing traffic makes it unreachable.

In addition to a health warning, present a risk message when exit node
is enabled.

Example:

```
$ tailscale up --exit-node=lizard
The following issues on your machine will likely make usage of exit nodes impossible:
- interface "ens4" has strict reverse-path filtering enabled
- interface "tailscale0" has strict reverse-path filtering enabled
Please set rp_filter=2 instead of rp_filter=1; see https://github.com/tailscale/tailscale/issues/3310
To skip this warning, use --accept-risk=linux-strict-rp-filter
$
```

Updates #3310

Signed-off-by: Anton Tolchanov <anton@tailscale.com>
This commit is contained in:
Anton Tolchanov
2025-05-22 20:12:59 +01:00
committed by Anton Tolchanov
parent cc8dc9e4dc
commit db34cdcfe7
10 changed files with 143 additions and 71 deletions

View File

@@ -4112,9 +4112,8 @@ func updateExitNodeUsageWarning(p ipn.PrefsView, state *netmon.State, healthTrac
var msg string
if p.ExitNodeIP().IsValid() || p.ExitNodeID() != "" {
warn, _ := netutil.CheckReversePathFiltering(state)
const comment = "please set rp_filter=2 instead of rp_filter=1; see https://github.com/tailscale/tailscale/issues/3310"
if len(warn) > 0 {
msg = fmt.Sprintf("%s: %v, %s", healthmsg.WarnExitNodeUsage, warn, comment)
msg = fmt.Sprintf("%s: %v, %s", healthmsg.WarnExitNodeUsage, warn, healthmsg.DisableRPFilter)
}
}
if len(msg) > 0 {

View File

@@ -32,6 +32,7 @@ import (
"tailscale.com/clientupdate"
"tailscale.com/drive"
"tailscale.com/envknob"
"tailscale.com/health/healthmsg"
"tailscale.com/hostinfo"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnauth"
@@ -82,71 +83,72 @@ var handler = map[string]LocalAPIHandler{
// The other /localapi/v0/NAME handlers are exact matches and contain only NAME
// without a trailing slash:
"alpha-set-device-attrs": (*Handler).serveSetDeviceAttrs, // see tailscale/corp#24690
"bugreport": (*Handler).serveBugReport,
"check-ip-forwarding": (*Handler).serveCheckIPForwarding,
"check-prefs": (*Handler).serveCheckPrefs,
"check-udp-gro-forwarding": (*Handler).serveCheckUDPGROForwarding,
"component-debug-logging": (*Handler).serveComponentDebugLogging,
"debug": (*Handler).serveDebug,
"debug-derp-region": (*Handler).serveDebugDERPRegion,
"debug-dial-types": (*Handler).serveDebugDialTypes,
"debug-log": (*Handler).serveDebugLog,
"debug-packet-filter-matches": (*Handler).serveDebugPacketFilterMatches,
"debug-packet-filter-rules": (*Handler).serveDebugPacketFilterRules,
"debug-peer-endpoint-changes": (*Handler).serveDebugPeerEndpointChanges,
"debug-portmap": (*Handler).serveDebugPortmap,
"derpmap": (*Handler).serveDERPMap,
"dev-set-state-store": (*Handler).serveDevSetStateStore,
"dial": (*Handler).serveDial,
"disconnect-control": (*Handler).disconnectControl,
"dns-osconfig": (*Handler).serveDNSOSConfig,
"dns-query": (*Handler).serveDNSQuery,
"drive/fileserver-address": (*Handler).serveDriveServerAddr,
"drive/shares": (*Handler).serveShares,
"goroutines": (*Handler).serveGoroutines,
"handle-push-message": (*Handler).serveHandlePushMessage,
"id-token": (*Handler).serveIDToken,
"login-interactive": (*Handler).serveLoginInteractive,
"logout": (*Handler).serveLogout,
"logtap": (*Handler).serveLogTap,
"metrics": (*Handler).serveMetrics,
"ping": (*Handler).servePing,
"pprof": (*Handler).servePprof,
"prefs": (*Handler).servePrefs,
"query-feature": (*Handler).serveQueryFeature,
"reload-config": (*Handler).reloadConfig,
"reset-auth": (*Handler).serveResetAuth,
"serve-config": (*Handler).serveServeConfig,
"set-dns": (*Handler).serveSetDNS,
"set-expiry-sooner": (*Handler).serveSetExpirySooner,
"set-gui-visible": (*Handler).serveSetGUIVisible,
"set-push-device-token": (*Handler).serveSetPushDeviceToken,
"set-udp-gro-forwarding": (*Handler).serveSetUDPGROForwarding,
"set-use-exit-node-enabled": (*Handler).serveSetUseExitNodeEnabled,
"start": (*Handler).serveStart,
"status": (*Handler).serveStatus,
"suggest-exit-node": (*Handler).serveSuggestExitNode,
"tka/affected-sigs": (*Handler).serveTKAAffectedSigs,
"tka/cosign-recovery-aum": (*Handler).serveTKACosignRecoveryAUM,
"tka/disable": (*Handler).serveTKADisable,
"tka/force-local-disable": (*Handler).serveTKALocalDisable,
"tka/generate-recovery-aum": (*Handler).serveTKAGenerateRecoveryAUM,
"tka/init": (*Handler).serveTKAInit,
"tka/log": (*Handler).serveTKALog,
"tka/modify": (*Handler).serveTKAModify,
"tka/sign": (*Handler).serveTKASign,
"tka/status": (*Handler).serveTKAStatus,
"tka/submit-recovery-aum": (*Handler).serveTKASubmitRecoveryAUM,
"tka/verify-deeplink": (*Handler).serveTKAVerifySigningDeeplink,
"tka/wrap-preauth-key": (*Handler).serveTKAWrapPreauthKey,
"update/check": (*Handler).serveUpdateCheck,
"update/install": (*Handler).serveUpdateInstall,
"update/progress": (*Handler).serveUpdateProgress,
"upload-client-metrics": (*Handler).serveUploadClientMetrics,
"usermetrics": (*Handler).serveUserMetrics,
"watch-ipn-bus": (*Handler).serveWatchIPNBus,
"whois": (*Handler).serveWhoIs,
"alpha-set-device-attrs": (*Handler).serveSetDeviceAttrs, // see tailscale/corp#24690
"bugreport": (*Handler).serveBugReport,
"check-ip-forwarding": (*Handler).serveCheckIPForwarding,
"check-prefs": (*Handler).serveCheckPrefs,
"check-reverse-path-filtering": (*Handler).serveCheckReversePathFiltering,
"check-udp-gro-forwarding": (*Handler).serveCheckUDPGROForwarding,
"component-debug-logging": (*Handler).serveComponentDebugLogging,
"debug": (*Handler).serveDebug,
"debug-derp-region": (*Handler).serveDebugDERPRegion,
"debug-dial-types": (*Handler).serveDebugDialTypes,
"debug-log": (*Handler).serveDebugLog,
"debug-packet-filter-matches": (*Handler).serveDebugPacketFilterMatches,
"debug-packet-filter-rules": (*Handler).serveDebugPacketFilterRules,
"debug-peer-endpoint-changes": (*Handler).serveDebugPeerEndpointChanges,
"debug-portmap": (*Handler).serveDebugPortmap,
"derpmap": (*Handler).serveDERPMap,
"dev-set-state-store": (*Handler).serveDevSetStateStore,
"dial": (*Handler).serveDial,
"disconnect-control": (*Handler).disconnectControl,
"dns-osconfig": (*Handler).serveDNSOSConfig,
"dns-query": (*Handler).serveDNSQuery,
"drive/fileserver-address": (*Handler).serveDriveServerAddr,
"drive/shares": (*Handler).serveShares,
"goroutines": (*Handler).serveGoroutines,
"handle-push-message": (*Handler).serveHandlePushMessage,
"id-token": (*Handler).serveIDToken,
"login-interactive": (*Handler).serveLoginInteractive,
"logout": (*Handler).serveLogout,
"logtap": (*Handler).serveLogTap,
"metrics": (*Handler).serveMetrics,
"ping": (*Handler).servePing,
"pprof": (*Handler).servePprof,
"prefs": (*Handler).servePrefs,
"query-feature": (*Handler).serveQueryFeature,
"reload-config": (*Handler).reloadConfig,
"reset-auth": (*Handler).serveResetAuth,
"serve-config": (*Handler).serveServeConfig,
"set-dns": (*Handler).serveSetDNS,
"set-expiry-sooner": (*Handler).serveSetExpirySooner,
"set-gui-visible": (*Handler).serveSetGUIVisible,
"set-push-device-token": (*Handler).serveSetPushDeviceToken,
"set-udp-gro-forwarding": (*Handler).serveSetUDPGROForwarding,
"set-use-exit-node-enabled": (*Handler).serveSetUseExitNodeEnabled,
"start": (*Handler).serveStart,
"status": (*Handler).serveStatus,
"suggest-exit-node": (*Handler).serveSuggestExitNode,
"tka/affected-sigs": (*Handler).serveTKAAffectedSigs,
"tka/cosign-recovery-aum": (*Handler).serveTKACosignRecoveryAUM,
"tka/disable": (*Handler).serveTKADisable,
"tka/force-local-disable": (*Handler).serveTKALocalDisable,
"tka/generate-recovery-aum": (*Handler).serveTKAGenerateRecoveryAUM,
"tka/init": (*Handler).serveTKAInit,
"tka/log": (*Handler).serveTKALog,
"tka/modify": (*Handler).serveTKAModify,
"tka/sign": (*Handler).serveTKASign,
"tka/status": (*Handler).serveTKAStatus,
"tka/submit-recovery-aum": (*Handler).serveTKASubmitRecoveryAUM,
"tka/verify-deeplink": (*Handler).serveTKAVerifySigningDeeplink,
"tka/wrap-preauth-key": (*Handler).serveTKAWrapPreauthKey,
"update/check": (*Handler).serveUpdateCheck,
"update/install": (*Handler).serveUpdateInstall,
"update/progress": (*Handler).serveUpdateProgress,
"upload-client-metrics": (*Handler).serveUploadClientMetrics,
"usermetrics": (*Handler).serveUserMetrics,
"watch-ipn-bus": (*Handler).serveWatchIPNBus,
"whois": (*Handler).serveWhoIs,
}
// Register registers a new LocalAPI handler for the given name.
@@ -1175,6 +1177,32 @@ func (h *Handler) serveCheckIPForwarding(w http.ResponseWriter, r *http.Request)
})
}
func (h *Handler) serveCheckReversePathFiltering(w http.ResponseWriter, r *http.Request) {
if !h.PermitRead {
http.Error(w, "reverse path filtering check access denied", http.StatusForbidden)
return
}
var warning string
state := h.b.Sys().NetMon.Get().InterfaceState()
warn, err := netutil.CheckReversePathFiltering(state)
if err == nil && len(warn) > 0 {
var msg strings.Builder
msg.WriteString(healthmsg.WarnExitNodeUsage + ":\n")
for _, w := range warn {
msg.WriteString("- " + w + "\n")
}
msg.WriteString(healthmsg.DisableRPFilter)
warning = msg.String()
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(struct {
Warning string
}{
Warning: warning,
})
}
func (h *Handler) serveCheckUDPGROForwarding(w http.ResponseWriter, r *http.Request) {
if !h.PermitRead {
http.Error(w, "UDP GRO forwarding check access denied", http.StatusForbidden)