diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/rust/dns-parser | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/dns-parser')
41 files changed, 2942 insertions, 0 deletions
diff --git a/third_party/rust/dns-parser/.cargo-checksum.json b/third_party/rust/dns-parser/.cargo-checksum.json new file mode 100644 index 0000000000..490d0f1eab --- /dev/null +++ b/third_party/rust/dns-parser/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"7b47cb4d67f549cce93b578b0911177365055bbbc15790644251cac1856a11f7","LICENSE-APACHE":"c6596eb7be8581c18be736c846fb9173b69eccf6ef94c5135893ec56bd92ba08","LICENSE-MIT":"71fb06f353ef01dbb828a61a74eadbfc57ab4e1b20eaae1db68229f1647c4183","README.md":"85171334a4b9e672811377819e7f178abdd1870f09d1a1250fe25a7ffaf4b5bf","bulk.yaml":"17c2548388e0cd3a63473021a2f1e4ddedee082d79d9167cb31ad06a1890d3fc","examples/sync_tcp_client.rs":"8ba349565ae9872fc3e246469eab0f7041355d38100355d07a2c1ff2cd048476","examples/sync_udp_client.rs":"ee32137e43d6ab4da6c6f1b36d956042fb74d2704ec319acd058ca74fdc489c1","src/builder.rs":"1dc20c218e5e3b8d0d0f7cdc7ab585882d9960493881be5f17ad2ea5ce8c9dd5","src/enums.rs":"0e0afe0987805ac7361bb6fcaee8a235f375312fa372a6fc8d9b36a85710a9c9","src/error.rs":"6abc85f52724ef8aa12e1ff40a747a02b018b99ae259880a9ff222959f567200","src/header.rs":"f96466c2c0c5647fab0718e9e8289aa9933e988015e454c38ecb7d3c0b6b6813","src/lib.rs":"98994eb0dfea6864d9cf26e1c6ff32f31249e9eaec4583387524dc919e4dde49","src/name.rs":"3d80f16e473e32780e60d814ea3af54094e8c813af576743b108deaf960da27e","src/parser.rs":"02af73135af936115a0ac99da7d102d5ef3c58890abe98d0cfefcd3814b98463","src/rdata/a.rs":"a66498dc4f02af5c95b53f09e2860fed69881410eb02a30bceee0b976e8f8c41","src/rdata/aaaa.rs":"0163de1dc6a691f2ede528cb865f1197511800742879bda189e65aadd682e6c4","src/rdata/all.rs":"005b8edc4aae6f140332f75420c900bae91be66dfb2fdcdd177d51280bbc3750","src/rdata/axfr.rs":"19e18099a16baf33e34ea23905ec5913834aa367a034343551fe935a367f20db","src/rdata/cname.rs":"f31f3c490b6fcd9d5300d4c3903c398cfdd7ff89154685fc3fb182b3fa553ecf","src/rdata/hinfo.rs":"3edd6e2685a048b7d35872582490cdffa90ca7aa688ecffe1d91c46b9ccede1c","src/rdata/maila.rs":"f3f5a40cebd458ed83da7d910601cab761883eee14e991c677d325e6f2fcd968","src/rdata/mailb.rs":"6e09a86dab9a516b4022eefb6a06dcb7d9b06e15d3a80e8027db285e43e5f870","src/rdata/mb.rs":"b867aa5e0f1ec0970d8d6d6406773a8b7af5291dbcf1d8d78be9d017d8cc5a81","src/rdata/mf.rs":"2494cf316f1c2bf803237ce01e7bbbdf05a736fcac2536a06a34ebaf51c815ae","src/rdata/mg.rs":"14a1c9813bc7a33811efa4855c0ac29288cbc1ec535f7c67609333a2a8a5612b","src/rdata/minfo.rs":"72bf7b87ca494865327866ddf9733fb9b137cf2cd4222338237f107b4ff5062d","src/rdata/mod.rs":"0a6f377059eee38371893d17c31d97fb214f209c89c43f5f9302e3560150a220","src/rdata/mr.rs":"0140e5a4c1b6af5e55a61027c21e1760cd2b5a8f1929604d3884225b9433054a","src/rdata/mx.rs":"a38113e7b6efbc65831582fbfacbb6ed1937e167f1b8a19a36a27b0b600ec085","src/rdata/ns.rs":"2acf2d552f3e4f94af50b1c46a6cefc99515a930b099e2d93e9a595f4e1746b5","src/rdata/nsec.rs":"0a712e38c35c60815a2b1394e4bb4f9e229311e80bc9e59b0e854354f928d5a5","src/rdata/null.rs":"0ee8104671d5238da7885b90248ffc952f1558eaa43dfe15b60292166a992a38","src/rdata/opt.rs":"24eb3346b88b3aec18985bd2c1db6498160a59ed7fabfc8b45ce174f10638653","src/rdata/ptr.rs":"9ab2459fc87f90edb2bc7d0fbb97ab008cedf3ce62c042d1b25181914a5ee5c6","src/rdata/soa.rs":"0bf7321ff2891af8f9a3d6b58199464a77b1556e705fa1e9b52f23358bb4bf47","src/rdata/srv.rs":"7d755cb64f42d3096a763739e31959ebec439855380869cd801196bb507102aa","src/rdata/txt.rs":"9e00d2b64cfc53fa8ac510a8c624f5d42320cd90f8a50d040cfc666b530fd8ad","src/rdata/wks.rs":"abfb5572d2c270838489a6ff611c76cb0d3dc7470f44a6810fb89894febd8840","src/structs.rs":"4692edebde24c3be4a7aae81f75b8dc69a9c1ed460bf80740a75bc13f5e40b82","vagga.yaml":"3662f1f49317908fd54ab7830d53074b4bdd753ae85bc3a6fcf6fabaa6b42c70"},"package":"c4d33be9473d06f75f58220f71f7a9317aca647dc061dbd3c361b0bef505fbea"}
\ No newline at end of file diff --git a/third_party/rust/dns-parser/Cargo.toml b/third_party/rust/dns-parser/Cargo.toml new file mode 100644 index 0000000000..085abfb82a --- /dev/null +++ b/third_party/rust/dns-parser/Cargo.toml @@ -0,0 +1,41 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g. crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +name = "dns-parser" +version = "0.8.0" +authors = ["Paul Colomiets <paul@colomiets.name>"] +description = " Pure-rust DNS protocol parser library. This does not support network, only\n raw protocol parser.\n" +homepage = "https://github.com/tailhook/dns-parser" +documentation = "https://docs.rs/dns-parser" +readme = "README.md" +keywords = ["dns", "domain", "name", "parser"] +categories = ["parser-implementations"] +license = "MIT/Apache-2.0" +[dependencies.byteorder] +version = "1" + +[dependencies.quick-error] +version = "1.0.0" + +[dependencies.serde] +version = "1.0" +optional = true + +[dependencies.serde_derive] +version = "1.0" +optional = true +[dev-dependencies.matches] +version = "0.1.2" + +[features] +with-serde = ["serde", "serde_derive"] diff --git a/third_party/rust/dns-parser/LICENSE-APACHE b/third_party/rust/dns-parser/LICENSE-APACHE new file mode 100644 index 0000000000..8f71f43fee --- /dev/null +++ b/third_party/rust/dns-parser/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/third_party/rust/dns-parser/LICENSE-MIT b/third_party/rust/dns-parser/LICENSE-MIT new file mode 100644 index 0000000000..11c705f7a3 --- /dev/null +++ b/third_party/rust/dns-parser/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2016 The dns-parser Developers + +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 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. diff --git a/third_party/rust/dns-parser/README.md b/third_party/rust/dns-parser/README.md new file mode 100644 index 0000000000..3d3133ebcd --- /dev/null +++ b/third_party/rust/dns-parser/README.md @@ -0,0 +1,31 @@ +DNS Parser +========== + +**Status: beta** + +[Documentation](https://docs.rs/dns-parser) | +[Github](https://github.com/tailhook/dns-parser) | +[Crate](https://crates.io/crates/dns-parser) + +This is a parser of DNS protocol packets that is independent of any networking +code. + + +License +======= + +Licensed under either of + +* Apache License, Version 2.0, + (./LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) +* MIT license (./LICENSE-MIT or http://opensource.org/licenses/MIT) + at your option. + +Contribution +------------ + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. + diff --git a/third_party/rust/dns-parser/bulk.yaml b/third_party/rust/dns-parser/bulk.yaml new file mode 100644 index 0000000000..cdb9763b67 --- /dev/null +++ b/third_party/rust/dns-parser/bulk.yaml @@ -0,0 +1,8 @@ +minimum-bulk: v0.4.5 + +versions: + +- file: Cargo.toml + block-start: ^\[package\] + block-end: ^\[.*\] + regex: ^version\s*=\s*"(\S+)" diff --git a/third_party/rust/dns-parser/examples/sync_tcp_client.rs b/third_party/rust/dns-parser/examples/sync_tcp_client.rs new file mode 100644 index 0000000000..732e8d3bfb --- /dev/null +++ b/third_party/rust/dns-parser/examples/sync_tcp_client.rs @@ -0,0 +1,77 @@ +extern crate dns_parser; + +use std::env; +use std::error::Error; +use std::io::{Read, Write}; +use std::net::TcpStream; +use std::process; + + +use dns_parser::{Builder, Packet, RData, ResponseCode}; +use dns_parser::rdata::a::Record; +use dns_parser::{QueryType, QueryClass}; + + +fn main() { + let mut code = 0; + for name in env::args().skip(1) { + match resolve(&name) { + Ok(()) => {}, + Err(e) => { + eprintln!("Error resolving {:?}: {}", name, e); + code = 1; + } + } + } + process::exit(code); +} + +fn resolve(name: &str) -> Result<(), Box<Error>> { + let mut conn = TcpStream::connect("127.0.0.1:53")?; + let mut builder = Builder::new_query(1, true); + builder.add_question(name, false, QueryType::A, QueryClass::IN); + let packet = builder.build().map_err(|_| "truncated packet")?; + let psize = [((packet.len() >> 8) & 0xFF) as u8, + (packet.len() & 0xFF) as u8]; + conn.write_all(&psize[..])?; + conn.write_all(&packet)?; + let mut buf = vec![0u8; 4096]; + let mut off = 0; + let pkt = loop { + if buf.len() - off < 4096 { + buf.extend(&[0u8; 4096][..]); + } + match conn.read(&mut buf[off..]) { + Ok(num) => { + off += num; + if off < 2 { continue; } + let bytes = ((buf[0] as usize) << 8) | buf[1] as usize; + if off < bytes + 2 { + continue; + } + if num == 0 { + return Err("Partial packet received".into()); + } + break Packet::parse(&buf[2..off])?; + } + Err(e) => { + return Err(Box::new(e)); + } + } + }; + if pkt.header.response_code != ResponseCode::NoError { + return Err(pkt.header.response_code.into()); + } + if pkt.answers.len() == 0 { + return Err("No records received".into()); + } + for ans in pkt.answers { + match ans.data { + RData::A(Record(ip)) => { + println!("{}", ip); + } + _ => {} // ignore + } + } + Ok(()) +} diff --git a/third_party/rust/dns-parser/examples/sync_udp_client.rs b/third_party/rust/dns-parser/examples/sync_udp_client.rs new file mode 100644 index 0000000000..f8bf4265d6 --- /dev/null +++ b/third_party/rust/dns-parser/examples/sync_udp_client.rs @@ -0,0 +1,53 @@ +extern crate dns_parser; + +use std::env; +use std::error::Error; +use std::net::UdpSocket; +use std::process; + + +use dns_parser::{Builder, Packet, RData, ResponseCode}; +use dns_parser::rdata::a::Record; +use dns_parser::{QueryType, QueryClass}; + + +fn main() { + let mut code = 0; + for name in env::args().skip(1) { + match resolve(&name) { + Ok(()) => {}, + Err(e) => { + eprintln!("Error resolving {:?}: {}", name, e); + code = 1; + } + } + } + process::exit(code); +} + +fn resolve(name: &str) -> Result<(), Box<Error>> { + let sock = UdpSocket::bind("127.0.0.1:0")?; + sock.connect("127.0.0.1:53")?; + let mut builder = Builder::new_query(1, true); + builder.add_question(name, false, QueryType::A, QueryClass::IN); + let packet = builder.build().map_err(|_| "truncated packet")?; + sock.send(&packet)?; + let mut buf = vec![0u8; 4096]; + sock.recv(&mut buf)?; + let pkt = Packet::parse(&buf)?; + if pkt.header.response_code != ResponseCode::NoError { + return Err(pkt.header.response_code.into()); + } + if pkt.answers.len() == 0 { + return Err("No records received".into()); + } + for ans in pkt.answers { + match ans.data { + RData::A(Record(ip)) => { + println!("{}", ip); + } + _ => {} // ignore + } + } + Ok(()) +} diff --git a/third_party/rust/dns-parser/src/builder.rs b/third_party/rust/dns-parser/src/builder.rs new file mode 100644 index 0000000000..22aff0b962 --- /dev/null +++ b/third_party/rust/dns-parser/src/builder.rs @@ -0,0 +1,132 @@ +use byteorder::{ByteOrder, BigEndian, WriteBytesExt}; + +use {Opcode, ResponseCode, Header, QueryType, QueryClass}; + +/// Allows to build a DNS packet +/// +/// Both query and answer packets may be built with this interface, although, +/// much of functionality is not implemented yet. +#[derive(Debug)] +pub struct Builder { + buf: Vec<u8>, +} + +impl Builder { + /// Creates a new query + /// + /// Initially all sections are empty. You're expected to fill + /// the questions section with `add_question` + pub fn new_query(id: u16, recursion: bool) -> Builder { + let mut buf = Vec::with_capacity(512); + let head = Header { + id: id, + query: true, + opcode: Opcode::StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: recursion, + recursion_available: false, + authenticated_data: false, + checking_disabled: false, + response_code: ResponseCode::NoError, + questions: 0, + answers: 0, + nameservers: 0, + additional: 0, + }; + buf.extend([0u8; 12].iter()); + head.write(&mut buf[..12]); + Builder { buf: buf } + } + /// Adds a question to the packet + /// + /// # Panics + /// + /// * Answers, nameservers or additional section has already been written + /// * There are already 65535 questions in the buffer. + /// * When name is invalid + pub fn add_question(&mut self, qname: &str, prefer_unicast: bool, + qtype: QueryType, qclass: QueryClass) + -> &mut Builder + { + if &self.buf[6..12] != b"\x00\x00\x00\x00\x00\x00" { + panic!("Too late to add a question"); + } + self.write_name(qname); + self.buf.write_u16::<BigEndian>(qtype as u16).unwrap(); + let prefer_unicast: u16 = if prefer_unicast { 0x8000 } else { 0x0000 }; + self.buf.write_u16::<BigEndian>(qclass as u16 | prefer_unicast).unwrap(); + let oldq = BigEndian::read_u16(&self.buf[4..6]); + if oldq == 65535 { + panic!("Too many questions"); + } + BigEndian::write_u16(&mut self.buf[4..6], oldq+1); + self + } + fn write_name(&mut self, name: &str) { + for part in name.split('.') { + assert!(part.len() < 63); + let ln = part.len() as u8; + self.buf.push(ln); + self.buf.extend(part.as_bytes()); + } + self.buf.push(0); + } + /// Returns the final packet + /// + /// When packet is not truncated method returns `Ok(packet)`. If + /// packet is truncated the method returns `Err(packet)`. In both + /// cases the packet is fully valid. + /// + /// In the server implementation you may use + /// `x.build().unwrap_or_else(|x| x)`. + /// + /// In the client implementation it's probably unwise to send truncated + /// packet, as it doesn't make sense. Even panicking may be more + /// appropriate. + // TODO(tailhook) does the truncation make sense for TCP, and how + // to treat it for EDNS0? + pub fn build(mut self) -> Result<Vec<u8>,Vec<u8>> { + // TODO(tailhook) optimize labels + if self.buf.len() > 512 { + Header::set_truncated(&mut self.buf[..12]); + Err(self.buf) + } else { + Ok(self.buf) + } + } +} + +#[cfg(test)] +mod test { + use QueryType as QT; + use QueryClass as QC; + use super::Builder; + + #[test] + fn build_query() { + let mut bld = Builder::new_query(1573, true); + bld.add_question("example.com", false, QT::A, QC::IN); + let result = b"\x06%\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\ + \x07example\x03com\x00\x00\x01\x00\x01"; + assert_eq!(&bld.build().unwrap()[..], &result[..]); + } + + #[test] + fn build_unicast_query() { + let mut bld = Builder::new_query(1573, true); + bld.add_question("example.com", true, QT::A, QC::IN); + let result = b"\x06%\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\ + \x07example\x03com\x00\x00\x01\x80\x01"; + assert_eq!(&bld.build().unwrap()[..], &result[..]); + } + + #[test] + fn build_srv_query() { + let mut bld = Builder::new_query(23513, true); + bld.add_question("_xmpp-server._tcp.gmail.com", false, QT::SRV, QC::IN); + let result = b"[\xd9\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\ + \x0c_xmpp-server\x04_tcp\x05gmail\x03com\x00\x00!\x00\x01"; + assert_eq!(&bld.build().unwrap()[..], &result[..]); + } +} diff --git a/third_party/rust/dns-parser/src/enums.rs b/third_party/rust/dns-parser/src/enums.rs new file mode 100644 index 0000000000..9f58ddfec1 --- /dev/null +++ b/third_party/rust/dns-parser/src/enums.rs @@ -0,0 +1,299 @@ +use {Error}; +use rdata::Record; +use rdata::*; + +/// The TYPE value according to RFC 1035 +/// +/// All "EXPERIMENTAL" markers here are from the RFC +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Type { + /// a host addresss + A = a::Record::TYPE, + /// an authoritative name server + NS = ns::Record::TYPE, + /// a mail forwarder (Obsolete - use MX) + MF = mf::Record::TYPE, + /// the canonical name for an alias + CNAME = cname::Record::TYPE, + /// marks the start of a zone of authority + SOA = soa::Record::TYPE, + /// a mailbox domain name (EXPERIMENTAL) + MB = mb::Record::TYPE, + /// a mail group member (EXPERIMENTAL) + MG = mg::Record::TYPE, + /// a mail rename domain name (EXPERIMENTAL) + MR = mr::Record::TYPE, + /// a null RR (EXPERIMENTAL) + NULL = null::Record::TYPE, + /// a well known service description + WKS = wks::Record::TYPE, + /// a domain name pointer + PTR = ptr::Record::TYPE, + /// host information + HINFO = hinfo::Record::TYPE, + /// mailbox or mail list information + MINFO = minfo::Record::TYPE, + /// mail exchange + MX = mx::Record::TYPE, + /// text strings + TXT = txt::Record::TYPE, + /// IPv6 host address (RFC 2782) + AAAA = aaaa::Record::TYPE, + /// service record (RFC 2782) + SRV = srv::Record::TYPE, + /// EDNS0 options (RFC 6891) + OPT = opt::Record::TYPE, + /// next secure record (RFC 4034, RFC 6762) + NSEC = nsec::Record::TYPE, +} + +/// The QTYPE value according to RFC 1035 +/// +/// All "EXPERIMENTAL" markers here are from the RFC +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "with-serde", derive(Serialize, Deserialize))] +pub enum QueryType { + /// a host addresss + A = a::Record::TYPE, + /// an authoritative name server + NS = ns::Record::TYPE, + /// a mail forwarder (Obsolete - use MX) + MF = mf::Record::TYPE, + /// the canonical name for an alias + CNAME = cname::Record::TYPE, + /// marks the start of a zone of authority + SOA = soa::Record::TYPE, + /// a mailbox domain name (EXPERIMENTAL) + MB = mb::Record::TYPE, + /// a mail group member (EXPERIMENTAL) + MG = mg::Record::TYPE, + /// a mail rename domain name (EXPERIMENTAL) + MR = mr::Record::TYPE, + /// a null RR (EXPERIMENTAL) + NULL = null::Record::TYPE, + /// a well known service description + WKS = wks::Record::TYPE, + /// a domain name pointer + PTR = ptr::Record::TYPE, + /// host information + HINFO = hinfo::Record::TYPE, + /// mailbox or mail list information + MINFO = minfo::Record::TYPE, + /// mail exchange + MX = mx::Record::TYPE, + /// text strings + TXT = txt::Record::TYPE, + /// IPv6 host address (RFC 2782) + AAAA = aaaa::Record::TYPE, + /// service record (RFC 2782) + SRV = srv::Record::TYPE, + /// A request for a transfer of an entire zone + AXFR = axfr::Record::TYPE, + /// A request for mailbox-related records (MB, MG or MR) + MAILB = mailb::Record::TYPE, + /// A request for mail agent RRs (Obsolete - see MX) + MAILA = maila::Record::TYPE, + /// A request for all records + All = all::Record::TYPE, +} + + +/// The CLASS value according to RFC 1035 +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Class { + /// the Internet + IN = 1, + /// the CSNET class (Obsolete - used only for examples in some obsolete + /// RFCs) + CS = 2, + /// the CHAOS class + CH = 3, + /// Hesiod [Dyer 87] + HS = 4, +} + +/// The QCLASS value according to RFC 1035 +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum QueryClass { + /// the Internet + IN = 1, + /// the CSNET class (Obsolete - used only for examples in some obsolete + /// RFCs) + CS = 2, + /// the CHAOS class + CH = 3, + /// Hesiod [Dyer 87] + HS = 4, + /// Any class + Any = 255, +} + +/// The OPCODE value according to RFC 1035 +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Opcode { + /// Normal query + StandardQuery, + /// Inverse query (query a name by IP) + InverseQuery, + /// Server status request + ServerStatusRequest, + /// Reserved opcode for future use + Reserved(u16), +} + +quick_error! { + /// The RCODE value according to RFC 1035 + #[derive(Debug, PartialEq, Eq, Clone, Copy)] + #[allow(missing_docs)] // names are from spec + pub enum ResponseCode { + NoError + FormatError + ServerFailure + NameError + NotImplemented + Refused + Reserved(code: u8) + } +} + +impl From<u16> for Opcode { + fn from(code: u16) -> Opcode { + use self::Opcode::*; + match code { + 0 => StandardQuery, + 1 => InverseQuery, + 2 => ServerStatusRequest, + x => Reserved(x), + } + } +} +impl Into<u16> for Opcode { + fn into(self) -> u16 { + use self::Opcode::*; + match self { + StandardQuery => 0, + InverseQuery => 1, + ServerStatusRequest => 2, + Reserved(x) => x, + } + } +} + +impl From<u8> for ResponseCode { + fn from(code: u8) -> ResponseCode { + use self::ResponseCode::*; + match code { + 0 => NoError, + 1 => FormatError, + 2 => ServerFailure, + 3 => NameError, + 4 => NotImplemented, + 5 => Refused, + 6...15 => Reserved(code), + x => panic!("Invalid response code {}", x), + } + } +} +impl Into<u8> for ResponseCode { + fn into(self) -> u8 { + use self::ResponseCode::*; + match self { + NoError => 0, + FormatError => 1, + ServerFailure => 2, + NameError => 3, + NotImplemented => 4, + Refused => 5, + Reserved(code) => code, + } + } +} + +impl QueryType { + /// Parse a query type code + pub fn parse(code: u16) -> Result<QueryType, Error> { + use self::QueryType::*; + match code as isize { + a::Record::TYPE => Ok(A), + ns::Record::TYPE => Ok(NS), + mf::Record::TYPE => Ok(MF), + cname::Record::TYPE => Ok(CNAME), + soa::Record::TYPE => Ok(SOA), + mb::Record::TYPE => Ok(MB), + mg::Record::TYPE => Ok(MG), + mr::Record::TYPE => Ok(MR), + null::Record::TYPE => Ok(NULL), + wks::Record::TYPE => Ok(WKS), + ptr::Record::TYPE => Ok(PTR), + hinfo::Record::TYPE => Ok(HINFO), + minfo::Record::TYPE => Ok(MINFO), + mx::Record::TYPE => Ok(MX), + txt::Record::TYPE => Ok(TXT), + aaaa::Record::TYPE => Ok(AAAA), + srv::Record::TYPE => Ok(SRV), + axfr::Record::TYPE => Ok(AXFR), + mailb::Record::TYPE => Ok(MAILB), + maila::Record::TYPE => Ok(MAILA), + all::Record::TYPE => Ok(All), + x => Err(Error::InvalidQueryType(x as u16)), + } + } +} + +impl QueryClass { + /// Parse a query class code + pub fn parse(code: u16) -> Result<QueryClass, Error> { + use self::QueryClass::*; + match code { + 1 => Ok(IN), + 2 => Ok(CS), + 3 => Ok(CH), + 4 => Ok(HS), + 255 => Ok(Any), + x => Err(Error::InvalidQueryClass(x)), + } + } +} + +impl Type { + /// Parse a type code + pub fn parse(code: u16) -> Result<Type, Error> { + use self::Type::*; + match code as isize { + a::Record::TYPE => Ok(A), + ns::Record::TYPE => Ok(NS), + mf::Record::TYPE => Ok(MF), + cname::Record::TYPE => Ok(CNAME), + soa::Record::TYPE => Ok(SOA), + mb::Record::TYPE => Ok(MB), + mg::Record::TYPE => Ok(MG), + mr::Record::TYPE => Ok(MR), + null::Record::TYPE => Ok(NULL), + wks::Record::TYPE => Ok(WKS), + ptr::Record::TYPE => Ok(PTR), + hinfo::Record::TYPE => Ok(HINFO), + minfo::Record::TYPE => Ok(MINFO), + mx::Record::TYPE => Ok(MX), + txt::Record::TYPE => Ok(TXT), + aaaa::Record::TYPE => Ok(AAAA), + srv::Record::TYPE => Ok(SRV), + opt::Record::TYPE => Ok(OPT), + nsec::Record::TYPE => Ok(NSEC), + x => Err(Error::InvalidType(x as u16)), + } + } +} + +impl Class { + /// Parse a class code + pub fn parse(code: u16) -> Result<Class, Error> { + use self::Class::*; + match code { + 1 => Ok(IN), + 2 => Ok(CS), + 3 => Ok(CH), + 4 => Ok(HS), + x => Err(Error::InvalidClass(x)), + } + } +} diff --git a/third_party/rust/dns-parser/src/error.rs b/third_party/rust/dns-parser/src/error.rs new file mode 100644 index 0000000000..ae6a39747d --- /dev/null +++ b/third_party/rust/dns-parser/src/error.rs @@ -0,0 +1,71 @@ +use std::str::Utf8Error; + +quick_error! { + /// Error parsing DNS packet + #[derive(Debug)] + pub enum Error { + /// Invalid compression pointer not pointing backwards + /// when parsing label + BadPointer { + description("invalid compression pointer not pointing backwards \ + when parsing label") + } + /// Packet is smaller than header size + HeaderTooShort { + description("packet is smaller than header size") + } + /// Packet ihas incomplete data + UnexpectedEOF { + description("packet is has incomplete data") + } + /// Wrong (too short or too long) size of RDATA + WrongRdataLength { + description("wrong (too short or too long) size of RDATA") + } + /// Packet has non-zero reserved bits + ReservedBitsAreNonZero { + description("packet has non-zero reserved bits") + } + /// Label in domain name has unknown label format + UnknownLabelFormat { + description("label in domain name has unknown label format") + } + /// Query type code is invalid + InvalidQueryType(code: u16) { + description("query type code is invalid") + display("query type {} is invalid", code) + } + /// Query class code is invalid + InvalidQueryClass(code: u16) { + description("query class code is invalid") + display("query class {} is invalid", code) + } + /// Type code is invalid + InvalidType(code: u16) { + description("type code is invalid") + display("type {} is invalid", code) + } + /// Class code is invalid + InvalidClass(code: u16) { + description("class code is invalid") + display("class {} is invalid", code) + } + /// Invalid characters encountered while reading label + LabelIsNotAscii { + description("invalid characters encountered while reading label") + } + /// Invalid characters encountered while reading TXT + TxtDataIsNotUTF8(error: Utf8Error) { + description("invalid characters encountered while reading TXT") + display("{:?}", error) + } + /// Parser is in the wrong state + WrongState { + description("parser is in the wrong state") + } + /// Additional OPT record found + AdditionalOPT { + description("additional OPT record found") + } + } +} diff --git a/third_party/rust/dns-parser/src/header.rs b/third_party/rust/dns-parser/src/header.rs new file mode 100644 index 0000000000..01d18add99 --- /dev/null +++ b/third_party/rust/dns-parser/src/header.rs @@ -0,0 +1,203 @@ +use byteorder::{BigEndian, ByteOrder}; + +use {Error, ResponseCode, Opcode}; + +mod flag { + pub const QUERY: u16 = 0b1000_0000_0000_0000; + pub const OPCODE_MASK: u16 = 0b0111_1000_0000_0000; + pub const AUTHORITATIVE: u16 = 0b0000_0100_0000_0000; + pub const TRUNCATED: u16 = 0b0000_0010_0000_0000; + pub const RECURSION_DESIRED: u16 = 0b0000_0001_0000_0000; + pub const RECURSION_AVAILABLE: u16 = 0b0000_0000_1000_0000; + pub const AUTHENTICATED_DATA: u16 = 0b0000_0000_0010_0000; + pub const CHECKING_DISABLED: u16 = 0b0000_0000_0001_0000; + pub const RESERVED_MASK: u16 = 0b0000_0000_0100_0000; + pub const RESPONSE_CODE_MASK: u16 = 0b0000_0000_0000_1111; +} + +/// Represents parsed header of the packet +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[allow(missing_docs)] // fields are from the spec I think +pub struct Header { + pub id: u16, + pub query: bool, + pub opcode: Opcode, + pub authoritative: bool, + pub truncated: bool, + pub recursion_desired: bool, + pub recursion_available: bool, + pub authenticated_data: bool, + pub checking_disabled: bool, + pub response_code: ResponseCode, + pub questions: u16, + pub answers: u16, + pub nameservers: u16, + pub additional: u16, +} + +impl Header { + /// Parse the header into a header structure + pub fn parse(data: &[u8]) -> Result<Header, Error> { + if data.len() < 12 { + return Err(Error::HeaderTooShort); + } + let flags = BigEndian::read_u16(&data[2..4]); + if flags & flag::RESERVED_MASK != 0 { + return Err(Error::ReservedBitsAreNonZero); + } + let header = Header { + id: BigEndian::read_u16(&data[..2]), + query: flags & flag::QUERY == 0, + opcode: ((flags & flag::OPCODE_MASK) + >> flag::OPCODE_MASK.trailing_zeros()).into(), + authoritative: flags & flag::AUTHORITATIVE != 0, + truncated: flags & flag::TRUNCATED != 0, + recursion_desired: flags & flag::RECURSION_DESIRED != 0, + recursion_available: flags & flag::RECURSION_AVAILABLE != 0, + authenticated_data: flags & flag::AUTHENTICATED_DATA != 0, + checking_disabled: flags & flag::CHECKING_DISABLED != 0, + response_code: From::from((flags&flag::RESPONSE_CODE_MASK) as u8), + questions: BigEndian::read_u16(&data[4..6]), + answers: BigEndian::read_u16(&data[6..8]), + nameservers: BigEndian::read_u16(&data[8..10]), + additional: BigEndian::read_u16(&data[10..12]), + }; + Ok(header) + } + /// Write a header to a buffer slice + /// + /// # Panics + /// + /// When buffer size is not exactly 12 bytes + pub fn write(&self, data: &mut [u8]) { + if data.len() != 12 { + panic!("Header size is exactly 12 bytes"); + } + let mut flags = 0u16; + flags |= Into::<u16>::into(self.opcode) + << flag::OPCODE_MASK.trailing_zeros(); + flags |= Into::<u8>::into(self.response_code) as u16; + if !self.query { flags |= flag::QUERY; } + if self.authoritative { flags |= flag::AUTHORITATIVE; } + if self.recursion_desired { flags |= flag::RECURSION_DESIRED; } + if self.recursion_available { flags |= flag::RECURSION_AVAILABLE; } + if self.truncated { flags |= flag::TRUNCATED; } + BigEndian::write_u16(&mut data[..2], self.id); + BigEndian::write_u16(&mut data[2..4], flags); + BigEndian::write_u16(&mut data[4..6], self.questions); + BigEndian::write_u16(&mut data[6..8], self.answers); + BigEndian::write_u16(&mut data[8..10], self.nameservers); + BigEndian::write_u16(&mut data[10..12], self.additional); + } + /// Set "truncated flag" in the raw data + // shouldn't this method be non-public? + pub fn set_truncated(data: &mut [u8]) { + let oldflags = BigEndian::read_u16(&data[2..4]); + BigEndian::write_u16(&mut data[2..4], oldflags & flag::TRUNCATED); + } + /// Returns a size of the header (always 12 bytes) + pub fn size() -> usize { 12 } +} + + +#[cfg(test)] +mod test { + + use {Header}; + use Opcode::*; + use ResponseCode::NoError; + + #[test] + fn parse_example_query() { + let query = b"\x06%\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\ + \x07example\x03com\x00\x00\x01\x00\x01"; + let header = Header::parse(query).unwrap(); + assert_eq!(header, Header { + id: 1573, + query: true, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: false, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 0, + nameservers: 0, + additional: 0, + }); + } + + #[test] + fn parse_example_response() { + let response = b"\x06%\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00\ + \x07example\x03com\x00\x00\x01\x00\x01\ + \xc0\x0c\x00\x01\x00\x01\x00\x00\x04\xf8\ + \x00\x04]\xb8\xd8\""; + let header = Header::parse(response).unwrap(); + assert_eq!(header, Header { + id: 1573, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 1, + nameservers: 0, + additional: 0, + }); + } + + #[test] + fn parse_query_with_ad_set() { + let query = b"\x06%\x01\x20\x00\x01\x00\x00\x00\x00\x00\x00\ + \x07example\x03com\x00\x00\x01\x00\x01"; + let header = Header::parse(query).unwrap(); + assert_eq!(header, Header { + id: 1573, + query: true, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: false, + authenticated_data: true, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 0, + nameservers: 0, + additional: 0, + }); + } + + #[test] + fn parse_query_with_cd_set() { + let query = b"\x06%\x01\x10\x00\x01\x00\x00\x00\x00\x00\x00\ + \x07example\x03com\x00\x00\x01\x00\x01"; + let header = Header::parse(query).unwrap(); + assert_eq!(header, Header { + id: 1573, + query: true, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: false, + authenticated_data: false, + checking_disabled: true, + response_code: NoError, + questions: 1, + answers: 0, + nameservers: 0, + additional: 0, + }); + } +} diff --git a/third_party/rust/dns-parser/src/lib.rs b/third_party/rust/dns-parser/src/lib.rs new file mode 100644 index 0000000000..e89c005244 --- /dev/null +++ b/third_party/rust/dns-parser/src/lib.rs @@ -0,0 +1,39 @@ +#![recursion_limit="100"] +//! The network-agnostic DNS parser library +//! +//! [Documentation](https://docs.rs/dns-parser) | +//! [Github](https://github.com/tailhook/dns-parser) | +//! [Crate](https://crates.io/crates/dns-parser) +//! +//! Use [`Builder`] to create a new outgoing packet. +//! +//! Use [`Packet::parse`] to parse a packet into a data structure. +//! +//! [`Builder`]: struct.Builder.html +//! [`Packet::parse`]: struct.Packet.html#method.parse +//! +#![warn(missing_docs)] +#![warn(missing_debug_implementations)] + +extern crate byteorder; +#[cfg(test)] #[macro_use] extern crate matches; +#[macro_use(quick_error)] extern crate quick_error; +#[cfg(feature = "with-serde")] #[macro_use] extern crate serde_derive; + +mod enums; +mod structs; +mod name; +mod parser; +mod error; +mod header; +mod builder; + +pub mod rdata; + +pub use enums::{Type, QueryType, Class, QueryClass, ResponseCode, Opcode}; +pub use structs::{Question, ResourceRecord, Packet}; +pub use name::{Name}; +pub use error::{Error}; +pub use header::{Header}; +pub use rdata::{RData}; +pub use builder::{Builder}; diff --git a/third_party/rust/dns-parser/src/name.rs b/third_party/rust/dns-parser/src/name.rs new file mode 100644 index 0000000000..8763259cbe --- /dev/null +++ b/third_party/rust/dns-parser/src/name.rs @@ -0,0 +1,183 @@ +use std::fmt; +use std::fmt::Write; +use std::str::from_utf8; + +// Deprecated since rustc 1.23 +#[allow(unused_imports, deprecated)] +use std::ascii::AsciiExt; + +use byteorder::{BigEndian, ByteOrder}; + +use {Error}; + +/// The DNS name as stored in the original packet +/// +/// This contains just a reference to a slice that contains the data. +/// You may turn this into a string using `.to_string()` +#[derive(Clone, Copy)] +pub struct Name<'a>{ + labels: &'a [u8], + /// This is the original buffer size. The compressed names in original + /// are calculated in this buffer + original: &'a [u8], +} + +impl<'a> Name<'a> { + /// Scan the data to get Name object + /// + /// The `data` should be a part of `original` where name should start. + /// The `original` is the data starting a the start of a packet, so + /// that offsets in compressed name starts from the `original`. + pub fn scan(data: &'a[u8], original: &'a[u8]) -> Result<Name<'a>, Error> { + let mut parse_data = data; + let mut return_pos = None; + let mut pos = 0; + if parse_data.len() <= pos { + return Err(Error::UnexpectedEOF); + } + // By setting the largest_pos to be the original len, a side effect + // is that the pos variable can move forwards in the buffer once. + let mut largest_pos = original.len(); + let mut byte = parse_data[pos]; + while byte != 0 { + if parse_data.len() <= pos { + return Err(Error::UnexpectedEOF); + } + if byte & 0b1100_0000 == 0b1100_0000 { + if parse_data.len() < pos+2 { + return Err(Error::UnexpectedEOF); + } + let off = (BigEndian::read_u16(&parse_data[pos..pos+2]) + & !0b1100_0000_0000_0000) as usize; + if off >= original.len() { + return Err(Error::UnexpectedEOF); + } + // Set value for return_pos which is the pos in the original + // data buffer that should be used to return after validating + // the offsetted labels. + if let None = return_pos { + return_pos = Some(pos); + } + + // Check then set largest_pos to ensure we never go backwards + // in the buffer. + if off >= largest_pos { + return Err(Error::BadPointer); + } + largest_pos = off; + pos = 0; + parse_data = &original[off..]; + } else if byte & 0b1100_0000 == 0 { + let end = pos + byte as usize + 1; + if parse_data.len() < end { + return Err(Error::UnexpectedEOF); + } + if !parse_data[pos+1..end].is_ascii() { + return Err(Error::LabelIsNotAscii); + } + pos = end; + if parse_data.len() <= pos { + return Err(Error::UnexpectedEOF); + } + } else { + return Err(Error::UnknownLabelFormat); + } + byte = parse_data[pos]; + } + if let Some(return_pos) = return_pos { + return Ok(Name {labels: &data[..return_pos+2], original: original}); + } else { + return Ok(Name {labels: &data[..pos+1], original: original }); + } + } + /// Number of bytes serialized name occupies + pub fn byte_len(&self) -> usize { + self.labels.len() + } +} + +impl<'a> fmt::Display for Name<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let data = self.labels; + let original = self.original; + let mut pos = 0; + loop { + let byte = data[pos]; + if byte == 0 { + return Ok(()); + } else if byte & 0b1100_0000 == 0b1100_0000 { + let off = (BigEndian::read_u16(&data[pos..pos+2]) + & !0b1100_0000_0000_0000) as usize; + if pos != 0 { + try!(fmt.write_char('.')); + } + return fmt::Display::fmt( + &Name::scan(&original[off..], original).unwrap(), fmt) + } else if byte & 0b1100_0000 == 0 { + if pos != 0 { + try!(fmt.write_char('.')); + } + let end = pos + byte as usize + 1; + try!(fmt.write_str(from_utf8(&data[pos+1..end]).unwrap())); + pos = end; + continue; + } else { + unreachable!(); + } + } + } +} +impl<'a> fmt::Debug for Name<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_tuple("Name") + .field(&format!("{}", self)) + .finish() + } +} + +#[cfg(test)] +mod test { + use Error; + use Name; + + #[test] + fn parse_badpointer_same_offset() { + // A buffer where an offset points to itself, + // which is a bad compression pointer. + let same_offset = vec![192, 2, 192, 2]; + let is_match = matches!(Name::scan(&same_offset, &same_offset), + Err(Error::BadPointer)); + + assert!(is_match); + } + + #[test] + fn parse_badpointer_forward_offset() { + // A buffer where the offsets points back to each other which causes + // infinite recursion if never checked, a bad compression pointer. + let forwards_offset = vec![192, 2, 192, 4, 192, 2]; + let is_match = matches!(Name::scan(&forwards_offset, &forwards_offset), + Err(Error::BadPointer)); + + assert!(is_match); + } + + #[test] + fn nested_names() { + // A buffer where an offset points to itself, a bad compression pointer. + let buf = b"\x02xx\x00\x02yy\xc0\x00\x02zz\xc0\x04"; + + assert_eq!(Name::scan(&buf[..], buf).unwrap().to_string(), + "xx"); + assert_eq!(Name::scan(&buf[..], buf).unwrap().labels, + b"\x02xx\x00"); + assert_eq!(Name::scan(&buf[4..], buf).unwrap().to_string(), + "yy.xx"); + assert_eq!(Name::scan(&buf[4..], buf).unwrap().labels, + b"\x02yy\xc0\x00"); + assert_eq!(Name::scan(&buf[9..], buf).unwrap().to_string(), + "zz.yy.xx"); + assert_eq!(Name::scan(&buf[9..], buf).unwrap().labels, + b"\x02zz\xc0\x04"); + } +} diff --git a/third_party/rust/dns-parser/src/parser.rs b/third_party/rust/dns-parser/src/parser.rs new file mode 100644 index 0000000000..0718798349 --- /dev/null +++ b/third_party/rust/dns-parser/src/parser.rs @@ -0,0 +1,455 @@ +use std::i32; + +use byteorder::{BigEndian, ByteOrder}; + +use {Header, Packet, Error, Question, Name, QueryType, QueryClass}; +use {Type, Class, ResourceRecord, RData}; +use rdata::opt::Record as Opt; + +const OPT_RR_START: [u8; 3] = [0, 0, 41]; + +impl<'a> Packet<'a> { + /// Parse a full DNS Packet and return a structure that has all the + /// data borrowed from the passed buffer. + pub fn parse(data: &[u8]) -> Result<Packet, Error> { + let header = try!(Header::parse(data)); + let mut offset = Header::size(); + let mut questions = Vec::with_capacity(header.questions as usize); + for _ in 0..header.questions { + let name = try!(Name::scan(&data[offset..], data)); + offset += name.byte_len(); + if offset + 4 > data.len() { + return Err(Error::UnexpectedEOF); + } + let qtype = try!(QueryType::parse( + BigEndian::read_u16(&data[offset..offset+2]))); + offset += 2; + + let (prefer_unicast, qclass) = try!(parse_qclass_code( + BigEndian::read_u16(&data[offset..offset+2]))); + offset += 2; + + questions.push(Question { + qname: name, + qtype: qtype, + prefer_unicast: prefer_unicast, + qclass: qclass, + }); + } + let mut answers = Vec::with_capacity(header.answers as usize); + for _ in 0..header.answers { + answers.push(try!(parse_record(data, &mut offset))); + } + let mut nameservers = Vec::with_capacity(header.nameservers as usize); + for _ in 0..header.nameservers { + nameservers.push(try!(parse_record(data, &mut offset))); + } + let mut additional = Vec::with_capacity(header.additional as usize); + let mut opt = None; + for _ in 0..header.additional { + if offset + 3 <= data.len() && data[offset..offset+3] == OPT_RR_START { + if opt.is_none() { + opt = Some(try!(parse_opt_record(data, &mut offset))); + } else { + return Err(Error::AdditionalOPT); + } + } else { + additional.push(try!(parse_record(data, &mut offset))); + } + } + Ok(Packet { + header: header, + questions: questions, + answers: answers, + nameservers: nameservers, + additional: additional, + opt: opt, + }) + } +} + +fn parse_qclass_code(value: u16) -> Result<(bool, QueryClass), Error> { + let prefer_unicast = value & 0x8000 == 0x8000; + let qclass_code = value & 0x7FFF; + + let qclass = try!(QueryClass::parse(qclass_code)); + Ok((prefer_unicast, qclass)) +} + +fn parse_class_code(value: u16) -> Result<(bool, Class), Error> { + let is_unique = value & 0x8000 == 0x8000; + let class_code = value & 0x7FFF; + + let cls = try!(Class::parse(class_code)); + Ok((is_unique, cls)) +} + +// Generic function to parse answer, nameservers, and additional records. +fn parse_record<'a>(data: &'a [u8], offset: &mut usize) -> Result<ResourceRecord<'a>, Error> { + let name = try!(Name::scan(&data[*offset..], data)); + *offset += name.byte_len(); + if *offset + 10 > data.len() { + return Err(Error::UnexpectedEOF); + } + let typ = try!(Type::parse( + BigEndian::read_u16(&data[*offset..*offset+2]))); + *offset += 2; + + let class_code = BigEndian::read_u16(&data[*offset..*offset+2]); + let (multicast_unique, cls) = try!(parse_class_code(class_code)); + *offset += 2; + + let mut ttl = BigEndian::read_u32(&data[*offset..*offset+4]); + if ttl > i32::MAX as u32 { + ttl = 0; + } + *offset += 4; + let rdlen = BigEndian::read_u16(&data[*offset..*offset+2]) as usize; + *offset += 2; + if *offset + rdlen > data.len() { + return Err(Error::UnexpectedEOF); + } + let data = try!(RData::parse(typ, + &data[*offset..*offset+rdlen], data)); + *offset += rdlen; + Ok(ResourceRecord { + name: name, + multicast_unique: multicast_unique, + cls: cls, + ttl: ttl, + data: data, + }) +} + +// Function to parse an RFC 6891 OPT Pseudo RR +fn parse_opt_record<'a>(data: &'a [u8], offset: &mut usize) -> Result<Opt<'a>, Error> { + if *offset + 11 > data.len() { + return Err(Error::UnexpectedEOF); + } + *offset += 1; + let typ = try!(Type::parse( + BigEndian::read_u16(&data[*offset..*offset+2]))); + if typ != Type::OPT { + return Err(Error::InvalidType(typ as u16)); + } + *offset += 2; + let udp = BigEndian::read_u16(&data[*offset..*offset+2]); + *offset += 2; + let extrcode = data[*offset]; + *offset += 1; + let version = data[*offset]; + *offset += 1; + let flags = BigEndian::read_u16(&data[*offset..*offset+2]); + *offset += 2; + let rdlen = BigEndian::read_u16(&data[*offset..*offset+2]) as usize; + *offset += 2; + if *offset + rdlen > data.len() { + return Err(Error::UnexpectedEOF); + } + let data = try!(RData::parse(typ, + &data[*offset..*offset+rdlen], data)); + *offset += rdlen; + + Ok(Opt { + udp: udp, + extrcode: extrcode, + version: version, + flags: flags, + data: data, + }) +} + +#[cfg(test)] +mod test { + + use std::net::Ipv4Addr; + use {Packet, Header}; + use Opcode::*; + use ResponseCode::NoError; + use QueryType as QT; + use QueryClass as QC; + use Class as C; + use RData; + + #[test] + fn parse_example_query() { + let query = b"\x06%\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\ + \x07example\x03com\x00\x00\x01\x00\x01"; + let packet = Packet::parse(query).unwrap(); + assert_eq!(packet.header, Header { + id: 1573, + query: true, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: false, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 0, + nameservers: 0, + additional: 0, + }); + assert_eq!(packet.questions.len(), 1); + assert_eq!(packet.questions[0].qtype, QT::A); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!(&packet.questions[0].qname.to_string()[..], "example.com"); + assert_eq!(packet.answers.len(), 0); + } + + #[test] + fn parse_example_response() { + let response = b"\x06%\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00\ + \x07example\x03com\x00\x00\x01\x00\x01\ + \xc0\x0c\x00\x01\x00\x01\x00\x00\x04\xf8\ + \x00\x04]\xb8\xd8\""; + let packet = Packet::parse(response).unwrap(); + assert_eq!(packet.header, Header { + id: 1573, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 1, + nameservers: 0, + additional: 0, + }); + assert_eq!(packet.questions.len(), 1); + assert_eq!(packet.questions[0].qtype, QT::A); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!(&packet.questions[0].qname.to_string()[..], "example.com"); + assert_eq!(packet.answers.len(), 1); + assert_eq!(&packet.answers[0].name.to_string()[..], "example.com"); + assert_eq!(packet.answers[0].multicast_unique, false); + assert_eq!(packet.answers[0].cls, C::IN); + assert_eq!(packet.answers[0].ttl, 1272); + match packet.answers[0].data { + RData::A(addr) => { + assert_eq!(addr.0, Ipv4Addr::new(93, 184, 216, 34)); + } + ref x => panic!("Wrong rdata {:?}", x), + } + } + + #[test] + fn parse_response_with_multicast_unique() { + let response = b"\x06%\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00\ + \x07example\x03com\x00\x00\x01\x00\x01\ + \xc0\x0c\x00\x01\x80\x01\x00\x00\x04\xf8\ + \x00\x04]\xb8\xd8\""; + let packet = Packet::parse(response).unwrap(); + + assert_eq!(packet.answers.len(), 1); + assert_eq!(packet.answers[0].multicast_unique, true); + assert_eq!(packet.answers[0].cls, C::IN); + } + + #[test] + fn parse_additional_record_response() { + let response = b"\x4a\xf0\x81\x80\x00\x01\x00\x01\x00\x01\x00\x01\ + \x03www\x05skype\x03com\x00\x00\x01\x00\x01\ + \xc0\x0c\x00\x05\x00\x01\x00\x00\x0e\x10\ + \x00\x1c\x07\x6c\x69\x76\x65\x63\x6d\x73\x0e\x74\ + \x72\x61\x66\x66\x69\x63\x6d\x61\x6e\x61\x67\x65\ + \x72\x03\x6e\x65\x74\x00\ + \xc0\x42\x00\x02\x00\x01\x00\x01\xd5\xd3\x00\x11\ + \x01\x67\x0c\x67\x74\x6c\x64\x2d\x73\x65\x72\x76\x65\x72\x73\ + \xc0\x42\ + \x01\x61\xc0\x55\x00\x01\x00\x01\x00\x00\xa3\x1c\ + \x00\x04\xc0\x05\x06\x1e"; + let packet = Packet::parse(response).unwrap(); + assert_eq!(packet.header, Header { + id: 19184, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 1, + nameservers: 1, + additional: 1, + }); + assert_eq!(packet.questions.len(), 1); + assert_eq!(packet.questions[0].qtype, QT::A); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!(&packet.questions[0].qname.to_string()[..], "www.skype.com"); + assert_eq!(packet.answers.len(), 1); + assert_eq!(&packet.answers[0].name.to_string()[..], "www.skype.com"); + assert_eq!(packet.answers[0].cls, C::IN); + assert_eq!(packet.answers[0].ttl, 3600); + match packet.answers[0].data { + RData::CNAME(cname) => { + assert_eq!(&cname.0.to_string()[..], "livecms.trafficmanager.net"); + } + ref x => panic!("Wrong rdata {:?}", x), + } + assert_eq!(packet.nameservers.len(), 1); + assert_eq!(&packet.nameservers[0].name.to_string()[..], "net"); + assert_eq!(packet.nameservers[0].cls, C::IN); + assert_eq!(packet.nameservers[0].ttl, 120275); + match packet.nameservers[0].data { + RData::NS(ns) => { + assert_eq!(&ns.0.to_string()[..], "g.gtld-servers.net"); + } + ref x => panic!("Wrong rdata {:?}", x), + } + assert_eq!(packet.additional.len(), 1); + assert_eq!(&packet.additional[0].name.to_string()[..], "a.gtld-servers.net"); + assert_eq!(packet.additional[0].cls, C::IN); + assert_eq!(packet.additional[0].ttl, 41756); + match packet.additional[0].data { + RData::A(addr) => { + assert_eq!(addr.0, Ipv4Addr::new(192, 5, 6, 30)); + } + ref x => panic!("Wrong rdata {:?}", x), + } + } + + #[test] + fn parse_multiple_answers() { + let response = b"\x9d\xe9\x81\x80\x00\x01\x00\x06\x00\x00\x00\x00\ + \x06google\x03com\x00\x00\x01\x00\x01\xc0\x0c\ + \x00\x01\x00\x01\x00\x00\x00\xef\x00\x04@\xe9\ + \xa4d\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\xef\ + \x00\x04@\xe9\xa4\x8b\xc0\x0c\x00\x01\x00\x01\ + \x00\x00\x00\xef\x00\x04@\xe9\xa4q\xc0\x0c\x00\ + \x01\x00\x01\x00\x00\x00\xef\x00\x04@\xe9\xa4f\ + \xc0\x0c\x00\x01\x00\x01\x00\x00\x00\xef\x00\x04@\ + \xe9\xa4e\xc0\x0c\x00\x01\x00\x01\x00\x00\x00\xef\ + \x00\x04@\xe9\xa4\x8a"; + let packet = Packet::parse(response).unwrap(); + assert_eq!(packet.header, Header { + id: 40425, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 6, + nameservers: 0, + additional: 0, + }); + assert_eq!(packet.questions.len(), 1); + assert_eq!(packet.questions[0].qtype, QT::A); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!(&packet.questions[0].qname.to_string()[..], "google.com"); + assert_eq!(packet.answers.len(), 6); + let ips = vec![ + Ipv4Addr::new(64, 233, 164, 100), + Ipv4Addr::new(64, 233, 164, 139), + Ipv4Addr::new(64, 233, 164, 113), + Ipv4Addr::new(64, 233, 164, 102), + Ipv4Addr::new(64, 233, 164, 101), + Ipv4Addr::new(64, 233, 164, 138), + ]; + for i in 0..6 { + assert_eq!(&packet.answers[i].name.to_string()[..], "google.com"); + assert_eq!(packet.answers[i].cls, C::IN); + assert_eq!(packet.answers[i].ttl, 239); + match packet.answers[i].data { + RData::A(addr) => { + assert_eq!(addr.0, ips[i]); + } + ref x => panic!("Wrong rdata {:?}", x), + } + } + } + + #[test] + fn parse_srv_query() { + let query = b"[\xd9\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\ + \x0c_xmpp-server\x04_tcp\x05gmail\x03com\x00\x00!\x00\x01"; + let packet = Packet::parse(query).unwrap(); + assert_eq!(packet.header, Header { + id: 23513, + query: true, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: false, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 0, + nameservers: 0, + additional: 0, + }); + assert_eq!(packet.questions.len(), 1); + assert_eq!(packet.questions[0].qtype, QT::SRV); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!(packet.questions[0].prefer_unicast, false); + assert_eq!(&packet.questions[0].qname.to_string()[..], + "_xmpp-server._tcp.gmail.com"); + assert_eq!(packet.answers.len(), 0); + } + + #[test] + fn parse_multicast_prefer_unicast_query() { + let query = b"\x06%\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\ + \x07example\x03com\x00\x00\x01\x80\x01"; + let packet = Packet::parse(query).unwrap(); + + assert_eq!(packet.questions.len(), 1); + assert_eq!(packet.questions[0].qtype, QT::A); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!(packet.questions[0].prefer_unicast, true); + } + + #[test] + fn parse_example_query_edns() { + let query = b"\x95\xce\x01\x00\x00\x01\x00\x00\x00\x00\x00\x01\ + \x06google\x03com\x00\x00\x01\x00\ + \x01\x00\x00\x29\x10\x00\x00\x00\x00\x00\x00\x00"; + let packet = Packet::parse(query).unwrap(); + assert_eq!(packet.header, Header { + id: 38350, + query: true, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: false, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 0, + nameservers: 0, + additional: 1, + }); + assert_eq!(packet.questions.len(), 1); + assert_eq!(packet.questions[0].qtype, QT::A); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!(&packet.questions[0].qname.to_string()[..], "google.com"); + assert_eq!(packet.answers.len(), 0); + match packet.opt { + Some(opt) => { + assert_eq!(opt.udp, 4096); + assert_eq!(opt.extrcode, 0); + assert_eq!(opt.version, 0); + assert_eq!(opt.flags, 0); + }, + None => panic!("Missing OPT RR") + } + } +} diff --git a/third_party/rust/dns-parser/src/rdata/a.rs b/third_party/rust/dns-parser/src/rdata/a.rs new file mode 100644 index 0000000000..f4196d5ac2 --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/a.rs @@ -0,0 +1,21 @@ +use std::net::Ipv4Addr; + +use Error; +use byteorder::{BigEndian, ByteOrder}; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Record(pub Ipv4Addr); + +impl<'a> super::Record<'a> for Record { + + const TYPE: isize = 1; + + fn parse(rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { + if rdata.len() != 4 { + return Err(Error::WrongRdataLength); + } + let address = Ipv4Addr::from(BigEndian::read_u32(rdata)); + let record = Record(address); + Ok(super::RData::A(record)) + } +} diff --git a/third_party/rust/dns-parser/src/rdata/aaaa.rs b/third_party/rust/dns-parser/src/rdata/aaaa.rs new file mode 100644 index 0000000000..e59937a525 --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/aaaa.rs @@ -0,0 +1,85 @@ +use std::net::Ipv6Addr; + +use Error; +use byteorder::{BigEndian, ByteOrder}; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Record(pub Ipv6Addr); + +impl<'a> super::Record<'a> for Record { + + const TYPE: isize = 28; + + fn parse(rdata: &'a [u8], _record: &'a [u8]) -> super::RDataResult<'a> { + if rdata.len() != 16 { + return Err(Error::WrongRdataLength); + } + let address = Ipv6Addr::new( + BigEndian::read_u16(&rdata[0..2]), + BigEndian::read_u16(&rdata[2..4]), + BigEndian::read_u16(&rdata[4..6]), + BigEndian::read_u16(&rdata[6..8]), + BigEndian::read_u16(&rdata[8..10]), + BigEndian::read_u16(&rdata[10..12]), + BigEndian::read_u16(&rdata[12..14]), + BigEndian::read_u16(&rdata[14..16]), + ); + let record = Record(address); + Ok(super::RData::AAAA(record)) + } +} + +#[cfg(test)] +mod test { + + use {Packet, Header}; + use Opcode::*; + use ResponseCode::NoError; + use QueryType as QT; + use QueryClass as QC; + use Class as C; + use RData; + use super::*; + + #[test] + fn parse_response() { + let response = b"\xa9\xd9\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00\x06\ + google\x03com\x00\x00\x1c\x00\x01\xc0\x0c\x00\x1c\x00\x01\x00\x00\ + \x00\x8b\x00\x10*\x00\x14P@\t\x08\x12\x00\x00\x00\x00\x00\x00 \x0e"; + + let packet = Packet::parse(response).unwrap(); + assert_eq!(packet.header, Header { + id: 43481, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 1, + nameservers: 0, + additional: 0, + }); + + assert_eq!(packet.questions.len(), 1); + assert_eq!(packet.questions[0].qtype, QT::AAAA); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!(&packet.questions[0].qname.to_string()[..], "google.com"); + assert_eq!(packet.answers.len(), 1); + assert_eq!(&packet.answers[0].name.to_string()[..], "google.com"); + assert_eq!(packet.answers[0].cls, C::IN); + assert_eq!(packet.answers[0].ttl, 139); + match packet.answers[0].data { + RData::AAAA(addr) => { + assert_eq!(addr.0, Ipv6Addr::new( + 0x2A00, 0x1450, 0x4009, 0x812, 0, 0, 0, 0x200e) + ); + } + ref x => panic!("Wrong rdata {:?}", x), + } + } +} diff --git a/third_party/rust/dns-parser/src/rdata/all.rs b/third_party/rust/dns-parser/src/rdata/all.rs new file mode 100644 index 0000000000..106c91b089 --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/all.rs @@ -0,0 +1,11 @@ +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Record; + +impl<'a> super::Record<'a> for Record { + + const TYPE: isize = 255; + + fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { + unimplemented!(); + } +} diff --git a/third_party/rust/dns-parser/src/rdata/axfr.rs b/third_party/rust/dns-parser/src/rdata/axfr.rs new file mode 100644 index 0000000000..b48dc1ae24 --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/axfr.rs @@ -0,0 +1,11 @@ +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Record; + +impl<'a> super::Record<'a> for Record { + + const TYPE: isize = 252; + + fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { + unimplemented!(); + } +} diff --git a/third_party/rust/dns-parser/src/rdata/cname.rs b/third_party/rust/dns-parser/src/rdata/cname.rs new file mode 100644 index 0000000000..0dcb46952f --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/cname.rs @@ -0,0 +1,102 @@ +use Name; + +#[derive(Debug, Clone, Copy)] +pub struct Record<'a>(pub Name<'a>); + +impl<'a> ToString for Record<'a> { + #[inline] + fn to_string(&self) -> String { + self.0.to_string() + } +} + +impl<'a> super::Record<'a> for Record<'a> { + + const TYPE: isize = 5; + + fn parse(rdata: &'a [u8], original: &'a [u8]) -> super::RDataResult<'a> { + let name = Name::scan(rdata, original)?; + let record = Record(name); + Ok(super::RData::CNAME(record)) + } +} + +#[cfg(test)] +mod test { + + use std::net::Ipv4Addr; + use {Packet, Header}; + use Opcode::*; + use ResponseCode::NoError; + use QueryType as QT; + use QueryClass as QC; + use Class as C; + use RData; + + #[test] + fn parse_response() { + let response = b"\xfc\x9d\x81\x80\x00\x01\x00\x06\x00\x02\x00\x02\x03\ + cdn\x07sstatic\x03net\x00\x00\x01\x00\x01\xc0\x0c\x00\x05\x00\x01\ + \x00\x00\x00f\x00\x02\xc0\x10\xc0\x10\x00\x01\x00\x01\x00\x00\x00\ + f\x00\x04h\x10g\xcc\xc0\x10\x00\x01\x00\x01\x00\x00\x00f\x00\x04h\ + \x10k\xcc\xc0\x10\x00\x01\x00\x01\x00\x00\x00f\x00\x04h\x10h\xcc\ + \xc0\x10\x00\x01\x00\x01\x00\x00\x00f\x00\x04h\x10j\xcc\xc0\x10\ + \x00\x01\x00\x01\x00\x00\x00f\x00\x04h\x10i\xcc\xc0\x10\x00\x02\ + \x00\x01\x00\x00\x99L\x00\x0b\x08cf-dns02\xc0\x10\xc0\x10\x00\x02\ + \x00\x01\x00\x00\x99L\x00\x0b\x08cf-dns01\xc0\x10\xc0\xa2\x00\x01\ + \x00\x01\x00\x00\x99L\x00\x04\xad\xf5:5\xc0\x8b\x00\x01\x00\x01\x00\ + \x00\x99L\x00\x04\xad\xf5;\x04"; + + let packet = Packet::parse(response).unwrap(); + assert_eq!(packet.header, Header { + id: 64669, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 6, + nameservers: 2, + additional: 2, + }); + + assert_eq!(packet.questions.len(), 1); + assert_eq!(packet.questions[0].qtype, QT::A); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!(&packet.questions[0].qname.to_string()[..], "cdn.sstatic.net"); + assert_eq!(packet.answers.len(), 6); + assert_eq!(&packet.answers[0].name.to_string()[..], "cdn.sstatic.net"); + assert_eq!(packet.answers[0].cls, C::IN); + assert_eq!(packet.answers[0].ttl, 102); + match packet.answers[0].data { + RData::CNAME(cname) => { + assert_eq!(&cname.0.to_string(), "sstatic.net"); + } + ref x => panic!("Wrong rdata {:?}", x), + } + + let ips = vec![ + Ipv4Addr::new(104, 16, 103, 204), + Ipv4Addr::new(104, 16, 107, 204), + Ipv4Addr::new(104, 16, 104, 204), + Ipv4Addr::new(104, 16, 106, 204), + Ipv4Addr::new(104, 16, 105, 204), + ]; + for i in 1..6 { + assert_eq!(&packet.answers[i].name.to_string()[..], "sstatic.net"); + assert_eq!(packet.answers[i].cls, C::IN); + assert_eq!(packet.answers[i].ttl, 102); + match packet.answers[i].data { + RData::A(addr) => { + assert_eq!(addr.0, ips[i-1]); + } + ref x => panic!("Wrong rdata {:?}", x), + } + } + } +} diff --git a/third_party/rust/dns-parser/src/rdata/hinfo.rs b/third_party/rust/dns-parser/src/rdata/hinfo.rs new file mode 100644 index 0000000000..6ee160bb86 --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/hinfo.rs @@ -0,0 +1,11 @@ +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Record; + +impl<'a> super::Record<'a> for Record { + + const TYPE: isize = 13; + + fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { + unimplemented!(); + } +} diff --git a/third_party/rust/dns-parser/src/rdata/maila.rs b/third_party/rust/dns-parser/src/rdata/maila.rs new file mode 100644 index 0000000000..ab1166cf31 --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/maila.rs @@ -0,0 +1,11 @@ +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Record; + +impl<'a> super::Record<'a> for Record { + + const TYPE: isize = 254; + + fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { + unimplemented!(); + } +} diff --git a/third_party/rust/dns-parser/src/rdata/mailb.rs b/third_party/rust/dns-parser/src/rdata/mailb.rs new file mode 100644 index 0000000000..9f043c60c3 --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/mailb.rs @@ -0,0 +1,11 @@ +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Record; + +impl<'a> super::Record<'a> for Record { + + const TYPE: isize = 253; + + fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { + unimplemented!(); + } +} diff --git a/third_party/rust/dns-parser/src/rdata/mb.rs b/third_party/rust/dns-parser/src/rdata/mb.rs new file mode 100644 index 0000000000..d14e7b4fc5 --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/mb.rs @@ -0,0 +1,11 @@ +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Record; + +impl<'a> super::Record<'a> for Record { + + const TYPE: isize = 7; + + fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { + unimplemented!(); + } +} diff --git a/third_party/rust/dns-parser/src/rdata/mf.rs b/third_party/rust/dns-parser/src/rdata/mf.rs new file mode 100644 index 0000000000..11c935d9f7 --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/mf.rs @@ -0,0 +1,11 @@ +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Record; + +impl<'a> super::Record<'a> for Record { + + const TYPE: isize = 4; + + fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { + unimplemented!(); + } +} diff --git a/third_party/rust/dns-parser/src/rdata/mg.rs b/third_party/rust/dns-parser/src/rdata/mg.rs new file mode 100644 index 0000000000..4fce456b7d --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/mg.rs @@ -0,0 +1,11 @@ +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Record; + +impl<'a> super::Record<'a> for Record { + + const TYPE: isize = 8; + + fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { + unimplemented!(); + } +} diff --git a/third_party/rust/dns-parser/src/rdata/minfo.rs b/third_party/rust/dns-parser/src/rdata/minfo.rs new file mode 100644 index 0000000000..29b3a459b2 --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/minfo.rs @@ -0,0 +1,11 @@ +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Record; + +impl<'a> super::Record<'a> for Record { + + const TYPE: isize = 14; + + fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { + unimplemented!(); + } +} diff --git a/third_party/rust/dns-parser/src/rdata/mod.rs b/third_party/rust/dns-parser/src/rdata/mod.rs new file mode 100644 index 0000000000..72df27c232 --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/mod.rs @@ -0,0 +1,83 @@ +//! Data types and methods for handling the RData field + +#![allow(missing_docs)] // resource records are pretty self-descriptive + +pub mod a; +pub mod aaaa; +pub mod all; +pub mod axfr; +pub mod cname; +pub mod hinfo; +pub mod maila; +pub mod mailb; +pub mod mb; +pub mod mf; +pub mod mg; +pub mod minfo; +pub mod mr; +pub mod mx; +pub mod ns; +pub mod nsec; +pub mod null; +pub mod opt; +pub mod ptr; +pub mod soa; +pub mod srv; +pub mod txt; +pub mod wks; + +use {Type, Error}; + +pub use self::a::Record as A; +pub use self::aaaa::Record as Aaaa; +pub use self::cname::Record as Cname; +pub use self::mx::Record as Mx; +pub use self::ns::Record as Ns; +pub use self::nsec::Record as Nsec; +pub use self::opt::Record as Opt; +pub use self::ptr::Record as Ptr; +pub use self::soa::Record as Soa; +pub use self::srv::Record as Srv; +pub use self::txt::Record as Txt; + +pub type RDataResult<'a> = Result<RData<'a>, Error>; + +/// The enumeration that represents known types of DNS resource records data +#[derive(Debug)] +pub enum RData<'a> { + A(A), + AAAA(Aaaa), + CNAME(Cname<'a>), + MX(Mx<'a>), + NS(Ns<'a>), + PTR(Ptr<'a>), + SOA(Soa<'a>), + SRV(Srv<'a>), + TXT(Txt<'a>), + /// Anything that can't be parsed yet + Unknown(&'a [u8]), +} + +pub (crate) trait Record<'a> { + const TYPE: isize; + + fn parse(rdata: &'a [u8], original: &'a [u8]) -> RDataResult<'a>; +} + +impl<'a> RData<'a> { + /// Parse an RR data and return RData enumeration + pub fn parse(typ: Type, rdata: &'a [u8], original: &'a [u8]) -> RDataResult<'a> { + match typ { + Type::A => A::parse(rdata, original), + Type::AAAA => Aaaa::parse(rdata, original), + Type::CNAME => Cname::parse(rdata, original), + Type::NS => Ns::parse(rdata, original), + Type::MX => Mx::parse(rdata, original), + Type::PTR => Ptr::parse(rdata, original), + Type::SOA => Soa::parse(rdata, original), + Type::SRV => Srv::parse(rdata, original), + Type::TXT => Txt::parse(rdata, original), + _ => Ok(RData::Unknown(rdata)), + } + } +} diff --git a/third_party/rust/dns-parser/src/rdata/mr.rs b/third_party/rust/dns-parser/src/rdata/mr.rs new file mode 100644 index 0000000000..e313372c4a --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/mr.rs @@ -0,0 +1,11 @@ +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Record; + +impl<'a> super::Record<'a> for Record { + + const TYPE: isize = 9; + + fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { + unimplemented!(); + } +} diff --git a/third_party/rust/dns-parser/src/rdata/mx.rs b/third_party/rust/dns-parser/src/rdata/mx.rs new file mode 100644 index 0000000000..33c5c41dfc --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/mx.rs @@ -0,0 +1,92 @@ +use {Name, Error}; +use byteorder::{BigEndian, ByteOrder}; + +#[derive(Debug, Clone, Copy)] +pub struct Record<'a> { + pub preference: u16, + pub exchange: Name<'a>, +} + +impl<'a> super::Record<'a> for Record<'a> { + + const TYPE: isize = 15; + + fn parse(rdata: &'a [u8], original: &'a [u8]) -> super::RDataResult<'a> { + if rdata.len() < 3 { + return Err(Error::WrongRdataLength); + } + let record = Record { + preference: BigEndian::read_u16(&rdata[..2]), + exchange: Name::scan(&rdata[2..], original)?, + }; + Ok(super::RData::MX(record)) + } +} + +#[cfg(test)] +mod test { + + use {Packet, Header}; + use Opcode::*; + use ResponseCode::NoError; + use QueryType as QT; + use QueryClass as QC; + use Class as C; + use RData; + use super::*; + + #[test] + fn parse_response() { + let response = b"\xe3\xe8\x81\x80\x00\x01\x00\x05\x00\x00\x00\x00\ + \x05gmail\x03com\x00\x00\x0f\x00\x01\xc0\x0c\x00\x0f\x00\x01\ + \x00\x00\x04|\x00\x1b\x00\x05\rgmail-smtp-in\x01l\x06google\xc0\ + \x12\xc0\x0c\x00\x0f\x00\x01\x00\x00\x04|\x00\t\x00\ + \n\x04alt1\xc0)\xc0\x0c\x00\x0f\x00\x01\x00\x00\x04|\ + \x00\t\x00(\x04alt4\xc0)\xc0\x0c\x00\x0f\x00\x01\x00\ + \x00\x04|\x00\t\x00\x14\x04alt2\xc0)\xc0\x0c\x00\x0f\ + \x00\x01\x00\x00\x04|\x00\t\x00\x1e\x04alt3\xc0)"; + let packet = Packet::parse(response).unwrap(); + assert_eq!(packet.header, Header { + id: 58344, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 5, + nameservers: 0, + additional: 0, + }); + assert_eq!(packet.questions.len(), 1); + assert_eq!(packet.questions[0].qtype, QT::MX); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!(&packet.questions[0].qname.to_string()[..], + "gmail.com"); + assert_eq!(packet.answers.len(), 5); + let items = vec![ + ( 5, "gmail-smtp-in.l.google.com"), + (10, "alt1.gmail-smtp-in.l.google.com"), + (40, "alt4.gmail-smtp-in.l.google.com"), + (20, "alt2.gmail-smtp-in.l.google.com"), + (30, "alt3.gmail-smtp-in.l.google.com"), + ]; + for i in 0..5 { + assert_eq!(&packet.answers[i].name.to_string()[..], + "gmail.com"); + assert_eq!(packet.answers[i].cls, C::IN); + assert_eq!(packet.answers[i].ttl, 1148); + match *&packet.answers[i].data { + RData::MX( Record { preference, exchange }) => { + assert_eq!(preference, items[i].0); + assert_eq!(exchange.to_string(), (items[i].1).to_string()); + } + ref x => panic!("Wrong rdata {:?}", x), + } + } + } +} diff --git a/third_party/rust/dns-parser/src/rdata/ns.rs b/third_party/rust/dns-parser/src/rdata/ns.rs new file mode 100644 index 0000000000..42a2f29ab1 --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/ns.rs @@ -0,0 +1,88 @@ +use Name; + +#[derive(Debug, Clone, Copy)] +pub struct Record<'a>(pub Name<'a>); + +impl<'a> ToString for Record<'a> { + #[inline] + fn to_string(&self) -> String { + self.0.to_string() + } +} + +impl<'a> super::Record<'a> for Record<'a> { + + const TYPE: isize = 2; + + fn parse(rdata: &'a [u8], original: &'a [u8]) -> super::RDataResult<'a> { + let name = Name::scan(rdata, original)?; + let record = Record(name); + Ok(super::RData::NS(record)) + } +} + +#[cfg(test)] +mod test { + + use {Packet, Header}; + use Opcode::*; + use ResponseCode::NoError; + use QueryType as QT; + use QueryClass as QC; + use Class as C; + use RData; + + #[test] + fn parse_response() { + let response = b"\x4a\xf0\x81\x80\x00\x01\x00\x01\x00\x01\x00\x00\ + \x03www\x05skype\x03com\x00\x00\x01\x00\x01\ + \xc0\x0c\x00\x05\x00\x01\x00\x00\x0e\x10\ + \x00\x1c\x07\x6c\x69\x76\x65\x63\x6d\x73\x0e\x74\ + \x72\x61\x66\x66\x69\x63\x6d\x61\x6e\x61\x67\x65\ + \x72\x03\x6e\x65\x74\x00\ + \xc0\x42\x00\x02\x00\x01\x00\x01\xd5\xd3\x00\x11\ + \x01\x67\x0c\x67\x74\x6c\x64\x2d\x73\x65\x72\x76\x65\x72\x73\ + \xc0\x42"; + let packet = Packet::parse(response).unwrap(); + assert_eq!(packet.header, Header { + id: 19184, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 1, + nameservers: 1, + additional: 0, + }); + assert_eq!(packet.questions.len(), 1); + assert_eq!(packet.questions[0].qtype, QT::A); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!(&packet.questions[0].qname.to_string()[..], "www.skype.com"); + assert_eq!(packet.answers.len(), 1); + assert_eq!(&packet.answers[0].name.to_string()[..], "www.skype.com"); + assert_eq!(packet.answers[0].cls, C::IN); + assert_eq!(packet.answers[0].ttl, 3600); + match packet.answers[0].data { + RData::CNAME(cname) => { + assert_eq!(&cname.0.to_string()[..], "livecms.trafficmanager.net"); + } + ref x => panic!("Wrong rdata {:?}", x), + } + assert_eq!(packet.nameservers.len(), 1); + assert_eq!(&packet.nameservers[0].name.to_string()[..], "net"); + assert_eq!(packet.nameservers[0].cls, C::IN); + assert_eq!(packet.nameservers[0].ttl, 120275); + match packet.nameservers[0].data { + RData::NS(ns) => { + assert_eq!(&ns.0.to_string()[..], "g.gtld-servers.net"); + } + ref x => panic!("Wrong rdata {:?}", x), + } + } +} diff --git a/third_party/rust/dns-parser/src/rdata/nsec.rs b/third_party/rust/dns-parser/src/rdata/nsec.rs new file mode 100644 index 0000000000..354c512273 --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/nsec.rs @@ -0,0 +1,11 @@ +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Record; + +impl<'a> super::Record<'a> for Record { + + const TYPE: isize = 47; + + fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { + unimplemented!(); + } +} diff --git a/third_party/rust/dns-parser/src/rdata/null.rs b/third_party/rust/dns-parser/src/rdata/null.rs new file mode 100644 index 0000000000..b3bfcd35ee --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/null.rs @@ -0,0 +1,11 @@ +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Record; + +impl<'a> super::Record<'a> for Record { + + const TYPE: isize = 10; + + fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { + unimplemented!(); + } +} diff --git a/third_party/rust/dns-parser/src/rdata/opt.rs b/third_party/rust/dns-parser/src/rdata/opt.rs new file mode 100644 index 0000000000..694d393440 --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/opt.rs @@ -0,0 +1,18 @@ +/// RFC 6891 OPT RR +#[derive(Debug)] +pub struct Record<'a> { + pub udp: u16, + pub extrcode: u8, + pub version: u8, + pub flags: u16, + pub data: super::RData<'a>, +} + +impl<'a> super::Record<'a> for Record<'a> { + + const TYPE: isize = 41; + + fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { + unimplemented!(); + } +} diff --git a/third_party/rust/dns-parser/src/rdata/ptr.rs b/third_party/rust/dns-parser/src/rdata/ptr.rs new file mode 100644 index 0000000000..315c3795a8 --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/ptr.rs @@ -0,0 +1,74 @@ +use Name; + +#[derive(Debug, Clone, Copy)] +pub struct Record<'a>(pub Name<'a>); + +impl<'a> ToString for Record<'a> { + #[inline] + fn to_string(&self) -> String { + self.0.to_string() + } +} + +impl<'a> super::Record<'a> for Record<'a> { + + const TYPE: isize = 12; + + fn parse(rdata: &'a [u8], original: &'a [u8]) -> super::RDataResult<'a> { + let name = Name::scan(rdata, original)?; + let record = Record(name); + Ok(super::RData::PTR(record)) + } +} + +#[cfg(test)] +mod test { + + use {Packet, Header}; + use Opcode::*; + use ResponseCode::NoError; + use QueryType as QT; + use QueryClass as QC; + use Class as C; + use RData; + + #[test] + fn parse_response() { + let response = b"\x53\xd6\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00\ + \x0269\x0293\x0275\x0272\x07in-addr\x04arpa\x00\ + \x00\x0c\x00\x01\ + \xc0\x0c\x00\x0c\x00\x01\x00\x01\x51\x80\x00\x1e\ + \x10pool-72-75-93-69\x07verizon\x03net\x00"; + let packet = Packet::parse(response).unwrap(); + assert_eq!(packet.header, Header { + id: 21462, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 1, + nameservers: 0, + additional: 0, + }); + assert_eq!(packet.questions.len(), 1); + assert_eq!(packet.questions[0].qtype, QT::PTR); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!(&packet.questions[0].qname.to_string()[..], "69.93.75.72.in-addr.arpa"); + assert_eq!(packet.answers.len(), 1); + assert_eq!(&packet.answers[0].name.to_string()[..], "69.93.75.72.in-addr.arpa"); + assert_eq!(packet.answers[0].cls, C::IN); + assert_eq!(packet.answers[0].ttl, 86400); + match packet.answers[0].data { + RData::PTR(name) => { + assert_eq!(&name.0.to_string()[..], "pool-72-75-93-69.verizon.net"); + } + ref x => panic!("Wrong rdata {:?}", x), + } + } +} diff --git a/third_party/rust/dns-parser/src/rdata/soa.rs b/third_party/rust/dns-parser/src/rdata/soa.rs new file mode 100644 index 0000000000..e6d5f1712d --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/soa.rs @@ -0,0 +1,101 @@ +use {Name, Error}; +use byteorder::{BigEndian, ByteOrder}; + +/// The SOA (Start of Authority) record +#[derive(Debug, Clone, Copy)] +pub struct Record<'a> { + pub primary_ns: Name<'a>, + pub mailbox: Name<'a>, + pub serial: u32, + pub refresh: u32, + pub retry: u32, + pub expire: u32, + pub minimum_ttl: u32, +} + +impl<'a> super::Record<'a> for Record<'a> { + + const TYPE: isize = 6; + + fn parse(rdata: &'a [u8], original: &'a [u8]) -> super::RDataResult<'a> { + let mut pos = 0; + let primary_name_server = try!(Name::scan(rdata, original)); + pos += primary_name_server.byte_len(); + let mailbox = try!(Name::scan(&rdata[pos..], original)); + pos += mailbox.byte_len(); + if rdata[pos..].len() < 20 { + return Err(Error::WrongRdataLength); + } + let record = Record { + primary_ns: primary_name_server, + mailbox: mailbox, + serial: BigEndian::read_u32(&rdata[pos..(pos+4)]), + refresh: BigEndian::read_u32(&rdata[(pos+4)..(pos+8)]), + retry: BigEndian::read_u32(&rdata[(pos+8)..(pos+12)]), + expire: BigEndian::read_u32(&rdata[(pos+12)..(pos+16)]), + minimum_ttl: BigEndian::read_u32(&rdata[(pos+16)..(pos+20)]), + }; + Ok(super::RData::SOA(record)) + } +} + +#[cfg(test)] +mod test { + + use {Packet, Header}; + use Opcode::*; + use ResponseCode::NameError; + use QueryType as QT; + use QueryClass as QC; + use Class as C; + use RData; + + #[test] + fn parse_response() { + let response = b"\x9f\xc5\x85\x83\x00\x01\x00\x00\x00\x01\x00\x00\ + \x0edlkfjkdjdslfkj\x07youtube\x03com\x00\x00\x01\x00\x01\ + \xc0\x1b\x00\x06\x00\x01\x00\x00\x2a\x30\x00\x1e\xc0\x1b\ + \x05admin\xc0\x1b\x77\xed\x2a\x73\x00\x00\x51\x80\x00\x00\ + \x0e\x10\x00\x00\x3a\x80\x00\x00\x2a\x30"; + let packet = Packet::parse(response).unwrap(); + assert_eq!(packet.header, Header { + id: 40901, + query: false, + opcode: StandardQuery, + authoritative: true, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NameError, + questions: 1, + answers: 0, + nameservers: 1, + additional: 0, + }); + assert_eq!(packet.questions.len(), 1); + assert_eq!(packet.questions[0].qtype, QT::A); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!(&packet.questions[0].qname.to_string()[..], "dlkfjkdjdslfkj.youtube.com"); + assert_eq!(packet.answers.len(), 0); + + assert_eq!(packet.nameservers.len(), 1); + assert_eq!(&packet.nameservers[0].name.to_string()[..], "youtube.com"); + assert_eq!(packet.nameservers[0].cls, C::IN); + assert_eq!(packet.nameservers[0].multicast_unique, false); + assert_eq!(packet.nameservers[0].ttl, 10800); + match packet.nameservers[0].data { + RData::SOA(ref soa_rec) => { + assert_eq!(&soa_rec.primary_ns.to_string()[..], "youtube.com"); + assert_eq!(&soa_rec.mailbox.to_string()[..], "admin.youtube.com"); + assert_eq!(soa_rec.serial, 2012031603); + assert_eq!(soa_rec.refresh, 20864); + assert_eq!(soa_rec.retry, 3600); + assert_eq!(soa_rec.expire, 14976); + assert_eq!(soa_rec.minimum_ttl, 10800); + } + ref x => panic!("Wrong rdata {:?}", x), + } + } +} diff --git a/third_party/rust/dns-parser/src/rdata/srv.rs b/third_party/rust/dns-parser/src/rdata/srv.rs new file mode 100644 index 0000000000..dbc151d8aa --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/srv.rs @@ -0,0 +1,102 @@ +use {Name, Error}; +use byteorder::{BigEndian, ByteOrder}; + +#[derive(Debug, Clone, Copy)] +pub struct Record<'a> { + pub priority: u16, + pub weight: u16, + pub port: u16, + pub target: Name<'a>, +} + +impl<'a> super::Record<'a> for Record<'a> { + + const TYPE: isize = 33; + + fn parse(rdata: &'a [u8], original: &'a [u8]) -> super::RDataResult<'a> { + if rdata.len() < 7 { + return Err(Error::WrongRdataLength); + } + let record = Record { + priority: BigEndian::read_u16(&rdata[..2]), + weight: BigEndian::read_u16(&rdata[2..4]), + port: BigEndian::read_u16(&rdata[4..6]), + target: Name::scan(&rdata[6..], original)?, + }; + Ok(super::RData::SRV(record)) + } +} + +#[cfg(test)] +mod test { + + use {Packet, Header}; + use Opcode::*; + use ResponseCode::NoError; + use QueryType as QT; + use QueryClass as QC; + use Class as C; + use RData; + use super::*; + + #[test] + fn parse_response() { + let response = b"[\xd9\x81\x80\x00\x01\x00\x05\x00\x00\x00\x00\ + \x0c_xmpp-server\x04_tcp\x05gmail\x03com\x00\x00!\x00\x01\ + \xc0\x0c\x00!\x00\x01\x00\x00\x03\x84\x00 \x00\x05\x00\x00\ + \x14\x95\x0bxmpp-server\x01l\x06google\x03com\x00\xc0\x0c\x00!\ + \x00\x01\x00\x00\x03\x84\x00%\x00\x14\x00\x00\x14\x95\ + \x04alt3\x0bxmpp-server\x01l\x06google\x03com\x00\ + \xc0\x0c\x00!\x00\x01\x00\x00\x03\x84\x00%\x00\x14\x00\x00\ + \x14\x95\x04alt1\x0bxmpp-server\x01l\x06google\x03com\x00\ + \xc0\x0c\x00!\x00\x01\x00\x00\x03\x84\x00%\x00\x14\x00\x00\ + \x14\x95\x04alt2\x0bxmpp-server\x01l\x06google\x03com\x00\ + \xc0\x0c\x00!\x00\x01\x00\x00\x03\x84\x00%\x00\x14\x00\x00\ + \x14\x95\x04alt4\x0bxmpp-server\x01l\x06google\x03com\x00"; + let packet = Packet::parse(response).unwrap(); + assert_eq!(packet.header, Header { + id: 23513, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 5, + nameservers: 0, + additional: 0, + }); + assert_eq!(packet.questions.len(), 1); + assert_eq!(packet.questions[0].qtype, QT::SRV); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!(&packet.questions[0].qname.to_string()[..], + "_xmpp-server._tcp.gmail.com"); + assert_eq!(packet.answers.len(), 5); + let items = vec![ + (5, 0, 5269, "xmpp-server.l.google.com"), + (20, 0, 5269, "alt3.xmpp-server.l.google.com"), + (20, 0, 5269, "alt1.xmpp-server.l.google.com"), + (20, 0, 5269, "alt2.xmpp-server.l.google.com"), + (20, 0, 5269, "alt4.xmpp-server.l.google.com"), + ]; + for i in 0..5 { + assert_eq!(&packet.answers[i].name.to_string()[..], + "_xmpp-server._tcp.gmail.com"); + assert_eq!(packet.answers[i].cls, C::IN); + assert_eq!(packet.answers[i].ttl, 900); + match *&packet.answers[i].data { + RData::SRV(Record { priority, weight, port, target }) => { + assert_eq!(priority, items[i].0); + assert_eq!(weight, items[i].1); + assert_eq!(port, items[i].2); + assert_eq!(target.to_string(), (items[i].3).to_string()); + } + ref x => panic!("Wrong rdata {:?}", x), + } + } + } +} diff --git a/third_party/rust/dns-parser/src/rdata/txt.rs b/third_party/rust/dns-parser/src/rdata/txt.rs new file mode 100644 index 0000000000..8f5f5fc26d --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/txt.rs @@ -0,0 +1,125 @@ +use Error; + +#[derive(Debug, Clone)] +pub struct Record<'a> { + bytes: &'a [u8], +} + +#[derive(Debug)] +pub struct RecordIter<'a> { + bytes: &'a [u8], +} + +impl<'a> Iterator for RecordIter<'a> { + type Item = &'a [u8]; + fn next(&mut self) -> Option<&'a [u8]> { + if self.bytes.len() >= 1 { + let len = self.bytes[0] as usize; + debug_assert!(self.bytes.len() >= len+1); + let (head, tail) = self.bytes[1..].split_at(len); + self.bytes = tail; + return Some(head); + } + return None; + } +} + +impl<'a> Record<'a> { + + // Returns iterator over text chunks + pub fn iter(&self) -> RecordIter<'a> { + RecordIter { + bytes: self.bytes, + } + } +} + +impl<'a> super::Record<'a> for Record<'a> { + + const TYPE: isize = 16; + + fn parse(rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { + // Just a quick check that record is valid + let len = rdata.len(); + if len < 1 { + return Err(Error::WrongRdataLength); + } + let mut pos = 0; + while pos < len { + let rdlen = rdata[pos] as usize; + pos += 1; + if len < rdlen + pos { + return Err(Error::WrongRdataLength); + } + pos += rdlen; + } + Ok(super::RData::TXT(Record { + bytes: rdata, + })) + } +} + +#[cfg(test)] +mod test { + + use std::str::from_utf8; + + use {Packet, Header}; + use Opcode::*; + use ResponseCode::NoError; + use QueryType as QT; + use QueryClass as QC; + use Class as C; + use RData; + + #[test] + fn parse_response_multiple_strings() { + let response = b"\x06%\x81\x80\x00\x01\x00\x01\x00\x00\x00\x00\ + \x08facebook\x03com\x00\x00\x10\x00\x01\ + \xc0\x0c\x00\x10\x00\x01\x00\x01\x51\x3d\x00\x23\ + \x15\x76\x3d\x73\x70\x66\x31\x20\x72\x65\x64\x69\ + \x72\x65\x63\x74\x3d\x5f\x73\x70\x66\x2e\ + \x0c\x66\x61\x63\x65\x62\x6f\x6f\x6b\x2e\x63\x6f\x6d"; + + let packet = Packet::parse(response).unwrap(); + assert_eq!(packet.header, Header { + id: 1573, + query: false, + opcode: StandardQuery, + authoritative: false, + truncated: false, + recursion_desired: true, + recursion_available: true, + authenticated_data: false, + checking_disabled: false, + response_code: NoError, + questions: 1, + answers: 1, + nameservers: 0, + additional: 0, + }); + assert_eq!(packet.questions.len(), 1); + assert_eq!(packet.questions[0].qtype, QT::TXT); + assert_eq!(packet.questions[0].qclass, QC::IN); + assert_eq!(&packet.questions[0].qname.to_string()[..], "facebook.com"); + assert_eq!(packet.answers.len(), 1); + assert_eq!(&packet.answers[0].name.to_string()[..], "facebook.com"); + assert_eq!(packet.answers[0].multicast_unique, false); + assert_eq!(packet.answers[0].cls, C::IN); + assert_eq!(packet.answers[0].ttl, 86333); + match packet.answers[0].data { + RData::TXT(ref text) => { + assert_eq!(text.iter() + .map(|x| from_utf8(x).unwrap()) + .collect::<Vec<_>>() + .concat(), "v=spf1 redirect=_spf.facebook.com"); + + // also assert boundaries are kept + assert_eq!(text.iter().collect::<Vec<_>>(), + ["v=spf1 redirect=_spf.".as_bytes(), + "facebook.com".as_bytes()]); + } + ref x => panic!("Wrong rdata {:?}", x), + } + } +} diff --git a/third_party/rust/dns-parser/src/rdata/wks.rs b/third_party/rust/dns-parser/src/rdata/wks.rs new file mode 100644 index 0000000000..ff6e5f1881 --- /dev/null +++ b/third_party/rust/dns-parser/src/rdata/wks.rs @@ -0,0 +1,11 @@ +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Record; + +impl<'a> super::Record<'a> for Record { + + const TYPE: isize = 11; + + fn parse(_rdata: &'a [u8], _original: &'a [u8]) -> super::RDataResult<'a> { + unimplemented!(); + } +} diff --git a/third_party/rust/dns-parser/src/structs.rs b/third_party/rust/dns-parser/src/structs.rs new file mode 100644 index 0000000000..4f60639047 --- /dev/null +++ b/third_party/rust/dns-parser/src/structs.rs @@ -0,0 +1,50 @@ +use {QueryType, QueryClass, Name, Class, Header, RData}; +use rdata::opt; + + +/// Parsed DNS packet +#[derive(Debug)] +#[allow(missing_docs)] // should be covered by spec +pub struct Packet<'a> { + pub header: Header, + pub questions: Vec<Question<'a>>, + pub answers: Vec<ResourceRecord<'a>>, + pub nameservers: Vec<ResourceRecord<'a>>, + pub additional: Vec<ResourceRecord<'a>>, + /// Optional Pseudo-RR + /// When present it is sent as an RR in the additional section. In this RR + /// the `class` and `ttl` fields store max udp packet size and flags + /// respectively. To keep `ResourceRecord` clean we store the OPT record + /// here. + pub opt: Option<opt::Record<'a>>, +} + +/// A parsed chunk of data in the Query section of the packet +#[derive(Debug)] +#[allow(missing_docs)] // should be covered by spec +pub struct Question<'a> { + pub qname: Name<'a>, + /// Whether or not we prefer unicast responses. + /// This is used in multicast DNS. + pub prefer_unicast: bool, + pub qtype: QueryType, + pub qclass: QueryClass, +} + +/// A single DNS record +/// +/// We aim to provide whole range of DNS records available. But as time is +/// limited we have some types of packets which are parsed and other provided +/// as unparsed slice of bytes. +#[derive(Debug)] +#[allow(missing_docs)] // should be covered by spec +pub struct ResourceRecord<'a> { + pub name: Name<'a>, + /// Whether or not the set of resource records is fully contained in the + /// packet, or whether there will be more resource records in future + /// packets. Only used for multicast DNS. + pub multicast_unique: bool, + pub cls: Class, + pub ttl: u32, + pub data: RData<'a>, +} diff --git a/third_party/rust/dns-parser/vagga.yaml b/third_party/rust/dns-parser/vagga.yaml new file mode 100644 index 0000000000..ddedd53c5c --- /dev/null +++ b/third_party/rust/dns-parser/vagga.yaml @@ -0,0 +1,44 @@ +commands: + + cargo: !Command + description: Run any cargo command + container: ubuntu + run: [cargo] + + make: !Command + description: Build the library + container: ubuntu + run: [cargo, build] + + test: !Command + description: Run the tests + container: ubuntu + environ: + RUST_BACKTRACE: 1 + run: [cargo, test] + + _bulk: !Command + description: Run `bulk` command (for version bookkeeping) + container: ubuntu + run: [bulk] + +containers: + + ubuntu: + setup: + - !Ubuntu xenial + - !UbuntuUniverse + - !Install [ca-certificates, build-essential, vim] + + - !TarInstall + url: "https://static.rust-lang.org/dist/rust-1.28.0-x86_64-unknown-linux-gnu.tar.gz" + script: "./install.sh --prefix=/usr \ + --components=rustc,rust-std-x86_64-unknown-linux-gnu,cargo" + - &bulk !Tar + url: "https://github.com/tailhook/bulk/releases/download/v0.4.11/bulk-v0.4.11.tar.gz" + sha256: b718bb8448e726690c94d98d004bf7575f7a429106ec26ad3faf11e0fd9a7978 + path: / + + environ: + HOME: /work/target + USER: pc |