mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 04:55:31 +00:00
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>
This commit is contained in:
parent
5a99940dfa
commit
aa42ae9058
81
tstest/natlab/vnet/vip.go
Normal file
81
tstest/natlab/vnet/vip.go
Normal file
@ -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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if destPort == 8008 && destIP == fakeTestAgentIP {
|
if destPort == 8008 && fakeTestAgent.Match(destIP) {
|
||||||
r.Complete(false)
|
r.Complete(false)
|
||||||
tc := gonet.NewTCPConn(&wq, ep)
|
tc := gonet.NewTCPConn(&wq, ep)
|
||||||
node := n.nodesByIP[clientRemoteIP]
|
node := n.nodesByIP[clientRemoteIP]
|
||||||
@ -290,7 +290,7 @@ func (n *network) acceptTCP(r *tcp.ForwarderRequest) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if destPort == 80 && destIP == fakeControlIP {
|
if destPort == 80 && fakeControl.Match(destIP) {
|
||||||
r.Complete(false)
|
r.Complete(false)
|
||||||
tc := gonet.NewTCPConn(&wq, ep)
|
tc := gonet.NewTCPConn(&wq, ep)
|
||||||
hs := &http.Server{Handler: n.s.control}
|
hs := &http.Server{Handler: n.s.control}
|
||||||
@ -298,28 +298,29 @@ func (n *network) acceptTCP(r *tcp.ForwarderRequest) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if destPort == 443 && (destIP == fakeDERP1IP || destIP == fakeDERP2IP) {
|
if fakeDERP1.Match(destIP) || fakeDERP2.Match(destIP) {
|
||||||
ds := n.s.derps[0]
|
if destPort == 443 {
|
||||||
if destIP == fakeDERP2IP {
|
ds := n.s.derps[0]
|
||||||
ds = n.s.derps[1]
|
if fakeDERP2.Match(destIP) {
|
||||||
|
ds = n.s.derps[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Complete(false)
|
||||||
|
tc := gonet.NewTCPConn(&wq, ep)
|
||||||
|
tlsConn := tls.Server(tc, ds.tlsConfig)
|
||||||
|
hs := &http.Server{Handler: ds.handler}
|
||||||
|
go hs.Serve(netutil.NewOneConnListener(tlsConn, nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Complete(false)
|
|
||||||
tc := gonet.NewTCPConn(&wq, ep)
|
|
||||||
tlsConn := tls.Server(tc, ds.tlsConfig)
|
|
||||||
hs := &http.Server{Handler: ds.handler}
|
|
||||||
go hs.Serve(netutil.NewOneConnListener(tlsConn, nil))
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if destPort == 80 && (destIP == fakeDERP1IP || destIP == fakeDERP2IP) {
|
if destPort == 443 && fakeLogCatcher.Match(destIP) {
|
||||||
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 {
|
|
||||||
|
|
||||||
r.Complete(false)
|
r.Complete(false)
|
||||||
tc := gonet.NewTCPConn(&wq, ep)
|
tc := gonet.NewTCPConn(&wq, ep)
|
||||||
go n.serveLogCatcherConn(clientRemoteIP, tc)
|
go n.serveLogCatcherConn(clientRemoteIP, tc)
|
||||||
@ -331,7 +332,7 @@ func (n *network) acceptTCP(r *tcp.ForwarderRequest) {
|
|||||||
var targetDial string
|
var targetDial string
|
||||||
if n.s.derpIPs.Contains(destIP) {
|
if n.s.derpIPs.Contains(destIP) {
|
||||||
targetDial = destIP.String() + ":" + strconv.Itoa(int(destPort))
|
targetDial = destIP.String() + ":" + strconv.Itoa(int(destPort))
|
||||||
} else if destIP == fakeProxyControlplaneIP {
|
} else if fakeProxyControlplane.Match(destIP) {
|
||||||
targetDial = "controlplane.tailscale.com:" + strconv.Itoa(int(destPort))
|
targetDial = "controlplane.tailscale.com:" + strconv.Itoa(int(destPort))
|
||||||
}
|
}
|
||||||
if targetDial != "" {
|
if targetDial != "" {
|
||||||
@ -399,17 +400,6 @@ func (n *network) serveLogCatcherConn(clientRemoteIP netip.Addr, c net.Conn) {
|
|||||||
hs.Serve(netutil.NewOneConnListener(tlsConn, nil))
|
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 {
|
type EthernetPacket struct {
|
||||||
le *layers.Ethernet
|
le *layers.Ethernet
|
||||||
gp gopacket.Packet
|
gp gopacket.Packet
|
||||||
@ -594,7 +584,8 @@ type Server struct {
|
|||||||
Name: "1a",
|
Name: "1a",
|
||||||
RegionID: 1,
|
RegionID: 1,
|
||||||
HostName: "derp1.tailscale",
|
HostName: "derp1.tailscale",
|
||||||
IPv4: fakeDERP1IP.String(),
|
IPv4: fakeDERP1.v4.String(),
|
||||||
|
IPv6: fakeDERP1.v6.String(),
|
||||||
InsecureForTests: true,
|
InsecureForTests: true,
|
||||||
CanPort80: true,
|
CanPort80: true,
|
||||||
},
|
},
|
||||||
@ -609,7 +600,8 @@ type Server struct {
|
|||||||
Name: "2a",
|
Name: "2a",
|
||||||
RegionID: 2,
|
RegionID: 2,
|
||||||
HostName: "derp2.tailscale",
|
HostName: "derp2.tailscale",
|
||||||
IPv4: fakeDERP2IP.String(),
|
IPv4: fakeDERP2.v4.String(),
|
||||||
|
IPv6: fakeDERP2.v6.String(),
|
||||||
InsecureForTests: true,
|
InsecureForTests: true,
|
||||||
CanPort80: 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
|
// IPv4ForDNS returns the IP address for the given DNS query name (for IPv4 A
|
||||||
// queries only).
|
// queries only).
|
||||||
func (s *Server) IPv4ForDNS(qname string) (netip.Addr, bool) {
|
func (s *Server) IPv4ForDNS(qname string) (netip.Addr, bool) {
|
||||||
switch qname {
|
if v, ok := vips[qname]; ok {
|
||||||
case "dns":
|
return v.v4, v.v4.IsValid()
|
||||||
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
|
|
||||||
}
|
}
|
||||||
return netip.Addr{}, false
|
return netip.Addr{}, false
|
||||||
}
|
}
|
||||||
@ -1049,7 +1028,7 @@ func (n *network) HandleEthernetIPv4PacketForRouter(ep EthernetPacket) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isUDP && dstIP == fakeSyslogIP {
|
if isUDP && fakeSyslog.Match(dstIP) {
|
||||||
node, ok := n.nodesByIP[srcIP]
|
node, ok := n.nodesByIP[srcIP]
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
@ -1196,7 +1175,7 @@ func (s *Server) createDHCPResponse(request gopacket.Packet) ([]byte, error) {
|
|||||||
},
|
},
|
||||||
layers.DHCPOption{
|
layers.DHCPOption{
|
||||||
Type: layers.DHCPOptDNS,
|
Type: layers.DHCPOptDNS,
|
||||||
Data: fakeDNSIP.AsSlice(),
|
Data: fakeDNS.v4.AsSlice(),
|
||||||
Length: 4,
|
Length: 4,
|
||||||
},
|
},
|
||||||
layers.DHCPOption{
|
layers.DHCPOption{
|
||||||
@ -1276,18 +1255,19 @@ func (s *Server) shouldInterceptTCP(pkt gopacket.Packet) bool {
|
|||||||
}
|
}
|
||||||
dstIP, _ := netip.AddrFromSlice(ipv4.DstIP.To4())
|
dstIP, _ := netip.AddrFromSlice(ipv4.DstIP.To4())
|
||||||
if tcp.DstPort == 80 || tcp.DstPort == 443 {
|
if tcp.DstPort == 80 || tcp.DstPort == 443 {
|
||||||
switch dstIP {
|
for _, v := range []virtualIP{fakeControl, fakeDERP1, fakeDERP2, fakeLogCatcher} {
|
||||||
case fakeControlIP, fakeDERP1IP, fakeDERP2IP, fakeLogCatcherIP:
|
if v.Match(dstIP) {
|
||||||
return true
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if dstIP == fakeProxyControlplaneIP {
|
if fakeProxyControlplane.Match(dstIP) {
|
||||||
return s.blendReality
|
return s.blendReality
|
||||||
}
|
}
|
||||||
if s.derpIPs.Contains(dstIP) {
|
if s.derpIPs.Contains(dstIP) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if tcp.DstPort == 8008 && dstIP == fakeTestAgentIP {
|
if tcp.DstPort == 8008 && fakeTestAgent.Match(dstIP) {
|
||||||
// Connection from cmd/tta.
|
// Connection from cmd/tta.
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -1305,7 +1285,7 @@ func isDNSRequest(pkt gopacket.Packet) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
dstIP, ok := netip.AddrFromSlice(ip.DstIP)
|
dstIP, ok := netip.AddrFromSlice(ip.DstIP)
|
||||||
if !ok || dstIP != fakeDNSIP {
|
if !ok || !fakeDNS.Match(dstIP) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
dns, ok := pkt.Layer(layers.LayerTypeDNS).(*layers.DNS)
|
dns, ok := pkt.Layer(layers.LayerTypeDNS).(*layers.DNS)
|
||||||
|
Loading…
Reference in New Issue
Block a user