1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
/*
 * Copyright (C) 2015-2016 Benjamin Fry <benjaminfry@me.com>
 *
 * 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.
 */
use ::rr::{Name, Record, RecordType, RData};


/// Set of resource records associated to a name and type
#[derive(Debug)]
pub struct RRSet {
  name: Name,
  record_type: RecordType,
  ttl: u32,
  records: Vec<Record>,
  rrsigs: Vec<Record>,
  serial: u32, // serial number at which this record was modified
}

impl RRSet {
  /// Creates a new Resource Record Set.
  ///
  /// # Arguments
  ///
  /// * `name` - The label for the `RRSet`
  /// * `record_type` - `RecordType` of this `RRSet`, all records in the `RRSet` must be of the
  ///                   specified `RecordType`.
  /// * `serial` - current serial number of the `SOA` record, this is to be used for `IXFR` and
  ///              signing for DNSSec after updates.
  ///
  /// # Return value
  ///
  /// The newly created Resource Record Set
  pub fn new(name: &Name, record_type: RecordType, serial: u32) -> RRSet {
    RRSet{name: name.clone(), record_type: record_type, ttl: 0, records: Vec::new(), rrsigs: Vec::new(), serial: serial}
  }

  /// # Return value
  ///
  /// Label of the Resource Record Set
  pub fn get_name(&self) -> &Name {
    &self.name
  }

  /// # Return value
  ///
  /// `RecordType` of the Resource Record Set
  pub fn get_record_type(&self) -> RecordType {
    self.record_type
  }

  /// # Return value
  ///
  /// TTL, time-to-live, of the Resource Record Set, this is the maximum length of time that an
  /// RRSet should be cached.
  pub fn get_ttl(&self) -> u32 {
    self.ttl
  }

  /// # Return value
  ///
  /// Slice of all records in the set
  pub fn get_records(&self, and_rrsigs: bool) -> Vec<&Record> {
    if and_rrsigs {
      self.records.iter().chain(self.rrsigs.iter()).collect()
    } else {
      self.records.iter().collect()
    }
  }

  /// # Return value
  ///
  /// True if there are no records in this set
  pub fn is_empty(&self) -> bool {
    self.records.is_empty()
  }

  /// # Return value
  ///
  /// The serial number at which the record was updated.
  pub fn get_serial(&self) -> u32 {
    self.serial
  }

  pub fn get_rrsigs(&self) -> &[Record] {
    &self.rrsigs
  }

  pub fn insert_rrsig(&mut self, rrsig: Record) {
    self.rrsigs.push(rrsig)
  }

  pub fn clear_rrsigs(&mut self) {
    self.rrsigs.clear()
  }

  fn updated(&mut self, serial: u32) {
    self.serial = serial;
    self.rrsigs.clear(); // on updates, the rrsigs are invalid
  }

  /// Inserts a new Resource Record into the Set.
  ///
  /// If the record is inserted, the ttl for the most recent record will be used for the ttl of
  /// the entire resource record set.
  ///
  /// This abides by the following restrictions in RFC 2136, April 1997:
  ///
  /// ```text
  /// 1.1.5. The following RR types cannot be appended to an RRset.  If the
  ///  following comparison rules are met, then an attempt to add the new RR
  ///  will result in the replacement of the previous RR:
  ///
  /// SOA    compare only NAME, CLASS and TYPE -- it is not possible to
  ///         have more than one SOA per zone, even if any of the data
  ///         fields differ.
  ///
  /// CNAME  compare only NAME, CLASS, and TYPE -- it is not possible
  ///         to have more than one CNAME RR, even if their data fields
  ///         differ.
  /// ```
  ///
  /// # Arguments
  ///
  /// * `record` - `Record` asserts that the `name` and `record_type` match the `RRSet`.
  /// * `serial` - current serial number of the `SOA` record, this is to be used for `IXFR` and
  ///              signing for DNSSec after updates. The serial will only be updated if the
  ///              record was added.
  ///
  /// # Return value
  ///
  /// True if the record was inserted.
  pub fn insert(&mut self, record: Record, serial: u32) -> bool {
    assert_eq!(record.get_name(), &self.name);
    assert_eq!(record.get_rr_type(), self.record_type);

    // RFC 2136                       DNS Update                     April 1997
    //
    // 1.1.5. The following RR types cannot be appended to an RRset.  If the
    //  following comparison rules are met, then an attempt to add the new RR
    //  will result in the replacement of the previous RR:
    match record.get_rr_type() {
      // SOA    compare only NAME, CLASS and TYPE -- it is not possible to
      //         have more than one SOA per zone, even if any of the data
      //         fields differ.
      RecordType::SOA => {
        assert!(self.records.len() <= 1);

        if let Some(soa_record) = self.records.iter().next() {
          match soa_record.get_rdata() {
            &RData::SOA(ref existing_soa) => {
              if let &RData::SOA(ref new_soa) = record.get_rdata() {
                if new_soa.get_serial() <= existing_soa.get_serial() {
                  info!("update ignored serial out of data: {:?} <= {:?}", new_soa, existing_soa);
                  return false;
                }
              } else {
                // not panicking here, b/c this is a bad record from the client or something, ingnore
                info!("wrong rdata for SOA update: {:?}", record.get_rdata());
                return false;
              }
            },
            rdata @ _ => panic!("wrong rdata: {:?}", rdata),
          }
        }

        // if we got here, we're updating...
        self.records.clear();
      },
      // CNAME  compare only NAME, CLASS, and TYPE -- it is not possible
      //         to have more than one CNAME RR, even if their data fields
      //         differ.
        RecordType::CNAME => {
        assert!(self.records.len() <= 1);
        self.records.clear();
      },
      _ => (),
    }

    // collect any records to update based on rdata
    let to_replace: Vec<usize> = self.records.iter().enumerate()
                                            .filter(|&(_, rr)| rr.get_rdata() == record.get_rdata())
                                            .map(|(i, _)| i)
                                            .collect::<Vec<usize>>();

    // if the Records are identical, ignore the update, update all that are not (ttl, etc.)
    let mut replaced = false;
    for i in to_replace {
      if self.records[i] == record {
        return false;
      }

      // TODO: this shouldn't really need a clone since there should only be one...
      self.records.push(record.clone());
      self.records.swap_remove(i);
      self.ttl = record.get_ttl();
      self.updated(serial);
      replaced = true;
    }

    if !replaced {
      self.ttl = record.get_ttl();
      self.updated(serial);
      self.records.push(record);
      true
    } else {
      replaced
    }
  }

  /// Removes the Resource Record if it exists.
  ///
  /// # Arguments
  ///
  /// * `record` - `Record` asserts that the `name` and `record_type` match the `RRSet`. Removes
  ///              any `record` if the record data, `RData`, match.
  /// * `serial` - current serial number of the `SOA` record, this is to be used for `IXFR` and
  ///              signing for DNSSec after updates. The serial will only be updated if the
  ///              record was added.
  ///
  /// # Return value
  ///
  /// True if a record was removed.
  pub fn remove(&mut self, record: &Record, serial: u32) -> bool {
    assert_eq!(record.get_name(), &self.name);
    assert!(record.get_rr_type() == self.record_type || record.get_rr_type() == RecordType::ANY);

    match record.get_rr_type() {
      // never delete the last NS record
      RecordType::NS => {
        if self.records.len() <= 1 {
          info!("ignoring delete of last NS record: {:?}", record);
          return false;
        }
      },
      // never delete SOA
      RecordType::SOA => {
        info!("ignored delete of SOA");
        return false;
      },
      _ => (), // move on to the delete
    }

    // remove the records, first collect all the indexes, then remove the records
    let to_remove: Vec<usize> = self.records.iter().enumerate()
                                            .filter(|&(_, rr)| rr.get_rdata() == record.get_rdata())
                                            .map(|(i, _)| i)
                                            .collect::<Vec<usize>>();

    let mut removed = false;
    for i in to_remove {
      self.records.remove(i);
      removed = true;
      self.updated(serial);
    }

    removed
  }
}

#[cfg(test)]
mod test {
  use std::net::Ipv4Addr;
  use ::rr::*;
  use ::rr::rdata::SOA;
  use super::RRSet;

  #[test]
  fn test_insert() {
    let name = Name::new().label("www").label("example").label("com");
    let record_type = RecordType::A;
    let mut rr_set = RRSet::new(&name, record_type, 0);

    let insert = Record::new().name(name.clone()).ttl(86400).rr_type(record_type).dns_class(DNSClass::IN).rdata(RData::A(Ipv4Addr::new(93,184,216,24))).clone();

    assert!(rr_set.insert(insert.clone(), 0));
    assert_eq!(rr_set.get_records(false).len(), 1);
    assert!(rr_set.get_records(false).contains(&&insert));

    // dups ignored
    assert!(!rr_set.insert(insert.clone(), 0));
    assert_eq!(rr_set.get_records(false).len(), 1);
    assert!(rr_set.get_records(false).contains(&&insert));

    // add one
    let insert1 = Record::new().name(name.clone()).ttl(86400).rr_type(record_type).dns_class(DNSClass::IN).rdata(RData::A(Ipv4Addr::new(93,184,216,25))).clone();
    assert!(rr_set.insert(insert1.clone(), 0));
    assert_eq!(rr_set.get_records(false).len(), 2);
    assert!(rr_set.get_records(false).contains(&&insert));
    assert!(rr_set.get_records(false).contains(&&insert1));
  }

  #[test]
  fn test_insert_soa() {
    let name = Name::new().label("example").label("com");
    let record_type = RecordType::SOA;
    let mut rr_set = RRSet::new(&name, record_type, 0);

    let insert = Record::new().name(name.clone()).ttl(3600).rr_type(RecordType::SOA).dns_class(DNSClass::IN).rdata(RData::SOA(SOA::new(Name::parse("sns.dns.icann.org.", None).unwrap(), Name::parse("noc.dns.icann.org.", None).unwrap(), 2015082403, 7200, 3600, 1209600, 3600 ))).clone();
    let same_serial = Record::new().name(name.clone()).ttl(3600).rr_type(RecordType::SOA).dns_class(DNSClass::IN).rdata(RData::SOA(SOA::new(Name::parse("sns.dns.icann.net.", None).unwrap(), Name::parse("noc.dns.icann.net.", None).unwrap(), 2015082403, 7200, 3600, 1209600, 3600 ))).clone();
    let new_serial = Record::new().name(name.clone()).ttl(3600).rr_type(RecordType::SOA).dns_class(DNSClass::IN).rdata(RData::SOA(SOA::new(Name::parse("sns.dns.icann.net.", None).unwrap(), Name::parse("noc.dns.icann.net.", None).unwrap(), 2015082404, 7200, 3600, 1209600, 3600 ))).clone();

    assert!(rr_set.insert(insert.clone(), 0));
    assert!(rr_set.get_records(false).contains(&&insert));
    // same serial number
    assert!(!rr_set.insert(same_serial.clone(), 0));
    assert!(rr_set.get_records(false).contains(&&insert));
    assert!(!rr_set.get_records(false).contains(&&same_serial));

    assert!(rr_set.insert(new_serial.clone(), 0));
    assert!(!rr_set.insert(same_serial.clone(), 0));
    assert!(!rr_set.insert(insert.clone(), 0));

    assert!(rr_set.get_records(false).contains(&&new_serial));
    assert!(!rr_set.get_records(false).contains(&&insert));
    assert!(!rr_set.get_records(false).contains(&&same_serial));
  }

  #[test]
  fn test_insert_cname() {
    let name = Name::new().label("web").label("example").label("com");
    let cname = Name::new().label("www").label("example").label("com");
    let new_cname = Name::new().label("w2").label("example").label("com");

    let record_type = RecordType::CNAME;
    let mut rr_set = RRSet::new(&name, record_type, 0);

    let insert = Record::new().name(name.clone()).ttl(3600).rr_type(RecordType::CNAME).dns_class(DNSClass::IN).rdata(RData::CNAME(cname.clone()) ).clone();
    let new_record = Record::new().name(name.clone()).ttl(3600).rr_type(RecordType::CNAME).dns_class(DNSClass::IN).rdata(RData::CNAME(new_cname.clone()) ).clone();

    assert!(rr_set.insert(insert.clone(), 0));
    assert!(rr_set.get_records(false).contains(&&insert));

    // update the record
    assert!(rr_set.insert(new_record.clone(), 0));
    assert!(!rr_set.get_records(false).contains(&&insert));
    assert!(rr_set.get_records(false).contains(&&new_record));
  }

  #[test]
  fn test_remove() {
    let name = Name::new().label("www").label("example").label("com");
    let record_type = RecordType::A;
    let mut rr_set = RRSet::new(&name, record_type, 0);

    let insert = Record::new().name(name.clone()).ttl(86400).rr_type(record_type).dns_class(DNSClass::IN).rdata(RData::A(Ipv4Addr::new(93,184,216,24))).clone();
    let insert1 = Record::new().name(name.clone()).ttl(86400).rr_type(record_type).dns_class(DNSClass::IN).rdata(RData::A(Ipv4Addr::new(93,184,216,25))).clone();

    assert!(rr_set.insert(insert.clone(), 0));
    assert!(rr_set.insert(insert1.clone(), 0));

    assert!(rr_set.remove(&insert, 0));
    assert!(!rr_set.remove(&insert, 0));
    assert!(rr_set.remove(&insert1, 0));
    assert!(!rr_set.remove(&insert1, 0));
  }

  #[test]
  fn test_remove_soa() {
    let name = Name::new().label("example").label("com");
    let record_type = RecordType::SOA;
    let mut rr_set = RRSet::new(&name, record_type, 0);

    let insert = Record::new().name(name.clone()).ttl(3600).rr_type(RecordType::SOA).dns_class(DNSClass::IN).rdata(RData::SOA(SOA::new(Name::parse("sns.dns.icann.org.", None).unwrap(), Name::parse("noc.dns.icann.org.", None).unwrap(), 2015082403, 7200, 3600, 1209600, 3600 ))).clone();

    assert!(rr_set.insert(insert.clone(), 0));
    assert!(!rr_set.remove(&insert, 0));
    assert!(rr_set.get_records(false).contains(&&insert));
  }

  #[test]
  fn test_remove_ns() {
    let name = Name::new().label("example").label("com");
    let record_type = RecordType::NS;
    let mut rr_set = RRSet::new(&name, record_type, 0);

    let ns1 = Record::new().name(name.clone()).ttl(86400).rr_type(RecordType::NS).dns_class(DNSClass::IN).rdata(RData::NS(Name::parse("a.iana-servers.net.", None).unwrap()) ).clone();
    let ns2 = Record::new().name(name.clone()).ttl(86400).rr_type(RecordType::NS).dns_class(DNSClass::IN).rdata(RData::NS(Name::parse("b.iana-servers.net.", None).unwrap()) ).clone();

    assert!(rr_set.insert(ns1.clone(), 0));
    assert!(rr_set.insert(ns2.clone(), 0));

    // ok to remove one, but not two...
    assert!(rr_set.remove(&ns1, 0));
    assert!(!rr_set.remove(&ns2, 0));

    // check that we can swap which ones are removed
    assert!(rr_set.insert(ns1.clone(), 0));

    assert!(rr_set.remove(&ns2, 0));
    assert!(!rr_set.remove(&ns1, 0));
  }
}