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
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//! Generate initial sequence numbers securely.

use core::hash::{Hash, Hasher};

use netstack3_base::{Instant, SeqNum};
use rand::Rng;
use siphasher::sip128::SipHasher24;

/// A generator of TCP initial sequence numbers.
#[derive(Default)]
pub struct IsnGenerator<Instant> {
    // Secret used to choose initial sequence numbers. It will be filled by a
    // CSPRNG upon initialization. RFC suggests an implementation "could"
    // change the secret key on a regular basis, this is not something we are
    // considering as Linux doesn't seem to do that either.
    secret: [u8; 16],
    // The initial timestamp that will be used to calculate the elapsed time
    // since the beginning and that information will then be used to generate
    // ISNs being requested.
    timestamp: Instant,
}

impl<I: Instant> IsnGenerator<I> {
    pub(crate) fn new(now: I, rng: &mut impl Rng) -> Self {
        let mut secret = [0; 16];
        rng.fill(&mut secret[..]);
        Self { secret, timestamp: now }
    }

    pub(crate) fn generate<A: Hash, P: Hash>(
        &self,
        now: I,
        local: (A, P),
        remote: (A, P),
    ) -> SeqNum {
        let Self { secret, timestamp } = self;

        // Per RFC 6528 Section 3 (https://tools.ietf.org/html/rfc6528#section-3):
        //
        // TCP SHOULD generate its Initial Sequence Numbers with the expression:
        //
        //   ISN = M + F(localip, localport, remoteip, remoteport, secretkey)
        //
        // where M is the 4 microsecond timer, and F() is a pseudorandom
        // function (PRF) of the connection-id.
        //
        // Siphash is used here as it is the hash function used by Linux.
        let h = {
            let mut hasher = SipHasher24::new_with_key(secret);
            local.hash(&mut hasher);
            remote.hash(&mut hasher);
            hasher.finish()
        };

        // Reduce the hashed output (h: u64) to 32 bits using XOR, but also
        // preserve entropy.
        let elapsed = now.saturating_duration_since(*timestamp);
        SeqNum::new(((elapsed.as_micros() / 4) as u32).wrapping_add(h as u32 ^ (h >> 32) as u32))
    }
}