WireGuard tutorial: Nat to NAT penetration using DNS-SD

Posted by daeken on Sat, 15 Jan 2022 21:56:42 +0100

Domestic udp is generally limited, so it can't succeed (guess). LAN test is ok

Original address: https://fuckcloudnative.io/posts/wireguard-endpoint-discovery-nat-traversal/

Original text from English: https://www.jordanwhited.com/posts/wireguard-endpoint-discovery-nat-traversal/

WireGuard uses the wgsd plug-in of CoreDNS to discover endpoint addresses

📅 January 28, 2021· ☕ 13 minutes· ✍️ Michelangelo Yang· 👀 1706 reading

Sealyun Kubernetes one click offline installation go and have a look!

WireGuard is a next-generation open source VPN protocol created by Jason A. Donenfeld and others. It aims to solve many problems perplexing other VPN protocols such as IPSec/IKEv2, OpenVPN or L2TP. On January 29, 2020, WireGuard was officially merged into the main line of Linux 5.6 kernel.

Using WireGuard, we can realize many wonderful functions, such as building Kubernetes clusters across public clouds, directly accessing Pod IP and Service IP in Kubernetes clusters of public clouds locally, directly connecting devices at home without public IP, and so on.

If this is the first time you have heard of WireGuard, I suggest you take some time to read the WireGuard I wrote before working principle . Then you can refer to the following two articles to get started quickly:

If you don't understand some details, refer to them again WireGuard configuration details.

This article will discuss a major problem encountered in the use of WireGuard: how to directly establish a connection between two clients behind NAT (without specifying a public network exit).

WireGuard does not distinguish between server and client. We are all clients. All clients connected to ourselves are called peers.

1. Peer with unfixed IP

The core of WireGuard is Cryptokey Routing , it works by associating a public key with an IP address list (allowed IPS). Each network interface has a private key and a Peer list, and each Peer has a public key and IP address list. When sending data, the IP address list can be regarded as a routing table; When receiving data, the IP address list can be regarded as an access control list.

The association between public key and IP address list constitutes the necessary configuration of Peer. From the perspective of tunnel verification, Peer does not need to have static IP address at all. Theoretically, WireGuard can realize IP roaming if the IP address of Peer changes at the same time.

Now let's go back to the original problem: suppose that both peers are behind the NAT, and the NAT is not under our control. UDP port forwarding cannot be configured, that is, the public network outlet cannot be specified. To establish a connection, we should dynamically discover not only the IP address of the Peer, but also the port of the Peer.

After looking around, the existing tools simply can't meet this requirement. This paper will focus on realizing the above requirements without making any changes to the WireGuard source code.

2. Central and radial network topology

You might ask me why I don't use it Hub and spoke network topology ? The central radiation network has a VPN gateway, which usually has a static IP address. All other clients need to connect to the VPN gateway, and then the gateway forwards the traffic to other clients. Assuming that Alice and Bob are behind NAT, Alice and Bob should establish a tunnel with the gateway, and then Alice and Bob can forward traffic through the VPN gateway to realize mutual communication.

In fact, this method is now used by everyone. There is nothing to say, and the disadvantages are quite obvious:

  • When there are more and more peers, VPN gateway will become the bottleneck of vertical expansion.
  • The cost of forwarding traffic through VPN gateway is very high. After all, the traffic of ECS is very expensive.
  • Forwarding traffic through VPN gateway will bring high delay.

The purpose of this paper is to establish a tunnel directly between Alice and Bob, which can not be achieved by hub and spoke network topology.

3. NAT penetration

To establish a WireGuard tunnel directly between Alice and Bob, they need to be able to pass through the NAT in front of them. WireGuard communicates with each other through UDP, so in theory UDP hole punching Is the best choice.

The fact that most UDP punches are loosely matched to the existing NAT packets. In this way, the port state can be reused to make holes, because the NAT router will not limit the traffic from the original destination address (messenger server), and the traffic from other clients can also be received.

For example, suppose Alice sends a UDP packet to the new host Carol, and Bob obtains the outbound source IP:Port used by Alice's NAT in the address translation process through some method, Bob can send a UDP packet to this IP:Port (2.2.2.2:7777) to establish contact with Alice.

In fact, the Full cone NAT discussed above is one-to-one NAT. It has the following characteristics:

  • Once the internal address (iAddr:iPort) is mapped to the external address (eAddr:ePort), all packets from iAddr:iPort are sent out via eAddr:ePort.
  • Any external host can send a packet to eAddr:ePort to iAddr:iPort.

Most NATs are such Nats. For a few other uncommon NATs, this hole drilling method has certain limitations and can not be used smoothly.

4. STUN

Returning to the above example, there are several important problems in the process of UDP drilling:

  • How can Alice know her public IP:Port?
  • How does Alice connect with Bob?
  • How to make holes with UDP in WireGuard?

RFC5389 The detailed description of STUN (Session Traversal Utilities for NAT) defines a protocol and answers some of the above questions. This is a long RFC, so I will try my best to summarize it. First of all, STUN can't directly solve the above problems. It's just a wrench. You have to take it to build a hand weighing tool:

STUN itself is not a solution to the NAT penetration problem. It just defines a mechanism that you can use to build an actual solution.

RFC5389

STUN (Session Traversal Utilities for NAT) Is a network protocol that allows clients behind nat (or multiple NATs) to find out their own public network address, which type of NAT they are behind, and the public network port bound by NAT for a local port. This information is used to establish UDP communication between two hosts behind the NAT router at the same time. This protocol is defined by RFC 5389.

STUN is a client server protocol. In the example above, Alice is the client and Carol is the server. Alice sends a STUN Binding request to Carol. When the Binding request passes through Alice's NAT, the source IP:Port will be overwritten. When Carol receives the Binding request, it will copy the source IP:Port of layer 3 and layer 4 into the payload of the Binding response and send it to Alice. The Binding response is forwarded to Alice in the intranet through Alice's NAT. At this time, the target IP:Port is rewritten to the intranet address, but the payload remains unchanged. After Alice receives the Binding response, she will realize that the public IP:Port of the Socket is 2.2.2.2:7777.

However, STUN is not a complete solution. It only provides a mechanism for applications to obtain its public IP:Port, but STUN does not provide a specific method to send signals to relevant directions. If you want to write an application with NAT penetration function again, you must use STUN to realize it. Of course, it is wise not to modify the source code of WireGuard. It is best to learn from the concept of STUN. In any case, you need a host with a static public address to act as a messenger server.

5. NAT penetration example

As early as August 2016, the creator of WireGuard was here WireGuard mailing list Shared one on NAT penetration example . Jason's example includes a client application and a server application. The client application runs with WireGuard, and the server runs on a host with a static address to discover the IP:Port of each Peer, which is used by the client raw socket Communicate with the server.

/* We use raw sockets so that the WireGuard interface can actually own the real socket. */
sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
if (sock < 0) {
	perror("socket");
	return errno;
}

As noted in the comments, WireGuard has "real sockets". By using raw socket, the client can disguise the source port of the local WireGuard to the server, which ensures that the target IP:Port will be mapped to the WireGuard socket when the response returned by the server passes through NAT.

The client uses one on its original socket Classic BPF filter To filter the replies sent by the server to the WireGuard port.

static void apply_bpf(int sock, uint16_t port, uint32_t ip)
{
	struct sock_filter filter[] = {
		BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 12 /* src ip */),
		BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ip, 0, 5),
		BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 /* src port */),
		BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, PORT, 0, 3),
		BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 22 /* dst port */),
		BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, port, 0, 1),
		BPF_STMT(BPF_RET + BPF_K, -1),
		BPF_STMT(BPF_RET + BPF_K, 0)
	};
	struct sock_fprog filter_prog = {
		.len = sizeof(filter) / sizeof(filter[0]),
		.filter = filter
	};
	if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter_prog, sizeof(filter_prog)) < 0) {
		perror("setsockopt(bpf)");
		exit(errno);
	}
}

The communication data between the client and the server are defined in the packet and reply structures:

struct {
    struct udphdr udp;
    uint8_t my_pubkey[32];
    uint8_t their_pubkey[32];
} __attribute__((packed)) packet = {
    .udp = {
        .len = htons(sizeof(packet)),
        .dest = htons(PORT)
    }
};
struct {
    struct iphdr iphdr;
    struct udphdr udp;
    uint32_t ip;
    uint16_t port;
} __attribute__((packed)) reply;

The client will traverse the configured wireguard peers (WG show < interface > peers) and send a packet for each Peer to the server, where my_pubkey and their_ The pubkey field is populated appropriately. When the server receives a packet from the client, it will insert or update a pubkey = my into the Peer memory table with the public key as the key_ The entry of pubkey, and then find pubkey = their from the table_ Once the entry of pubkey is found to exist, the IP:Port will be sent to the client. When the client receives a reply, it will unpack the IP and port from the packet and configure the Peer's endpoint address (WG set < interface > Peer < key > < options... > endpoint < IP >: < port >).

entry structure source code:

struct entry {
	uint8_t pubkey[32];
	uint32_t ip;
	uint16_t port;
};

The IP and port fields in the entry structure are the IP and UDP headers extracted from the packets received by the client. Each time the client requests the IP and port information of Peer, it will refresh its own IP and port information in the Peer list.

The above example shows how WireGuard implements UDP punching, but it is still too complex, because not all peers can open raw socket s, and not all peers can use BPF filters. And here also used the custom wire protocol , the data at the code level (linked list, queue and binary tree) are structured, but the network layer sees binary streams. The so-called wire protocol serializes the structured data into binary streams and sends them out, and the other party can deserialize them in the same format. This method is difficult to debug, so we need to find another way and use the existing mature tools to achieve the goal.

6. Positive solution of wireguard NAT penetration

In fact, there is no need to be so troublesome. We can directly use the characteristics of WireGuard to realize UDP hole drilling. See the figure directly:

You may think this is a hub and spoke network topology, but there are some differences. The Registry Peer here will not act as a gateway because it has no corresponding route and will not forward traffic. The WireGuard interface address of Registry is 10.0.0.254/32. The AllowedIPs of Alice and Bob only contain 10.0.0.254/32, which means that only traffic from Registry is received. Therefore, Alice and Bob cannot communicate through Registry.

This is very important. Registry has established two tunnels with Alice and Bob respectively, which will open a hole in Alice and Bob's NAT. We need to find a way to query the IP:Port of these holes from Registry Peer. Naturally, we thought of the DNS protocol. DNS has obvious advantages. It is relatively simple, mature and cross platform. There is a DNS record type called SRV record (Service Record) It is used to record the services provided by the server, that is, to identify the IP and port of the service, RFC6763 This record type is extended with specific structure and query mode to discover services in a given domain. We can directly use these extended semantics.

7. CoreDNS

After selecting the service discovery protocol, you also need a method to connect it with WireGuard. CoreDNS It is a plug-in DNS server written by Golang. It is the default DNS server built in Kubernetes at present, and has been downloaded from CNCF Graduation. We can directly write a CoreDNS plug-in to accept DNS-SD (DNS based service discovery) queries and return relevant WireGuard Peer information, in which the public key is used as the record name, fuckcloud native IO as domain. If you are familiar with bind style domain files, you can imagine a domain data like this:

_wireguard._udp         IN PTR          alice._wireguard._udp.fuckcloudnative.io.
_wireguard._udp         IN PTR          bob._wireguard._udp.fuckcloudnative.io.
alice._wireguard._udp   IN SRV 0 1 7777 alice.fuckcloudnative.io.
alice                   IN A            2.2.2.2
bob._wireguard._udp     IN SRV 0 1 8888 bob.fuckcloudnative.io.
bob                     IN A            3.3.3.3

Does the public key use Base64 or Base32?

So far, we have used the aliases Alice and Bob to replace their corresponding WireGuard public keys. WireGuard public key is Base64 encoded, with a length of 44 bytes:

$ wg genkey | wg pubkey
UlVJVmPSwuG4U9BwyVILFDNlM+Gk9nQ7444HimPPgQg=

The Base 64 encoding is designed to represent an arbitrary sequence of octets in a form that allows the use of uppercase and lowercase letters.

RFC4648

Unfortunately, the service name of the DNS SRV record is case insensitive:

Each node in the DNS tree has a name composed of zero or more labels [STD13, RFC1591, RFC2606], which are case insensitive.

RFC4343

Base32 generates a slightly longer string (56 bytes), but its representation allows us to represent WireGuard public key inside DNS:

The purpose of Base32 encoding is to represent any sequence of octets, and its form must be case insensitive.

We can use base64 and base32 commands to convert the encoding format back and forth, for example:

$ wg genkey | wg pubkey > pub.txt
$ cat pub.txt
O9rAAiO5qTejOEtFbsQhCl745ovoM9coTGiprFTaHUE=
$ cat pub.txt | base64 -D | base32
HPNMAARDXGUTPIZYJNCW5RBBBJPPRZUL5AZ5OKCMNCU2YVG2DVAQ====
$ cat pub.txt | base64 -D | base32 | base32 -d | base64
O9rAAiO5qTejOEtFbsQhCl745ovoM9coTGiprFTaHUE=

We can directly use base32, a case insensitive public key encoding, to make it compatible with DNS.

Compiling plug-ins

CoreDNS provides Documentation of plug-ins , the plug-in must implement plugin Handler interface:

type Handler interface {
    ServeDNS(context.Context, dns.ResponseWriter, *dns.Msg) (int, error)
    Name() string
}

I have written a plug-in to provide the Peer information of WireGuard through DNS-SD (DNS based service discovery) semantics. The plug-in name is wgsd . The plug-ins written by yourself do not belong to the official built-in plug-ins. The executable programs downloaded from the official download page of CoreDNS do not include these two plug-ins, so you need to compile CoreDNS yourself.

Compiling CoreDNS is not complicated. It can be compiled without external plug-ins:

$ git clone https://github.com/coredns/coredns.git
$ cd coredns
$ make

If you want to add a wgsd plug-in, you need to modify the plugin before making Cfg file, add the following line:

wgsd:github.com/jwhited/wgsd

Then start compiling:

$ go generate
$ go build

Check whether the compiled binary file contains the plug-in:

$ ./coredns -plugins | grep wgsd
  dns.wgsd

After compilation, you can enable the wgsd plug-in in the configuration file:

.:53 {
  wgsd <zone> <wg device>
}

You can test it. The configuration file is as follows:

$ cat Corefile
.:53 {
  debug
  wgsd fuckcloudnative.io. wg0
}

Run CoreDNS:

$ ./coredns -conf Corefile
.:53
CoreDNS-1.8.1
linux/amd64, go1.15,

WireGuard information of current node:

$ sudo wg show
interface: wg0
  listening port: 52022

peer: mvplwow3agnGM8G78+BiJ3tmlPf9gDtbJ2NdxqV44D8=
  endpoint: 3.3.3.3:8888
  allowed ips: 10.0.0.2/32

Here is a list of all peers when witnessing Miracles:

$ dig @127.0.0.1 _wireguard._udp.fuckcloudnative.io. PTR +noall +answer +additional

; <<>> DiG 9.10.6 <<>> @127.0.0.1 _wireguard._udp.fuckcloudnative.io. PTR +noall +answer +additional
; (1 server found)
;; global options: +cmd
_wireguard._udp.fuckcloudnative.io. 0 IN  PTR     TL5GLQUMG5VATRRTYG57HYDCE55WNFHX7WADWWZHMNO4NJLY4A7Q====._wireguard._udp.fuckcloudnative.io.

Query the IP and port of each Peer:

$ dig @127.0.0.1 TL5GLQUMG5VATRRTYG57HYDCE55WNFHX7WADWWZHMNO4NJLY4A7Q====._wireguard._udp.fuckcloudnative.io. SRV +noall +answer +additional

; <<>> DiG 9.10.6 <<>> @127.0.0.1 TL5GLQUMG5VATRRTYG57HYDCE55WNFHX7WADWWZHMNO4NJLY4A7Q====._wireguard._udp.fuckcloudnative.io. SRV +noall +answer +additional
; (1 server found)
;; global options: +cmd
tl5glqumg5vatrrtyg57hydce55wnfhx7wadwwzhmno4njly4a7q====._wireguard._udp.fuckcloudnative.io. 0 IN SRV 0 0 8888 TL5GLQUMG5VATRRTYG57HYDCE55WNFHX7WADWWZHMNO4NJLY4A7Q====.fuckcloudnative.io.
TL5GLQUMG5VATRRTYG57HYDCE55WNFHX7WADWWZHMNO4NJLY4A7Q====.fuckcloudnative.io. 0 IN A 3.3.3.3

🎉 🎉 🎉 Perfect! 🎉 🎉 🎉

Verify that the public keys match:

$ wg show wg0 peers
mvplwow3agnGM8G78+BiJ3tmlPf9gDtbJ2NdxqV44D8=
$ dig @127.0.0.1 _wireguard._udp.fuckcloudnative.io. PTR +short | cut -d. -f1 | base32 -d | base64
mvplwow3agnGM8G78+BiJ3tmlPf9gDtbJ2NdxqV44D8=

👍 👍 👍

8. Final communication process

The final communication process is as follows:

At the beginning, Alice and Bob established tunnels with Registry respectively; Next, the wgsd client on Alice sends a query request to the CoreDNS plug-in (wgsd) running on the Registry node, which retrieves Bob's endpoint information from the WireGuard information and returns it to the wgsd client; Then wgsd client starts to set Bob's endpoint; Finally, a tunnel was built directly between Alice and Bob.

Any reference to "building a tunnel" only means that a handshake occurs and packets can be transmitted between peers. Although WireGuard does have a handshake system, it is more like a connectionless protocol than you think.

Any security protocol needs to maintain some state, so the initial handshake is very simple, just establishing a symmetric key for data transmission. This handshake occurs every few minutes to provide rotation keys for perfect forward confidentiality. It is completed according to the time, not according to the content of the previous packet, because it is designed to deal with the problem of packet loss gracefully.

wireguard.com/protocol

Now everything is ready. We just need to implement wgsd client.

9. Implement wgsd client

Wgsd client is responsible for keeping the Peer's endpoint configuration up-to-date. It will retrieve the list of peers in the configuration, query the matching public key in CoreDNS, and then update the endpoint value for the corresponding Peer when necessary. The initial implementation method is to run in a scheduled task or similar scheduling mechanism, check all peers in a serialized manner, set the endpoint, and then exit. At present, it is not a daemon, and it will continue to improve and optimize in the future.

The source code of wgsd client is located in the wgsd repository cmd/wgsd-client catalogue

Let's start the final test.

Alice and Bob are behind NAT. Registry does not have nat and has a fixed public network address. The information of these three peers is as follows:

PeerPublic KeyTunnel Address
AlicexScVkH3fUGUv4RrJFfmcqm8rs3SEHr41km6+yffAHw4=10.0.0.1
BobsyKB97XhGnvC+kynh2KqQJPXoOoOpx/HmpMRTc+r4js=10.0.0.2
RegistryJeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY=10.0.0.254

Their respective initial configurations:

Alice

$ cat /etc/wireguard/wg0.conf
[Interface]
Address = 10.0.0.1/32
PrivateKey = 0CtieMOYKa2RduPbJss/Um9BiQPSjgvHW+B7Mor5OnE=
ListenPort = 51820

# Registry
[Peer]
PublicKey = JeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY=
Endpoint = 4.4.4.4:51820
PersistentKeepalive = 5
AllowedIPs = 10.0.0.254/32

# Bob
[Peer]
PublicKey = syKB97XhGnvC+kynh2KqQJPXoOoOpx/HmpMRTc+r4js=
PersistentKeepalive = 5
AllowedIPs = 10.0.0.2/32

$ wg show
interface: wg0
  public key: xScVkH3fUGUv4RrJFfmcqm8rs3SEHr41km6+yffAHw4=
  private key: (hidden)
  listening port: 51820

peer: JeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY=
  endpoint: 4.4.4.4:51820
  allowed ips: 10.0.0.254/32
  latest handshake: 48 seconds ago
  transfer: 1.67 KiB received, 11.99 KiB sent
  persistent keepalive: every 5 seconds

peer: syKB97XhGnvC+kynh2KqQJPXoOoOpx/HmpMRTc+r4js=
  allowed ips: 10.0.0.2/32
  persistent keepalive: every 5 seconds

Bob

$ cat /etc/wireguard/wg0.conf
[Interface]
Address = 10.0.0.2/32
PrivateKey = cIN5NqeWcbreXoaIhR/4wgrrQJGym/E7WrTttMtK8Gc=
ListenPort = 51820

# Registry
[Peer]
PublicKey = JeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY=
Endpoint = 4.4.4.4:51820
PersistentKeepalive = 5
AllowedIPs = 10.0.0.254/32

# Alice
[Peer]
PublicKey = xScVkH3fUGUv4RrJFfmcqm8rs3SEHr41km6+yffAHw4=
PersistentKeepalive = 5
AllowedIPs = 10.0.0.1/32

$ wg show
interface: wg0
  public key: syKB97XhGnvC+kynh2KqQJPXoOoOpx/HmpMRTc+r4js=
  private key: (hidden)
  listening port: 51820

peer: JeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY=
  endpoint: 4.4.4.4:51820
  allowed ips: 10.0.0.254/32
  latest handshake: 26 seconds ago
  transfer: 1.54 KiB received, 11.75 KiB sent
  persistent keepalive: every 5 seconds

peer: xScVkH3fUGUv4RrJFfmcqm8rs3SEHr41km6+yffAHw4=
  allowed ips: 10.0.0.1/32
  persistent keepalive: every 5 seconds

Registry

$ cat /etc/wireguard/wg0.conf
[Interface]
Address = 10.0.0.254/32
PrivateKey = wLw2ja5AapryT+3SsBiyYVNVDYABJiWfPxLzyuiy5nE=
ListenPort = 51820

# Alice
[Peer]
PublicKey = xScVkH3fUGUv4RrJFfmcqm8rs3SEHr41km6+yffAHw4=
AllowedIPs = 10.0.0.1/32

# Bob
[Peer]
PublicKey = syKB97XhGnvC+kynh2KqQJPXoOoOpx/HmpMRTc+r4js=
AllowedIPs = 10.0.0.2/32

$ wg show
interface: wg0
  public key: JeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY=
  private key: (hidden)
  listening port: 51820

peer: xScVkH3fUGUv4RrJFfmcqm8rs3SEHr41km6+yffAHw4=
  endpoint: 2.2.2.2:41424
  allowed ips: 10.0.0.1/32
  latest handshake: 6 seconds ago
  transfer: 510.29 KiB received, 52.11 KiB sent

peer: syKB97XhGnvC+kynh2KqQJPXoOoOpx/HmpMRTc+r4js=
  endpoint: 3.3.3.3:51820
  allowed ips: 10.0.0.2/32
  latest handshake: 1 minute, 46 seconds ago
  transfer: 498.04 KiB received, 50.59 KiB sent

Registry has established connections with Alice and Bob. You can directly query their endpoint information:

$ dig @4.4.4.4 -p 53 _wireguard._udp.fuckcloudnative.io. PTR +noall +answer +additional

; <<>> DiG 9.10.6 <<>> @4.4.4.4 -p 53 _wireguard._udp.fuckcloudnative.io. PTR +noall +answer +additional
; (1 server found)
;; global options: +cmd
_wireguard._udp.fuckcloudnative.io. 0 IN  PTR     YUTRLED535IGKL7BDLERL6M4VJXSXM3UQQPL4NMSN27MT56AD4HA====._wireguard._udp.fuckcloudnative.io.
_wireguard._udp.fuckcloudnative.io. 0 IN  PTR     WMRID55V4ENHXQX2JSTYOYVKICJ5PIHKB2TR7R42SMIU3T5L4I5Q====._wireguard._udp.fuckcloudnative.io.

$ dig @4.4.4.4 -p 53 YUTRLED535IGKL7BDLERL6M4VJXSXM3UQQPL4NMSN27MT56AD4HA====._wireguard._udp.fuckcloudnative.io. SRV +noall +answer +additional

; <<>> DiG 9.10.6 <<>> @4.4.4.4 -p 53 YUTRLED535IGKL7BDLERL6M4VJXSXM3UQQPL4NMSN27MT56AD4HA====._wireguard._udp.fuckcloudnative.io. SRV +noall +answer +additional
; (1 server found)
;; global options: +cmd
yutrled535igkl7bdlerl6m4vjxsxm3uqqpl4nmsn27mt56ad4ha====._wireguard._udp.fuckcloudnative.io. 0 IN SRV 0 0 41424 YUTRLED535IGKL7BDLERL6M4VJXSXM3UQQPL4NMSN27MT56AD4HA====.fuckcloudnative.io.
YUTRLED535IGKL7BDLERL6M4VJXSXM3UQQPL4NMSN27MT56AD4HA====.fuckcloudnative.io. 0 IN A 2.2.2.2

Perfect. Let's start wgsd client on Alice and Bob respectively:

# Alice
$ ./wgsd-client -device=wg0 -dns=4.4.4.4:53 -zone=fuckcloudnative.io.
2020/05/20 13:24:02 [JeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY=] no SRV records found
jwhited@Alice:~$ ping 10.0.0.2
PING 10.0.0.2 (10.0.0.2): 56 data bytes
64 bytes from 10.0.0.2: icmp_seq=0 ttl=64 time=173.260 ms
^C
jwhited@Alice:~$ wg show
interface: wg0
  public key: xScVkH3fUGUv4RrJFfmcqm8rs3SEHr41km6+yffAHw4=
  private key: (hidden)
  listening port: 51820

peer: syKB97XhGnvC+kynh2KqQJPXoOoOpx/HmpMRTc+r4js=
  endpoint: 3.3.3.3:51820
  allowed ips: 10.0.0.2/32
  latest handshake: 2 seconds ago
  transfer: 252 B received, 264 B sent
  persistent keepalive: every 5 seconds

peer: JeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY=
  endpoint: 4.4.4.4:51820
  allowed ips: 10.0.0.254/32
  latest handshake: 1 minute, 19 seconds ago
  transfer: 184 B received, 1.57 KiB sent
  persistent keepalive: every 5 seconds
# Bob
$ ./wgsd-client -device=wg0 -dns=4.4.4.4:53 -zone=fuckcloudnative.io.
2020/05/20 13:24:04 [JeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY=] no SRV records found
jwhited@Bob:~$ wg show
interface: wg0
  public key: syKB97XhGnvC+kynh2KqQJPXoOoOpx/HmpMRTc+r4js=
  private key: (hidden)
  listening port: 51820

peer: xScVkH3fUGUv4RrJFfmcqm8rs3SEHr41km6+yffAHw4=
  endpoint: 2.2.2.2:41424
  allowed ips: 10.0.0.1/32
  latest handshake: 22 seconds ago
  transfer: 392 B received, 9.73 KiB sent
  persistent keepalive: every 5 seconds

peer: JeZlz14G8tg1Bqh6apteFCwVhNhpexJ19FDPfuxQtUY=
  endpoint: 4.4.4.4:51820
  allowed ips: 10.0.0.254/32
  latest handshake: 1 minute, 14 seconds ago
  transfer: 2.08 KiB received, 17.59 KiB sent
  persistent keepalive: every 5 seconds

Wgsd client successfully found Peer's endpoint address and updated the WireGuard configuration. Finally, a tunnel was directly established between Alice and Bob!

summary

This paper discusses how to establish a WireGuard tunnel directly between two peers limited by NAT. The solutions provided in this article use the existing protocols and service discovery technology, and write a pluggable plug-in. You can directly use dig or nslookup for debugging without disturbing or modifying WireGuard itself.

Of course, the CoreDNS plug-in can be optimized, and wgsd client needs to continue to be optimized. For example, should the CoreDNS server be restricted to the tunnel of the Registry? Should the domain be signed? Is it necessary to query the Peer information of WireGuard every time DNS is queried, or can it be solved by caching? These are questions worth thinking about.

wgsd plug-in Our code is open source. Welcome to contribute enthusiastically.

-------When we meet in the Jianghu in the future, we will drink and talk-------

share

Your encouragement is my greatest motivation

Author: michelangela Yang

Yunyuan brick Porter

Related content

WireGuard configuration tutorial: using WG Gen web to manage WireGuard configuration

Wireguard full mesh configuration guide

Topics: IT