// c-ares 1.27 event-loop integration. No callback throws or allocates C++ storage. struct SocketAddress { sockaddr_storage address{}; socklen_t length=0; }; static SocketAddress socketNumeric(const std::string& host,int port) { SocketAddress out; auto* v4=reinterpret_cast(&out.address); auto* v6=reinterpret_cast(&out.address); if(inet_pton(AF_INET,host.c_str(),&v4->sin_addr)==1) { v4->sin_family=AF_INET; v4->sin_port=htons(static_cast(port)); out.length=sizeof(*v4); } else if(inet_pton(AF_INET6,host.c_str(),&v6->sin6_addr)==1) { v6->sin6_family=AF_INET6; v6->sin6_port=htons(static_cast(port)); out.length=sizeof(*v6); } return out; } static void socketHost(const std::string& host,bool listener,const Source& s) { if(host.find('\0')!=std::string::npos) fail(s,"socket host contains NUL"); if(socketNumeric(host,1).length) return; auto invalid=[&]() { fail(s,"socket host must be numeric IPv4/IPv6, localhost or valid DNS hostname"); }; if(listener || host.empty() || host.size()>253) invalid(); bool letter=false; std::size_t label=0; for(std::size_t i=0;i(host[i]); bool alpha=(ch>='A' && ch<='Z') || (ch>='a' && ch<='z'); letter|=alpha; if(ch=='.') { if(!label || host[i-1]=='-') invalid(); label=0; } else { if(!(alpha || (ch>='0' && ch<='9') || ch=='-') || (!label && ch=='-') || ++label>63) invalid(); } } if(!letter || !label || host.back()=='-') invalid(); } void validateNetworkOptions(const Options& options,const Source& s) { if((!options.dnsServer.empty() || !options.tlsCAFile.empty()) && !options.allowNetwork) fail(s,"DNS/TLS configuration requires --allow-network"); if(options.tlsCAFile.size()>4096) fail(s,"TLS CA path too long"); if(options.dnsServer.empty()) return; const auto& server=options.dnsServer; auto colon=server.rfind(':'); if(colon==std::string::npos || colon+1==server.size() || server.size()>80) fail(s,"invalid --dns-server; use numeric-IP:port or [IPv6]:port"); auto host=server.substr(0,colon),port=server.substr(colon+1); if(host.size()>2 && host.front()=='[' && host.back()==']') host=host.substr(1,host.size()-2); else if(host.find(':')!=std::string::npos) fail(s,"--dns-server IPv6 requires brackets"); if(!socketNumeric(host,1).length || !std::all_of(port.begin(),port.end(),[](char c){ return c>='0' && c<='9'; })) fail(s,"invalid --dns-server numeric address/port"); integer(port,1,65535,s); } struct SocketDNS { ares_channel channel=nullptr; std::array addresses{}; std::array descriptors{}; std::size_t count=0; int status=ARES_ECANCELLED,osError=0; unsigned sends=0; bool done=false,overflow=false; SocketDNS() { for(auto& p:descriptors) p.fd=-1; } ~SocketDNS() { if(channel) { ares_cancel(channel); ares_destroy(channel); } } static void result(void* arg,int status,int,ares_addrinfo* result) noexcept { auto& self=*static_cast(arg); self.status=status; self.done=true; if(result) { for(auto* p=result->nodes;p;p=p->ai_next) { if(self.count==self.addresses.size()) { self.overflow=true; break; } if(!p->ai_addr || (p->ai_family!=AF_INET && p->ai_family!=AF_INET6)) continue; auto length=p->ai_family==AF_INET ? sizeof(sockaddr_in) : sizeof(sockaddr_in6); if(p->ai_addrlen!=length) continue; auto& dst=self.addresses[self.count++]; dst.length=static_cast(length); std::memcpy(&dst.address,p->ai_addr,length); } ares_freeaddrinfo(result); } } static void state(void* arg,ares_socket_t fd,int read,int write) noexcept { auto& self=*static_cast(arg); for(auto& p:self.descriptors) if(p.fd==fd) { p={read || write ? fd : -1,static_cast((read ? POLLIN : 0)|(write ? POLLOUT : 0)),0}; return; } if(!read && !write) return; for(auto& p:self.descriptors) if(p.fd<0) { p={fd,static_cast((read ? POLLIN : 0)|(write ? POLLOUT : 0)),0}; return; } self.overflow=true; } static void os(void* arg) noexcept { if(errno==ENOMEM || errno==ENOBUFS || errno==EMFILE || errno==ENFILE) static_cast(arg)->osError=errno; } static ares_socket_t open(int family,int type,int protocol,void* arg) noexcept { int fd=::socket(family,type|SOCK_NONBLOCK|SOCK_CLOEXEC,protocol); if(fd<0) os(arg); return fd; } static int close(ares_socket_t fd,void*) noexcept { return ::close(fd); } static int connect(ares_socket_t fd,const sockaddr* address,ares_socklen_t length,void* arg) noexcept { int rc=::connect(fd,address,length); if(rc<0) os(arg); return rc; } static ares_ssize_t receive(ares_socket_t fd,void* data,std::size_t size,int flags,sockaddr* address,ares_socklen_t* length,void* arg) noexcept { auto n=::recvfrom(fd,data,size,flags,address,length); if(n<0) os(arg); return n; } static ares_ssize_t send(ares_socket_t fd,const iovec* data,int count,void* arg) noexcept { auto& self=*static_cast(arg); if(++self.sends>16) { self.overflow=true; errno=EIO; return -1; } msghdr message{}; message.msg_iov=const_cast(data); message.msg_iovlen=static_cast(count); auto n=::sendmsg(fd,&message,MSG_NOSIGNAL); if(n<0) os(arg); return n; } static int configure(ares_socket_t fd,int,void* arg) noexcept { auto& self=*static_cast(arg); int fl=fcntl(fd,F_GETFL),flags=fcntl(fd,F_GETFD); if(fl<0 || flags<0 || fcntl(fd,F_SETFL,fl|O_NONBLOCK)<0 || fcntl(fd,F_SETFD,flags|FD_CLOEXEC)<0) { self.osError=errno; return -1; } return 0; } }; static void socketDNSError(int status,const Source& s,const std::function& release) { if(status==ARES_ENOMEM) { release(); failLimit(s,"socket DNS memory resource limit"); } if(status!=ARES_SUCCESS) fail(s,"socket DNS resolution failed"); } static std::vector socketResolve(const std::string& host,int port,const Options& options,SocketClock::time_point deadline,const Source& s,const std::function& release) { auto resource=[&](const char* message) { release(); failLimit(s,message); }; auto numeric=socketNumeric(host=="localhost" ? "127.0.0.1" : host,port); if(numeric.length) return {numeric}; struct Library { int status=ares_library_init(ARES_LIB_INIT_ALL); ~Library() { if(status==ARES_SUCCESS) ares_library_cleanup(); } }; static Library library; socketDNSError(library.status,s,release); SocketDNS query; ares_options config{}; char lookups[]="b"; config.flags=ARES_FLAG_NOSEARCH|ARES_FLAG_NOALIASES|ARES_FLAG_PRIMARY; config.tries=1; config.timeout=30000; config.lookups=lookups; config.sock_state_cb=SocketDNS::state; config.sock_state_cb_data=&query; socketDNSError(ares_init_options(&query.channel,&config,ARES_OPT_FLAGS|ARES_OPT_TRIES|ARES_OPT_TIMEOUTMS|ARES_OPT_LOOKUPS|ARES_OPT_SOCK_STATE_CB),s,release); static const ares_socket_functions io{SocketDNS::open,SocketDNS::close,SocketDNS::connect,SocketDNS::receive,SocketDNS::send}; ares_set_socket_functions(query.channel,&io,&query); ares_set_socket_configure_callback(query.channel,SocketDNS::configure,&query); if(!options.dnsServer.empty()) socketDNSError(ares_set_servers_ports_csv(query.channel,options.dnsServer.c_str()),s,release); ares_addrinfo_hints hints{}; hints.ai_flags=ARES_AI_NOSORT|ARES_AI_NUMERICSERV; hints.ai_family=AF_UNSPEC; hints.ai_socktype=SOCK_STREAM; hints.ai_protocol=IPPROTO_TCP; auto service=std::to_string(port); socketDeadline(deadline,s); ares_getaddrinfo(query.channel,host.c_str(),service.c_str(),&hints,SocketDNS::result,&query); for(unsigned turn=0;!query.done;++turn) { socketDeadline(deadline,s); if(query.osError) resource("socket DNS OS resource limit"); if(turn>=1024 || query.overflow) resource("socket DNS work/storage limit"); auto us=std::chrono::duration_cast(deadline-SocketClock::now()).count(); us=std::max(1,us); timeval maximum{static_cast(us/1000000),static_cast(us%1000000)},tv{}; auto* wait=ares_timeout(query.channel,&maximum,&tv); int ms=static_cast(std::max(1,wait->tv_sec*1000+(wait->tv_usec+999)/1000)); auto ready=query.descriptors; int rc=::poll(ready.data(),ready.size(),ms); if(rc<0 && errno==EINTR) continue; if(rc<0) { if(errno==ENOMEM || errno==ENOBUFS) resource("socket DNS poll resource limit"); fail(s,"socket DNS poll failed"); } socketDeadline(deadline,s); if(!rc) ares_process_fd(query.channel,ARES_SOCKET_BAD,ARES_SOCKET_BAD); else for(const auto& p:ready) { if(query.done) break; // A previous process_fd may have closed another fd in this snapshot. bool active=false; for(const auto& current:query.descriptors) if(current.fd==p.fd && p.fd>=0) active=true; if(!active || !p.revents) continue; ares_process_fd(query.channel,(p.revents&(POLLIN|POLLERR|POLLHUP)) ? p.fd : ARES_SOCKET_BAD, (p.revents&(POLLOUT|POLLERR|POLLHUP)) ? p.fd : ARES_SOCKET_BAD); } } if(query.overflow) resource("socket DNS candidate/storage limit (16 candidates)"); if(query.osError) resource("socket DNS OS resource/configuration limit"); socketDNSError(query.status,s,release); socketDeadline(deadline,s); if(!query.count) fail(s,"socket DNS returned no addresses"); return {query.addresses.begin(),query.addresses.begin()+static_cast(query.count)}; }