iroh_http_core/endpoint/
bind.rs1#![allow(clippy::disallowed_types)]
9
10use std::{sync::Arc, time::Duration};
11
12use iroh::{
13 address_lookup::{DnsAddressLookup, PkarrPublisher},
14 endpoint::{IdleTimeout, QuicTransportConfig},
15 Endpoint, RelayMode, SecretKey,
16};
17
18use crate::ffi::handles::{HandleStore, StoreConfig};
19use crate::http::transport::pool::ConnectionPool;
20use crate::{ALPN, ALPN_DUPLEX};
21
22use super::{
23 config::NodeOptions, ffi_bridge::FfiBridge, http_runtime::HttpRuntime,
24 session_runtime::SessionRuntime, transport::Transport, EndpointInner, IrohEndpoint,
25};
26
27impl IrohEndpoint {
28 pub async fn bind(opts: NodeOptions) -> Result<Self, crate::CoreError> {
30 if opts.networking.disabled
33 && opts
34 .networking
35 .relay_mode
36 .as_deref()
37 .is_some_and(|m| !matches!(m, "disabled"))
38 {
39 return Err(crate::CoreError::invalid_input(
40 "networking.disabled is true but relay_mode is set to a non-disabled value; \
41 set relay_mode to \"disabled\" or omit it when networking.disabled is true",
42 ));
43 }
44
45 let relay_mode = if opts.networking.disabled {
46 RelayMode::Disabled
47 } else {
48 match opts.networking.relay_mode.as_deref() {
49 None | Some("default") => RelayMode::Default,
50 Some("staging") => RelayMode::Staging,
51 Some("disabled") => RelayMode::Disabled,
52 Some("custom") => {
53 if opts.networking.relays.is_empty() {
54 return Err(crate::CoreError::invalid_input(
55 "relay_mode \"custom\" requires at least one URL in `relays`",
56 ));
57 }
58 let urls = opts
59 .networking
60 .relays
61 .iter()
62 .map(|u| {
63 u.parse::<iroh::RelayUrl>()
64 .map_err(crate::CoreError::invalid_input)
65 })
66 .collect::<Result<Vec<_>, _>>()?;
67 RelayMode::custom(urls)
68 }
69 Some(other) => {
70 return Err(crate::CoreError::invalid_input(format!(
71 "unknown relay_mode: {other}"
72 )))
73 }
74 }
75 };
76
77 for cap in &opts.capabilities {
79 if !crate::KNOWN_ALPNS.contains(&cap.as_str()) {
80 return Err(crate::CoreError::invalid_input(format!(
81 "unknown ALPN capability: \"{cap}\"; valid values are {:?}",
82 crate::KNOWN_ALPNS,
83 )));
84 }
85 }
86
87 if let Some(level) = opts.compression.as_ref().and_then(|c| c.level) {
89 if !(1..=22).contains(&level) {
90 return Err(crate::CoreError::invalid_input(format!(
91 "compression level must be 1–22, got {level}"
92 )));
93 }
94 }
95
96 let alpns: Vec<Vec<u8>> = if opts.capabilities.is_empty() {
97 vec![ALPN_DUPLEX.to_vec(), ALPN.to_vec()]
99 } else {
100 let mut list: Vec<Vec<u8>> = opts
101 .capabilities
102 .iter()
103 .map(|c| c.as_bytes().to_vec())
104 .collect();
105 if !list.iter().any(|a| a == ALPN) {
107 list.push(ALPN.to_vec());
108 }
109 list
110 };
111
112 let mut builder = Endpoint::builder(iroh::endpoint::presets::Minimal)
113 .relay_mode(relay_mode)
114 .alpns(alpns);
115
116 if !opts.networking.disabled && opts.discovery.enabled {
118 if let Some(ref url_str) = opts.discovery.dns_server {
119 let url: url::Url = url_str.parse().map_err(|e| {
120 crate::CoreError::invalid_input(format!("invalid dns_discovery URL: {e}"))
121 })?;
122 builder = builder
123 .address_lookup(PkarrPublisher::builder(url.clone()))
124 .address_lookup(DnsAddressLookup::builder(
125 url.host_str().unwrap_or_default().to_string(),
126 ));
127 } else {
128 builder = builder
129 .address_lookup(PkarrPublisher::n0_dns())
130 .address_lookup(DnsAddressLookup::n0_dns());
131 }
132 }
133
134 if let Some(key_bytes) = opts.key {
135 builder = builder.secret_key(SecretKey::from_bytes(&key_bytes));
136 }
137
138 if let Some(ms) = opts.networking.idle_timeout_ms {
139 let timeout = IdleTimeout::try_from(Duration::from_millis(ms)).map_err(|e| {
140 crate::CoreError::invalid_input(format!("idle_timeout_ms out of range: {e}"))
141 })?;
142 let transport = QuicTransportConfig::builder()
143 .max_idle_timeout(Some(timeout))
144 .max_concurrent_bidi_streams(128u32.into())
145 .build();
146 builder = builder.transport_config(transport);
147 } else {
148 let transport = QuicTransportConfig::builder()
149 .max_concurrent_bidi_streams(128u32.into())
150 .build();
151 builder = builder.transport_config(transport);
152 }
153
154 for addr_str in &opts.networking.bind_addrs {
156 let sock: std::net::SocketAddr = addr_str.parse().map_err(|e| {
157 crate::CoreError::invalid_input(format!("invalid bind address \"{addr_str}\": {e}"))
158 })?;
159 builder = builder.bind_addr(sock).map_err(|e| {
160 crate::CoreError::invalid_input(format!("bind address \"{addr_str}\": {e}"))
161 })?;
162 }
163
164 if let Some(ref proxy) = opts.networking.proxy_url {
166 let url: url::Url = proxy
167 .parse()
168 .map_err(|e| crate::CoreError::invalid_input(format!("invalid proxy URL: {e}")))?;
169 builder = builder.proxy_url(url);
170 } else if opts.networking.proxy_from_env {
171 builder = builder.proxy_from_env();
172 }
173
174 if opts.keylog {
175 builder = builder.keylog(true);
176 }
177
178 let ep: Endpoint = builder.bind().await.map_err(classify_bind_error)?;
179 let node_id_str = crate::base32_encode(ep.id().as_bytes());
180
181 let store_config = StoreConfig {
182 channel_capacity: opts
183 .streaming
184 .channel_capacity
185 .unwrap_or(crate::ffi::handles::DEFAULT_CHANNEL_CAPACITY)
186 .max(1),
187 max_chunk_size: opts
188 .streaming
189 .max_chunk_size_bytes
190 .unwrap_or(crate::ffi::handles::DEFAULT_MAX_CHUNK_SIZE)
191 .max(1),
192 drain_timeout: Duration::from_millis(
193 opts.streaming
194 .drain_timeout_ms
195 .unwrap_or(crate::ffi::handles::DEFAULT_DRAIN_TIMEOUT_MS),
196 ),
197 max_handles: crate::ffi::handles::DEFAULT_MAX_HANDLES,
198 ttl: Duration::from_millis(
199 opts.streaming
200 .handle_ttl_ms
201 .unwrap_or(crate::ffi::handles::DEFAULT_SLAB_TTL_MS),
202 ),
203 };
204 let sweep_ttl = store_config.ttl;
205 let sweep_interval = Duration::from_millis(
206 opts.streaming
207 .sweep_interval_ms
208 .unwrap_or(crate::ffi::handles::DEFAULT_SWEEP_INTERVAL_MS),
209 );
210 let (closed_tx, closed_rx) = tokio::sync::watch::channel(false);
211 let (event_tx, event_rx) =
212 tokio::sync::mpsc::channel::<crate::http::events::TransportEvent>(256);
213
214 let pool = ConnectionPool::new(
215 opts.pool.max_connections,
216 opts.pool
217 .idle_timeout_ms
218 .map(std::time::Duration::from_millis),
219 Some(event_tx.clone()),
220 );
221
222 let max_header_size = match opts.max_header_size {
223 Some(0) => {
224 return Err(crate::CoreError::invalid_input(
225 "max_header_size must be > 0; use None for the default (65536)",
226 ));
227 }
228 None => 64 * 1024,
229 Some(n) => n,
230 };
231
232 let inner = Arc::new(EndpointInner {
233 transport: Transport { ep, node_id_str },
234 http: HttpRuntime {
235 pool,
236 max_header_size,
237 max_response_body_bytes: opts
238 .max_response_body_bytes
239 .unwrap_or(crate::http::server::DEFAULT_MAX_RESPONSE_BODY_BYTES),
240 active_connections: Arc::new(std::sync::atomic::AtomicUsize::new(0)),
241 active_requests: Arc::new(std::sync::atomic::AtomicUsize::new(0)),
242 compression: opts.compression,
243 },
244 session: SessionRuntime {
245 serve_handle: std::sync::Mutex::new(None),
246 serve_stopped_early: std::sync::atomic::AtomicBool::new(false),
247 serve_done_rx: std::sync::Mutex::new(None),
248 closed_tx,
249 closed_rx,
250 event_tx,
251 event_rx: std::sync::Mutex::new(Some(event_rx)),
252 path_subs: dashmap::DashMap::new(),
253 },
254 ffi: FfiBridge {
255 handles: HandleStore::new(store_config),
256 },
257 });
258
259 if !sweep_ttl.is_zero() {
261 let weak = Arc::downgrade(&inner);
262 tokio::spawn(async move {
263 let mut ticker = tokio::time::interval(sweep_interval);
264 loop {
265 ticker.tick().await;
266 let Some(inner) = weak.upgrade() else {
267 break;
268 };
269 inner.ffi.handles.sweep(sweep_ttl);
270 drop(inner); }
272 });
273 }
274
275 Ok(Self { inner })
276 }
277}
278
279pub(super) fn classify_bind_error(e: impl std::fmt::Display) -> crate::CoreError {
281 let msg = e.to_string();
282 crate::CoreError::connection_failed(msg)
283}
284
285pub fn parse_direct_addrs(
290 addrs: &Option<Vec<String>>,
291) -> Result<Option<Vec<std::net::SocketAddr>>, String> {
292 match addrs {
293 None => Ok(None),
294 Some(v) => {
295 let mut out = Vec::with_capacity(v.len());
296 for s in v {
297 let addr = s
298 .parse::<std::net::SocketAddr>()
299 .map_err(|e| format!("invalid direct address {s:?}: {e}"))?;
300 out.push(addr);
301 }
302 Ok(Some(out))
303 }
304 }
305}