Skip to main content

steamkit/util/
net_helpers.rs

1//! IP address and endpoint helpers.
2
3use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
4
5use steamkit_protos::steam::{c_msg_ip_address, CMsgIpAddress};
6
7use crate::base::generated::steam_language_internal::MsgClientLogon;
8use crate::error::SteamError;
9
10/// Helpers for dealing with IP addresses and endpoints.
11#[derive(Debug)]
12pub struct NetHelpers;
13
14impl NetHelpers {
15    /// The local IP of a connected socket, from its local endpoint. An error if
16    /// it's unspecified.
17    pub fn get_local_ip(local_end_point: Option<SocketAddr>) -> Result<IpAddr, SteamError> {
18        match local_end_point {
19            Some(end_point) if !end_point.ip().is_unspecified() => Ok(end_point.ip()),
20            _ => Err(SteamError::Connection("Socket not connected".to_string())),
21        }
22    }
23
24    /// Converts a packed IPv4 address (network byte order) to an [`IpAddr`].
25    /// The inverse of [`NetHelpers::get_ip_address_as_uint`].
26    pub fn get_ip_address(ip_addr: u32) -> IpAddr {
27        IpAddr::V4(Ipv4Addr::from(ip_addr))
28    }
29
30    /// Packs an IPv4 address into a `u32` (network byte order). The inverse of
31    /// [`NetHelpers::get_ip_address`]. IPv6 is an error.
32    pub fn get_ip_address_as_uint(ip_addr: &IpAddr) -> Result<u32, SteamError> {
33        match ip_addr {
34            IpAddr::V4(address) => Ok(u32::from_be_bytes(address.octets())),
35            IpAddr::V6(_) => Err(SteamError::InvalidArgument(
36                "GetIPAddressAsUInt only works with IPv4 addresses.".to_string(),
37            )),
38        }
39    }
40
41    /// Converts a `CMsgIpAddress` to an [`IpAddr`], preferring the IPv6 form
42    /// when it's there. An IPv6 address that isn't 16 bytes gives `None`.
43    pub fn get_ip_address_from_msg(ip_addr: &CMsgIpAddress) -> Option<IpAddr> {
44        match &ip_addr.ip {
45            Some(c_msg_ip_address::Ip::V6(bytes)) => {
46                let octets: [u8; 16] = bytes.as_slice().try_into().ok()?;
47                Some(IpAddr::V6(Ipv6Addr::from(octets)))
48            }
49            Some(c_msg_ip_address::Ip::V4(value)) => {
50                Some(IpAddr::V4(Ipv4Addr::from(*value)))
51            }
52            None => None,
53        }
54    }
55
56    /// Converts an [`IpAddr`] to a `CMsgIpAddress`.
57    pub fn get_msg_ip_address(ip_addr: &IpAddr) -> CMsgIpAddress {
58        let ip = match ip_addr {
59            IpAddr::V4(address) => {
60                c_msg_ip_address::Ip::V4(u32::from_be_bytes(address.octets()))
61            }
62            IpAddr::V6(address) => c_msg_ip_address::Ip::V6(address.octets().to_vec()),
63        };
64
65        CMsgIpAddress { ip: Some(ip) }
66    }
67
68    /// Obfuscates a private IP address in place: IPv6 is XORed with the
69    /// repeating key `0D F0 AD BA`, IPv4 with `MsgClientLogon::OBFUSCATION_MASK`.
70    pub fn obfuscate_private_ip(msg_ip_address: &mut CMsgIpAddress) {
71        match &mut msg_ip_address.ip {
72            Some(c_msg_ip_address::Ip::V6(bytes)) => {
73                const KEY: [u8; 4] = [0x0D, 0xF0, 0xAD, 0xBA];
74
75                for (index, byte) in bytes.iter_mut().enumerate() {
76                    *byte ^= KEY[index % KEY.len()];
77                }
78            }
79            Some(c_msg_ip_address::Ip::V4(value)) => {
80                *value ^= MsgClientLogon::OBFUSCATION_MASK;
81            }
82            None => {}
83        }
84    }
85
86    /// Parses an `address:port` endpoint. Port 0 is rejected.
87    pub fn try_parse_ip_end_point(string_value: &str) -> Option<SocketAddr> {
88        let end_point = string_value.parse::<SocketAddr>().ok()?;
89
90        if end_point.port() == 0 {
91            return None;
92        }
93
94        Some(end_point)
95    }
96
97    /// The host part of an endpoint.
98    pub fn extract_endpoint_host(end_point: &SocketAddr) -> String {
99        end_point.ip().to_string()
100    }
101
102    /// The port of an endpoint.
103    pub fn extract_endpoint_port(end_point: &SocketAddr) -> u16 {
104        end_point.port()
105    }
106}