tailcfg,wgengine: add initial support for WireGuard only peers
A peer can have IsWireGuardOnly, which means it will not support DERP or Disco, and it must have Endpoints filled in order to be usable. In the present implementation only the first Endpoint will be used as the bestAddr. Updates tailscale/corp#10351 Co-authored-by: Charlotte Brandhorst-Satzkorn <charlotte@tailscale.com> Co-authored-by: James Tucker <james@tailscale.com> Signed-off-by: James Tucker <james@tailscale.com>
This commit is contained in:
committed by
James Tucker
parent
6cfcb3cae4
commit
c573bef0aa
@@ -39,6 +39,7 @@ import (
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/net/connstats"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/net/stun/stuntest"
|
||||
"tailscale.com/net/tstun"
|
||||
"tailscale.com/tailcfg"
|
||||
@@ -200,6 +201,7 @@ func newMagicStackWithKey(t testing.TB, logf logger.Logf, l nettype.PacketListen
|
||||
}
|
||||
|
||||
func (s *magicStack) Reconfig(cfg *wgcfg.Config) error {
|
||||
s.tsTun.SetWGConfig(cfg)
|
||||
s.wgLogger.SetPeers(cfg.Peers)
|
||||
return wgcfg.ReconfigDevice(s.dev, cfg, s.conn.logf)
|
||||
}
|
||||
@@ -2102,3 +2104,175 @@ func Test_batchingUDPConn_coalesceMessages(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// newWireguard starts up a new wireguard-go device attached to a test tun, and
|
||||
// returns the device, tun and netpoint address. To add peers call device.IpcSet
|
||||
// with UAPI instructions.
|
||||
func newWireguard(t *testing.T, uapi string, aips []netip.Prefix) (*device.Device, *tuntest.ChannelTUN, netip.AddrPort) {
|
||||
wgtun := tuntest.NewChannelTUN()
|
||||
wglogf := func(f string, args ...any) {
|
||||
t.Logf("wg-go: "+f, args...)
|
||||
}
|
||||
wglog := device.Logger{
|
||||
Verbosef: func(string, ...any) {},
|
||||
Errorf: wglogf,
|
||||
}
|
||||
wgdev := wgcfg.NewDevice(wgtun.TUN(), wgconn.NewDefaultBind(), &wglog)
|
||||
|
||||
if err := wgdev.IpcSet(uapi); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := wgdev.Up(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var wgEp netip.AddrPort
|
||||
|
||||
s, err := wgdev.IpcGet()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, line := range strings.Split(s, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
k, v, _ := strings.Cut(line, "=")
|
||||
if k == "listen_port" {
|
||||
wgEp = netip.MustParseAddrPort("127.0.0.1:" + v)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !wgEp.IsValid() {
|
||||
t.Fatalf("failed to get endpoint out of wg-go")
|
||||
}
|
||||
t.Logf("wg-go endpoint: %s", wgEp)
|
||||
|
||||
return wgdev, wgtun, wgEp
|
||||
}
|
||||
|
||||
func TestIsWireGuardOnlyPeer(t *testing.T) {
|
||||
derpMap, cleanup := runDERPAndStun(t, t.Logf, localhostListener{}, netaddr.IPv4(127, 0, 0, 1))
|
||||
defer cleanup()
|
||||
|
||||
tskey := key.NewNode()
|
||||
tsaip := netip.MustParsePrefix("100.111.222.111/32")
|
||||
|
||||
wgkey := key.NewNode()
|
||||
wgaip := netip.MustParsePrefix("100.222.111.222/32")
|
||||
|
||||
uapi := fmt.Sprintf("private_key=%s\npublic_key=%s\nallowed_ip=%s\n\n",
|
||||
wgkey.UntypedHexString(), tskey.Public().UntypedHexString(), tsaip.String())
|
||||
wgdev, wgtun, wgEp := newWireguard(t, uapi, []netip.Prefix{wgaip})
|
||||
defer wgdev.Close()
|
||||
|
||||
m := newMagicStackWithKey(t, t.Logf, localhostListener{}, derpMap, tskey)
|
||||
defer m.Close()
|
||||
|
||||
nm := &netmap.NetworkMap{
|
||||
Name: "ts",
|
||||
PrivateKey: m.privateKey,
|
||||
NodeKey: m.privateKey.Public(),
|
||||
Addresses: []netip.Prefix{tsaip},
|
||||
Peers: []*tailcfg.Node{
|
||||
{
|
||||
Key: wgkey.Public(),
|
||||
Endpoints: []string{wgEp.String()},
|
||||
IsWireGuardOnly: true,
|
||||
Addresses: []netip.Prefix{wgaip},
|
||||
AllowedIPs: []netip.Prefix{wgaip},
|
||||
},
|
||||
},
|
||||
}
|
||||
m.conn.SetNetworkMap(nm)
|
||||
|
||||
cfg, err := nmcfg.WGCfg(nm, t.Logf, netmap.AllowSingleHosts|netmap.AllowSubnetRoutes, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m.Reconfig(cfg)
|
||||
|
||||
pbuf := tuntest.Ping(wgaip.Addr(), tsaip.Addr())
|
||||
m.tun.Outbound <- pbuf
|
||||
|
||||
select {
|
||||
case p := <-wgtun.Inbound:
|
||||
if !bytes.Equal(p, pbuf) {
|
||||
t.Errorf("got unexpected packet: %x", p)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("no packet after 1s")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsWireGuardOnlyPeerWithMasquerade(t *testing.T) {
|
||||
derpMap, cleanup := runDERPAndStun(t, t.Logf, localhostListener{}, netaddr.IPv4(127, 0, 0, 1))
|
||||
defer cleanup()
|
||||
|
||||
tskey := key.NewNode()
|
||||
tsaip := netip.MustParsePrefix("100.111.222.111/32")
|
||||
|
||||
wgkey := key.NewNode()
|
||||
wgaip := netip.MustParsePrefix("10.64.0.1/32")
|
||||
|
||||
// the ip that the wireguard peer has in allowed ips and expects as a masq source
|
||||
masqip := netip.MustParsePrefix("10.64.0.2/32")
|
||||
|
||||
uapi := fmt.Sprintf("private_key=%s\npublic_key=%s\nallowed_ip=%s\n\n",
|
||||
wgkey.UntypedHexString(), tskey.Public().UntypedHexString(), masqip.String())
|
||||
wgdev, wgtun, wgEp := newWireguard(t, uapi, []netip.Prefix{wgaip})
|
||||
defer wgdev.Close()
|
||||
|
||||
m := newMagicStackWithKey(t, t.Logf, localhostListener{}, derpMap, tskey)
|
||||
defer m.Close()
|
||||
|
||||
nm := &netmap.NetworkMap{
|
||||
Name: "ts",
|
||||
PrivateKey: m.privateKey,
|
||||
NodeKey: m.privateKey.Public(),
|
||||
Addresses: []netip.Prefix{tsaip},
|
||||
Peers: []*tailcfg.Node{
|
||||
{
|
||||
Key: wgkey.Public(),
|
||||
Endpoints: []string{wgEp.String()},
|
||||
IsWireGuardOnly: true,
|
||||
Addresses: []netip.Prefix{wgaip},
|
||||
AllowedIPs: []netip.Prefix{wgaip},
|
||||
SelfNodeV4MasqAddrForThisPeer: masqip.Addr(),
|
||||
},
|
||||
},
|
||||
}
|
||||
m.conn.SetNetworkMap(nm)
|
||||
|
||||
cfg, err := nmcfg.WGCfg(nm, t.Logf, netmap.AllowSingleHosts|netmap.AllowSubnetRoutes, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m.Reconfig(cfg)
|
||||
|
||||
pbuf := tuntest.Ping(wgaip.Addr(), tsaip.Addr())
|
||||
m.tun.Outbound <- pbuf
|
||||
|
||||
select {
|
||||
case p := <-wgtun.Inbound:
|
||||
|
||||
// TODO(raggi): move to a bytes.Equal based test later, once
|
||||
// tuntest.Ping produces correct checksums!
|
||||
|
||||
var pkt packet.Parsed
|
||||
pkt.Decode(p)
|
||||
if pkt.ICMP4Header().Type != packet.ICMP4EchoRequest {
|
||||
t.Fatalf("unexpected packet: %x", p)
|
||||
}
|
||||
if pkt.Src.Addr() != masqip.Addr() {
|
||||
t.Fatalf("bad source IP, got %s, want %s", pkt.Src.Addr(), masqip.Addr())
|
||||
}
|
||||
if pkt.Dst.Addr() != wgaip.Addr() {
|
||||
t.Fatalf("bad source IP, got %s, want %s", pkt.Src.Addr(), masqip.Addr())
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("no packet after 1s")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user