tstest/natlab: make a new virtualIP type in prep for IPv6 support

All the magic service names with virtual IPs will need IPv6 variants.

Pull this out in prep.

Updates #13038

Change-Id: I53b5eebd0679f9fa43dc0674805049258c83a0de
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/13250/head
Brad Fitzpatrick 3 months ago committed by Brad Fitzpatrick
parent 5a99940dfa
commit aa42ae9058

@ -0,0 +1,81 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package vnet
import (
"fmt"
"net/netip"
)
var vips = map[string]virtualIP{} // DNS name => details
var (
fakeDNS = newVIP("dns", "4.11.4.11", "2000:4:11::4:11")
fakeProxyControlplane = newVIP("controlplane.tailscale.com", 1)
fakeTestAgent = newVIP("test-driver.tailscale", 2)
fakeControl = newVIP("control.tailscale", 3)
fakeDERP1 = newVIP("derp1.tailscale", "33.4.0.1") // 3340=DERP; 1=derp 1
fakeDERP2 = newVIP("derp2.tailscale", "33.4.0.2") // 3340=DERP; 2=derp 2
fakeLogCatcher = newVIP("log.tailscale.io", 4)
fakeSyslog = newVIP("syslog.tailscale", 9)
)
type virtualIP struct {
name string // for DNS
v4 netip.Addr
v6 netip.Addr
}
func (v virtualIP) Match(a netip.Addr) bool {
return v.v4 == a.Unmap() || v.v6 == a
}
// newVIP returns a new virtual IP.
//
// opts may be an IPv4 an IPv6 (in string form) or an int (bounded by uint8) to
// use IPv4 of 52.52.0.x.
//
// If the IPv6 is omitted, one is derived from the IPv4.
//
// If an opt is invalid or the DNS name is already used, it panics.
func newVIP(name string, opts ...any) (v virtualIP) {
if _, ok := vips[name]; ok {
panic(fmt.Sprintf("duplicate VIP %q", name))
}
v.name = name
for _, o := range opts {
switch o := o.(type) {
case string:
if ip, err := netip.ParseAddr(o); err == nil {
if ip.Is4() {
v.v4 = ip
} else if ip.Is6() {
v.v6 = ip
}
} else {
panic(fmt.Sprintf("unsupported string option %q", o))
}
case int:
if o <= 0 || o > 255 {
panic(fmt.Sprintf("bad octet %d", o))
}
v.v4 = netip.AddrFrom4([4]byte{52, 52, 0, byte(o)})
default:
panic(fmt.Sprintf("unknown option type %T", o))
}
}
if !v.v6.IsValid() && v.v4.IsValid() {
// Map 1.2.3.4 to 2052::0102:0304
a := [16]byte{0: 2, 2: 5, 3: 2} // 2052::
copy(a[12:], v.v4.AsSlice())
v.v6 = netip.AddrFrom16(a)
}
for _, b := range vips {
if b.Match(v.v4) || b.Match(v.v6) {
panic(fmt.Sprintf("VIP %q collides with %q", name, v.name))
}
}
vips[name] = v
return v
}

@ -281,7 +281,7 @@ func (n *network) acceptTCP(r *tcp.ForwarderRequest) {
return
}
if destPort == 8008 && destIP == fakeTestAgentIP {
if destPort == 8008 && fakeTestAgent.Match(destIP) {
r.Complete(false)
tc := gonet.NewTCPConn(&wq, ep)
node := n.nodesByIP[clientRemoteIP]
@ -290,7 +290,7 @@ func (n *network) acceptTCP(r *tcp.ForwarderRequest) {
return
}
if destPort == 80 && destIP == fakeControlIP {
if destPort == 80 && fakeControl.Match(destIP) {
r.Complete(false)
tc := gonet.NewTCPConn(&wq, ep)
hs := &http.Server{Handler: n.s.control}
@ -298,9 +298,10 @@ func (n *network) acceptTCP(r *tcp.ForwarderRequest) {
return
}
if destPort == 443 && (destIP == fakeDERP1IP || destIP == fakeDERP2IP) {
if fakeDERP1.Match(destIP) || fakeDERP2.Match(destIP) {
if destPort == 443 {
ds := n.s.derps[0]
if destIP == fakeDERP2IP {
if fakeDERP2.Match(destIP) {
ds = n.s.derps[1]
}
@ -311,15 +312,15 @@ func (n *network) acceptTCP(r *tcp.ForwarderRequest) {
go hs.Serve(netutil.NewOneConnListener(tlsConn, nil))
return
}
if destPort == 80 && (destIP == fakeDERP1IP || destIP == fakeDERP2IP) {
if destPort == 80 {
r.Complete(false)
tc := gonet.NewTCPConn(&wq, ep)
hs := &http.Server{Handler: n.s.derps[0].handler}
go hs.Serve(netutil.NewOneConnListener(tc, nil))
return
}
if destPort == 443 && destIP == fakeLogCatcherIP {
}
if destPort == 443 && fakeLogCatcher.Match(destIP) {
r.Complete(false)
tc := gonet.NewTCPConn(&wq, ep)
go n.serveLogCatcherConn(clientRemoteIP, tc)
@ -331,7 +332,7 @@ func (n *network) acceptTCP(r *tcp.ForwarderRequest) {
var targetDial string
if n.s.derpIPs.Contains(destIP) {
targetDial = destIP.String() + ":" + strconv.Itoa(int(destPort))
} else if destIP == fakeProxyControlplaneIP {
} else if fakeProxyControlplane.Match(destIP) {
targetDial = "controlplane.tailscale.com:" + strconv.Itoa(int(destPort))
}
if targetDial != "" {
@ -399,17 +400,6 @@ func (n *network) serveLogCatcherConn(clientRemoteIP netip.Addr, c net.Conn) {
hs.Serve(netutil.NewOneConnListener(tlsConn, nil))
}
var (
fakeDNSIP = netip.AddrFrom4([4]byte{4, 11, 4, 11})
fakeProxyControlplaneIP = netip.AddrFrom4([4]byte{52, 52, 0, 1}) // real controlplane.tailscale.com proxy
fakeTestAgentIP = netip.AddrFrom4([4]byte{52, 52, 0, 2})
fakeControlIP = netip.AddrFrom4([4]byte{52, 52, 0, 3}) // 3=C for "Control"
fakeDERP1IP = netip.AddrFrom4([4]byte{33, 4, 0, 1}) // 3340=DERP; 1=derp 1
fakeDERP2IP = netip.AddrFrom4([4]byte{33, 4, 0, 2}) // 3340=DERP; 1=derp 1
fakeLogCatcherIP = netip.AddrFrom4([4]byte{52, 52, 0, 4})
fakeSyslogIP = netip.AddrFrom4([4]byte{52, 52, 0, 9})
)
type EthernetPacket struct {
le *layers.Ethernet
gp gopacket.Packet
@ -594,7 +584,8 @@ var derpMap = &tailcfg.DERPMap{
Name: "1a",
RegionID: 1,
HostName: "derp1.tailscale",
IPv4: fakeDERP1IP.String(),
IPv4: fakeDERP1.v4.String(),
IPv6: fakeDERP1.v6.String(),
InsecureForTests: true,
CanPort80: true,
},
@ -609,7 +600,8 @@ var derpMap = &tailcfg.DERPMap{
Name: "2a",
RegionID: 2,
HostName: "derp2.tailscale",
IPv4: fakeDERP2IP.String(),
IPv4: fakeDERP2.v4.String(),
IPv6: fakeDERP2.v6.String(),
InsecureForTests: true,
CanPort80: true,
},
@ -666,21 +658,8 @@ func (s *Server) HWAddr(mac MAC) net.HardwareAddr {
// IPv4ForDNS returns the IP address for the given DNS query name (for IPv4 A
// queries only).
func (s *Server) IPv4ForDNS(qname string) (netip.Addr, bool) {
switch qname {
case "dns":
return fakeDNSIP, true
case "log.tailscale.io":
return fakeLogCatcherIP, true
case "test-driver.tailscale":
return fakeTestAgentIP, true
case "controlplane.tailscale.com":
return fakeProxyControlplaneIP, true
case "control.tailscale":
return fakeControlIP, true
case "derp1.tailscale":
return fakeDERP1IP, true
case "derp2.tailscale":
return fakeDERP2IP, true
if v, ok := vips[qname]; ok {
return v.v4, v.v4.IsValid()
}
return netip.Addr{}, false
}
@ -1049,7 +1028,7 @@ func (n *network) HandleEthernetIPv4PacketForRouter(ep EthernetPacket) {
return
}
if isUDP && dstIP == fakeSyslogIP {
if isUDP && fakeSyslog.Match(dstIP) {
node, ok := n.nodesByIP[srcIP]
if !ok {
return
@ -1196,7 +1175,7 @@ func (s *Server) createDHCPResponse(request gopacket.Packet) ([]byte, error) {
},
layers.DHCPOption{
Type: layers.DHCPOptDNS,
Data: fakeDNSIP.AsSlice(),
Data: fakeDNS.v4.AsSlice(),
Length: 4,
},
layers.DHCPOption{
@ -1276,18 +1255,19 @@ func (s *Server) shouldInterceptTCP(pkt gopacket.Packet) bool {
}
dstIP, _ := netip.AddrFromSlice(ipv4.DstIP.To4())
if tcp.DstPort == 80 || tcp.DstPort == 443 {
switch dstIP {
case fakeControlIP, fakeDERP1IP, fakeDERP2IP, fakeLogCatcherIP:
for _, v := range []virtualIP{fakeControl, fakeDERP1, fakeDERP2, fakeLogCatcher} {
if v.Match(dstIP) {
return true
}
if dstIP == fakeProxyControlplaneIP {
}
if fakeProxyControlplane.Match(dstIP) {
return s.blendReality
}
if s.derpIPs.Contains(dstIP) {
return true
}
}
if tcp.DstPort == 8008 && dstIP == fakeTestAgentIP {
if tcp.DstPort == 8008 && fakeTestAgent.Match(dstIP) {
// Connection from cmd/tta.
return true
}
@ -1305,7 +1285,7 @@ func isDNSRequest(pkt gopacket.Packet) bool {
return false
}
dstIP, ok := netip.AddrFromSlice(ip.DstIP)
if !ok || dstIP != fakeDNSIP {
if !ok || !fakeDNS.Match(dstIP) {
return false
}
dns, ok := pkt.Layer(layers.LayerTypeDNS).(*layers.DNS)

Loading…
Cancel
Save