A C library for asynchronous DNS requests (grpc依赖)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

288 lines
9.8 KiB

/* MIT License
*
* Copyright (c) The c-ares project and its contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* SPDX-License-Identifier: MIT
*/
#include "ares-test.h"
#include "dns-proto.h"
#include <sstream>
#include <vector>
namespace ares {
namespace test {
TEST_F(LibraryTest, ParseTxtReplyOK) {
DNSPacket pkt;
std::string expected1 = "txt1.example.com";
std::string expected2a = "txt2a";
std::string expected2b("ABC\0ABC", 7);
pkt.set_qid(0x1234).set_response().set_aa()
.add_question(new DNSQuestion("example.com", T_MX))
.add_answer(new DNSTxtRR("example.com", 100, {expected1}))
.add_answer(new DNSTxtRR("example.com", 100, {expected2a, expected2b}));
std::vector<byte> data = pkt.data();
struct ares_txt_reply* txt = nullptr;
EXPECT_EQ(ARES_SUCCESS, ares_parse_txt_reply(data.data(), (int)data.size(), &txt));
ASSERT_NE(nullptr, txt);
EXPECT_EQ(std::vector<byte>(expected1.data(), expected1.data() + expected1.size()),
std::vector<byte>(txt->txt, txt->txt + txt->length));
struct ares_txt_reply* txt2 = txt->next;
ASSERT_NE(nullptr, txt2);
Modernization: replace multiple hand-parsers with new memory-safe parser (#581) New DNS record parsing code. The old code was basically just some helper macros and functions for parsing an entire DNS message. The caller had to know the RFCs to use the parsers, except for some pre-made exceptions. The new parsing code parses the entire DNS message into an opaque data structure in a memory safe manner with various accessors for reading and manipulating the data. The existing parser helpers for the various record types were reimplemented as wrappers around the new parser. The accessors allow easy iteration across the DNS record datastructure, and can be used to easily create dig-like output without needing to know anything about the various record types and formats as dynamic helpers are provided for enumeration of values and data types of those values. At some point in the future, this new DNS record structure, accessors, and parser will be exposed publicly. This is not done at this point as we don't want to do that until the API is completely stable. Likely a write() function to output the DNS record back into an actual message buffer will be introduced with the stable API as well. Some subtle bugs in the existing code were uncovered, some which had test cases which turned out to be bogus. Validation with third-party implementations (e.g. BIND9) were performed to validate such cases were indeed bugs. Adding additional RR parsers such as for TLSA (#470) or SVCB/HTTPS (#566) are trivial now since focus can be put on only parsing the data within the RR, not the entire message. That said, as the new parser is not yet public, it isn't clear the best way to expose any new RRs (probably best to wait for the new parser to be public rather than hacking in another legacy function). Some additional RRs that are part of DNS RFC1035 or EDNS RFC6891 that didn't have previously implemented parsers are now also implemented (e.g. HINFO, OPT). Any unrecognized RRs are encapsulated into a "RAW_RR" as binary data which can be inserted or extracted, but are otherwise not interpreted in any way. Fix By: Brad House (@bradh352)
1 year ago
std::vector<byte> rsp = std::vector<byte>(expected2a.data(), expected2a.data() + expected2a.size());
rsp.insert(rsp.end(), expected2b.data(), expected2b.data() + expected2b.size());
EXPECT_EQ(rsp,
std::vector<byte>(txt2->txt, txt2->txt + txt2->length));
ares_free_data(txt);
}
TEST_F(LibraryTest, ParseTxtExtReplyOK) {
DNSPacket pkt;
std::string expected1 = "txt1.example.com";
std::string expected2a = "txt2a";
std::string expected2b("ABC\0ABC", 7);
pkt.set_qid(0x1234).set_response().set_aa()
.add_question(new DNSQuestion("example.com", T_MX))
.add_answer(new DNSTxtRR("example.com", 100, {expected1}))
.add_answer(new DNSTxtRR("example.com", 100, {expected2a, expected2b}));
std::vector<byte> data = pkt.data();
struct ares_txt_ext* txt = nullptr;
EXPECT_EQ(ARES_SUCCESS, ares_parse_txt_reply_ext(data.data(), (int)data.size(), &txt));
ASSERT_NE(nullptr, txt);
EXPECT_EQ(std::vector<byte>(expected1.data(), expected1.data() + expected1.size()),
std::vector<byte>(txt->txt, txt->txt + txt->length));
EXPECT_EQ(1, txt->record_start);
struct ares_txt_ext* txt2 = txt->next;
ASSERT_NE(nullptr, txt2);
Modernization: replace multiple hand-parsers with new memory-safe parser (#581) New DNS record parsing code. The old code was basically just some helper macros and functions for parsing an entire DNS message. The caller had to know the RFCs to use the parsers, except for some pre-made exceptions. The new parsing code parses the entire DNS message into an opaque data structure in a memory safe manner with various accessors for reading and manipulating the data. The existing parser helpers for the various record types were reimplemented as wrappers around the new parser. The accessors allow easy iteration across the DNS record datastructure, and can be used to easily create dig-like output without needing to know anything about the various record types and formats as dynamic helpers are provided for enumeration of values and data types of those values. At some point in the future, this new DNS record structure, accessors, and parser will be exposed publicly. This is not done at this point as we don't want to do that until the API is completely stable. Likely a write() function to output the DNS record back into an actual message buffer will be introduced with the stable API as well. Some subtle bugs in the existing code were uncovered, some which had test cases which turned out to be bogus. Validation with third-party implementations (e.g. BIND9) were performed to validate such cases were indeed bugs. Adding additional RR parsers such as for TLSA (#470) or SVCB/HTTPS (#566) are trivial now since focus can be put on only parsing the data within the RR, not the entire message. That said, as the new parser is not yet public, it isn't clear the best way to expose any new RRs (probably best to wait for the new parser to be public rather than hacking in another legacy function). Some additional RRs that are part of DNS RFC1035 or EDNS RFC6891 that didn't have previously implemented parsers are now also implemented (e.g. HINFO, OPT). Any unrecognized RRs are encapsulated into a "RAW_RR" as binary data which can be inserted or extracted, but are otherwise not interpreted in any way. Fix By: Brad House (@bradh352)
1 year ago
std::vector<byte> rsp = std::vector<byte>(expected2a.data(), expected2a.data() + expected2a.size());
rsp.insert(rsp.end(), expected2b.data(), expected2b.data() + expected2b.size());
EXPECT_EQ(rsp,
std::vector<byte>(txt2->txt, txt2->txt + txt2->length));
EXPECT_EQ(1, txt2->record_start);
ares_free_data(txt);
}
TEST_F(LibraryTest, ParseTxtMalformedReply1) {
std::vector<byte> data = {
0x12, 0x34, // qid
0x84, // response + query + AA + not-TC + not-RD
0x00, // not-RA + not-Z + not-AD + not-CD + rc=NoError
0x00, 0x01, // num questions
0x00, 0x01, // num answer RRs
0x00, 0x00, // num authority RRs
0x00, 0x00, // num additional RRs
// Question
0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
0x03, 'c', 'o', 'm',
0x00,
0x00, 0x10, // type TXT
0x00, 0x01, // class IN
// Answer 1
0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
0x03, 'c', 'o', 'm',
0x00,
0x00, 0x10, // RR type
0x00, 0x01, // class IN
0x01, 0x02, 0x03, 0x04, // TTL
0x00, 0x03, // rdata length
0x12, 'a', 'b', // invalid length
};
struct ares_txt_reply* txt = nullptr;
EXPECT_EQ(ARES_EBADRESP, ares_parse_txt_reply(data.data(), (int)data.size(), &txt));
ASSERT_EQ(nullptr, txt);
}
TEST_F(LibraryTest, ParseTxtMalformedReply2) {
std::vector<byte> data = {
0x12, 0x34, // qid
0x84, // response + query + AA + not-TC + not-RD
0x00, // not-RA + not-Z + not-AD + not-CD + rc=NoError
0x00, 0x01, // num questions
0x00, 0x01, // num answer RRs
0x00, 0x00, // num authority RRs
0x00, 0x00, // num additional RRs
// Question
0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
0x03, 'c', 'o', 'm',
0x00,
0x00, 0x10, // type TXT
0x00, 0x01, // class IN
// Answer 1
0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
0x03, 'c', 'o', 'm',
0x00,
0x00, 0x10, // RR type
// truncated
};
struct ares_txt_reply* txt = nullptr;
EXPECT_EQ(ARES_EBADRESP, ares_parse_txt_reply(data.data(), (int)data.size(), &txt));
ASSERT_EQ(nullptr, txt);
}
TEST_F(LibraryTest, ParseTxtMalformedReply3) {
std::vector<byte> data = {
0x12, 0x34, // qid
0x84, // response + query + AA + not-TC + not-RD
0x00, // not-RA + not-Z + not-AD + not-CD + rc=NoError
0x00, 0x01, // num questions
0x00, 0x01, // num answer RRs
0x00, 0x00, // num authority RRs
0x00, 0x00, // num additional RRs
// Question
0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
0x03, 'c', 'o', 'm',
0x00,
0x00, 0x10, // type TXT
0x00, 0x01, // class IN
// Answer 1
0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
0x03, 'c', 'o', 'm',
0x00,
0x00, 0x10, // RR type
0x00, 0x01, // class IN
0x01, 0x02, 0x03, 0x04, // TTL
0x00, 0x13, // rdata length INVALID
0x02, 'a', 'b',
};
struct ares_txt_reply* txt = nullptr;
EXPECT_EQ(ARES_EBADRESP, ares_parse_txt_reply(data.data(), (int)data.size(), &txt));
ASSERT_EQ(nullptr, txt);
}
TEST_F(LibraryTest, ParseTxtMalformedReply4) {
std::vector<byte> data = {
0x12, 0x34, // qid
0x84, // response + query + AA + not-TC + not-RD
0x00, // not-RA + not-Z + not-AD + not-CD + rc=NoError
0x00, 0x01, // num questions
0x00, 0x01, // num answer RRs
0x00, 0x00, // num authority RRs
0x00, 0x00, // num additional RRs
// Question
0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e',
0x03, 'c', 'o', 'm',
0x00,
0x00, 0x10, // type TXT
0x00, // TRUNCATED
};
struct ares_txt_reply* txt = nullptr;
EXPECT_EQ(ARES_EBADRESP, ares_parse_txt_reply(data.data(), (int)data.size(), &txt));
ASSERT_EQ(nullptr, txt);
}
TEST_F(LibraryTest, ParseTxtReplyErrors) {
DNSPacket pkt;
std::string expected1 = "txt1.example.com";
std::string expected2a = "txt2a";
std::string expected2b = "txt2b";
pkt.set_qid(0x1234).set_response().set_aa()
.add_question(new DNSQuestion("example.com", T_MX))
.add_answer(new DNSTxtRR("example.com", 100, {expected1}))
.add_answer(new DNSTxtRR("example.com", 100, {expected1}))
.add_answer(new DNSTxtRR("example.com", 100, {expected2a, expected2b}));
std::vector<byte> data = pkt.data();
struct ares_txt_reply* txt = nullptr;
Coverage code annotations for identification of desirable paths that need testing (#775) Add code annotations for ignoring specific code paths for coverage calculations. The primary purpose of this is to make it easy to see the code paths that we could (and probably should) write test cases for, as these would have the most impact on delivery of a stable product. The annotations used are: `LCOV_EXCL_LINE: <designation>`, `LCOV_EXCL_START: <designation>`, `LCOV_EXCL_STOP` Unfortunately `LCOV_EXCL_BR_LINE` does not appear to be supported by coveralls as it would have been a more elegant solution over START/STOP. We specifically include the `<designation>` not just for future reference but because it makes it easy to identify in case we want to address these conditions in a different way in the future. The main areas designated for exclusion are: 1. `OutOfMemory` - these are hard to test cases, and on modern systems, are likely to never occur due to optimistic memory allocations, which can then later cause the kernel to terminate your application due to memory not actually being available. c-ares does have *some* testing framework for this, if we wish to expand in the future, we can easily use sed to get rid of of these annotations. 2. `DefensiveCoding` - these are impossible to reach paths at the point in time the code was written. They are there for defensive coding in case code is refactored in the future to prevent unexpected behavior. 3. `UntestablePath` - these are code paths that aren't possible to test, such as failure of a system call. 4. `FallbackCode` - This is an entire set of code that is untestable because its not able to simulate a failure of the primary path. This PR also does add some actual coverage in the test cases where it is easy to do. Fix By: Brad House (@bradh352)
6 months ago
struct ares_txt_ext* txt_ext = nullptr;
// No question.
pkt.questions_.clear();
data = pkt.data();
txt = nullptr;
EXPECT_EQ(ARES_EBADRESP, ares_parse_txt_reply(data.data(), (int)data.size(), &txt));
EXPECT_EQ(nullptr, txt);
pkt.add_question(new DNSQuestion("example.com", T_MX));
#ifdef DISABLED
// Question != answer
pkt.questions_.clear();
pkt.add_question(new DNSQuestion("Axample.com", T_TXT));
data = pkt.data();
EXPECT_EQ(ARES_ENODATA, ares_parse_txt_reply(data.data(), (int)data.size(), &txt));
pkt.questions_.clear();
pkt.add_question(new DNSQuestion("example.com", T_TXT));
#endif
// Two questions.
pkt.add_question(new DNSQuestion("example.com", T_MX));
data = pkt.data();
txt = nullptr;
EXPECT_EQ(ARES_EBADRESP, ares_parse_txt_reply(data.data(), (int)data.size(), &txt));
EXPECT_EQ(nullptr, txt);
pkt.questions_.clear();
pkt.add_question(new DNSQuestion("example.com", T_MX));
// No answer.
pkt.answers_.clear();
data = pkt.data();
txt = nullptr;
EXPECT_EQ(ARES_ENODATA, ares_parse_txt_reply(data.data(), (int)data.size(), &txt));
EXPECT_EQ(nullptr, txt);
pkt.add_answer(new DNSTxtRR("example.com", 100, {expected1}));
// Truncated packets.
for (size_t len = 1; len < data.size(); len++) {
txt = nullptr;
EXPECT_NE(ARES_SUCCESS, ares_parse_txt_reply(data.data(), (int)len, &txt));
EXPECT_EQ(nullptr, txt);
}
Coverage code annotations for identification of desirable paths that need testing (#775) Add code annotations for ignoring specific code paths for coverage calculations. The primary purpose of this is to make it easy to see the code paths that we could (and probably should) write test cases for, as these would have the most impact on delivery of a stable product. The annotations used are: `LCOV_EXCL_LINE: <designation>`, `LCOV_EXCL_START: <designation>`, `LCOV_EXCL_STOP` Unfortunately `LCOV_EXCL_BR_LINE` does not appear to be supported by coveralls as it would have been a more elegant solution over START/STOP. We specifically include the `<designation>` not just for future reference but because it makes it easy to identify in case we want to address these conditions in a different way in the future. The main areas designated for exclusion are: 1. `OutOfMemory` - these are hard to test cases, and on modern systems, are likely to never occur due to optimistic memory allocations, which can then later cause the kernel to terminate your application due to memory not actually being available. c-ares does have *some* testing framework for this, if we wish to expand in the future, we can easily use sed to get rid of of these annotations. 2. `DefensiveCoding` - these are impossible to reach paths at the point in time the code was written. They are there for defensive coding in case code is refactored in the future to prevent unexpected behavior. 3. `UntestablePath` - these are code paths that aren't possible to test, such as failure of a system call. 4. `FallbackCode` - This is an entire set of code that is untestable because its not able to simulate a failure of the primary path. This PR also does add some actual coverage in the test cases where it is easy to do. Fix By: Brad House (@bradh352)
6 months ago
// Negative Length
EXPECT_EQ(ARES_EBADRESP, ares_parse_txt_reply(data.data(), -1, &txt));
EXPECT_EQ(ARES_EBADRESP, ares_parse_txt_reply_ext(data.data(), -1, &txt_ext));
}
TEST_F(LibraryTest, ParseTxtReplyAllocFail) {
DNSPacket pkt;
std::string expected1 = "txt1.example.com";
std::string expected2a = "txt2a";
std::string expected2b = "txt2b";
pkt.set_qid(0x1234).set_response().set_aa()
.add_question(new DNSQuestion("example.com", T_MX))
.add_answer(new DNSCnameRR("example.com", 300, "c.example.com"))
.add_answer(new DNSTxtRR("c.example.com", 100, {expected1}))
.add_answer(new DNSTxtRR("c.example.com", 100, {expected1}))
.add_answer(new DNSTxtRR("c.example.com", 100, {expected2a, expected2b}));
std::vector<byte> data = pkt.data();
struct ares_txt_reply* txt = nullptr;
for (int ii = 1; ii <= 13; ii++) {
ClearFails();
SetAllocFail(ii);
EXPECT_EQ(ARES_ENOMEM, ares_parse_txt_reply(data.data(), (int)data.size(), &txt)) << ii;
}
}
} // namespace test
} // namespace ares