Repository: mitmproxy/mitmproxy

Issue:
Duplicate answers in DNS queries
#### Problem Description

Two duplicate records are returned for each unique A/AAAA record in a DNS query when using DNS mode.

#### Steps to reproduce the behavior:

##### Without mitmproxy

1. Run `dig +short google.com`
2. Correct output: `142.250.193.206`

##### With mitmproxy

1. Start mitmproxy `mitmproxy --mode dns@53535`
2. Run `dig @127.0.0.1 -p 53535 +short google.com`
3. Output with duplicates:
    ```
    142.250.193.206
    142.250.193.206
    142.250.193.206
    ```

#### System Information

```
Mitmproxy: 11.0.0.dev (+19, commit d638213)
Python:    3.12.1
OpenSSL:   OpenSSL 3.1.4 24 Oct 2023
Platform:  Linux-6.6.14-200.fc39.x86_64-x86_64-with-glibc2.38
```


#### Additional Notes

This is happening because the `dns_resolver` addon calls `getaddrinfo` here:

https://github.com/mitmproxy/mitmproxy/blob/1a02ebb89f6765d827f2fe0086dfe5960eb6e093/mitmproxy/addons/dns_resolver.py#L29

Which is returning one tuple each for UDP, TCP and a raw socket.

We could just do the following since I assume all requests are currently using UDP:
```python
addrinfos = await loop.getaddrinfo(host=question.name, port=0, family=family, type=socket.SOCK_DGRAM)
```

What do you think?


Discussion:
Yes, please send a PR! We eventually should really support DNS-over-TCP, but this is a good mitigation for now.

Resolving patch:
diff --git a/mitmproxy/addons/dns_resolver.py b/mitmproxy/addons/dns_resolver.py
--- a/mitmproxy/addons/dns_resolver.py
+++ b/mitmproxy/addons/dns_resolver.py
@@ -26,7 +26,9 @@ async def resolve_question_by_name(
     ip: Callable[[str], ipaddress.IPv4Address | ipaddress.IPv6Address],
 ) -> Iterable[dns.ResourceRecord]:
     try:
-        addrinfos = await loop.getaddrinfo(host=question.name, port=0, family=family)
+        addrinfos = await loop.getaddrinfo(
+            host=question.name, port=0, family=family, type=socket.SOCK_STREAM
+        )
     except socket.gaierror as e:
         if e.errno == socket.EAI_NONAME:
             raise ResolveError(dns.response_codes.NXDOMAIN)


Test patch:
diff --git a/test/mitmproxy/addons/test_dns_resolver.py b/test/mitmproxy/addons/test_dns_resolver.py
--- a/test/mitmproxy/addons/test_dns_resolver.py
+++ b/test/mitmproxy/addons/test_dns_resolver.py
@@ -41,18 +41,18 @@ async def getnameinfo(self, socketaddr: Address, flags: int = 0):
         e.errno = socket.EAI_NONAME
         raise e
 
-    async def getaddrinfo(self, host: str, port: int, *, family: int):
+    async def getaddrinfo(self, host: str, port: int, *, family: int, type: int):
         e = socket.gaierror()
         e.errno = socket.EAI_NONAME
         if family == socket.AF_INET:
             if host == "dns.google":
-                return [(socket.AF_INET, None, None, None, ("8.8.8.8", port))]
+                return [(socket.AF_INET, type, None, None, ("8.8.8.8", port))]
         elif family == socket.AF_INET6:
             if host == "dns.google":
                 return [
                     (
                         socket.AF_INET6,
-                        None,
+                        type,
                         None,
                         None,
                         ("2001:4860:4860::8888", port, None, None),
diff --git a/test/mitmproxy/addons/test_proxyserver.py b/test/mitmproxy/addons/test_proxyserver.py
--- a/test/mitmproxy/addons/test_proxyserver.py
+++ b/test/mitmproxy/addons/test_proxyserver.py
@@ -270,9 +270,9 @@ class DummyResolver:
     async def dns_request(self, flow: dns.DNSFlow) -> None:
         flow.response = await dns_resolver.resolve_message(flow.request, self)
 
-    async def getaddrinfo(self, host: str, port: int, *, family: int):
+    async def getaddrinfo(self, host: str, port: int, *, family: int, type: int):
         if family == socket.AF_INET and host == "dns.google":
-            return [(socket.AF_INET, None, None, None, ("8.8.8.8", port))]
+            return [(socket.AF_INET, type, None, None, ("8.8.8.8", port))]
         e = socket.gaierror()
         e.errno = socket.EAI_NONAME
         raise e