v. vanilla 2.0

restored/socket_dns.inc

← All source files · Download this file

10,098 bytes · SHA256 9c74412fb85c7c389278ec085d302a1af88172218c77271ef64ba57be19caefe

// 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<sockaddr_in*>(&out.address);
    auto* v6=reinterpret_cast<sockaddr_in6*>(&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<uint16_t>(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<uint16_t>(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.size();++i) {
        unsigned char ch=static_cast<unsigned char>(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<SocketAddress,16> addresses{};
    std::array<pollfd,8> 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<SocketDNS*>(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<socklen_t>(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<SocketDNS*>(arg);
        for(auto& p:self.descriptors) if(p.fd==fd) {
            p={read || write ? fd : -1,static_cast<short>((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<short>((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<SocketDNS*>(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<SocketDNS*>(arg);
        if(++self.sends>16) { self.overflow=true; errno=EIO; return -1; }
        msghdr message{}; message.msg_iov=const_cast<iovec*>(data); message.msg_iovlen=static_cast<std::size_t>(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<SocketDNS*>(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<void()>& 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<SocketAddress> socketResolve(const std::string& host,int port,const Options& options,SocketClock::time_point deadline,const Source& s,const std::function<void()>& 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<std::chrono::microseconds>(deadline-SocketClock::now()).count();
        us=std::max<std::int64_t>(1,us);
        timeval maximum{static_cast<time_t>(us/1000000),static_cast<suseconds_t>(us%1000000)},tv{};
        auto* wait=ares_timeout(query.channel,&maximum,&tv);
        int ms=static_cast<int>(std::max<std::int64_t>(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<std::ptrdiff_t>(query.count)};
}