286 lines
7.2 KiB
Rust
286 lines
7.2 KiB
Rust
use byteorder::ByteOrder;
|
|
use bytes::{Bytes, Buf, BufMut, BigEndian};
|
|
use common_types::{DnsCompressedName, Class, types};
|
|
use errors::*;
|
|
use packet::Resource;
|
|
use records::UnknownRecord;
|
|
use ser::packet::{DnsPacketData, DnsPacketWriteContext, get_blob, remaining_bytes};
|
|
use std::io::{Cursor, Read};
|
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
|
pub enum OptError {
|
|
UnknownVersion,
|
|
InvalidData,
|
|
}
|
|
|
|
/// parsing OPT can fail without having to throw away the packet
|
|
/// completely; just fail in some way
|
|
///
|
|
/// TODO: define some useful error...
|
|
pub type OptResult<T> = ::std::result::Result<T, OptError>;
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
|
pub struct OptFlag(pub u16);
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
|
pub struct OptFlags(pub u16);
|
|
|
|
impl ::std::ops::BitOr<OptFlag> for OptFlags {
|
|
type Output = bool;
|
|
|
|
fn bitor(self, rhs: OptFlag) -> Self::Output {
|
|
0 != self.0 & rhs.0
|
|
}
|
|
}
|
|
|
|
impl ::std::ops::BitOr<OptFlags> for OptFlag {
|
|
type Output = bool;
|
|
|
|
fn bitor(self, rhs: OptFlags) -> Self::Output {
|
|
0 != self.0 & rhs.0
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
|
pub enum DnsOption {
|
|
/// DNS Name Server Identifier (0x0003)
|
|
NSID(Bytes),
|
|
// DAU (0x0005)
|
|
// DHU (0x0006)
|
|
// N3U (0x0007)
|
|
// edns-client-subnet (0x0008)
|
|
ClientSubnet {
|
|
source_prefix_length: u8,
|
|
scope_prefix_length: u8,
|
|
addr: IpAddr,
|
|
},
|
|
// EDNS EXPIRE (0x0009)
|
|
// COOKIE (0x000a)
|
|
// edns-tcp-keepalive (0x000b)
|
|
// Padding (0x000c)
|
|
// CHAIN (0x000d)
|
|
// edns-key-tag (0x000e)
|
|
// DeviceID (26946 = 0x6942)
|
|
Unknown {
|
|
code: u16,
|
|
data: Bytes,
|
|
}
|
|
}
|
|
|
|
impl DnsOption {
|
|
fn parse_client_subnet(data: &mut Cursor<Bytes>) -> Result<Self> {
|
|
let addr_family = u16::deserialize(data)?;
|
|
let source_prefix_length = u8::deserialize(data)?;
|
|
let scope_prefix_length = u8::deserialize(data)?;
|
|
let addr_prefix_len = ((source_prefix_length + 7) / 8) as usize;
|
|
ensure!(scope_prefix_length <= source_prefix_length, "scope prefix {} > source prefix {}", scope_prefix_length, source_prefix_length);
|
|
let addr = match addr_family {
|
|
1 => {
|
|
ensure!(source_prefix_length <= 32, "invalid prefix for IPv4");
|
|
let mut o = [0u8; 4];
|
|
data.read_exact(&mut o[0..addr_prefix_len])?;
|
|
if 0 != source_prefix_length % 8 {
|
|
let mask = 0xff >> (source_prefix_length % 8);
|
|
ensure!(0 == o[addr_prefix_len - 1] & mask, "non-zero padding");
|
|
}
|
|
IpAddr::V4(Ipv4Addr::from(o))
|
|
},
|
|
2 => {
|
|
ensure!(source_prefix_length <= 128, "invalid prefix for IPv6");
|
|
let mut o = [0u8; 16];
|
|
data.read_exact(&mut o[0..addr_prefix_len])?;
|
|
if 0 != source_prefix_length % 8 {
|
|
let mask = 0xff >> (source_prefix_length % 8);
|
|
ensure!(0 == o[addr_prefix_len - 1] & mask, "non-zero padding");
|
|
}
|
|
IpAddr::V6(Ipv6Addr::from(o))
|
|
},
|
|
_ => {
|
|
bail!("unknown address family {}", addr_family);
|
|
},
|
|
};
|
|
Ok(DnsOption::ClientSubnet{
|
|
source_prefix_length,
|
|
scope_prefix_length,
|
|
addr,
|
|
})
|
|
}
|
|
|
|
fn parse_opt(code: u16, opt_data: Bytes) -> Result<Self> {
|
|
let mut data = Cursor::new(opt_data);
|
|
|
|
let result = (|| Ok(match code {
|
|
0x0003 => DnsOption::NSID(remaining_bytes(&mut data)),
|
|
0x0008 => DnsOption::parse_client_subnet(&mut data)?,
|
|
_ => bail!("unknown option {}", code),
|
|
}))()?;
|
|
|
|
ensure!(!data.has_remaining(), "option data remaining: {} bytes", data.remaining());
|
|
|
|
Ok(result)
|
|
}
|
|
|
|
fn write_opt_data(&self, _context: &mut DnsPacketWriteContext, packet: &mut Vec<u8>) -> Result<()> {
|
|
match *self {
|
|
DnsOption::NSID(ref id) => {
|
|
packet.reserve(id.len());
|
|
packet.put_slice(id);
|
|
},
|
|
DnsOption::ClientSubnet{source_prefix_length, scope_prefix_length, ref addr} => {
|
|
let addr_prefix_len = ((source_prefix_length + 7) / 8) as usize;
|
|
packet.reserve(4 + addr_prefix_len);
|
|
packet.put_u16::<BigEndian>(match *addr {
|
|
IpAddr::V4(_) => 1,
|
|
IpAddr::V6(_) => 2,
|
|
});
|
|
packet.put_u8(source_prefix_length);
|
|
packet.put_u8(scope_prefix_length);
|
|
|
|
match *addr {
|
|
IpAddr::V4(ref addr) => {
|
|
let o = addr.octets();
|
|
packet.put_slice(&o[..addr_prefix_len]);
|
|
},
|
|
IpAddr::V6(ref addr) => {
|
|
let o = addr.octets();
|
|
packet.put_slice(&o[..addr_prefix_len]);
|
|
},
|
|
}
|
|
},
|
|
DnsOption::Unknown{ref data, ..} => {
|
|
packet.reserve(data.len());
|
|
packet.put_slice(data);
|
|
},
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl DnsPacketData for DnsOption {
|
|
fn deserialize(data: &mut Cursor<Bytes>) -> Result<Self> {
|
|
let code = u16::deserialize(data)?;
|
|
let opt_len = u16::deserialize(data)? as usize;
|
|
|
|
let opt_data = get_blob(data, opt_len)?;
|
|
|
|
DnsOption::parse_opt(code, opt_data.clone()).or_else(|_| {
|
|
Ok(DnsOption::Unknown{
|
|
code: code,
|
|
data: opt_data,
|
|
})
|
|
})
|
|
}
|
|
|
|
fn serialize(&self, context: &mut DnsPacketWriteContext, packet: &mut Vec<u8>) -> Result<()> {
|
|
let code: u16 = match *self {
|
|
DnsOption::NSID(_) => 0x0003,
|
|
DnsOption::ClientSubnet{..} => 0x0008,
|
|
DnsOption::Unknown{code, ..} => code,
|
|
};
|
|
code.serialize(context, packet)?;
|
|
|
|
let opt_len_pos = packet.len();
|
|
packet.reserve(2);
|
|
packet.put_u16::<BigEndian>(0); // stub
|
|
|
|
let opt_start = packet.len();
|
|
self.write_opt_data(context, packet)?;
|
|
let opt_end = packet.len();
|
|
let opt_len = opt_end - opt_start;
|
|
|
|
ensure!(opt_len < 0x1_0000, "OPTION DATA too big");
|
|
|
|
// now patch length
|
|
BigEndian::write_u16(&mut packet[opt_len_pos..][..2], opt_len as u16);
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub const DNSSEC_OK: OptFlag = OptFlag(0x8000);
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
|
pub struct Opt {
|
|
// "CLASS"
|
|
pub udp_payload_size: u16,
|
|
// "TTL"
|
|
pub extended_rcode_high: u8,
|
|
pub version: u8,
|
|
pub flags: OptFlags,
|
|
|
|
// RDATA
|
|
pub options: Vec<DnsOption>,
|
|
}
|
|
|
|
impl Opt {
|
|
fn deserialize_options(ur: &UnknownRecord) -> Result<Vec<DnsOption>> {
|
|
let mut data = Cursor::new(ur.raw().clone());
|
|
let mut options = Vec::new();
|
|
while data.has_remaining() {
|
|
options.push(DnsOption::deserialize(&mut data)?);
|
|
}
|
|
|
|
Ok(options)
|
|
}
|
|
|
|
pub fn deserialize(r: &Resource) -> Result<OptResult<Self>> {
|
|
let udp_payload_size = r.class.0;
|
|
let extended_rcode_high = (r.ttl >> 24) as u8;
|
|
let version = (r.ttl >> 16) as u8;
|
|
let flags = OptFlags(r.ttl as u16);
|
|
|
|
if version > 0 { return Ok(Err(OptError::UnknownVersion)); }
|
|
|
|
let ur = match r.data.as_any().downcast_ref::<UnknownRecord>() {
|
|
Some(ur) => ur,
|
|
None => bail!("need to parse OPT from UnknownRecord"),
|
|
};
|
|
|
|
let options = match Opt::deserialize_options(ur) {
|
|
Ok(options) => options,
|
|
Err(_) => return Ok(Err(OptError::InvalidData)),
|
|
};
|
|
|
|
Ok(Ok(Opt {
|
|
udp_payload_size,
|
|
extended_rcode_high,
|
|
version,
|
|
flags,
|
|
options,
|
|
}))
|
|
}
|
|
|
|
pub fn serialize(&self) -> Result<Resource> {
|
|
let mut data = Vec::new();
|
|
let mut ctx = DnsPacketWriteContext::new();
|
|
|
|
for o in &self.options {
|
|
o.serialize(&mut ctx, &mut data)?;
|
|
}
|
|
let ur = UnknownRecord::new(types::OPT, data.into());
|
|
|
|
let ttl = ((self.extended_rcode_high as u32) << 24)
|
|
| ((self.version as u32) << 16)
|
|
| self.flags.0 as u32;
|
|
Ok(Resource{
|
|
name: DnsCompressedName::new_root(),
|
|
class: Class(self.udp_payload_size),
|
|
ttl: ttl,
|
|
data: Box::new(ur) as _,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Default for Opt {
|
|
fn default() -> Self {
|
|
Opt {
|
|
udp_payload_size: 500,
|
|
extended_rcode_high: 0,
|
|
version: 0,
|
|
flags: OptFlags(0),
|
|
options: Vec::new(),
|
|
}
|
|
}
|
|
}
|