[Dnsmasq-discuss] dnsmasq rejects TCP queries originating from Kubernetes pods
Simon Kelley
simon at thekelleys.org.uk
Fri Dec 5 15:04:27 UTC 2025
On 12/4/25 09:18, Nicolas Cavallari wrote:
> On 12/3/25 23:55, Sławomir Zborowski wrote:
>> > There's not really such a thing as an "inet interface" or an "inet6
>> > interface" an interface, represented by an index, can have multiple
>> > addresses, both IPv4 and IPv6.
>>
>> You're right. That was imprecise mental shortcut.
>>
>> > What I would expect to see here is that the interface had both IPv4
>> > addresses and IPv6 addresses when it was added to the daemon-
>> >interfaces
>> > list, so this check will find at least one entry on that list which
>> came
>> > from the arrival interface and has the correct address family.
>>
>> The thing is it only has the IPv6 address. This is how Calico (or the OS
>> - I'm not entirely sure) configures it.
>>
>> > Your description implies that the TCP connection is over IPv4. Does
>> the
>> > interface is arrives on have an IP4 address?
>>
>> Destination is a regular eth0 iface on the server with IPv4 address only.
>> The connection originates from a Pod that is connected through veth pair.
>> That veth pair has only IPv6 address in the system.
>>
>> > As usual, the most useful resource here is the simplest configuration
>> > which demonstrates this problem on the most vanilla kernel network
>> > configuration.
>>
>> I'm not sure if that's the most vanilla way out there, but I believe that
>> Calico does something along following. I'm not sure if the proxy_arp
>> thing is necessary. With following I was able to reproduce the problem.
>>
>> # ip netns add fakepod
>> # ip link add host-veth type veth peer name pod-veth
>> # ip link set pod-veth netns fakepod
>> # ip link set host-veth up
>> # echo 1 | sudo tee /proc/sys/net/ipv4/conf/host-veth/proxy_arp
>> # ip netns exec fakepod bash
>> # ip link set lo up
>> # ip link set pod-veth up
>> # ip addr add 10.44.0.51/32 <http://10.44.0.51/32> dev pod-veth
>> # ip route add default dev pod-veth
>> # ip route add 169.254.1.1/32 <http://169.254.1.1/32> dev pod-veth
>> # exit
>> # ip route add 10.44.0.51/32 <http://10.44.0.51/32> dev host-veth
>>
>> Now, start dnsmasq on the host:
>> # sudo ./src/dnsmasq -d --log-debug 2>&1 | tee /tmp/dnsmasq.log
>>
>> And trigger the behavior:
>> # ip netns exec fakepod dig duckduckgo.com <http://duckduckgo.com>. A
>> +tcp @[REDACTED: server eth0 physical IPv4 address]
>> ;; communications error to [REDACTED: server eth0 physical IPv4
>> address]#53: end of file
>>
>> ;; communications error to [REDACTED: server eth0 physical IPv4
>> address]#53: end of file
>>
>> This is, by the way, how the iface shows up in the system:
>> 266: host-veth at if265: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc
>> noqueue state UP group default qlen 1000
>> link/ether d6:35:97:b9:51:f0 brd ff:ff:ff:ff:ff:ff link-netns
>> fakepod
>> inet6 fe80::d435:97ff:feb9:51f0/64 scope link
>> valid_lft forever preferred_lft forever
>>
>> And dnsmasq will recognize it as IPv6. I added some debug prints
>> to verify that it happens. Incoming TCP addr sa_family is 0x2, iface
>> sa_family is 0xA.
>>
>> I think the IPv6 address that is attached to the veth host end is added
>> by the OS.
>>
>> Hopefully all of the above is helpful.
>
> So basically, dnsmasq receives a TCP connection from 10.44.0.51 to
> [redacted] through the host-veth interface, but the host-veth interface
> only have a ipv6 link-local and no ipv4. [redacted] is actually
> configured on eth0.
>
> It is quite difficult for a TCP server to know from which an interface a
> request comes from. Finding the interface where the destination address
> was configured only works on systems with a strong host model, but linux
> uses a weak host model and your setup rely on it.
>
> A weak host model system will accept packets from 10.44.0.51 to
> [redacted] regardless of the interface the packet came from. And
> replies are sent to whatever interface the FIB says should be used and
> the interface may change over time.
>
> A workaround would be to use RTM_GETROUTE on the source address and
> consider that as the interface the request came from, but that could
> cause other problems.
[This is a reply to both Sławomir's second email and Nicolas's email.
It's easier to reply to Nicolas's since the information he provides is
highly pertinent.]
The aim of the code in question is to only accept TCP connections which
arrive via an allowed interface, when the set of allowed interfaces is
configured using --interface=<name> and --address=<ip> options to dnsmasq.
Ideally UDP using a single, wildcard-bound socket, UDP with a socket for
each address, and TCP should work the same.
TCP is kind of difficult, and looking at this code, it's more
complicated than it needs to be, and (as Sławomir found) it doesn't
behave the same as UDP.
I think I can improve this to solve the problem, but I'm definitely not
going to try and do that for the 2.92 release. This is the sort of thing
that's very difficult to fix without breaking some installations. Even
if you make the behaviour better, there will always be someone who is
inadvertently relying on the old, broken behaviour.
TL;DR Sławomir, you can remove the
iface->addr.sa.sa_family == tcp_addr.sa.sa_family
condition, which should fix your immediate problem without undesirable
side-affects. I'll work on improving this code, and push somethingout
one 2.92 is released.
Cheers,
Simon.
>
> _______________________________________________
> Dnsmasq-discuss mailing list
> Dnsmasq-discuss at lists.thekelleys.org.uk
> https://lists.thekelleys.org.uk/cgi-bin/mailman/listinfo/dnsmasq-discuss
More information about the Dnsmasq-discuss
mailing list