diff --git a/ipn/ipnstate/ipnstate.go b/ipn/ipnstate/ipnstate.go
index e8f41c9ca..8210d518e 100644
--- a/ipn/ipnstate/ipnstate.go
+++ b/ipn/ipnstate/ipnstate.go
@@ -18,6 +18,7 @@ import (
"sync"
"time"
+ "inet.af/netaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
)
@@ -25,6 +26,7 @@ import (
// Status represents the entire state of the IPN network.
type Status struct {
BackendState string
+ TailscaleIPs []netaddr.IP // Tailscale IP(s) assigned to this node
Peer map[key.Public]*PeerStatus
User map[tailcfg.UserID]tailcfg.UserProfile
}
@@ -109,6 +111,18 @@ func (sb *StatusBuilder) AddUser(id tailcfg.UserID, up tailcfg.UserProfile) {
sb.st.User[id] = up
}
+// AddIP adds a Tailscale IP address to the status.
+func (sb *StatusBuilder) AddTailscaleIP(ip netaddr.IP) {
+ sb.mu.Lock()
+ defer sb.mu.Unlock()
+ if sb.locked {
+ log.Printf("[unexpected] ipnstate: AddIP after Locked")
+ return
+ }
+
+ sb.st.TailscaleIPs = append(sb.st.TailscaleIPs, ip)
+}
+
// AddPeer adds a peer node to the status.
//
// Its PeerStatus is mixed with any previous status already added.
@@ -218,6 +232,12 @@ table tbody tr:nth-child(even) td { background-color: #f5f5f5; }
//f("
logid: %s
\n", logid)
//f("opts: %s
\n", html.EscapeString(fmt.Sprintf("%+v", opts)))
+ ips := make([]string, 0, len(st.TailscaleIPs))
+ for _, ip := range st.TailscaleIPs {
+ ips = append(ips, ip.String())
+ }
+ f("Tailscale IP: %s", strings.Join(ips, ", "))
+
f("
\n\n")
f("Peer | Node | Owner | Rx | Tx | Activity | Endpoints |
\n")
f("\n\n")
diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go
index 478a431e5..149c1ae09 100644
--- a/wgengine/magicsock/magicsock.go
+++ b/wgengine/magicsock/magicsock.go
@@ -81,9 +81,8 @@ var (
debugReSTUNStopOnIdle, _ = strconv.ParseBool(os.Getenv("TS_DEBUG_RESTUN_STOP_ON_IDLE"))
)
-// inTest binds magicsock to 127.0.0.1 instead of its usual 0.0.0.0,
-// to avoid macOS prompting for firewall permissions during
-// interactive tests.
+// inTest reports whether the running program is a test that set the
+// IN_TS_TEST environment variable.
//
// Unlike the other debug tweakables above, this one needs to be
// checked every time at runtime, because tests set this after program
@@ -2860,6 +2859,17 @@ func (c *Conn) UpdateStatus(sb *ipnstate.StatusBuilder) {
c.mu.Lock()
defer c.mu.Unlock()
+ if c.netMap != nil {
+ for _, addr := range c.netMap.Addresses {
+ if (addr.IP.Is4() && addr.Mask != 32) || (addr.IP.Is6() && addr.Mask != 128) {
+ continue
+ }
+ if ip, ok := netaddr.FromStdIP(addr.IP.IP()); ok {
+ sb.AddTailscaleIP(ip)
+ }
+ }
+ }
+
for dk, n := range c.nodeOfDisco {
ps := &ipnstate.PeerStatus{InMagicSock: true}
ps.Addrs = append(ps.Addrs, n.Endpoints...)
diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go
index 00af5e142..70386b811 100644
--- a/wgengine/magicsock/magicsock_test.go
+++ b/wgengine/magicsock/magicsock_test.go
@@ -118,7 +118,6 @@ type magicStack struct {
tun *tuntest.ChannelTUN // tuntap device to send/receive packets
tsTun *tstun.TUN // wrapped tun that implements filtering and wgengine hooks
dev *device.Device // the wireguard-go Device that connects the previous things
- tsIP chan netaddr.IP // buffered, guaranteed to yield at least 1 value
}
// newMagicStack builds and initializes an idle magicsock and
@@ -181,7 +180,6 @@ func newMagicStack(t *testing.T, logf logger.Logf, l nettype.PacketListener, der
tun: tun,
tsTun: tsTun,
dev: dev,
- tsIP: make(chan netaddr.IP, 1),
}
}
@@ -205,18 +203,20 @@ func (s *magicStack) Status() *ipnstate.Status {
return sb.Status()
}
-// AwaitIP waits for magicStack to receive a Tailscale IP address on
-// tsIP, and returns the IP. It's intended for use with magicStacks
-// that have been meshed with meshStacks, to wait for configs to have
-// propagated enough that everyone has a Tailscale IP that should
-// work.
-func (s *magicStack) AwaitIP() netaddr.IP {
- select {
- case ip := <-s.tsIP:
- return ip
- case <-time.After(2 * time.Second):
- panic("timed out waiting for magicStack to get an IP")
+// IP returns the Tailscale IP address assigned to this magicStack.
+//
+// Something external needs to provide a NetworkMap and WireGuard
+// configs to the magicStack in order for it to acquire an IP
+// address. See meshStacks for one possible source of netmaps and IPs.
+func (s *magicStack) IP(t *testing.T) netaddr.IP {
+ for deadline := time.Now().Add(5 * time.Second); time.Now().Before(deadline); time.Sleep(10 * time.Millisecond) {
+ st := s.Status()
+ if len(st.TailscaleIPs) > 0 {
+ return st.TailscaleIPs[0]
+ }
}
+ t.Fatal("timed out waiting for magicstack to get an IP assigned")
+ panic("unreachable") // compiler doesn't know t.Fatal panics
}
// meshStacks monitors epCh on all given ms, and plumbs network maps
@@ -271,11 +271,6 @@ func meshStacks(logf logger.Logf, ms []*magicStack) (cleanup func()) {
for i, m := range ms {
netmap := buildNetmapLocked(i)
- nip, _ := netaddr.FromStdIP(netmap.Addresses[0].IP.IP())
- select {
- case m.tsIP <- nip:
- default:
- }
m.conn.SetNetworkMap(netmap)
peerSet := make(map[key.Public]struct{}, len(netmap.Peers))
for _, peer := range netmap.Peers {
@@ -707,7 +702,7 @@ type devices struct {
// newPinger starts continuously sending test packets from srcM to
// dstM, until cleanup is invoked to stop it. Each ping has 1 second
// to transit the network. It is a test failure to lose a ping.
-func newPinger(t *testing.T, logf logger.Logf, srcM, dstM *magicStack, srcIP, dstIP netaddr.IP) (cleanup func()) {
+func newPinger(t *testing.T, logf logger.Logf, src, dst *magicStack) (cleanup func()) {
ctx, cancel := context.WithCancel(context.Background())
done := make(chan struct{})
one := func() bool {
@@ -717,10 +712,10 @@ func newPinger(t *testing.T, logf logger.Logf, srcM, dstM *magicStack, srcIP, ds
// failure). Figure out what kind of thing would be
// acceptable to test instead of "every ping must
// transit".
- pkt := tuntest.Ping(dstIP.IPAddr().IP, srcIP.IPAddr().IP)
- srcM.tun.Outbound <- pkt
+ pkt := tuntest.Ping(dst.IP(t).IPAddr().IP, src.IP(t).IPAddr().IP)
+ src.tun.Outbound <- pkt
select {
- case <-dstM.tun.Inbound:
+ case <-dst.tun.Inbound:
return true
case <-time.After(10 * time.Second):
// Very generous timeout here because depending on
@@ -737,7 +732,7 @@ func newPinger(t *testing.T, logf logger.Logf, srcM, dstM *magicStack, srcIP, ds
// natlab may still be delivering a packet to us from a
// goroutine.
select {
- case <-dstM.tun.Inbound:
+ case <-dst.tun.Inbound:
case <-time.After(time.Second):
}
return false
@@ -758,7 +753,7 @@ func newPinger(t *testing.T, logf logger.Logf, srcM, dstM *magicStack, srcIP, ds
}
go func() {
- logf("sending ping stream from %s (%s) to %s (%s)", srcM, srcIP, dstM, dstIP)
+ logf("sending ping stream from %s (%s) to %s (%s)", src, src.IP(t), dst, dst.IP(t))
defer close(done)
for one() {
}
@@ -792,11 +787,11 @@ func testActiveDiscovery(t *testing.T, d *devices) {
cleanup = meshStacks(logf, []*magicStack{m1, m2})
defer cleanup()
- m1IP := m1.AwaitIP()
- m2IP := m2.AwaitIP()
+ m1IP := m1.IP(t)
+ m2IP := m2.IP(t)
logf("IPs: %s %s", m1IP, m2IP)
- cleanup = newPinger(t, logf, m1, m2, m1IP, m2IP)
+ cleanup = newPinger(t, logf, m1, m2)
defer cleanup()
// Everything is now up and running, active discovery should find