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
//! This module is for NAT Behavior Discovery based on RFC5780.
//! To use this module, the STUN server side must support the OTHER-ADDRESS and CHANGE-REQUEST attributes.
use std::collections::HashMap;
use std::net::IpAddr;

use async_std::net::{SocketAddr, ToSocketAddrs};
use pnet::datalink;
use pnet::ipnetwork::IpNetwork;

use super::client::*;
use super::error::*;
use super::message::*;

/// Defines a NAT type based on mapping behavior.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NATMappingType {
    NoNAT,
    EndpointIndependent,
    AddressDependent,
    AddressAndPortDependent,
    Unknown,
}

/// Defines a NAT type based on filtering behavior.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NATFilteringType {
    EndpointIndependent,
    AddressDependent,
    AddressAndPortDependent,
    Unknown,
}

/// Results of behavior discovery based on NAT mapping behavior.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NATMappingTypeResult {
    pub test1_xor_mapped_addr: Option<SocketAddr>,
    pub test2_xor_mapped_addr: Option<SocketAddr>,
    pub test3_xor_mapped_addr: Option<SocketAddr>,
    pub mapping_type: NATMappingType,
}

/// Results of behavior discovery based on NAT filtering behavior.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct NATFilteringTypeResult {
    pub xor_mapped_addr: Option<SocketAddr>,
    pub filtering_type: NATFilteringType,
}

/// Check NAT mapping behavior.
pub async fn check_nat_mapping_behavior<A: ToSocketAddrs>(
    client: &mut Client,
    stun_addr: A,
) -> Result<NATMappingTypeResult, STUNClientError> {
    let mut result = NATMappingTypeResult {
        test1_xor_mapped_addr: None,
        test2_xor_mapped_addr: None,
        test3_xor_mapped_addr: None,
        mapping_type: NATMappingType::Unknown,
    };

    // get NIC IPs
    let local_ips: Vec<IpNetwork> = datalink::interfaces()
        .iter()
        .flat_map(|i| i.ips.clone())
        .collect();

    // Test1
    // Send a Binding request and check the Endpoint mapped to NAT.
    // Compare with the IP of the NIC and check if it is behind the NAT.
    let t1_res = client.binding_request(&stun_addr, None).await?;
    let other_addr = Attribute::get_other_address(&t1_res).ok_or(
        STUNClientError::NotSupportedError(String::from("OTHER-ADDRESS")),
    )?;
    result.test1_xor_mapped_addr = Some(Attribute::get_xor_mapped_address(&t1_res).ok_or(
        STUNClientError::NotSupportedError(String::from("XOR-MAPPED-ADDRESS")),
    )?);
    match result.test1_xor_mapped_addr.unwrap().ip() {
        IpAddr::V4(addr) => {
            for local_ip in local_ips {
                if let IpNetwork::V4(v4_lip) = local_ip {
                    if v4_lip.ip() == addr {
                        result.mapping_type = NATMappingType::NoNAT;
                        return Ok(result);
                    }
                }
            }
        }
        IpAddr::V6(addr) => {
            for local_ip in local_ips {
                if let IpNetwork::V6(v6_lip) = local_ip {
                    if v6_lip.ip() == addr {
                        result.mapping_type = NATMappingType::NoNAT;
                        return Ok(result);
                    }
                }
            }
            return Err(STUNClientError::ParseError());
        }
    }

    // Test2
    // Send Binding Request to IP:Port of OTHER-ADDRESS.
    // Compare Test1 and Test2 XOR-MAPPED-ADDRESS to check if it is EIM-NAT.
    let t2_res = client.binding_request(&other_addr, None).await?;
    result.test2_xor_mapped_addr = Some(Attribute::get_xor_mapped_address(&t2_res).ok_or(
        STUNClientError::NotSupportedError(String::from("XOR-MAPPED-ADDRESS")),
    )?);
    if result.test1_xor_mapped_addr == result.test2_xor_mapped_addr {
        result.mapping_type = NATMappingType::EndpointIndependent;
        return Ok(result);
    }

    // Test3
    // Send a Binding Request to the IP used in Test1 and the Port used in Test2.
    // (That is, use the primary IP and secondary Port.)
    // Compare Test2 and Test3 XOR-MAPPED-ADDRESS to check if it is ADM-NAT or APDM-NAT.
    // stun_addr is a known value, so it's okay to unwrap it.
    let mut t3_addr = stun_addr.to_socket_addrs().await.unwrap().next().unwrap();
    t3_addr.set_port(other_addr.port());
    let t3_res = client.binding_request(&t3_addr, None).await?;
    result.test3_xor_mapped_addr = Some(Attribute::get_xor_mapped_address(&t3_res).ok_or(
        STUNClientError::NotSupportedError(String::from("XOR-MAPPED-ADDRESS")),
    )?);
    if result.test2_xor_mapped_addr == result.test3_xor_mapped_addr {
        result.mapping_type = NATMappingType::AddressDependent;
        return Ok(result);
    }

    result.mapping_type = NATMappingType::AddressAndPortDependent;
    Ok(result)
}

/// Check NAT filtering behavior.
pub async fn check_nat_filtering_behavior<A: ToSocketAddrs>(
    client: &mut Client,
    stun_addr: A,
) -> Result<NATFilteringTypeResult, STUNClientError> {
    // Test1
    // Send a Binding request and check the Endpoint mapped to NAT.
    let t1_res = client.binding_request(&stun_addr, None).await?;
    let xor_mapped_addr = Some(Attribute::get_xor_mapped_address(&t1_res).ok_or(
        STUNClientError::NotSupportedError(String::from("XOR-MAPPED-ADDRESS")),
    )?);

    // Test2
    // Send Binding Request with the "change IP" and "change port" flags of CHANGE-REQUEST turned on.
    // As a result, the response is sent from IP:Port which is different from the sent IP:Port.
    // If the response can be received, it is EIF-NAT.
    let mut attrs = HashMap::new();
    let change_request = Attribute::generate_change_request_value(true, true);
    attrs.insert(Attribute::ChangeRequest, change_request);
    let t2_res = client.binding_request(&stun_addr, Some(attrs)).await;
    match t2_res {
        Ok(_) => {
            return Ok(NATFilteringTypeResult {
                xor_mapped_addr: xor_mapped_addr,
                filtering_type: NATFilteringType::EndpointIndependent,
            })
        }
        Err(e) => {
            match e {
                STUNClientError::TimeoutError() => { /* Run Test3 below */ }
                _ => return Err(e),
            }
        }
    }

    // Test3
    // Send a binding request with only the "change port" flag in CHANGE-REQUEST turned on.
    // As a result, the response is sent from Port which is different from the sent Port.(Same IP address)
    // If the response can be received, it is ADF-NAT, and if it cannot be received, it is APDF-NAT.
    let mut attrs = HashMap::new();
    let change_request = Attribute::generate_change_request_value(false, true);
    attrs.insert(Attribute::ChangeRequest, change_request);
    let t3_res = client.binding_request(&stun_addr, Some(attrs)).await;
    match t3_res {
        Ok(_) => {
            return Ok(NATFilteringTypeResult {
                xor_mapped_addr: xor_mapped_addr,
                filtering_type: NATFilteringType::AddressDependent,
            })
        }
        Err(e) => match e {
            STUNClientError::TimeoutError() => {
                return Ok(NATFilteringTypeResult {
                    xor_mapped_addr: xor_mapped_addr,
                    filtering_type: NATFilteringType::AddressAndPortDependent,
                })
            }
            _ => return Err(e),
        },
    }
}