简体   繁体   中英

ASIO UDP client never receiving messages

I'm trying to write a DNS Resolver with user-supplied resolvers(just a text file with several IP addresses that could be used for querying) using the standalone ASIO/C++ library and I have failed on every attempt to make the receiver work. All the resolvers do not seem to be responding( udp::receive_from ) to the query I'm sending. However, when I try to use the same resolver file with an external library like dnslib , they work like charm, so problem lies in my code. Here's the code I'm using to send data to the DNS servers.

struct DNSPktHeader
{
    uint16_t id{};
    uint16_t bitfields{};
    uint16_t qdcount{};
    uint16_t ancount{};
    uint16_t nscount{};
    uint16_t arcount{};
};

// dnsname, for example is -> google.com
// dns_resolver is a list of udp::endpoint of IPv4 address on port 53.
// ip is the final result
// returns 0 on success and negative value on failure

int get_host_by_name( char const *dnsname, std::vector<udp::endpoint> const & dns_resolvers, OUT uint16_t* ip )
{
    uint8_t netbuf[128]{};
    char const *funcname = "get_host_by_name";

    uint16_t const dns_id = rand() % 2345; // warning!!! Simply for testing purpose

    DNSPktHeader dns_qry{};
    dns_qry.id = dns_id;
    dns_qry.qdcount = 1;
    dns_qry.bitfields = 0x8; // set the RD field of the header to 1

    // custom_htons sets the buffer pointed to by the second argument netbuf
    // to the htons of the first argument
    custom_htons( dns_qry.id, netbuf + 0 );
    custom_htons( dns_qry.bitfields, netbuf + 2 );
    custom_htons( dns_qry.qdcount, netbuf + 4 );
    custom_htons( dns_qry.ancount, netbuf + 6 );
    custom_htons( dns_qry.nscount, netbuf + 8 );
    custom_htons( dns_qry.arcount, netbuf + 10 );

    unsigned char* question_start = netbuf + sizeof( DNSPktHeader ) + 1;
    // creates the DNS question segment into netbuf's specified starting index
    int len = create_question_section( dnsname, (char**) &question_start, thisdns::dns_record_type::DNS_REC_A, 
        thisdns::dns_class::DNS_CLS_IN );
    if( len < 0 ){
        fmt::print( stderr, "{}: {} ({})\n", funcname, dnslib_errno_strings[DNSLIB_ERRNO_BADNAME - 1], dnsname );
        return -EFAULT;
    }
    len += sizeof( DNSPktHeader );
    fmt::print( stdout, "{}: Submitting DNS A-record query for domain name ({})\n", funcname, dnsname );
    asio::error_code resolver_ec{};

    udp::socket udp_socket{ DNSResolver::GetIOService() };
    udp_socket.open( udp::v4() );
    // set 5 seconds timeout on receive and reuse the address
    udp_socket.set_option( asio::ip::udp::socket::reuse_address( true ) );
    udp_socket.set_option( asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO>{ 5'000 } );
    udp_socket.bind( udp::endpoint{ asio::ip::make_address( "127.0.0.1" ), 53 } );
    std::size_t bytes_read = 0, retries = 1;
    int const max_retries = 10;
    asio::error_code receiver_err{};
    uint8_t receive_buf[0x200]{};
    udp::endpoint default_receiver{};

    do{
        udp::endpoint const & resolver_endpoint{ dns_resolvers[retries] };
        int bytes_sent = udp_socket.send_to( asio::buffer( netbuf, len ), resolver_endpoint, 0, resolver_ec );
        if( bytes_sent < len || resolver_ec ){
            fmt::print( stderr, "{}: (found {}, expected {})\n", funcname, i, sizeof( DNSPktHeader ) );
            return -EFAULT;
        }
        // ======== the problem ==============
        bytes_read = udp_socket.receive_from( asio::buffer( receive_buf, sizeof( receive_buf ) ), default_receiver, 0,
            receiver_err );
        // bytes_read always return 0
        if( receiver_err ){
            fmt::print( stderr, "{}\n\n", receiver_err.message() );
        }
    } while( bytes_read == 0 && retries++ < max_retries );

    //...
}

I have tried my best but it clearly isn't enough. Could you please take a look at this and help figure where the problem lies? It's my very first time using ASIO on any real-life project.

Don't know if this would be relevant but here's create_question_section .

int create_question_section( const char *dnsname, char** buf, thisdns::dns_record_type type, thisdns::dns_class class_ )
{
    char const *funcname = "create_question_section";
    if( dnsname[0] == '\0' ){  // Blank DNS name?
        fmt::print( stderr, "{}: Blank DNS name?\n", funcname );
        return -EBADF;
    }

    uint8_t len{};
    int index{};
    int j{};
    bool found = false;
    do{
        if( dnsname[index] != '.' ){
            j = 1;
            found = false;
            do{
                if( dnsname[index + j] == '.' || dnsname[index + j] == '\0' ){
                    len = j;
                    strncpy( *buf, (char*) &len, 1 );
                    ++( *buf );
                    strncpy( *buf, (char*) dnsname + index, j );
                    ( *buf ) += j;
                    found = true;
                    if( dnsname[index + j] != '\0' )
                        index += j + 1;
                    else
                        index += j;
                } else{
                    j++;
                }
            } while( !found && j < 64 );
        } else{
            fmt::print( stderr, "{}: DNS addresses can't start with a dot!\n", funcname );
            return -EBADF;  // DNS addresses can't start with a dot!
        }
    } while( dnsname[index] );
    uint8_t metadata_buf[5]{};

    custom_htons( (uint16_t)type, metadata_buf + 1 );
    custom_htons( (uint16_t)class_, metadata_buf + 3 );
    strncpy( *buf, (char*) metadata_buf, sizeof(metadata_buf) );

    return sizeof( metadata_buf ) + index + 1;
}

There are at least two issues why it's not working for you. They all boil down to the fact that the DNS packet you send out is malformed.

This line

unsigned char* question_start = netbuf + sizeof( DNSPktHeader ) + 1;

sets the pointer into the buffer one position farther than you want. Instead of starting the encoded FQDN at position 12 (as indexed from 0) it starts at position 13. What that means is that the DNS server sees a zero length domain name and some garbage record type and class and ignores the rest. And so it decides not to respond to your query at all. Just get rid of +1 .

Another possible issue could be in encoding all the records with custom_htons() . I have no clue how it's implemented and so cannot tell you whether it works correctly.

Furthermore, although not directly responsible for your observed behaviour, the following call to bind() will have zero effect unless you run the binary as root (or with appropriate capabilities on linux) because you are trying to bind to a privileged port

udp_socket.bind( udp::endpoint{ asio::ip::make_address( "127.0.0.1" ), 53 } );

Also, this dns_qry.bitfields = 0x8; doesn't do what you want. It should be dns_qry.bitfields = 0x80; .

Check this and this RFC out for reference on how to form a valid DNS request.

Important note: I would strongly recommend to you not to mix C++ with C. Pick one but since you tagged C++ and use Boost and libfmt, stick with C++. Replace all your C-style casts with appropriate C++ versions ( static_cast , reinterpret_cast , etc.). Instead of using C-style arrays, use std::array , don't use strncpy , etc.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM