steamkit/steam/cdn/
client_lancache.rs1use std::net::IpAddr;
4use std::sync::atomic::{AtomicBool, Ordering};
5
6use reqwest::header::{HeaderMap, HeaderValue, HOST, USER_AGENT};
7use reqwest::Url;
8
9use crate::error::SteamError;
10
11use super::client::Client;
12use super::server::Server;
13
14static USE_LANCACHE_SERVER: AtomicBool = AtomicBool::new(false);
16
17const TRIGGER_DOMAIN: &str = "lancache.steamcontent.com";
19
20const LANCACHE_USER_AGENT: &str = "Valve/Steam HTTP Client 1.0";
23
24impl Client {
25 pub fn use_lancache_server() -> bool {
28 USE_LANCACHE_SERVER.load(Ordering::Relaxed)
29 }
30
31 pub async fn detect_lancache_server_async() -> Result<(), SteamError> {
37 let addresses = tokio::net::lookup_host((TRIGGER_DOMAIN, 80u16))
38 .await
39 .map_err(|error| {
40 SteamError::Connection(format!(
41 "Failed to resolve {TRIGGER_DOMAIN}: {error}"
42 ))
43 })?;
44
45 let use_lancache = addresses
48 .into_iter()
49 .map(|address| address.ip())
50 .any(|address| Self::is_private_address(&address));
51
52 USE_LANCACHE_SERVER.store(use_lancache, Ordering::Relaxed);
53
54 Ok(())
55 }
56
57 pub fn is_private_address(to_test: &IpAddr) -> bool {
59 match to_test {
60 IpAddr::V4(address) => {
61 if address.is_loopback() {
62 return true;
63 }
64
65 match address.octets() {
66 [10, _, _, _] => true,
67 [172, second, _, _] => (16..32).contains(&second),
68 [192, 168, _, _] => true,
69 _ => false,
70 }
71 }
72 IpAddr::V6(address) => {
73 let segments = address.segments();
75
76 (segments[0] & 0xFE00) == 0xFC00
77 || address.is_unicast_link_local()
78 || address.is_loopback()
79 }
80 }
81 }
82
83 pub(crate) fn build_lancache_request(
85 http_client: &reqwest::Client,
86 server: &Server,
87 command: &str,
88 query: Option<&str>,
89 ) -> Result<reqwest::RequestBuilder, SteamError> {
90 let mut url = Url::parse(&format!(
91 "http://{TRIGGER_DOMAIN}:80/{}",
92 command.trim_start_matches('/')
93 ))
94 .map_err(|error| SteamError::InvalidArgument(format!("Invalid lancache url: {error}")))?;
95
96 if let Some(query) = query {
97 if !query.is_empty() {
98 url.set_query(Some(query));
99 }
100 }
101
102 let mut headers = HeaderMap::new();
103
104 if let Some(host) = server.host.as_deref() {
105 if !host.is_empty() {
106 headers.insert(
107 HOST,
108 HeaderValue::from_str(host).map_err(|error| {
109 SteamError::InvalidArgument(format!("Invalid lancache host header: {error}"))
110 })?,
111 );
112 }
113 }
114
115 headers.insert(USER_AGENT, HeaderValue::from_static(LANCACHE_USER_AGENT));
118
119 Ok(http_client.get(url).headers(headers))
120 }
121}