smoldot_light/platform/
address_parse.rs

1// Smoldot
2// Copyright (C) 2023  Pierre Krieger
3// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
4
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU General Public License for more details.
14
15// You should have received a copy of the GNU General Public License
16// along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18use smoldot::libp2p::{
19    multiaddr::{Multiaddr, Protocol},
20    multihash::Multihash,
21};
22
23use super::{Address, ConnectionType, DnsFamily, MultiStreamAddress};
24use core::{
25    net::{IpAddr, Ipv4Addr, Ipv6Addr},
26    str,
27};
28
29pub enum AddressOrMultiStreamAddress<'a> {
30    Address(Address<'a>),
31    MultiStreamAddress(MultiStreamAddress<'a>),
32}
33
34impl<'a> From<&'a AddressOrMultiStreamAddress<'a>> for ConnectionType {
35    fn from(address: &'a AddressOrMultiStreamAddress<'a>) -> ConnectionType {
36        match address {
37            AddressOrMultiStreamAddress::Address(a) => ConnectionType::from(a),
38            AddressOrMultiStreamAddress::MultiStreamAddress(a) => ConnectionType::from(a),
39        }
40    }
41}
42
43/// Parses a [`Multiaddr`] into an [`Address`] or [`MultiStreamAddress`].
44pub fn multiaddr_to_address(
45    multiaddr: &'_ Multiaddr,
46) -> Result<AddressOrMultiStreamAddress<'_>, Error> {
47    let mut iter = multiaddr.iter().fuse();
48
49    let proto1 = iter.next().ok_or(Error::UnknownCombination)?;
50    let proto2 = iter.next().ok_or(Error::UnknownCombination)?;
51    let proto3 = iter.next();
52    let proto4 = iter.next();
53
54    if iter.next().is_some() {
55        return Err(Error::UnknownCombination);
56    }
57
58    Ok(match (proto1, proto2, proto3, proto4) {
59        (Protocol::Ip4(ip), Protocol::Tcp(port), None, None) => {
60            AddressOrMultiStreamAddress::Address(Address::TcpIp {
61                ip: IpAddr::V4(Ipv4Addr::from(ip)),
62                port,
63            })
64        }
65        (Protocol::Ip6(ip), Protocol::Tcp(port), None, None) => {
66            AddressOrMultiStreamAddress::Address(Address::TcpIp {
67                ip: IpAddr::V6(Ipv6Addr::from(ip)),
68                port,
69            })
70        }
71        (
72            Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr),
73            Protocol::Tcp(port),
74            None,
75            None,
76        ) => AddressOrMultiStreamAddress::Address(Address::TcpDns {
77            hostname: str::from_utf8(addr.into_bytes()).map_err(Error::NonUtf8DomainName)?,
78            port,
79        }),
80        (Protocol::Ip4(ip), Protocol::Tcp(port), Some(Protocol::Ws), None) => {
81            AddressOrMultiStreamAddress::Address(Address::WebSocketIp {
82                ip: IpAddr::V4(Ipv4Addr::from(ip)),
83                port,
84            })
85        }
86        (Protocol::Ip6(ip), Protocol::Tcp(port), Some(Protocol::Ws), None) => {
87            AddressOrMultiStreamAddress::Address(Address::WebSocketIp {
88                ip: IpAddr::V6(Ipv6Addr::from(ip)),
89                port,
90            })
91        }
92        (
93            Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr),
94            Protocol::Tcp(port),
95            Some(Protocol::Ws),
96            None,
97        ) => AddressOrMultiStreamAddress::Address(Address::WebSocketDns {
98            hostname: str::from_utf8(addr.into_bytes()).map_err(Error::NonUtf8DomainName)?,
99            port,
100            secure: false,
101        }),
102        (
103            Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr),
104            Protocol::Tcp(port),
105            Some(Protocol::Wss),
106            None,
107        )
108        | (
109            Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr),
110            Protocol::Tcp(port),
111            Some(Protocol::Tls),
112            Some(Protocol::Ws),
113        ) => AddressOrMultiStreamAddress::Address(Address::WebSocketDns {
114            hostname: str::from_utf8(addr.into_bytes()).map_err(Error::NonUtf8DomainName)?,
115            port,
116            secure: true,
117        }),
118
119        (
120            Protocol::Ip4(ip),
121            Protocol::Udp(port),
122            Some(Protocol::WebRtcDirect),
123            Some(Protocol::Certhash(multihash)),
124        ) => AddressOrMultiStreamAddress::MultiStreamAddress(MultiStreamAddress::WebRtc {
125            ip: IpAddr::V4(Ipv4Addr::from(ip)),
126            port,
127            remote_certificate_sha256: certhash_sha256(&multihash)?,
128        }),
129
130        (
131            Protocol::Ip6(ip),
132            Protocol::Udp(port),
133            Some(Protocol::WebRtcDirect),
134            Some(Protocol::Certhash(multihash)),
135        ) => AddressOrMultiStreamAddress::MultiStreamAddress(MultiStreamAddress::WebRtc {
136            ip: IpAddr::V6(Ipv6Addr::from(ip)),
137            port,
138            remote_certificate_sha256: certhash_sha256(&multihash)?,
139        }),
140        (
141            dns @ (Protocol::Dns(addr) | Protocol::Dns4(addr) | Protocol::Dns6(addr)),
142            Protocol::Udp(port),
143            Some(Protocol::WebRtcDirect),
144            Some(Protocol::Certhash(multihash)),
145        ) => AddressOrMultiStreamAddress::MultiStreamAddress(MultiStreamAddress::WebRtcDns {
146            hostname: str::from_utf8(addr.into_bytes()).map_err(Error::NonUtf8DomainName)?,
147            family: match dns {
148                Protocol::Dns(_) => DnsFamily::Any,
149                Protocol::Dns4(_) => DnsFamily::Ipv4,
150                Protocol::Dns6(_) => DnsFamily::Ipv6,
151                _ => unreachable!(),
152            },
153            port,
154            remote_certificate_sha256: certhash_sha256(&multihash)?,
155        }),
156        _ => return Err(Error::UnknownCombination),
157    })
158}
159
160/// Extracts the SHA-256 hash out of the multihash of a `/certhash` component.
161fn certhash_sha256<'a>(multihash: &Multihash<&'a [u8]>) -> Result<&'a [u8; 32], Error> {
162    if multihash.hash_algorithm_code() != 0x12 {
163        return Err(Error::NonSha256Certhash);
164    }
165    <&[u8; 32]>::try_from(multihash.data_ref()).map_err(|_| Error::InvalidMultihashLength)
166}
167
168#[derive(Debug, Clone, derive_more::Display, derive_more::Error)]
169pub enum Error {
170    /// Unknown combination of protocols.
171    UnknownCombination,
172
173    /// Multiaddress contains a domain name that isn't UTF-8.
174    ///
175    /// > **Note**: According to RFC2181 section 11, a domain name is not necessarily an UTF-8
176    /// >           string. Any binary data can be used as a domain name, provided it follows
177    /// >           a few restrictions (notably its length). However, in this context, we
178    /// >           automatically consider as non-supported a multiaddress that contains a
179    /// >           non-UTF-8 domain name, for the sake of simplicity.
180    NonUtf8DomainName(str::Utf8Error),
181
182    /// Multiaddr contains a `/certhash` components whose multihash isn't using SHA-256, but the
183    /// rest of the multiaddr requires SHA-256.
184    NonSha256Certhash,
185
186    /// Multiaddr contains a multihash whose length doesn't match its hash algorithm.
187    InvalidMultihashLength,
188}
189
190#[cfg(test)]
191mod tests {
192    use super::{AddressOrMultiStreamAddress, Error, multiaddr_to_address};
193    use crate::platform::{ConnectionType, DnsFamily, MultiStreamAddress};
194    use core::net::{IpAddr, Ipv4Addr, Ipv6Addr};
195    use smoldot::libp2p::multiaddr::Multiaddr;
196
197    /// SHA-256 certhash of a real `webrtc-direct` bootnode.
198    const CERTHASH: &str = "uEiBsqkcr8pOaNjl6px_v1nBatWMfXB9C_sU8fDat3mZWfQ";
199
200    fn multistream(multiaddr: &Multiaddr) -> MultiStreamAddress<'_> {
201        match multiaddr_to_address(multiaddr).unwrap() {
202            AddressOrMultiStreamAddress::MultiStreamAddress(addr) => addr,
203            AddressOrMultiStreamAddress::Address(addr) => {
204                panic!("expected a multistream address, got {addr:?}")
205            }
206        }
207    }
208
209    fn cert_of(addr: &MultiStreamAddress) -> [u8; 32] {
210        match addr {
211            MultiStreamAddress::WebRtc {
212                remote_certificate_sha256,
213                ..
214            }
215            | MultiStreamAddress::WebRtcDns {
216                remote_certificate_sha256,
217                ..
218            } => **remote_certificate_sha256,
219        }
220    }
221
222    #[test]
223    fn webrtc_with_ip_address() {
224        let ip4: Multiaddr = format!("/ip4/1.2.3.4/udp/30333/webrtc-direct/certhash/{CERTHASH}")
225            .parse()
226            .unwrap();
227        let parsed = multistream(&ip4);
228        assert!(matches!(
229            parsed,
230            MultiStreamAddress::WebRtc { ip: IpAddr::V4(ip), port: 30333, .. }
231                if ip == Ipv4Addr::new(1, 2, 3, 4)
232        ));
233        assert_eq!(ConnectionType::from(&parsed), ConnectionType::WebRtcIpv4);
234
235        let ip6: Multiaddr = format!("/ip6/::1/udp/30333/webrtc-direct/certhash/{CERTHASH}")
236            .parse()
237            .unwrap();
238        let parsed = multistream(&ip6);
239        assert!(matches!(
240            parsed,
241            MultiStreamAddress::WebRtc { ip: IpAddr::V6(ip), port: 30333, .. }
242                if ip == Ipv6Addr::LOCALHOST
243        ));
244        assert_eq!(ConnectionType::from(&parsed), ConnectionType::WebRtcIpv6);
245    }
246
247    #[test]
248    fn webrtc_with_domain_name() {
249        let ip4: Multiaddr = format!("/ip4/1.2.3.4/udp/30333/webrtc-direct/certhash/{CERTHASH}")
250            .parse()
251            .unwrap();
252        let expected_cert = cert_of(&multistream(&ip4));
253
254        for (protocol, family) in [
255            ("dns", DnsFamily::Any),
256            ("dns4", DnsFamily::Ipv4),
257            ("dns6", DnsFamily::Ipv6),
258        ] {
259            let multiaddr: Multiaddr =
260                format!("/{protocol}/example.com/udp/30333/webrtc-direct/certhash/{CERTHASH}")
261                    .parse()
262                    .unwrap();
263            let parsed = multistream(&multiaddr);
264            assert_eq!(
265                parsed,
266                MultiStreamAddress::WebRtcDns {
267                    hostname: "example.com",
268                    family,
269                    port: 30333,
270                    remote_certificate_sha256: &expected_cert,
271                },
272                "{protocol}"
273            );
274            assert_eq!(ConnectionType::from(&parsed), ConnectionType::WebRtcDns);
275        }
276    }
277
278    #[test]
279    fn webrtc_rejected_combinations() {
280        // `dnsaddr` is never dialed directly.
281        let multiaddr: Multiaddr =
282            format!("/dnsaddr/example.com/udp/1/webrtc-direct/certhash/{CERTHASH}")
283                .parse()
284                .unwrap();
285        assert!(matches!(
286            multiaddr_to_address(&multiaddr),
287            Err(Error::UnknownCombination)
288        ));
289
290        // A certhash is mandatory.
291        let multiaddr: Multiaddr = "/dns/example.com/udp/1/webrtc-direct".parse().unwrap();
292        assert!(matches!(
293            multiaddr_to_address(&multiaddr),
294            Err(Error::UnknownCombination)
295        ));
296
297        // SHA-1 multihash (code 0x11, 20 zero bytes), base64url without padding.
298        let sha1_certhash = format!("u{}{}", "ERQ", "A".repeat(27));
299        for host in ["dns/example.com", "ip4/1.2.3.4"] {
300            let multiaddr: Multiaddr =
301                format!("/{host}/udp/1/webrtc-direct/certhash/{sha1_certhash}")
302                    .parse()
303                    .unwrap();
304            assert!(
305                matches!(
306                    multiaddr_to_address(&multiaddr),
307                    Err(Error::NonSha256Certhash)
308                ),
309                "{host}"
310            );
311        }
312    }
313}