summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMichael Ben-Ami <mzb@tailscale.com>2026-04-17 10:41:14 -0400
committerMichael Ben-Ami <mzb@tailscale.com>2026-04-17 10:41:14 -0400
commit0db350d1ce9b0461d513e456f4c0d8a428a55db9 (patch)
tree79a0110e86b542a6319b5c36fb976228aced198e
parent47ecbe5845b90efc4511f6f9e712aa55bb2b4ec7 (diff)
downloadtailscale-mzb/conn25-access-to-ext.tar.xz
tailscale-mzb/conn25-access-to-ext.zip
-rw-r--r--feature/conn25/conn25.go65
-rw-r--r--feature/conn25/conn25_test.go42
2 files changed, 52 insertions, 55 deletions
diff --git a/feature/conn25/conn25.go b/feature/conn25/conn25.go
index e5db9619b..81b64a135 100644
--- a/feature/conn25/conn25.go
+++ b/feature/conn25/conn25.go
@@ -72,9 +72,10 @@ func normalizeDNSName(name string) (dnsname.FQDN, error) {
func init() {
feature.Register(featureName)
ipnext.RegisterExtension(featureName, func(logf logger.Logf, sb ipnext.SafeBackend) (ipnext.Extension, error) {
+ conn25 := newConn25(logger.WithPrefix(logf, "conn25: "))
+ conn25.backend = sb
return &extension{
- conn25: newConn25(logger.WithPrefix(logf, "conn25: ")),
- backend: sb,
+ conn25: conn25,
}, nil
})
ipnlocal.RegisterPeerAPIHandler("/v0/connector/transit-ip", handleConnectorTransitIP)
@@ -97,10 +98,7 @@ func handleConnectorTransitIP(h ipnlocal.PeerAPIHandler, w http.ResponseWriter,
// extension is an [ipnext.Extension] managing the connector on platforms
// that import this package.
type extension struct {
- conn25 *Conn25 // safe for concurrent access and only set at creation
- backend ipnext.SafeBackend // safe for concurrent access and only set at creation
-
- host ipnext.Host // set in Init, read-only after
+ conn25 *Conn25 // safe for concurrent access and only set at creation
ctxCancel context.CancelCauseFunc // cancels sendLoop goroutine
}
@@ -119,7 +117,7 @@ func (e *extension) Init(host ipnext.Host) error {
if e.ctxCancel != nil {
return nil
}
- e.host = host
+ e.conn25.host = host
dph := newDatapathHandler(e.conn25, e.conn25.client.logf)
if err := e.installHooks(dph); err != nil {
@@ -129,17 +127,17 @@ func (e *extension) Init(host ipnext.Host) error {
ctx, cancel := context.WithCancelCause(context.Background())
e.ctxCancel = cancel
- go e.sendLoop(ctx)
+ go e.conn25.sendLoop(ctx)
return nil
}
func (e *extension) installHooks(dph *datapathHandler) error {
// Make sure we can access the DNS manager and the system tun.
- dnsManager, ok := e.backend.Sys().DNSManager.GetOK()
+ dnsManager, ok := e.conn25.backend.Sys().DNSManager.GetOK()
if !ok {
return errors.New("could not access system dns manager")
}
- tun, ok := e.backend.Sys().Tun.GetOK()
+ tun, ok := e.conn25.backend.Sys().Tun.GetOK()
if !ok {
return errors.New("could not access system tun")
}
@@ -170,15 +168,15 @@ func (e *extension) installHooks(dph *datapathHandler) error {
// Manage how we react to changes to the current node,
// including property changes (e.g. HostInfo, Capabilities, CapMap).
- e.host.Hooks().OnSelfChange.Add(e.onSelfChange)
+ e.conn25.host.Hooks().OnSelfChange.Add(e.onSelfChange)
// Manage how we react profile state changes, which include
// prefs changes.
- e.host.Hooks().ProfileStateChange.Add(e.profileStateChange)
+ e.conn25.host.Hooks().ProfileStateChange.Add(e.profileStateChange)
// Allow the client to send packets with Transit IP destinations
// in the link-local space.
- e.host.Hooks().Filter.LinkLocalAllowHooks.Add(func(p packet.Parsed) (bool, string) {
+ e.conn25.host.Hooks().Filter.LinkLocalAllowHooks.Add(func(p packet.Parsed) (bool, string) {
if !e.conn25.isConfigured() {
return false, ""
}
@@ -187,7 +185,7 @@ func (e *extension) installHooks(dph *datapathHandler) error {
// Allow the connector to receive packets with Transit IP destinations
// in the link-local space.
- e.host.Hooks().Filter.LinkLocalAllowHooks.Add(func(p packet.Parsed) (bool, string) {
+ e.conn25.host.Hooks().Filter.LinkLocalAllowHooks.Add(func(p packet.Parsed) (bool, string) {
if !e.conn25.isConfigured() {
return false, ""
}
@@ -196,7 +194,7 @@ func (e *extension) installHooks(dph *datapathHandler) error {
// Allow the connector to receive packets with Transit IP destinations
// that are not "local" to it, and that it does not advertise.
- e.host.Hooks().Filter.IngressAllowHooks.Add(func(p packet.Parsed) (bool, string) {
+ e.conn25.host.Hooks().Filter.IngressAllowHooks.Add(func(p packet.Parsed) (bool, string) {
if !e.conn25.isConfigured() {
return false, ""
}
@@ -204,7 +202,7 @@ func (e *extension) installHooks(dph *datapathHandler) error {
})
// Give the client the Magic IP range to install on the OS.
- e.host.Hooks().ExtraRouterConfigRoutes.Set(func() views.Slice[netip.Prefix] {
+ e.conn25.host.Hooks().ExtraRouterConfigRoutes.Set(func() views.Slice[netip.Prefix] {
if !e.conn25.isConfigured() {
return views.Slice[netip.Prefix]{}
}
@@ -212,7 +210,7 @@ func (e *extension) installHooks(dph *datapathHandler) error {
})
// Tell WireGuard what Transit IPs belong to which connector peers.
- e.host.Hooks().ExtraWireGuardAllowedIPs.Set(func(k key.NodePublic) views.Slice[netip.Prefix] {
+ e.conn25.host.Hooks().ExtraWireGuardAllowedIPs.Set(func(k key.NodePublic) views.Slice[netip.Prefix] {
if !e.conn25.isConfigured() {
return views.Slice[netip.Prefix]{}
}
@@ -227,7 +225,7 @@ func (e *extension) installHooks(dph *datapathHandler) error {
// for Conn25 to be fully configured and ready to use.
func (e *extension) seedPrefsConfig() {
var cfg config
- cfg.prefs = configFromPrefs(e.host.Profiles().CurrentPrefs())
+ cfg.prefs = configFromPrefs(e.conn25.host.Profiles().CurrentPrefs())
e.conn25.reconfig(cfg)
}
@@ -315,7 +313,10 @@ type appAddr struct {
// Conn25 holds state for routing traffic for a domain via a connector.
type Conn25 struct {
- mu sync.Mutex // mu protects reconfiguration of client and connector
+ mu sync.Mutex // mu protects reconfiguration of client and connector
+ host ipnext.Host
+ backend ipnext.SafeBackend
+
client *client
connector *connector
}
@@ -778,30 +779,30 @@ func (c *client) addTransitIPForConnector(tip netip.Addr, conn tailcfg.NodeView)
return c.assignments.insertTransitConnMapping(tip, conn.Key())
}
-func (e *extension) sendLoop(ctx context.Context) {
+func (c *Conn25) sendLoop(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
- case as := <-e.conn25.client.addrsCh:
- if err := e.handleAddressAssignment(ctx, as); err != nil {
- e.conn25.client.logf("error handling transit IP assignment (app: %s, mip: %v, src: %v): %v", as.app, as.magic, as.dst, err)
+ case as := <-c.client.addrsCh:
+ if err := c.handleAddressAssignment(ctx, as); err != nil {
+ c.client.logf("error handling transit IP assignment (app: %s, mip: %v, src: %v): %v", as.app, as.magic, as.dst, err)
}
}
}
}
-func (e *extension) handleAddressAssignment(ctx context.Context, as addrs) error {
- conn, err := e.sendAddressAssignment(ctx, as)
+func (c *Conn25) handleAddressAssignment(ctx context.Context, as addrs) error {
+ conn, err := c.sendAddressAssignment(ctx, as)
if err != nil {
return err
}
- err = e.conn25.client.addTransitIPForConnector(as.transit, conn)
+ err = c.client.addTransitIPForConnector(as.transit, conn)
if err != nil {
return err
}
- e.host.AuthReconfigAsync()
+ c.host.AuthReconfigAsync()
return nil
}
@@ -868,14 +869,14 @@ func makePeerAPIReq(ctx context.Context, httpClient *http.Client, urlBase string
return nil
}
-func (e *extension) sendAddressAssignment(ctx context.Context, as addrs) (tailcfg.NodeView, error) {
- app, ok := e.conn25.client.getConfig().nv.appsByName[as.app]
+func (c *Conn25) sendAddressAssignment(ctx context.Context, as addrs) (tailcfg.NodeView, error) {
+ app, ok := c.client.getConfig().nv.appsByName[as.app]
if !ok {
- e.conn25.client.logf("App not found for app: %s (domain: %s)", as.app, as.domain)
+ c.client.logf("App not found for app: %s (domain: %s)", as.app, as.domain)
return tailcfg.NodeView{}, errors.New("app not found")
}
- nb := e.host.NodeBackend()
+ nb := c.host.NodeBackend()
peers := appc.PickConnector(nb, app)
var urlBase string
var conn tailcfg.NodeView
@@ -889,7 +890,7 @@ func (e *extension) sendAddressAssignment(ctx context.Context, as addrs) (tailcf
if urlBase == "" {
return tailcfg.NodeView{}, errors.New("no connector peer found to handle address assignment")
}
- client := e.backend.Sys().Dialer.Get().PeerAPIHTTPClient()
+ client := c.backend.Sys().Dialer.Get().PeerAPIHTTPClient()
return conn, makePeerAPIReq(ctx, client, urlBase, as)
}
diff --git a/feature/conn25/conn25_test.go b/feature/conn25/conn25_test.go
index 8e829724b..658fd1519 100644
--- a/feature/conn25/conn25_test.go
+++ b/feature/conn25/conn25_test.go
@@ -1095,12 +1095,10 @@ func TestAddressAssignmentIsHandled(t *testing.T) {
Key: key.NodePublicFromRaw32(mem.B([]byte{0: 0xff, 1: 0xff, 31: 0x01})),
}).View()
- ext := &extension{
- conn25: newConn25(logger.Discard),
- backend: newTestSafeBackend(),
- }
+ conn25 := newConn25(logger.Discard)
+ backend := newTestSafeBackend()
authReconfigAsyncCalled := make(chan struct{}, 1)
- if err := ext.Init(&testHost{
+ host := &testHost{
nb: &testNodeBackend{
peers: []tailcfg.NodeView{connectorPeer},
peerAPIURL: peersAPI.URL,
@@ -1109,10 +1107,10 @@ func TestAddressAssignmentIsHandled(t *testing.T) {
authReconfigAsync: func() {
authReconfigAsyncCalled <- struct{}{}
},
- }); err != nil {
- t.Fatal(err)
}
- defer ext.Shutdown()
+ conn25.backend = backend
+ conn25.host = host
+ go conn25.sendLoop(t.Context())
sn := makeSelfNode(t, []appctype.Conn25Attr{{
Name: "app1",
@@ -1121,7 +1119,7 @@ func TestAddressAssignmentIsHandled(t *testing.T) {
}}, []string{})
cfg := mustConfig(t, sn, testPrefsNotConnector)
- ext.conn25.reconfig(cfg)
+ conn25.reconfig(cfg)
as := addrs{
dst: netip.MustParseAddr("1.2.3.4"),
@@ -1130,10 +1128,10 @@ func TestAddressAssignmentIsHandled(t *testing.T) {
domain: "example.com.",
app: "app1",
}
- if err := ext.conn25.client.assignments.insert(as); err != nil {
+ if err := conn25.client.assignments.insert(as); err != nil {
t.Fatalf("error inserting address assignments: %v", err)
}
- ext.conn25.client.enqueueAddressAssignment(as)
+ conn25.client.enqueueAddressAssignment(as)
select {
case got := <-received:
@@ -1521,12 +1519,10 @@ func TestHandleAddressAssignmentStoresTransitIPs(t *testing.T) {
}).View(),
}
- ext := &extension{
- conn25: newConn25(logger.Discard),
- backend: newTestSafeBackend(),
- }
+ conn25 := newConn25(logger.Discard)
+ backend := newTestSafeBackend()
authReconfigAsyncCalled := make(chan struct{}, 1)
- if err := ext.Init(&testHost{
+ host := &testHost{
nb: &testNodeBackend{
peers: connectorPeers,
peerAPIURL: peersAPI.URL,
@@ -1535,10 +1531,10 @@ func TestHandleAddressAssignmentStoresTransitIPs(t *testing.T) {
authReconfigAsync: func() {
authReconfigAsyncCalled <- struct{}{}
},
- }); err != nil {
- t.Fatal(err)
}
- defer ext.Shutdown()
+ conn25.backend = backend
+ conn25.host = host
+ go conn25.sendLoop(t.Context())
sn := makeSelfNode(t, []appctype.Conn25Attr{
{
@@ -1554,7 +1550,7 @@ func TestHandleAddressAssignmentStoresTransitIPs(t *testing.T) {
}, []string{})
cfg := mustConfig(t, sn, testPrefsNotConnector)
- ext.conn25.reconfig(cfg)
+ conn25.reconfig(cfg)
type lookup struct {
connKey key.NodePublic
@@ -1651,10 +1647,10 @@ func TestHandleAddressAssignmentStoresTransitIPs(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
// Add and enqueue the addrs, and then wait for the send to complete
// (as indicated by authReconfig being called).
- if err := ext.conn25.client.assignments.insert(tt.as); err != nil {
+ if err := conn25.client.assignments.insert(tt.as); err != nil {
t.Fatalf("error inserting address assignment: %v", err)
}
- if err := ext.conn25.client.enqueueAddressAssignment(tt.as); err != nil {
+ if err := conn25.client.enqueueAddressAssignment(tt.as); err != nil {
t.Fatalf("error enqueuing address assignment: %v", err)
}
select {
@@ -1665,7 +1661,7 @@ func TestHandleAddressAssignmentStoresTransitIPs(t *testing.T) {
// Check that each of the lookups behaves as expected
for i, lu := range tt.lookups {
- got, ok := ext.conn25.client.assignments.lookupTransitIPsByConnKey(lu.connKey)
+ got, ok := conn25.client.assignments.lookupTransitIPsByConnKey(lu.connKey)
if ok != lu.expectedOk {
t.Fatalf("unexpected ok result at index %d wanted %v, got %v", i, lu.expectedOk, ok)
}