dnsd: add support for DoH

dnsd now opens UDP+TCP sockets and accepts http: DoH requests to obtain
the same, configured answers (records, delays, error codes) as over UDP.

DoH: use `async->queries_ongoing` like all other resolvers instead of
the internal `pending` counter. Fixes waiting for results.

Tests: in pytest, parameterize dnsd tests to use both DNS and DoH.

Closes #22506
This commit is contained in:
Stefan Eissing 2026-08-06 13:46:41 +02:00 committed by Daniel Stenberg
parent 24ff74fa8d
commit 7f6a75664f
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
9 changed files with 1357 additions and 724 deletions

View file

@ -251,8 +251,8 @@ static void doh_probe_done(struct Curl_easy *doh,
return;
}
dohp->pending--;
infof(doh, "a DoH request is completed, %u to go", dohp->pending);
async->queries_ongoing--;
infof(doh, "a DoH request is completed, %u to go", async->queries_ongoing);
dohp->probe_resp[i].result = result;
/* We expect either the meta data still to exist or the sub request
* to have already failed. */
@ -267,7 +267,7 @@ static void doh_probe_done(struct Curl_easy *doh,
if(result)
infof(doh, "DoH request %s", curl_easy_strerror(result));
if(!dohp->pending) {
if(!async->queries_ongoing) {
/* DoH completed, run master to act on results */
Curl_multi_mark_dirty(master);
}
@ -487,7 +487,7 @@ CURLcode Curl_doh(struct Curl_easy *data,
&dohp->probe_resp[DOH_SLOT_IPV4].probe_mid);
if(result)
goto error;
dohp->pending++;
async->queries_ongoing++;
}
#ifdef USE_IPV6
@ -499,7 +499,7 @@ CURLcode Curl_doh(struct Curl_easy *data,
&dohp->probe_resp[DOH_SLOT_IPV6].probe_mid);
if(result)
goto error;
dohp->pending++;
async->queries_ongoing++;
}
#endif
@ -520,7 +520,7 @@ CURLcode Curl_doh(struct Curl_easy *data,
curlx_free(qname);
if(result)
goto error;
dohp->pending++;
async->queries_ongoing++;
}
#endif
return CURLE_OK;
@ -1211,7 +1211,7 @@ CURLcode Curl_doh_take_result(struct Curl_easy *data,
return async->for_proxy ?
CURLE_COULDNT_RESOLVE_PROXY : CURLE_COULDNT_RESOLVE_HOST;
}
else if(!dohp->pending) {
else if(!async->queries_ongoing) {
struct Curl_dns_entry *dns = NULL;
DOHcode rc[DOH_SLOT_COUNT];
bool negative = TRUE;
@ -1235,7 +1235,7 @@ CURLcode Curl_doh_take_result(struct Curl_easy *data,
if(rc[slot] && (rc[slot] != DOH_DNS_NXDOMAIN))
negative = FALSE;
if(rc[slot]) {
CURL_TRC_DNS(data, "[%s] [DoH] error: %s type %s for %s",
CURL_TRC_DNS(data, "[%s] [DoH] error: %s of type %s for %s",
Curl_resolv_query_str(async->dns_queries),
doh_strerror(rc[slot]),
doh_type2name(p->dnstype), async->peer->hostname);
@ -1306,7 +1306,7 @@ CURLcode Curl_doh_take_result(struct Curl_easy *data,
if(dns)
result = Curl_dnscache_add(data, dns);
*pdns = dns;
} /* !dohp->pending */
} /* !async->queries_ongoing */
else
/* wait for pending DoH transactions to complete */
return CURLE_AGAIN;

View file

@ -109,7 +109,6 @@ struct doh_response {
* as easy meta for CURL_EZM_DOH_MASTER */
struct doh_probes {
struct doh_response probe_resp[DOH_SLOT_COUNT];
unsigned int pending; /* still outstanding probes */
};
/*

View file

@ -124,73 +124,84 @@ class TestResolve:
r.check_stats(count=count, http_status=0, exitcode=6)
assert r.duration > timedelta(milliseconds=count * delay_ms), f'{r}'
# dnsd with no answers
@pytest.mark.skipif(condition=not Env.curl_override_dns(), reason="no DNS override")
def test_21_06_dnsd_empty(self, env: Env, httpd, dnsd):
dnsd.set_answers()
def dns_settings(self, dns_method, dnsd):
xargs = []
run_env = os.environ.copy()
run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}'
run_env['CURL_DEBUG'] = 'dns,doh'
if dns_method == 'DoH':
if not Env.curl_can_doh():
pytest.skip(reason="curl built without DoH")
xargs = ['--doh-insecure', '--doh-url', f'http://127.0.0.1:{dnsd.port}/']
else:
if not Env.curl_override_dns():
pytest.skip(reason="no DNS override")
run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}'
run_env['CURL_QUICK_EXIT'] = '1'
return run_env, xargs
# dnsd with no answers
@pytest.mark.parametrize("dns_method", ["DNS", "DoH"])
def test_21_06_dnsd_empty(self, env: Env, httpd, dnsd, dns_method):
dnsd.set_answers()
run_env, xargs = self.dns_settings(dns_method, dnsd)
curl = CurlClient(env=env, run_env=run_env, force_resolv=False)
url = 'https://test-dnsd.http.curl.invalid/'
r = curl.http_download(urls=[url], with_stats=True)
r = curl.http_download(urls=[url], with_stats=True, extra_args=xargs)
r.check_exit_code(6) # could not resolve host
r.check_stats(count=1, http_status=0, exitcode=6)
# dnsd with one answer for A
@pytest.mark.skipif(condition=not Env.curl_override_dns(), reason="no DNS override")
def test_21_07_dnsd_a(self, env: Env, httpd, dnsd):
@pytest.mark.parametrize("dns_method", ["DNS", "DoH"])
def test_21_07_dnsd_a(self, env: Env, httpd, dnsd, dns_method):
dnsd.set_answers(addr_a=['127.0.0.1'])
run_env = os.environ.copy()
run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}'
run_env, xargs = self.dns_settings(dns_method, dnsd)
curl = CurlClient(env=env, run_env=run_env, force_resolv=False)
url = f'https://{env.authority_for(env.domain1, "http/1.1")}/data.json'
r = curl.http_download(urls=[url], with_stats=True)
r = curl.http_download(urls=[url], with_stats=True, extra_args=xargs)
r.check_exit_code(0)
r.check_stats(count=1, http_status=200, exitcode=0)
assert r.stats[0]['remote_ip'] == '127.0.0.1'
# dnsd with one answer for AAAA
@pytest.mark.skipif(condition=not Env.curl_override_dns(), reason="no DNS override")
@pytest.mark.skipif(condition=not Env.curl_has_feature('IPv6'), reason="no IPv6")
def test_21_08_dnsd_aaaa(self, env: Env, httpd, dnsd):
@pytest.mark.parametrize("dns_method", ["DNS", "DoH"])
def test_21_08_dnsd_aaaa(self, env: Env, httpd, dnsd, dns_method):
dnsd.set_answers(addr_aaaa=['[::1]'])
run_env = os.environ.copy()
run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}'
run_env['CURL_QUICK_EXIT'] = '1'
run_env, xargs = self.dns_settings(dns_method, dnsd)
curl = CurlClient(env=env, run_env=run_env, force_resolv=False)
url = f'https://{env.authority_for(env.domain1, "http/1.1")}/data.json'
r = curl.http_download(urls=[url], with_stats=True)
r = curl.http_download(urls=[url], with_stats=True, extra_args=xargs)
r.check_exit_code(0)
r.check_stats(count=1, http_status=200, exitcode=0)
assert r.stats[0]['remote_ip'] == '::1'
# dnsd with one answer for A, delayed one for AAAA
@pytest.mark.skipif(condition=not Env.curl_override_dns(), reason="no DNS override")
def test_21_09_dnsd_a_delay(self, env: Env, httpd, dnsd):
@pytest.mark.parametrize("dns_method", ["DNS", "DoH"])
def test_21_09_dnsd_a_delay(self, env: Env, httpd, dnsd, dns_method):
if dns_method == 'DoH':
pytest.skip(reason='DoH does not handle partial responses')
dnsd.set_answers(addr_a=['127.0.0.1'], addr_aaaa=['[::1]'],
delay_aaaa_ms=env.test_timeout * 1000)
run_env = os.environ.copy()
run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}'
run_env['CURL_QUICK_EXIT'] = '1'
run_env, xargs = self.dns_settings(dns_method, dnsd)
curl = CurlClient(env=env, run_env=run_env, force_resolv=False)
url = f'https://{env.authority_for(env.domain1, "http/1.1")}/data.json'
r = curl.http_download(urls=[url], with_stats=True)
r = curl.http_download(urls=[url], with_stats=True, extra_args=xargs)
r.check_exit_code(0)
r.check_stats(count=1, http_status=200, exitcode=0)
assert r.stats[0]['remote_ip'] == '127.0.0.1'
# dnsd with one answer for AAAA, delayed one for A
@pytest.mark.skipif(condition=not Env.curl_override_dns(), reason="no DNS override")
@pytest.mark.skipif(condition=not Env.curl_has_feature('IPv6'), reason="no IPv6")
def test_21_10_dnsd_aaaa_delay(self, env: Env, httpd, dnsd):
@pytest.mark.parametrize("dns_method", ["DNS", "DoH"])
def test_21_10_dnsd_aaaa_delay(self, env: Env, httpd, dnsd, dns_method):
if dns_method == 'DoH':
pytest.skip(reason='DoH does not handle partial responses')
dnsd.set_answers(addr_a=['127.0.0.1'], addr_aaaa=['[::1]'],
delay_a_ms=env.test_timeout * 1000)
run_env = os.environ.copy()
run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}'
run_env['CURL_QUICK_EXIT'] = '1'
run_env, xargs = self.dns_settings(dns_method, dnsd)
curl = CurlClient(env=env, run_env=run_env, force_resolv=False)
url = f'https://{env.authority_for(env.domain1, "http/1.1")}/data.json'
r = curl.http_download(urls=[url], with_stats=True)
r = curl.http_download(urls=[url], with_stats=True, extra_args=xargs)
r.check_exit_code(0)
r.check_stats(count=1, http_status=200, exitcode=0)
assert r.stats[0]['remote_ip'] == '::1'
@ -234,15 +245,14 @@ class TestResolve:
# dnsd giving NXDOMAIN for all families: the negative answer is
# cached and a second lookup of the same name uses the cache
@pytest.mark.skipif(condition=not Env.curl_override_dns(), reason="no DNS override")
def test_21_13_dnsd_nxdomain_cached(self, env: Env, httpd, dnsd):
@pytest.mark.parametrize("dns_method", ["DNS", "DoH"])
def test_21_13_dnsd_nxdomain_cached(self, env: Env, httpd, dnsd, dns_method):
count = 2
dnsd.set_answers(rcode_a=3, rcode_aaaa=3)
run_env = os.environ.copy()
run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}'
run_env, xargs = self.dns_settings(dns_method, dnsd)
curl = CurlClient(env=env, run_env=run_env, force_resolv=False)
urls = [f'https://test-nx.http.curl.invalid/?id={i}' for i in range(count)]
r = curl.http_download(urls=urls, with_stats=True)
r = curl.http_download(urls=urls, with_stats=True, extra_args=xargs)
r.check_exit_code(6)
r.check_stats(count=count, http_status=0, exitcode=6)
if env.curl_is_verbose():
@ -250,15 +260,14 @@ class TestResolve:
# dnsd failing one family with SERVFAIL: not an authoritative
# negative answer, a second lookup of the same name tries again
@pytest.mark.skipif(condition=not Env.curl_override_dns(), reason="no DNS override")
def test_21_14_dnsd_servfail_uncached(self, env: Env, httpd, dnsd):
@pytest.mark.parametrize("dns_method", ["DNS", "DoH"])
def test_21_14_dnsd_servfail_uncached(self, env: Env, httpd, dnsd, dns_method):
count = 2
dnsd.set_answers(rcode_a=2, rcode_aaaa=3)
run_env = os.environ.copy()
run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}'
run_env, xargs = self.dns_settings(dns_method, dnsd)
curl = CurlClient(env=env, run_env=run_env, force_resolv=False)
urls = [f'https://test-sf.http.curl.invalid/?id={i}' for i in range(count)]
r = curl.http_download(urls=urls, with_stats=True)
r = curl.http_download(urls=urls, with_stats=True, extra_args=xargs)
r.check_exit_code(6)
r.check_stats(count=count, http_status=0, exitcode=6)
if env.curl_is_verbose():

View file

@ -33,7 +33,6 @@ log = logging.getLogger(__name__)
@pytest.mark.skipif(condition=not Env.curl_is_debug(), reason="needs curl debug")
@pytest.mark.skipif(condition=not Env.curl_override_dns(), reason="no DNS override")
@pytest.mark.skipif(condition=not Env.curl_has_feature('HTTPSRR'), reason="no HTTPSRR support")
class TestHTTPSRR:
@ -44,86 +43,86 @@ class TestHTTPSRR:
yield dnsd
dnsd.stop()
# dnsd a HTTPS-RR that prefers HTTP/1.1.
def test_22_01_httpsrr_h1(self, env: Env, httpd, dnsd):
dnsd.set_answers(addr_a=['127.0.0.1'],
https=['10 . alpn=http/1.1'])
def dns_settings(self, dns_method, dnsd):
run_env = os.environ.copy()
run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}'
run_env['CURL_DBG_AWAIT_HTTPSRR'] = '1'
run_env['CURL_QUICK_EXIT'] = '1'
run_env['CURL_DEBUG'] = 'dns,https-connect'
run_env['CURL_DBG_AWAIT_HTTPSRR'] = '1'
xargs = []
if dns_method == 'DoH':
if not Env.curl_can_doh():
pytest.skip(reason="curl built without DoH")
xargs = ['--doh-insecure', '--doh-url', f'http://127.0.0.1:{dnsd.port}/']
else:
if not Env.curl_override_dns():
pytest.skip(reason="no DNS override")
run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}'
return run_env, xargs
# dnsd a HTTPS-RR that prefers HTTP/1.1.
@pytest.mark.parametrize("dns_method", ["DNS", "DoH"])
def test_22_01_httpsrr_h1(self, env: Env, httpd, dnsd, dns_method):
dnsd.set_answers(addr_a=['127.0.0.1'],
https=['10 . alpn=http/1.1'])
run_env, xargs = self.dns_settings(dns_method, dnsd)
curl = CurlClient(env=env, run_env=run_env, force_resolv=False)
url = f'https://{env.authority_for(env.domain1, "http/1.1")}/data.json'
r = curl.http_download(urls=[url], with_stats=True)
r = curl.http_download(urls=[url], with_stats=True, extra_args=xargs)
r.check_exit_code(0)
r.check_stats(count=1, http_status=200, exitcode=0)
assert r.stats[0]['http_version'] == '1.1', f'{r}'
# dnsd a HTTPS-RR that prefers HTTP/2, this overrides the --http3 option.
@pytest.mark.skipif(condition=not Env.have_h3(), reason="missing HTTP/3 support")
def test_22_02_httpsrr_h3(self, env: Env, httpd, dnsd, nghttpx):
@pytest.mark.parametrize("dns_method", ["DNS", "DoH"])
def test_22_02_httpsrr_h3(self, env: Env, httpd, dnsd, nghttpx, dns_method):
dnsd.set_answers(addr_a=['127.0.0.1'],
https=['10 . alpn=h2'])
run_env = os.environ.copy()
run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}'
run_env['CURL_DBG_AWAIT_HTTPSRR'] = '1'
run_env['CURL_QUICK_EXIT'] = '1'
run_env['CURL_DEBUG'] = 'dns,https-connect'
run_env, xargs = self.dns_settings(dns_method, dnsd)
xargs.append('--http3')
curl = CurlClient(env=env, run_env=run_env, force_resolv=False)
url = f'https://{env.authority_for(env.domain1, "http/1.1")}/data.json'
r = curl.http_download(urls=[url], with_stats=True, extra_args=[
'--http3'
])
r = curl.http_download(urls=[url], with_stats=True, extra_args=xargs)
r.check_exit_code(0)
r.check_stats(count=1, http_status=200, exitcode=0)
assert r.stats[0]['http_version'] == '2', f'{r}'
# dnsd a HTTPS-RR that prefers HTTP/3.
@pytest.mark.skipif(condition=not Env.have_h3(), reason="missing HTTP/3 support")
def test_22_03_httpsrr_h3(self, env: Env, httpd, dnsd, nghttpx):
@pytest.mark.parametrize("dns_method", ["DNS", "DoH"])
def test_22_03_httpsrr_h3(self, env: Env, httpd, dnsd, nghttpx, dns_method):
dnsd.set_answers(addr_a=['127.0.0.1'],
https=['10 . alpn=h3,h2'])
run_env = os.environ.copy()
run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}'
run_env['CURL_DBG_AWAIT_HTTPSRR'] = '1'
run_env['CURL_QUICK_EXIT'] = '1'
run_env['CURL_DEBUG'] = 'dns,https-connect'
run_env, xargs = self.dns_settings(dns_method, dnsd)
curl = CurlClient(env=env, run_env=run_env, force_resolv=False)
url = f'https://{env.authority_for(env.domain1, "http/1.1")}/data.json'
r = curl.http_download(urls=[url], with_stats=True)
r = curl.http_download(urls=[url], with_stats=True, extra_args=xargs)
r.check_exit_code(0)
r.check_stats(count=1, http_status=200, exitcode=0)
assert r.stats[0]['http_version'] == '3', f'{r}'
# dnsd a HTTPS-RR that prefers HTTP/1.1 for another target, so ignored.
def test_22_04_httpsrr_wrong_target(self, env: Env, httpd, dnsd):
@pytest.mark.parametrize("dns_method", ["DNS", "DoH"])
def test_22_04_httpsrr_wrong_target(self, env: Env, httpd, dnsd, dns_method):
dnsd.set_answers(addr_a=['127.0.0.1'],
https=['10 another alpn=http/1.1'])
run_env = os.environ.copy()
run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}'
run_env['CURL_DBG_AWAIT_HTTPSRR'] = '1'
run_env['CURL_QUICK_EXIT'] = '1'
run_env['CURL_DEBUG'] = 'dns,https-connect'
run_env, xargs = self.dns_settings(dns_method, dnsd)
curl = CurlClient(env=env, run_env=run_env, force_resolv=False)
url = f'https://{env.authority_for(env.domain1, "http/1.1")}/data.json'
r = curl.http_download(urls=[url], with_stats=True)
r = curl.http_download(urls=[url], with_stats=True, extra_args=xargs)
r.check_exit_code(0)
r.check_stats(count=1, http_status=200, exitcode=0)
assert r.stats[0]['http_version'] == '2', f'{r}'
# dnsd a HTTPS-RR with no-default-alpn, ignored by curl for now
def test_22_05_httpsrr_no_default_alpn(self, env: Env, httpd, dnsd):
@pytest.mark.parametrize("dns_method", ["DNS", "DoH"])
def test_22_05_httpsrr_no_default_alpn(self, env: Env, httpd, dnsd, dns_method):
dnsd.set_answers(addr_a=['127.0.0.1'],
https=['10 . no-default-alpn alpn=http/1.1'])
run_env = os.environ.copy()
run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}'
run_env['CURL_DBG_AWAIT_HTTPSRR'] = '1'
run_env['CURL_QUICK_EXIT'] = '1'
run_env['CURL_DEBUG'] = 'dns,https-connect'
run_env, xargs = self.dns_settings(dns_method, dnsd)
curl = CurlClient(env=env, run_env=run_env, force_resolv=False)
url = f'https://{env.authority_for(env.domain1, "http/1.1")}/data.json'
r = curl.http_download(urls=[url], with_stats=True)
r = curl.http_download(urls=[url], with_stats=True, extra_args=xargs)
r.check_exit_code(0)
r.check_stats(count=1, http_status=200, exitcode=0)
assert r.stats[0]['http_version'] == '2', f'{r}'
@ -132,16 +131,14 @@ class TestHTTPSRR:
@pytest.mark.skipif(condition=not Env.curl_has_feature('HTTPS-proxy'),
reason='curl lacks HTTPS-proxy support')
@pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
def test_22_06_httpsrr_proxy(self, env: Env, httpd, nghttpx_fwd, dnsd):
@pytest.mark.parametrize("dns_method", ["DNS", "DoH"])
def test_22_06_httpsrr_proxy(self, env: Env, httpd, nghttpx_fwd, dnsd, dns_method):
dnsd.set_answers(addr_a=['127.0.0.1'],
https=['10 . alpn=http/1.1'])
run_env = os.environ.copy()
run_env['CURL_DNS_SERVER'] = f'127.0.0.1:{dnsd.port}'
run_env['CURL_DBG_AWAIT_HTTPSRR'] = '1'
run_env['CURL_QUICK_EXIT'] = '1'
run_env, xargs = self.dns_settings(dns_method, dnsd)
curl = CurlClient(env=env, run_env=run_env)
url = f'https://localhost:{env.https_port}/data.json'
xargs = curl.get_proxy_args(tunnel=True)
xargs.extend(curl.get_proxy_args(tunnel=True))
r = curl.http_download(urls=[url], alpn_proto='h2', with_stats=True,
extra_args=xargs)
r.check_response(count=1, http_status=200)

View file

@ -163,6 +163,7 @@ class EnvConfig:
self.curl_can_cert_status = 'cert-status: ON' in p.stdout
self.curl_override_dns = 'override-dns: ON' in p.stdout
self.curl_resolv_threaded = 'resolv-threaded: ON' in p.stdout
self.curl_can_doh = 'DoH: ON' in p.stdout
self.ports = {}
@ -590,6 +591,10 @@ class Env:
def curl_resolv_threaded() -> bool:
return Env.CONFIG.curl_resolv_threaded
@staticmethod
def curl_can_doh() -> bool:
return Env.CONFIG.curl_can_doh
@staticmethod
def curl_can_early_data() -> bool:
if Env.curl_uses_lib('gnutls'):

File diff suppressed because it is too large Load diff

View file

@ -159,6 +159,14 @@ extern curl_socket_t sockdaemon(curl_socket_t sock,
uint16_t *listenport,
const char *unix_socket,
bool bind_only);
extern int open_udp_sock(curl_socket_t *psock, uint16_t *pport);
extern int open_stream_sock(curl_socket_t *psock, uint16_t *pport);
extern curl_socket_t accept_connection(curl_socket_t listen_sock);
extern bool curlx_str_case_equal(const struct Curl_str *s1,
const struct Curl_str *s2);
/* returns true if the current socket is an IP one */
extern bool socket_domain_is_ip(void);
/* global variables */
static const char *srcpath = "."; /* pointing to the test directory */

View file

@ -189,23 +189,6 @@ static char *data_to_hex(const char *data, size_t len)
/* work around for handling trailing headers */
static int already_recv_zeroed_chunk = FALSE;
#if defined(TCP_NODELAY) && defined(CURL_TCP_NODELAY_SUPPORTED)
/* returns true if the current socket is an IP one */
static bool socket_domain_is_ip(void)
{
switch(socket_domain) {
case AF_INET:
#ifdef USE_IPV6
case AF_INET6:
#endif
return TRUE;
default:
/* case AF_UNIX: */
return FALSE;
}
}
#endif
/* parse the file on disk that might have a test number for us */
static int parse_cmdfile(struct sws_httprequest *req)
{
@ -1749,92 +1732,6 @@ static void http_upgrade(struct sws_httprequest *req)
/* left to implement */
}
/* returns a socket handle, or 0 if there are no more waiting sockets,
or < 0 if there was an error */
static curl_socket_t accept_connection(curl_socket_t sock)
{
curl_socket_t msgsock = CURL_SOCKET_BAD;
int sockerr;
char errbuf[STRERROR_LEN];
int flag = 1;
if(MAX_SOCKETS == num_sockets) {
logmsg("Too many open sockets!");
return CURL_SOCKET_BAD;
}
msgsock = accept(sock, NULL, NULL);
if(got_exit_signal) {
if(msgsock != CURL_SOCKET_BAD)
sclose(msgsock);
return CURL_SOCKET_BAD;
}
if(msgsock == CURL_SOCKET_BAD) {
sockerr = SOCKERRNO;
if(SOCK_EAGAIN(sockerr)) {
/* nothing to accept */
return 0;
}
logmsg("MAJOR ERROR, accept() failed with error (%d) %s",
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
return CURL_SOCKET_BAD;
}
if(curlx_nonblock(msgsock, TRUE)) {
sockerr = SOCKERRNO;
logmsg("curlx_nonblock failed with error (%d) %s",
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
sclose(msgsock);
return CURL_SOCKET_BAD;
}
#if defined(_WIN32) && defined(USE_UNIX_SOCKETS)
if(socket_domain != AF_UNIX) {
#endif
if(setsockopt(msgsock, SOL_SOCKET, SO_KEEPALIVE,
(void *)&flag, sizeof(flag))) {
sockerr = SOCKERRNO;
logmsg("setsockopt(SO_KEEPALIVE) failed with error (%d) %s",
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
sclose(msgsock);
return CURL_SOCKET_BAD;
}
#if defined(_WIN32) && defined(USE_UNIX_SOCKETS)
}
#endif
/*
* As soon as this server accepts a connection from the test harness it
* must set the server logs advisor read lock to indicate that server
* logs should not be read until this lock is removed by this server.
*/
if(!serverlogslocked)
set_advisor_read_lock(loglockfile);
serverlogslocked += 1;
logmsg("====> Client connect");
all_sockets[num_sockets] = msgsock;
num_sockets += 1;
#if defined(TCP_NODELAY) && defined(CURL_TCP_NODELAY_SUPPORTED)
if(socket_domain_is_ip()) {
/*
* Disable the Nagle algorithm to make it easier to send out a large
* response in many small segments to torture the clients more.
*/
if(setsockopt(msgsock, IPPROTO_TCP, TCP_NODELAY,
(void *)&flag, sizeof(flag)))
logmsg("====> TCP_NODELAY failed");
}
#endif
return msgsock;
}
/* returns 1 if the connection should be serviced again immediately, 0 if there
is no data waiting, or < 0 if it should be closed */
static int service_connection(curl_socket_t *msgsock,
@ -1914,11 +1811,9 @@ static int service_connection(curl_socket_t *msgsock,
static int test_sws(int argc, const char *argv[])
{
srvr_sockaddr_union_t me;
curl_socket_t sock = CURL_SOCKET_BAD;
int wrotepidfile = 0;
int wroteportfile = 0;
int flag;
#ifdef USE_UNIX_SOCKETS
bool unlink_socket = FALSE;
#endif
@ -1932,6 +1827,7 @@ static int test_sws(int argc, const char *argv[])
const char *location_str = port_str;
int keepalive_secs = 5;
const char *protocol_type = "HTTP";
int result = 0;
/* a default CONNECT port is pointless, but still ... */
size_t socket_idx;
@ -2005,6 +1901,7 @@ static int test_sws(int argc, const char *argv[])
arg++;
if(argc > arg) {
#ifdef USE_UNIX_SOCKETS
srvr_sockaddr_union_t me;
server_unix_socket = argv[arg];
if(strlen(server_unix_socket) >= sizeof(me.sau.sun_path)) {
fprintf(stderr,
@ -2091,115 +1988,13 @@ static int test_sws(int argc, const char *argv[])
if(!req)
goto sws_cleanup;
sock = socket(socket_domain, SOCK_STREAM, 0);
result = open_stream_sock(&sock, &server_port);
if(result)
goto sws_cleanup;
all_sockets[0] = sock;
num_sockets = 1;
if(sock == CURL_SOCKET_BAD) {
sockerr = SOCKERRNO;
logmsg("Error creating socket (%d) %s",
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
goto sws_cleanup;
}
#if defined(_WIN32) && defined(USE_UNIX_SOCKETS)
if(socket_domain != AF_UNIX) {
#endif
flag = 1;
if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
(void *)&flag, sizeof(flag))) {
sockerr = SOCKERRNO;
logmsg("setsockopt(SO_REUSEADDR) failed with error (%d) %s",
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
goto sws_cleanup;
}
#if defined(_WIN32) && defined(USE_UNIX_SOCKETS)
}
#endif
if(curlx_nonblock(sock, TRUE)) {
sockerr = SOCKERRNO;
logmsg("curlx_nonblock failed with error (%d) %s",
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
goto sws_cleanup;
}
switch(socket_domain) {
case AF_INET:
memset(&me.sa4, 0, sizeof(me.sa4));
me.sa4.sin_family = AF_INET;
me.sa4.sin_addr.s_addr = INADDR_ANY;
me.sa4.sin_port = htons(server_port);
rc = bind(sock, &me.sa, sizeof(me.sa4));
break;
#ifdef USE_IPV6
case AF_INET6:
memset(&me.sa6, 0, sizeof(me.sa6));
me.sa6.sin6_family = AF_INET6;
me.sa6.sin6_addr = in6addr_any;
me.sa6.sin6_port = htons(server_port);
rc = bind(sock, &me.sa, sizeof(me.sa6));
break;
#endif /* USE_IPV6 */
#ifdef USE_UNIX_SOCKETS
case AF_UNIX:
rc = bind_unix_socket(sock, server_unix_socket, &me.sau);
#endif /* USE_UNIX_SOCKETS */
}
if(rc) {
sockerr = SOCKERRNO;
#ifdef USE_UNIX_SOCKETS
if(socket_domain == AF_UNIX)
logmsg("Error binding socket on path %s (%d) %s", server_unix_socket,
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
else
#endif
logmsg("Error binding socket on port %hu (%d) %s", server_port,
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
goto sws_cleanup;
}
if(!server_port) {
/* The system was supposed to choose a port number, figure out which
port we actually got and update the listener port value with it. */
curl_socklen_t la_size;
srvr_sockaddr_union_t localaddr;
memset(&localaddr, 0, sizeof(localaddr));
#ifdef USE_IPV6
if(socket_domain == AF_INET6)
la_size = sizeof(localaddr.sa6);
else
#endif
la_size = sizeof(localaddr.sa4);
if(getsockname(sock, &localaddr.sa, &la_size) < 0) {
sockerr = SOCKERRNO;
logmsg("getsockname() failed with error (%d) %s",
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
sclose(sock);
goto sws_cleanup;
}
switch(localaddr.sa.sa_family) {
case AF_INET:
server_port = ntohs(localaddr.sa4.sin_port);
break;
#ifdef USE_IPV6
case AF_INET6:
server_port = ntohs(localaddr.sa6.sin6_port);
break;
#endif
default:
break;
}
if(!server_port) {
/* Real failure, listener port shall not be zero beyond this point. */
logmsg("Apparently getsockname() succeeded, with listener port zero.");
logmsg("A valid reason for this failure is a binary built without");
logmsg("proper network library linkage. This might not be the only");
logmsg("reason, but double check it before anything else.");
sclose(sock);
goto sws_cleanup;
}
}
#ifdef USE_UNIX_SOCKETS
if(socket_domain != AF_UNIX)
#endif
@ -2302,11 +2097,19 @@ static int test_sws(int argc, const char *argv[])
/* Service all queued connections */
curl_socket_t msgsock;
do {
if(MAX_SOCKETS == num_sockets) {
logmsg("Too many open sockets!");
goto sws_cleanup;
}
msgsock = accept_connection(sock);
if(!msgsock)
break;
logmsg("accept_connection %ld returned %ld",
(long)sock, (long)msgsock);
if(msgsock == CURL_SOCKET_BAD)
goto sws_cleanup;
all_sockets[num_sockets] = msgsock;
num_sockets += 1;
if(req->delay)
curlx_wait_ms(req->delay);
} while(msgsock > 0);
@ -2414,5 +2217,5 @@ sws_cleanup:
restore_signal_handlers(FALSE);
return 0;
return result;
}

View file

@ -886,3 +886,342 @@ curl_socket_t sockdaemon(curl_socket_t sock,
return sock;
}
int open_udp_sock(curl_socket_t *psock, uint16_t *pport)
{
srvr_sockaddr_union_t me;
curl_socket_t sock = CURL_SOCKET_BAD;
uint16_t port = *pport;
int sockerr, flag, rc;
char errbuf[STRERROR_LEN];
int result = 0;
sock = socket(socket_domain, SOCK_DGRAM, 0);
if(sock == CURL_SOCKET_BAD) {
sockerr = SOCKERRNO;
logmsg("Error creating socket (%d) %s",
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
result = 1;
goto out;
}
flag = 1;
if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&flag, sizeof(flag))) {
sockerr = SOCKERRNO;
logmsg("setsockopt(SO_REUSEADDR) failed with error (%d) %s",
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
result = 1;
goto out;
}
#ifdef USE_IPV6
if(socket_domain == AF_INET6) {
memset(&me.sa6, 0, sizeof(me.sa6));
me.sa6.sin6_family = AF_INET6;
me.sa6.sin6_addr = in6addr_any;
me.sa6.sin6_port = htons(port);
rc = bind(sock, &me.sa, sizeof(me.sa6));
}
else
#endif
{
memset(&me.sa4, 0, sizeof(me.sa4));
me.sa4.sin_family = AF_INET;
me.sa4.sin_addr.s_addr = INADDR_ANY;
me.sa4.sin_port = htons(port);
rc = bind(sock, &me.sa, sizeof(me.sa4));
}
if(rc) {
sockerr = SOCKERRNO;
logmsg("Error binding socket on port %hu (%d) %s", port,
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
result = 1;
goto out;
}
if(!port) {
/* The system was supposed to choose a port number, figure out which
port we actually got and update the listener port value with it. */
curl_socklen_t la_size;
srvr_sockaddr_union_t localaddr;
memset(&localaddr, 0, sizeof(localaddr));
#ifdef USE_IPV6
if(socket_domain == AF_INET6)
la_size = sizeof(localaddr.sa6);
else
#endif
la_size = sizeof(localaddr.sa4);
if(getsockname(sock, &localaddr.sa, &la_size) < 0) {
sockerr = SOCKERRNO;
logmsg("getsockname() failed with error (%d) %s",
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
result = 1;
goto out;
}
switch(localaddr.sa.sa_family) {
case AF_INET:
port = ntohs(localaddr.sa4.sin_port);
break;
#ifdef USE_IPV6
case AF_INET6:
port = ntohs(localaddr.sa6.sin6_port);
break;
#endif
default:
break;
}
if(!port) {
/* Real failure, listener port shall not be zero beyond this point. */
logmsg("Apparently getsockname() succeeded, with listener port zero.");
logmsg("A valid reason for this failure is a binary built without");
logmsg("proper network library linkage. This might not be the only");
logmsg("reason, but double check it before anything else.");
result = 2;
goto out;
}
}
out:
if(result) {
if(sock != CURL_SOCKET_BAD)
sclose(sock);
sock = CURL_SOCKET_BAD;
port = 0;
}
*psock = sock;
*pport = port;
return result;
}
bool socket_domain_is_ip(void)
{
switch(socket_domain) {
case AF_INET:
#ifdef USE_IPV6
case AF_INET6:
#endif
return TRUE;
default:
/* case AF_UNIX: */
return FALSE;
}
}
int open_stream_sock(curl_socket_t *psock, uint16_t *pport)
{
srvr_sockaddr_union_t me;
char errbuf[STRERROR_LEN];
int flag, sockerr, rc = 0;
curl_socket_t sock = CURL_SOCKET_BAD;
uint16_t port = *pport;
int result = 0;
sock = socket(socket_domain, SOCK_STREAM, 0);
if(sock == CURL_SOCKET_BAD) {
sockerr = SOCKERRNO;
logmsg("Error creating socket (%d) %s",
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
result = 1;
goto out;
}
#if defined(_WIN32) && defined(USE_UNIX_SOCKETS)
if(socket_domain != AF_UNIX) {
#endif
flag = 1;
if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
(void *)&flag, sizeof(flag))) {
sockerr = SOCKERRNO;
logmsg("setsockopt(SO_REUSEADDR) failed with error (%d) %s",
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
result = 1;
goto out;
}
#if defined(_WIN32) && defined(USE_UNIX_SOCKETS)
}
#endif
if(curlx_nonblock(sock, TRUE)) {
sockerr = SOCKERRNO;
logmsg("curlx_nonblock failed with error (%d) %s",
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
result = 1;
goto out;
}
switch(socket_domain) {
case AF_INET:
memset(&me.sa4, 0, sizeof(me.sa4));
me.sa4.sin_family = AF_INET;
me.sa4.sin_addr.s_addr = INADDR_ANY;
me.sa4.sin_port = htons(port);
rc = bind(sock, &me.sa, sizeof(me.sa4));
break;
#ifdef USE_IPV6
case AF_INET6:
memset(&me.sa6, 0, sizeof(me.sa6));
me.sa6.sin6_family = AF_INET6;
me.sa6.sin6_addr = in6addr_any;
me.sa6.sin6_port = htons(port);
rc = bind(sock, &me.sa, sizeof(me.sa6));
break;
#endif /* USE_IPV6 */
#ifdef USE_UNIX_SOCKETS
case AF_UNIX:
rc = bind_unix_socket(sock, server_unix_socket, &me.sau);
#endif /* USE_UNIX_SOCKETS */
}
if(rc) {
sockerr = SOCKERRNO;
#ifdef USE_UNIX_SOCKETS
if(socket_domain == AF_UNIX)
logmsg("Error binding socket on path %s (%d) %s", server_unix_socket,
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
else
#endif
logmsg("Error binding socket on port %hu (%d) %s", port,
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
result = 1;
goto out;
}
if(!port) {
/* The system was supposed to choose a port number, figure out which
port we actually got and update the listener port value with it. */
curl_socklen_t la_size;
srvr_sockaddr_union_t localaddr;
memset(&localaddr, 0, sizeof(localaddr));
#ifdef USE_IPV6
if(socket_domain != AF_INET6)
#endif
la_size = sizeof(localaddr.sa4);
#ifdef USE_IPV6
else
la_size = sizeof(localaddr.sa6);
#endif
if(getsockname(sock, &localaddr.sa, &la_size) < 0) {
sockerr = SOCKERRNO;
logmsg("getsockname() failed with error (%d) %s",
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
result = 1;
goto out;
}
switch(localaddr.sa.sa_family) {
case AF_INET:
port = ntohs(localaddr.sa4.sin_port);
break;
#ifdef USE_IPV6
case AF_INET6:
port = ntohs(localaddr.sa6.sin6_port);
break;
#endif
default:
break;
}
if(!port) {
/* Real failure, listener port shall not be zero beyond this point. */
logmsg("Apparently getsockname() succeeded, with listener port zero.");
logmsg("A valid reason for this failure is a binary built without");
logmsg("proper network library linkage. This might not be the only");
logmsg("reason, but double check it before anything else.");
result = 1;
goto out;
}
}
out:
if(result) {
if(sock != CURL_SOCKET_BAD)
sclose(sock);
sock = CURL_SOCKET_BAD;
port = 0;
}
*psock = sock;
*pport = port;
return result;
}
/* returns a socket handle, or 0 if there are no more waiting sockets,
or < 0 if there was an error */
curl_socket_t accept_connection(curl_socket_t listen_sock)
{
curl_socket_t msgsock = CURL_SOCKET_BAD;
int sockerr;
char errbuf[STRERROR_LEN];
int flag = 1;
msgsock = accept(listen_sock, NULL, NULL);
if(got_exit_signal) {
if(msgsock != CURL_SOCKET_BAD)
sclose(msgsock);
return CURL_SOCKET_BAD;
}
if(msgsock == CURL_SOCKET_BAD) {
sockerr = SOCKERRNO;
if(SOCK_EAGAIN(sockerr)) {
/* nothing to accept */
return 0;
}
logmsg("MAJOR ERROR, accept() failed with error (%d) %s",
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
return CURL_SOCKET_BAD;
}
if(curlx_nonblock(msgsock, TRUE)) {
sockerr = SOCKERRNO;
logmsg("curlx_nonblock failed with error (%d) %s",
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
sclose(msgsock);
return CURL_SOCKET_BAD;
}
#if defined(_WIN32) && defined(USE_UNIX_SOCKETS)
if(socket_domain != AF_UNIX) {
#endif
if(setsockopt(msgsock, SOL_SOCKET, SO_KEEPALIVE,
(void *)&flag, sizeof(flag))) {
sockerr = SOCKERRNO;
logmsg("setsockopt(SO_KEEPALIVE) failed with error (%d) %s",
sockerr, curlx_strerror(sockerr, errbuf, sizeof(errbuf)));
sclose(msgsock);
return CURL_SOCKET_BAD;
}
#if defined(_WIN32) && defined(USE_UNIX_SOCKETS)
}
#endif
/*
* As soon as this server accepts a connection from the test harness it
* must set the server logs advisor read lock to indicate that server
* logs should not be read until this lock is removed by this server.
*/
if(!serverlogslocked)
set_advisor_read_lock(loglockfile);
serverlogslocked += 1;
logmsg("====> Client connect");
#if defined(TCP_NODELAY) && defined(CURL_TCP_NODELAY_SUPPORTED)
if(socket_domain_is_ip()) {
/*
* Disable the Nagle algorithm to make it easier to send out a large
* response in many small segments to torture the clients more.
*/
if(setsockopt(msgsock, IPPROTO_TCP, TCP_NODELAY,
(void *)&flag, sizeof(flag)))
logmsg("====> TCP_NODELAY failed");
}
#endif
return msgsock;
}
bool curlx_str_case_equal(const struct Curl_str *s1,
const struct Curl_str *s2)
{
return ((s1->len == s2->len) &&
!CURL_STRNICMP(s1->str, s2->str, s1->len));
}