• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "net/dns/opt_record_rdata.h"
6 
7 #include <algorithm>
8 #include <memory>
9 #include <utility>
10 
11 #include "base/big_endian.h"
12 #include "net/dns/dns_response.h"
13 #include "net/dns/dns_test_util.h"
14 #include "net/test/gtest_util.h"
15 #include "testing/gmock/include/gmock/gmock.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17 #include "third_party/abseil-cpp/absl/types/optional.h"
18 
19 namespace net {
20 namespace {
21 
22 using ::testing::ElementsAreArray;
23 using ::testing::IsNull;
24 using ::testing::NotNull;
25 using ::testing::SizeIs;
26 
MakeStringPiece(const uint8_t * data,unsigned size)27 base::StringPiece MakeStringPiece(const uint8_t* data, unsigned size) {
28   const char* data_cc = reinterpret_cast<const char*>(data);
29   return base::StringPiece(data_cc, size);
30 }
31 
TEST(OptRecordRdataTest,ParseOptRecord)32 TEST(OptRecordRdataTest, ParseOptRecord) {
33   // This is just the rdata portion of an OPT record, rather than a complete
34   // record.
35   const uint8_t rdata[] = {
36       // First OPT
37       0x00, 0x01,  // OPT code
38       0x00, 0x02,  // OPT data size
39       0xDE, 0xAD,  // OPT data
40       // Second OPT
41       0x00, 0xFF,             // OPT code
42       0x00, 0x04,             // OPT data size
43       0xDE, 0xAD, 0xBE, 0xEF  // OPT data
44   };
45 
46   base::StringPiece rdata_strpiece = MakeStringPiece(rdata, sizeof(rdata));
47   std::unique_ptr<OptRecordRdata> rdata_obj =
48       OptRecordRdata::Create(rdata_strpiece);
49 
50   ASSERT_THAT(rdata_obj, NotNull());
51   ASSERT_EQ(rdata_obj->OptCount(), 2u);
52 
53   // Check contains
54   ASSERT_TRUE(rdata_obj->ContainsOptCode(1));
55   ASSERT_FALSE(rdata_obj->ContainsOptCode(30));
56 
57   // Check elements
58 
59   // Note: When passing string or StringPiece as argument, make sure to
60   // construct arguments with length. Otherwise, strings containing a '\0'
61   // character will be truncated.
62   // https://crbug.com/1348679
63 
64   std::unique_ptr<OptRecordRdata::UnknownOpt> opt0 =
65       OptRecordRdata::UnknownOpt::CreateForTesting(1,
66                                                    std::string("\xde\xad", 2));
67   std::unique_ptr<OptRecordRdata::UnknownOpt> opt1 =
68       OptRecordRdata::UnknownOpt::CreateForTesting(
69           255, std::string("\xde\xad\xbe\xef", 4));
70 
71   ASSERT_EQ(*(rdata_obj->GetOpts()[0]), *(opt0.get()));
72   ASSERT_EQ(*(rdata_obj->GetOpts()[1]), *(opt1.get()));
73 }
74 
TEST(OptRecordRdataTest,ParseOptRecordWithShorterSizeThanData)75 TEST(OptRecordRdataTest, ParseOptRecordWithShorterSizeThanData) {
76   // This is just the rdata portion of an OPT record, rather than a complete
77   // record.
78   const uint8_t rdata[] = {
79       0x00, 0xFF,             // OPT code
80       0x00, 0x02,             // OPT data size (incorrect, should be 4)
81       0xDE, 0xAD, 0xBE, 0xEF  // OPT data
82   };
83 
84   DnsRecordParser parser(rdata, sizeof(rdata), 0, /*num_records=*/0);
85   base::StringPiece rdata_strpiece = MakeStringPiece(rdata, sizeof(rdata));
86 
87   std::unique_ptr<OptRecordRdata> rdata_obj =
88       OptRecordRdata::Create(rdata_strpiece);
89   ASSERT_THAT(rdata_obj, IsNull());
90 }
91 
TEST(OptRecordRdataTest,ParseOptRecordWithLongerSizeThanData)92 TEST(OptRecordRdataTest, ParseOptRecordWithLongerSizeThanData) {
93   // This is just the rdata portion of an OPT record, rather than a complete
94   // record.
95   const uint8_t rdata[] = {
96       0x00, 0xFF,  // OPT code
97       0x00, 0x04,  // OPT data size (incorrect, should be 4)
98       0xDE, 0xAD   // OPT data
99   };
100 
101   DnsRecordParser parser(rdata, sizeof(rdata), 0, /*num_records=*/0);
102   base::StringPiece rdata_strpiece = MakeStringPiece(rdata, sizeof(rdata));
103 
104   std::unique_ptr<OptRecordRdata> rdata_obj =
105       OptRecordRdata::Create(rdata_strpiece);
106   ASSERT_THAT(rdata_obj, IsNull());
107 }
108 
TEST(OptRecordRdataTest,CreateEdeOpt)109 TEST(OptRecordRdataTest, CreateEdeOpt) {
110   OptRecordRdata::EdeOpt opt0(22, std::string("Don Quixote"));
111 
112   ASSERT_EQ(opt0.data(), std::string("\x00\x16"
113                                      "Don Quixote",
114                                      13));
115   ASSERT_EQ(opt0.info_code(), 22u);
116   ASSERT_EQ(opt0.extra_text(), std::string("Don Quixote"));
117 
118   std::unique_ptr<OptRecordRdata::EdeOpt> opt1 =
119       OptRecordRdata::EdeOpt::Create(std::string("\x00\x08"
120                                                  "Manhattan",
121                                                  11));
122 
123   ASSERT_EQ(opt1->data(), std::string("\x00\x08"
124                                       "Manhattan",
125                                       11));
126   ASSERT_EQ(opt1->info_code(), 8u);
127   ASSERT_EQ(opt1->extra_text(), std::string("Manhattan"));
128 }
129 
TEST(OptRecordRdataTest,TestEdeInfoCode)130 TEST(OptRecordRdataTest, TestEdeInfoCode) {
131   std::unique_ptr<OptRecordRdata::EdeOpt> edeOpt0 =
132       std::make_unique<OptRecordRdata::EdeOpt>(0, "bullettrain");
133   std::unique_ptr<OptRecordRdata::EdeOpt> edeOpt1 =
134       std::make_unique<OptRecordRdata::EdeOpt>(27, "ferrari");
135   std::unique_ptr<OptRecordRdata::EdeOpt> edeOpt2 =
136       std::make_unique<OptRecordRdata::EdeOpt>(28, "sukrit ganesh");
137   ASSERT_EQ(edeOpt0->GetEnumFromInfoCode(),
138             OptRecordRdata::EdeOpt::EdeInfoCode::kOtherError);
139   ASSERT_EQ(
140       edeOpt1->GetEnumFromInfoCode(),
141       OptRecordRdata::EdeOpt::EdeInfoCode::kUnsupportedNsec3IterationsValue);
142   ASSERT_EQ(edeOpt2->GetEnumFromInfoCode(),
143             OptRecordRdata::EdeOpt::EdeInfoCode::kUnrecognizedErrorCode);
144   ASSERT_EQ(OptRecordRdata::EdeOpt::GetEnumFromInfoCode(15),
145             OptRecordRdata::EdeOpt::kBlocked);
146 }
147 
148 // Test that an Opt EDE record is parsed correctly
TEST(OptRecordRdataTest,ParseEdeOptRecords)149 TEST(OptRecordRdataTest, ParseEdeOptRecords) {
150   const uint8_t rdata[] = {
151       // First OPT (non-EDE record)
152       0x00, 0x06,              // OPT code (6)
153       0x00, 0x04,              // OPT data size (4)
154       0xB0, 0xBA, 0xFE, 0x77,  // OPT data (Boba Fett)
155 
156       // Second OPT (EDE record)
157       0x00, 0x0F,     // OPT code (15 for EDE)
158       0x00, 0x05,     // OPT data size (info code + extra text)
159       0x00, 0x0D,     // EDE info code (13 for Cached Error)
160       'M', 'T', 'A',  // UTF-8 EDE extra text ("MTA")
161 
162       // Third OPT (EDE record)
163       0x00, 0x0F,         // OPT code (15 for EDE)
164       0x00, 0x06,         // OPT data size (info code + extra text)
165       0x00, 0x10,         // EDE info code (16 for Censored)
166       'M', 'B', 'T', 'A'  // UTF-8 EDE extra text ("MBTA")
167   };
168 
169   base::StringPiece rdata_strpiece = MakeStringPiece(rdata, sizeof(rdata));
170   std::unique_ptr<OptRecordRdata> rdata_obj =
171       OptRecordRdata::Create(rdata_strpiece);
172 
173   // Test Size of Query
174   ASSERT_THAT(rdata_obj, NotNull());
175   ASSERT_EQ(rdata_obj->OptCount(), 3u);
176 
177   // Test Unknown Opt
178   std::unique_ptr<OptRecordRdata::UnknownOpt> opt0 =
179       OptRecordRdata::UnknownOpt::CreateForTesting(
180           6, std::string("\xb0\xba\xfe\x77", 4));
181 
182   ASSERT_THAT(rdata_obj->GetOpts(), SizeIs(3));
183   ASSERT_EQ(*rdata_obj->GetOpts()[0], *opt0.get());
184 
185   // Test EDE
186   OptRecordRdata::EdeOpt edeOpt0(13, std::string("MTA", 3));
187   OptRecordRdata::EdeOpt edeOpt1(16, std::string("MBTA", 4));
188 
189   ASSERT_THAT(rdata_obj->GetEdeOpts(), SizeIs(2));
190   ASSERT_EQ(*rdata_obj->GetEdeOpts()[0], edeOpt0);
191   ASSERT_EQ(*rdata_obj->GetEdeOpts()[1], edeOpt1);
192 
193   // Check that member variables are alright
194   ASSERT_EQ(rdata_obj->GetEdeOpts()[0]->data(), edeOpt0.data());
195   ASSERT_EQ(rdata_obj->GetEdeOpts()[1]->data(), edeOpt1.data());
196 
197   ASSERT_EQ(rdata_obj->GetEdeOpts()[0]->extra_text(), std::string("MTA", 3));
198   ASSERT_EQ(rdata_obj->GetEdeOpts()[1]->extra_text(), std::string("MBTA", 4));
199 
200   ASSERT_EQ(rdata_obj->GetEdeOpts()[0]->info_code(), edeOpt0.info_code());
201   ASSERT_EQ(rdata_obj->GetEdeOpts()[1]->info_code(), edeOpt1.info_code());
202 }
203 
204 // Test the Opt equality operator (and its subclasses as well)
TEST(OptRecordRdataTest,OptEquality)205 TEST(OptRecordRdataTest, OptEquality) {
206   // `rdata_obj0` second opt has extra text "BIOS"
207   // `rdata_obj1` second opt has extra text "BIOO"
208   // Note: rdata_obj0 and rdata_obj1 have 2 common Opts and 1 different one.
209   OptRecordRdata rdata_obj0;
210   rdata_obj0.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
211       6, std::string("\xb0\xba\xfe\x77", 4)));
212   rdata_obj0.AddOpt(
213       std::make_unique<OptRecordRdata::EdeOpt>(13, std::string("USA", 3)));
214   rdata_obj0.AddOpt(
215       std::make_unique<OptRecordRdata::EdeOpt>(16, std::string("BIOS", 4)));
216   ASSERT_EQ(rdata_obj0.OptCount(), 3u);
217 
218   OptRecordRdata rdata_obj1;
219   rdata_obj1.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
220       6, std::string("\xb0\xba\xfe\x77", 4)));
221   rdata_obj1.AddOpt(
222       std::make_unique<OptRecordRdata::EdeOpt>(13, std::string("USA", 3)));
223   rdata_obj1.AddOpt(
224       std::make_unique<OptRecordRdata::EdeOpt>(16, std::string("BIOO", 4)));
225   ASSERT_EQ(rdata_obj1.OptCount(), 3u);
226 
227   auto opts0 = rdata_obj0.GetOpts();
228   auto opts1 = rdata_obj1.GetOpts();
229   auto edeOpts0 = rdata_obj0.GetEdeOpts();
230   auto edeOpts1 = rdata_obj1.GetEdeOpts();
231   ASSERT_THAT(opts0, SizeIs(3));
232   ASSERT_THAT(opts1, SizeIs(3));
233   ASSERT_THAT(edeOpts0, SizeIs(2));
234   ASSERT_THAT(edeOpts1, SizeIs(2));
235 
236   // Opt equality
237   ASSERT_EQ(*opts0[0], *opts1[0]);
238   ASSERT_EQ(*opts0[1], *opts1[1]);
239   ASSERT_NE(*opts0[0], *opts1[1]);
240 
241   // EdeOpt equality
242   ASSERT_EQ(*edeOpts0[0], *edeOpts1[0]);
243   ASSERT_NE(*edeOpts0[1], *edeOpts1[1]);
244 
245   // EdeOpt equality with Opt
246   ASSERT_EQ(*edeOpts0[0], *opts1[1]);
247   ASSERT_NE(*edeOpts0[1], *opts1[2]);
248 
249   // Opt equality with EdeOpt
250   // Should work if raw data matches
251   ASSERT_EQ(*opts1[1], *edeOpts0[0]);
252   ASSERT_NE(*opts1[2], *edeOpts0[1]);
253 }
254 
255 // Check that rdata is null if the data section of an EDE record is too small
256 // (<2 bytes)
TEST(OptRecordRdataTest,EdeRecordTooSmall)257 TEST(OptRecordRdataTest, EdeRecordTooSmall) {
258   const uint8_t rdata[] = {
259       0x00, 0x0F,  // OPT code (15 for EDE)
260       0x00, 0x01,  // OPT data size (info code + extra text)
261       0x00         // Fragment of Info Code
262   };
263 
264   base::StringPiece rdata_strpiece = MakeStringPiece(rdata, sizeof(rdata));
265   std::unique_ptr<OptRecordRdata> rdata_obj =
266       OptRecordRdata::Create(rdata_strpiece);
267   ASSERT_THAT(rdata_obj, IsNull());
268 }
269 
270 // Check that an EDE record with no extra text is parsed correctly.
TEST(OptRecordRdataTest,EdeRecordNoExtraText)271 TEST(OptRecordRdataTest, EdeRecordNoExtraText) {
272   const uint8_t rdata[] = {
273       0x00, 0x0F,  // OPT code (15 for EDE)
274       0x00, 0x02,  // OPT data size (info code + extra text)
275       0x00, 0x05   // Info Code
276   };
277 
278   base::StringPiece rdata_strpiece = MakeStringPiece(rdata, sizeof(rdata));
279   std::unique_ptr<OptRecordRdata> rdata_obj =
280       OptRecordRdata::Create(rdata_strpiece);
281   ASSERT_THAT(rdata_obj, NotNull());
282   ASSERT_THAT(rdata_obj->GetEdeOpts(), SizeIs(1));
283   ASSERT_EQ(rdata_obj->GetEdeOpts()[0]->data(), std::string("\x00\x05", 2));
284   ASSERT_EQ(rdata_obj->GetEdeOpts()[0]->info_code(), 5u);
285   ASSERT_EQ(rdata_obj->GetEdeOpts()[0]->extra_text(), "");
286 }
287 
288 // Check that an EDE record with non-UTF-8 fails to parse.
TEST(OptRecordRdataTest,EdeRecordExtraTextNonUTF8)289 TEST(OptRecordRdataTest, EdeRecordExtraTextNonUTF8) {
290   const uint8_t rdata[] = {
291       0x00, 0x0F,             // OPT code (15 for EDE)
292       0x00, 0x06,             // OPT data size (info code + extra text)
293       0x00, 0x05,             // Info Code
294       0xB1, 0x05, 0xF0, 0x0D  // Extra Text (non-UTF-8)
295   };
296 
297   ASSERT_FALSE(base::IsStringUTF8(std::string("\xb1\x05\xf0\x0d", 4)));
298 
299   base::StringPiece rdata_strpiece = MakeStringPiece(rdata, sizeof(rdata));
300   std::unique_ptr<OptRecordRdata> rdata_obj =
301       OptRecordRdata::Create(rdata_strpiece);
302   ASSERT_THAT(rdata_obj, IsNull());
303 }
304 
305 // Check that an EDE record with an unknown info code is parsed correctly.
TEST(OptRecordRdataTest,EdeRecordUnknownInfoCode)306 TEST(OptRecordRdataTest, EdeRecordUnknownInfoCode) {
307   const uint8_t rdata[] = {
308       0x00, 0x0F,                     // OPT code (15 for EDE)
309       0x00, 0x08,                     // OPT data size (info code + extra text)
310       0x00, 0x44,                     // Info Code (68 doesn't exist)
311       'B',  'O',  'S', 'T', 'O', 'N'  // Extra Text ("BOSTON")
312   };
313 
314   base::StringPiece rdata_strpiece = MakeStringPiece(rdata, sizeof(rdata));
315   std::unique_ptr<OptRecordRdata> rdata_obj =
316       OptRecordRdata::Create(rdata_strpiece);
317   ASSERT_THAT(rdata_obj, NotNull());
318   ASSERT_THAT(rdata_obj->GetEdeOpts(), SizeIs(1));
319   auto* opt = rdata_obj->GetEdeOpts()[0];
320   ASSERT_EQ(opt->data(), std::string("\x00\x44"
321                                      "BOSTON",
322                                      8));
323   ASSERT_EQ(opt->info_code(), 68u);
324   ASSERT_EQ(opt->extra_text(), std::string("BOSTON", 6));
325   ASSERT_EQ(opt->GetEnumFromInfoCode(),
326             OptRecordRdata::EdeOpt::EdeInfoCode::kUnrecognizedErrorCode);
327 }
328 
TEST(OptRecordRdataTest,CreatePaddingOpt)329 TEST(OptRecordRdataTest, CreatePaddingOpt) {
330   std::unique_ptr<OptRecordRdata::PaddingOpt> opt0 =
331       std::make_unique<OptRecordRdata::PaddingOpt>(12);
332 
333   ASSERT_EQ(opt0->data(), std::string(12, '\0'));
334   ASSERT_THAT(opt0->data(), SizeIs(12u));
335 
336   std::unique_ptr<OptRecordRdata::PaddingOpt> opt1 =
337       std::make_unique<OptRecordRdata::PaddingOpt>("MASSACHUSETTS");
338 
339   ASSERT_EQ(opt1->data(), std::string("MASSACHUSETTS"));
340   ASSERT_THAT(opt1->data(), SizeIs(13u));
341 }
342 
TEST(OptRecordRdataTest,ParsePaddingOpt)343 TEST(OptRecordRdataTest, ParsePaddingOpt) {
344   const uint8_t rdata[] = {
345       // First OPT
346       0x00, 0x0C,  // OPT code
347       0x00, 0x07,  // OPT data size
348       0xB0, 0x03,  // OPT data padding (Book of Boba Fett)
349       0x0F, 0xB0, 0xBA, 0xFE, 0x77,
350   };
351 
352   base::StringPiece rdata_strpiece = MakeStringPiece(rdata, sizeof(rdata));
353   std::unique_ptr<OptRecordRdata> rdata_obj =
354       OptRecordRdata::Create(rdata_strpiece);
355 
356   ASSERT_THAT(rdata_obj, NotNull());
357   ASSERT_EQ(rdata_obj->OptCount(), 1u);
358   ASSERT_THAT(rdata_obj->GetOpts(), SizeIs(1));
359   ASSERT_THAT(rdata_obj->GetPaddingOpts(), SizeIs(1));
360 
361   // Check elements
362   OptRecordRdata::PaddingOpt opt0(
363       std::string("\xb0\x03\x0f\xb0\xba\xfe\x77", 7));
364 
365   ASSERT_EQ(*(rdata_obj->GetOpts()[0]), opt0);
366   ASSERT_EQ(*(rdata_obj->GetPaddingOpts()[0]), opt0);
367   ASSERT_THAT(opt0.data(), SizeIs(7u));
368 }
369 
TEST(OptRecordRdataTest,AddOptToOptRecord)370 TEST(OptRecordRdataTest, AddOptToOptRecord) {
371   // This is just the rdata portion of an OPT record, rather than a complete
372   // record.
373   const uint8_t expected_rdata[] = {
374       0x00, 0xFF,             // OPT code
375       0x00, 0x04,             // OPT data size
376       0xDE, 0xAD, 0xBE, 0xEF  // OPT data
377   };
378 
379   OptRecordRdata rdata;
380   rdata.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
381       255, std::string("\xde\xad\xbe\xef", 4)));
382   EXPECT_THAT(rdata.buf(), ElementsAreArray(expected_rdata));
383 }
384 
385 // Test the OptRecordRdata equality operator.
386 // Equality must be order sensitive. If Opts are same but inserted in different
387 // order, test will fail epically.
TEST(OptRecordRdataTest,EqualityIsOptOrderSensitive)388 TEST(OptRecordRdataTest, EqualityIsOptOrderSensitive) {
389   // Control rdata
390   OptRecordRdata rdata_obj0;
391   rdata_obj0.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
392       1, std::string("\xb0\xba\xfe\x77", 4)));
393   rdata_obj0.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
394       2, std::string("\xb1\x05\xf0\x0d", 4)));
395   ASSERT_EQ(rdata_obj0.OptCount(), 2u);
396 
397   // Same as `rdata_obj0`
398   OptRecordRdata rdata_obj1;
399   rdata_obj1.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
400       1, std::string("\xb0\xba\xfe\x77", 4)));
401   rdata_obj1.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
402       2, std::string("\xb1\x05\xf0\x0d", 4)));
403   ASSERT_EQ(rdata_obj1.OptCount(), 2u);
404 
405   ASSERT_EQ(rdata_obj0, rdata_obj1);
406 
407   // Same contents as `rdata_obj0` & `rdata_obj1`, but different order
408   OptRecordRdata rdata_obj2;
409   rdata_obj2.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
410       2, std::string("\xb1\x05\xf0\x0d", 4)));
411   rdata_obj2.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
412       1, std::string("\xb0\xba\xfe\x77", 4)));
413   ASSERT_EQ(rdata_obj2.OptCount(), 2u);
414 
415   // Order matters! obj0 and obj2 contain same Opts but in different order.
416   ASSERT_FALSE(rdata_obj0.IsEqual(&rdata_obj2));
417 
418   // Contains only `rdata_obj0` first opt
419   // 2nd opt is added later
420   OptRecordRdata rdata_obj3;
421   rdata_obj3.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
422       1, std::string("\xb0\xba\xfe\x77", 4)));
423   ASSERT_EQ(rdata_obj3.OptCount(), 1u);
424 
425   ASSERT_FALSE(rdata_obj0.IsEqual(&rdata_obj3));
426 
427   rdata_obj3.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
428       2, std::string("\xb1\x05\xf0\x0d", 4)));
429 
430   ASSERT_TRUE(rdata_obj0.IsEqual(&rdata_obj3));
431 
432   // Test == operator
433   ASSERT_TRUE(rdata_obj0 == rdata_obj1);
434   ASSERT_EQ(rdata_obj0, rdata_obj1);
435   ASSERT_NE(rdata_obj0, rdata_obj2);
436 }
437 
438 // Test that GetOpts() follows specified order.
439 // Sort by key, then by insertion order.
TEST(OptRecordRdataTest,TestGetOptsOrder)440 TEST(OptRecordRdataTest, TestGetOptsOrder) {
441   OptRecordRdata rdata_obj0;
442   rdata_obj0.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
443       10, std::string("\x33\x33", 2)));
444   rdata_obj0.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
445       5, std::string("\x11\x11", 2)));
446   rdata_obj0.AddOpt(OptRecordRdata::UnknownOpt::CreateForTesting(
447       5, std::string("\x22\x22", 2)));
448   ASSERT_EQ(rdata_obj0.OptCount(), 3u);
449 
450   auto opts = rdata_obj0.GetOpts();
451   ASSERT_EQ(opts[0]->data(),
452             std::string("\x11\x11", 2));  // opt code 5 (inserted first)
453   ASSERT_EQ(opts[1]->data(),
454             std::string("\x22\x22", 2));  // opt code 5 (inserted second)
455   ASSERT_EQ(opts[2]->data(), std::string("\x33\x33", 2));  // opt code 10
456 }
457 
458 }  // namespace
459 }  // namespace net
460