What is the issue?
When querying DNS whilst connected to tailscale, I will use tailscale's DNS server (100.100.100.100 in my case).
When a DNS response is larger than 1232 bytes, it should be truncated to 1232 bytes and the have the truncated bit set in the response, per https://www.dnsflagday.net/2020/
From that post:
An EDNS buffer size of 1232 bytes will avoid fragmentation on nearly all current networks. This is based on an MTU of 1280, which is required by the IPv6 specification, minus 48 bytes for the IPv6 and UDP headers and the aforementioned research.
Instead the full response is sent, the truncated bit is not set, and the packet is subsequently fragmented because I am connected to tailscale and the packet size is larger than the MTU for packets encapsulated within the VPN.
Tools such as nslookup and dig both support reconstructing fragmented packets and thus unless a user is using something like wireshark or tcpdump they will most likely not notice.
However not all tools do this, and this is by design. For example golang deliberately sets a hard limit:
https://github.com/golang/go/blob/ca1123f9c51e95d4b2383fbfe1652d70b373aac6/src/net/dnsclient_unix.go#L37-L39,
And no attempt is made to reconstruct the fragments. In that specific case leaving users with a truncated response.
Steps to reproduce
If you would like to use go as an example (this is not isolated to go, this is merely for illustration), we can lookup a DNS record that has a suitably large response...
nat.travisci.net typically returns a response of something like 79 A Records pointing to TCP4 addresses. If we build the following go example and run it whilst connected to tailscale, and disconnected from tailscale (assuming you are using tailscale DNS whilst connected) you will see something like 79 responses whilst disconnected and 29 whilst connected.
package main
import (
"fmt"
"net"
)
func main() {
ipList, err := net.LookupIP("nat.travisci.net")
if err != nil {
fmt.Printf("there was a problem: %s\n", err)
return
}
fmt.Printf("len: %d\n", len(ipList))
for x, y := range ipList {
fmt.Printf("%d => %v\n", x, y)
}
return
}
To further demonstrate that the issue is the DNS server, and thus rule out the VPN connection itself, we can modify the above code to override the system resolver and use another public DNS resolver....
package main
import (
"context"
"fmt"
"net"
"time"
)
func main() {
resolver := &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer{
Timeout: time.Second * 30,
}
return d.DialContext(ctx, network, "8.8.8.8:53")
},
}
ipList, err := resolver.LookupIP(context.Background(), "ip", "nat.travisci.net")
if err != nil {
fmt.Printf("there was a problem: %s\n", err)
return
}
fmt.Printf("len: %d\n", len(ipList))
for x, y := range ipList {
fmt.Printf("%d => %v\n", x, y)
}
return
}
You should see that when using a different resolver, you should obtain the full set of results irrespective of if you are connected to tailscale or not.
Just an additional thought on this, I would assume that configuring your resolvers to the suggested 1232 max size would probably be beneficial to you aside from this, as I assume that everyone querying them will be using a VPN and getting fragments in this scenario quite regularly.
Are there any recent changes that introduced the issue?
Not that I am aware of
OS
Linux, macOS
OS version
23.5.0 Darwin Kernel Version 23.5.0 ARM64
Tailscale version
1.74.1
Other software
n/a
Bug report
No response
What is the issue?
When querying DNS whilst connected to tailscale, I will use tailscale's DNS server (
100.100.100.100in my case).When a DNS response is larger than 1232 bytes, it should be truncated to 1232 bytes and the have the
truncatedbit set in the response, per https://www.dnsflagday.net/2020/From that post:
Instead the full response is sent, the truncated bit is not set, and the packet is subsequently fragmented because I am connected to tailscale and the packet size is larger than the MTU for packets encapsulated within the VPN.
Tools such as
nslookupanddigboth support reconstructing fragmented packets and thus unless a user is using something likewiresharkortcpdumpthey will most likely not notice.However not all tools do this, and this is by design. For example golang deliberately sets a hard limit:
https://github.com/golang/go/blob/ca1123f9c51e95d4b2383fbfe1652d70b373aac6/src/net/dnsclient_unix.go#L37-L39,
And no attempt is made to reconstruct the fragments. In that specific case leaving users with a truncated response.
Steps to reproduce
If you would like to use go as an example (this is not isolated to go, this is merely for illustration), we can lookup a DNS record that has a suitably large response...
nat.travisci.nettypically returns a response of something like 79 A Records pointing to TCP4 addresses. If we build the following go example and run it whilst connected to tailscale, and disconnected from tailscale (assuming you are using tailscale DNS whilst connected) you will see something like 79 responses whilst disconnected and 29 whilst connected.To further demonstrate that the issue is the DNS server, and thus rule out the VPN connection itself, we can modify the above code to override the system resolver and use another public DNS resolver....
You should see that when using a different resolver, you should obtain the full set of results irrespective of if you are connected to tailscale or not.
Just an additional thought on this, I would assume that configuring your resolvers to the suggested 1232 max size would probably be beneficial to you aside from this, as I assume that everyone querying them will be using a VPN and getting fragments in this scenario quite regularly.
Are there any recent changes that introduced the issue?
Not that I am aware of
OS
Linux, macOS
OS version
23.5.0 Darwin Kernel Version 23.5.0 ARM64
Tailscale version
1.74.1
Other software
n/a
Bug report
No response