wahern / dns

dns.c: Single file non-blocking DNS C library without callbacks or external dependencies.
http://25thandclement.com/~william/projects/dns.c.html
MIT License
256 stars 64 forks source link

On linux if this is not the first connect() call for a udp socket, connect(AF_UNSPEC) first #20

Open daurnimator opened 7 years ago

daurnimator commented 7 years ago

Related to #19

daurnimator commented 7 years ago

Actually this might not be the total fix. The 'resolve' interface doesn't move on in the case of the ECONNREFUSED:

$ cat /etc/resolv.conf
nameserver 127.0.0.2
nameserver 8.8.8.8
$ strace ./dns resolve-stub google.com
execve("./dns", ["./dns", "resolve-stub", "google.com"], [/* 56 vars */]) = 0
brk(NULL)                               = 0x10ce000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=215350, ...}) = 0
mmap(NULL, 215350, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f62fb0e5000
close(3)                                = 0
open("/usr/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260\3\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1951744, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f62fb0e3000
mmap(NULL, 3791152, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f62fab5a000
mprotect(0x7f62facef000, 2093056, PROT_NONE) = 0
mmap(0x7f62faeee000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x194000) = 0x7f62faeee000
mmap(0x7f62faef4000, 14640, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f62faef4000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f62fb0e4480) = 0
mprotect(0x7f62faeee000, 16384, PROT_READ) = 0
mprotect(0x61b000, 4096, PROT_READ)     = 0
mprotect(0x7f62fb11a000, 4096, PROT_READ) = 0
munmap(0x7f62fb0e5000, 215350)          = 0
brk(NULL)                               = 0x10ce000
brk(0x10ef000)                          = 0x10ef000
uname({sysname="Linux", nodename="daurn-m73", ...}) = 0
open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=40, ...}) = 0
lseek(3, 0, SEEK_SET)                   = 0
read(3, "nameserver 127.0.0.2\nnameserver "..., 4096) = 40
read(3, "", 4096)                       = 0
close(3)                                = 0
open("/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=312, ...}) = 0
read(3, "# Begin /etc/nsswitch.conf\n\npass"..., 4096) = 312
read(3, "", 4096)                       = 0
read(3, "", 4096)                       = 0
read(3, "", 4096)                       = 0
close(3)                                = 0
open("/etc/hosts", O_RDONLY|O_CLOEXEC)  = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=195, ...}) = 0
lseek(3, 0, SEEK_SET)                   = 0
read(3, "#\n# /etc/hosts: static lookup ta"..., 4096) = 195
read(3, "", 4096)                       = 0
close(3)                                = 0
socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(10218), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.2")}, 16) = 0
sendto(3, "\317/\1\0\0\1\0\0\0\0\0\0\3www\6google\3com\0\0\1\0\1", 32, 0, NULL, 0) = 32
recvfrom(3, 0x10d08ec, 768, 0, NULL, NULL) = -1 ECONNREFUSED (Connection refused)
write(2, "dns: ", 5dns: )                    = 5
write(2, "(resolve_query:9532) dns_res_che"..., 60(resolve_query:9532) dns_res_check: Connection refused (111)) = 60
write(2, "\n", 1
)                       = 1
exit_group(1)                           = ?
+++ exited with 1 +++
daurnimator commented 7 years ago

With that change the behaviour becomes the IMO more sensible:

$ strace ./dns resolve-stub google.com
execve("./dns", ["./dns", "resolve-stub", "google.com"], [/* 56 vars */]) = 0
brk(NULL)                               = 0x2422000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 4
fstat(4, {st_mode=S_IFREG|0644, st_size=215350, ...}) = 0
mmap(NULL, 215350, PROT_READ, MAP_PRIVATE, 4, 0) = 0x7fc4c22bb000
close(4)                                = 0
open("/usr/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 4
read(4, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260\3\2\0\0\0\0\0"..., 832) = 832
fstat(4, {st_mode=S_IFREG|0755, st_size=1951744, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc4c22b9000
mmap(NULL, 3791152, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 4, 0) = 0x7fc4c1d30000
mprotect(0x7fc4c1ec5000, 2093056, PROT_NONE) = 0
mmap(0x7fc4c20c4000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 4, 0x194000) = 0x7fc4c20c4000
mmap(0x7fc4c20ca000, 14640, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fc4c20ca000
close(4)                                = 0
arch_prctl(ARCH_SET_FS, 0x7fc4c22ba480) = 0
mprotect(0x7fc4c20c4000, 16384, PROT_READ) = 0
mprotect(0x61b000, 4096, PROT_READ)     = 0
mprotect(0x7fc4c22f0000, 4096, PROT_READ) = 0
munmap(0x7fc4c22bb000, 215350)          = 0
brk(NULL)                               = 0x2422000
brk(0x2443000)                          = 0x2443000
uname({sysname="Linux", nodename="daurn-m73", ...}) = 0
open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 4
fstat(4, {st_mode=S_IFREG|0644, st_size=40, ...}) = 0
lseek(4, 0, SEEK_SET)                   = 0
read(4, "nameserver 127.0.0.2\nnameserver "..., 4096) = 40
read(4, "", 4096)                       = 0
close(4)                                = 0
open("/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 4
fstat(4, {st_mode=S_IFREG|0644, st_size=312, ...}) = 0
read(4, "# Begin /etc/nsswitch.conf\n\npass"..., 4096) = 312
read(4, "", 4096)                       = 0
read(4, "", 4096)                       = 0
read(4, "", 4096)                       = 0
close(4)                                = 0
open("/etc/hosts", O_RDONLY|O_CLOEXEC)  = 4
fstat(4, {st_mode=S_IFREG|0644, st_size=195, ...}) = 0
lseek(4, 0, SEEK_SET)                   = 0
read(4, "#\n# /etc/hosts: static lookup ta"..., 4096) = 195
read(4, "", 4096)                       = 0
close(4)                                = 0
socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 4
bind(4, {sa_family=AF_INET, sin_port=htons(10218), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
connect(4, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.2")}, 16) = 0
sendto(4, "\317/\1\0\0\1\0\0\0\0\0\0\3www\6google\3com\0\0\1\0\1", 32, 0, NULL, 0) = 32
recvfrom(4, 0x24248ec, 768, 0, NULL, NULL) = -1 ECONNREFUSED (Connection refused)
connect(4, {sa_family=AF_UNSPEC, sa_data="\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, 16) = 0
connect(4, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.2")}, 16) = 0
sendto(4, "\317/\1\0\0\1\0\0\0\0\0\0\3www\6google\3com\0\0\1\0\1", 32, 0, NULL, 0) = 32
recvfrom(4, 0x24248ec, 768, 0, NULL, NULL) = -1 ECONNREFUSED (Connection refused)
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 8), ...}) = 0
write(1, ";; [HEADER]\n", 12;; [HEADER]
)           = 12
write(1, ";;    qid : 0\n", 14;;    qid : 0
)         = 14
write(1, ";;     qr : RESPONSE(1)\n", 24;;     qr : RESPONSE(1)
) = 24
write(1, ";; opcode : QUERY(0)\n", 21;; opcode : QUERY(0)
)  = 21
write(1, ";;     aa : NON-AUTHORITATIVE(0)"..., 33;;     aa : NON-AUTHORITATIVE(0)
) = 33
write(1, ";;     tc : NOT-TRUNCATED(0)\n", 29;;     tc : NOT-TRUNCATED(0)
) = 29
write(1, ";;     rd : RECURSION-NOT-DESIRE"..., 37;;     rd : RECURSION-NOT-DESIRED(0)
) = 37
write(1, ";;     ra : RECURSION-NOT-ALLOWE"..., 37;;     ra : RECURSION-NOT-ALLOWED(0)
) = 37
write(1, ";;  rcode : SERVFAIL(2)\n", 24;;  rcode : SERVFAIL(2)
) = 24
write(1, "\n", 1
)                       = 1
write(1, ";; [QUESTION:1]\n", 16;; [QUESTION:1]
)       = 16
write(1, ";www.google.com. IN A\n", 22;www.google.com. IN A
) = 22
write(1, "\n", 1
)                       = 1
write(1, ";; queries:  2\n", 15;; queries:  2
)        = 15
write(1, ";; udp sent: 2 in 64 bytes\n", 27;; udp sent: 2 in 64 bytes
) = 27
write(1, ";; udp rcvd: 0 in 0 bytes\n", 26;; udp rcvd: 0 in 0 bytes
) = 26
write(1, ";; tcp sent: 0 in 0 bytes\n", 26;; tcp sent: 0 in 0 bytes
) = 26
write(1, ";; tcp rcvd: 0 in 0 bytes\n", 26;; tcp rcvd: 0 in 0 bytes
) = 26
close(4)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++
daurnimator commented 7 years ago

Eek, that wasn't the result I thought it was: the second time around recv() returns ECONNREFUSED as well. This PR doesn't work.

daurnimator commented 7 years ago

Oops, I jumped to that conclusion too fast: my code didn't iterate the nameserver and hence it just tried the same one again. strace excerpt showing that AF_UNSPEC does work:

socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 5
connect(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.2")}, 16) = 0
sendto(5, "test", 4, 0, NULL, 0)        = 4
recvfrom(5, 0x7ffdf8fcaca0, 200, 0, NULL, NULL) = -1 ECONNREFUSED (Connection refused)
connect(5, {sa_family=AF_UNSPEC, sa_data="\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, 16) = 0
connect(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("8.8.8.8")}, 16) = 0
sendto(5, "test", 4, 0, NULL, 0)        = 4
recvfrom(5, 0x7ffdf8fcaca0, 200, 0, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable)
exit_group(0)                           = ?
daurnimator commented 7 years ago

I've updated this PR with fixed code. strace output:

$ strace ./dns resolve-stub google.com
execve("./dns", ["./dns", "resolve-stub", "google.com"], [/* 56 vars */]) = 0
brk(NULL)                               = 0x2038000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 5
fstat(5, {st_mode=S_IFREG|0644, st_size=215350, ...}) = 0
mmap(NULL, 215350, PROT_READ, MAP_PRIVATE, 5, 0) = 0x7f66a2706000
close(5)                                = 0
open("/usr/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 5
read(5, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260\3\2\0\0\0\0\0"..., 832) = 832
fstat(5, {st_mode=S_IFREG|0755, st_size=1951744, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f66a2704000
mmap(NULL, 3791152, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 5, 0) = 0x7f66a217b000
mprotect(0x7f66a2310000, 2093056, PROT_NONE) = 0
mmap(0x7f66a250f000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 5, 0x194000) = 0x7f66a250f000
mmap(0x7f66a2515000, 14640, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f66a2515000
close(5)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f66a2705480) = 0
mprotect(0x7f66a250f000, 16384, PROT_READ) = 0
mprotect(0x61b000, 4096, PROT_READ)     = 0
mprotect(0x7f66a273b000, 4096, PROT_READ) = 0
munmap(0x7f66a2706000, 215350)          = 0
brk(NULL)                               = 0x2038000
brk(0x2059000)                          = 0x2059000
uname({sysname="Linux", nodename="daurn-m73", ...}) = 0
open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC) = 5
fstat(5, {st_mode=S_IFREG|0644, st_size=40, ...}) = 0
lseek(5, 0, SEEK_SET)                   = 0
read(5, "nameserver 127.0.0.2\nnameserver "..., 4096) = 40
read(5, "", 4096)                       = 0
close(5)                                = 0
open("/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 5
fstat(5, {st_mode=S_IFREG|0644, st_size=312, ...}) = 0
read(5, "# Begin /etc/nsswitch.conf\n\npass"..., 4096) = 312
read(5, "", 4096)                       = 0
read(5, "", 4096)                       = 0
read(5, "", 4096)                       = 0
close(5)                                = 0
open("/etc/hosts", O_RDONLY|O_CLOEXEC)  = 5
fstat(5, {st_mode=S_IFREG|0644, st_size=195, ...}) = 0
lseek(5, 0, SEEK_SET)                   = 0
read(5, "#\n# /etc/hosts: static lookup ta"..., 4096) = 195
read(5, "", 4096)                       = 0
close(5)                                = 0
socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 5
bind(5, {sa_family=AF_INET, sin_port=htons(10218), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
connect(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.2")}, 16) = 0
sendto(5, "\317/\1\0\0\1\0\0\0\0\0\0\3www\6google\3com\0\0\1\0\1", 32, 0, NULL, 0) = 32
recvfrom(5, 0x203a8ec, 768, 0, NULL, NULL) = -1 ECONNREFUSED (Connection refused)
connect(5, {sa_family=AF_UNSPEC, sa_data="\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, 16) = 0
connect(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("8.8.8.8")}, 16) = 0
sendto(5, "\317/\1\0\0\1\0\0\0\0\0\0\3www\6google\3com\0\0\1\0\1", 32, 0, NULL, 0) = 32
recvfrom(5, 0x203a8ec, 768, 0, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable)
select(6, [5], [], NULL, {tv_sec=1, tv_usec=0}) = 1 (in [5], left {tv_sec=0, tv_usec=987061})
recvfrom(5, "\317/\201\200\0\1\0\1\0\0\0\0\3www\6google\3com\0\0\1\0\1"..., 768, 0, NULL, NULL) = 48
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 8), ...}) = 0
write(1, ";; [HEADER]\n", 12;; [HEADER]
)           = 12
write(1, ";;    qid : 53039\n", 18;;    qid : 53039
)     = 18
write(1, ";;     qr : RESPONSE(1)\n", 24;;     qr : RESPONSE(1)
) = 24
write(1, ";; opcode : QUERY(0)\n", 21;; opcode : QUERY(0)
)  = 21
write(1, ";;     aa : NON-AUTHORITATIVE(0)"..., 33;;     aa : NON-AUTHORITATIVE(0)
) = 33
write(1, ";;     tc : NOT-TRUNCATED(0)\n", 29;;     tc : NOT-TRUNCATED(0)
) = 29
write(1, ";;     rd : RECURSION-DESIRED(1)"..., 33;;     rd : RECURSION-DESIRED(1)
) = 33
write(1, ";;     ra : RECURSION-ALLOWED(1)"..., 33;;     ra : RECURSION-ALLOWED(1)
) = 33
write(1, ";;  rcode : NOERROR(0)\n", 23;;  rcode : NOERROR(0)
) = 23
write(1, "\n", 1
)                       = 1
write(1, ";; [QUESTION:1]\n", 16;; [QUESTION:1]
)       = 16
write(1, ";www.google.com. IN A\n", 22;www.google.com. IN A
) = 22
write(1, "\n", 1
)                       = 1
write(1, ";; [ANSWER:1]\n", 14;; [ANSWER:1]
)         = 14
write(1, "www.google.com. 159 IN A 216.58."..., 40www.google.com. 159 IN A 216.58.200.100
) = 40
write(1, "\n", 1
)                       = 1
write(1, ";; queries:  2\n", 15;; queries:  2
)        = 15
write(1, ";; udp sent: 2 in 64 bytes\n", 27;; udp sent: 2 in 64 bytes
) = 27
write(1, ";; udp rcvd: 1 in 48 bytes\n", 27;; udp rcvd: 1 in 48 bytes
) = 27
write(1, ";; tcp sent: 0 in 0 bytes\n", 26;; tcp sent: 0 in 0 bytes
) = 26
write(1, ";; tcp rcvd: 0 in 0 bytes\n", 26;; tcp rcvd: 0 in 0 bytes
) = 26
close(5)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++
anr2me commented 1 year ago

This fix matches the description at https://man7.org/linux/man-pages/man2/connect.2.html

Some protocol sockets (e.g., datagram sockets in the UNIX and Internet domains) may use connect() multiple times to change their association.

Some protocol sockets (e.g., TCP sockets as well as datagram sockets in the UNIX and Internet domains) may dissolve the association by connecting to an address with the sa_family member of sockaddr set to AF_UNSPEC; thereafter, the socket can be connected to another address. (AF_UNSPEC is supported since Linux 2.2.)

By connecting using AF_UNSPEC will dissolve the association from previous connect (which probably lingers when there are no service running at remote side), thus allowing to connect to a different address just like a first time.