mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-22 07:48:26 +00:00
Add DNS hostname support in peer addresses for UDP and TCP transports
Add resolve_socket_addr() with IP fast path and tokio::net::lookup_host() fallback for DNS hostnames. Peer addresses can now use hostnames like "peer1.example.com:2121" alongside IP addresses. UDP transport adds a per-transport DNS cache (60s TTL) to avoid per-packet resolution. TCP resolves at connect time (one-shot). Update design docs, config examples, and changelog to reflect hostname support in transport addressing.
This commit is contained in:
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- DNS hostname support in peer addresses for UDP and TCP transports — addresses
|
||||
can now use `host:port` with either IP addresses or DNS hostnames (e.g.,
|
||||
`"peer1.example.com:2121"`). DNS resolution with 60-second cache for UDP,
|
||||
one-shot resolution at connect time for TCP.
|
||||
|
||||
## [0.1.0] - 2026-03-12
|
||||
|
||||
### Added (Initial Release)
|
||||
|
||||
@@ -382,7 +382,7 @@ Static peer list. Each entry defines a peer to connect to.
|
||||
| `peers[].npub` | string | *(required)* | Peer's Nostr public key (npub-encoded) |
|
||||
| `peers[].alias` | string | *(none)* | Human-readable name for logging |
|
||||
| `peers[].addresses[].transport` | string | *(required)* | Transport type: `udp`, `tcp`, or `ethernet` |
|
||||
| `peers[].addresses[].addr` | string | *(required)* | Transport address. UDP/TCP: `"ip:port"`. Ethernet: `"interface/mac"` (e.g., `"eth0/aa:bb:cc:dd:ee:ff"`) |
|
||||
| `peers[].addresses[].addr` | string | *(required)* | Transport address. UDP/TCP: `"host:port"` (IP or DNS hostname). Ethernet: `"interface/mac"` (e.g., `"eth0/aa:bb:cc:dd:ee:ff"`) |
|
||||
| `peers[].addresses[].priority` | u8 | `100` | Address priority (lower = preferred) |
|
||||
| `peers[].connect_policy` | string | `"auto_connect"` | Connection policy: `auto_connect`, `on_demand`, or `manual` |
|
||||
| `peers[].auto_reconnect` | bool | `true` | Automatically reconnect after MMP link-dead removal (exponential backoff, unlimited retries) |
|
||||
|
||||
@@ -216,7 +216,7 @@ address prepends `fd` to the first 15 bytes of the node_addr, providing a
|
||||
ULA overlay address for unmodified IP applications via the TUN interface.
|
||||
|
||||
Below the FIPS identity layer, each transport uses its own native addressing
|
||||
— IP:port tuples, MAC addresses, .onion identifiers. These **link
|
||||
— IP:port or hostname:port addresses, MAC addresses, .onion identifiers. These **link
|
||||
addresses** are opaque to everything above FMP and discarded once link
|
||||
authentication completes.
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ updates, coordinate discovery, and forwarded session datagrams — all encrypted
|
||||
per-hop.
|
||||
|
||||
FMP is the boundary between opaque transport addresses and identified peers.
|
||||
Below FMP, everything is transport-specific addresses (IP:port, MAC, .onion).
|
||||
Below FMP, everything is transport-specific addresses (host:port, MAC, .onion).
|
||||
Above FMP, everything is peers identified by public keys and routable by
|
||||
node_addr. The transport layer never sees FIPS-level structure; FSP never sees
|
||||
transport addresses or routing details.
|
||||
@@ -280,7 +280,7 @@ authenticated peer regardless of what transport address the packet arrived
|
||||
from. FMP updates the peer's current address to the packet's source address,
|
||||
and subsequent outbound packets use the updated address.
|
||||
|
||||
This allows peers to change transport addresses (e.g., IP:port for UDP)
|
||||
This allows peers to change transport addresses (e.g., host:port for UDP)
|
||||
without session interruption. The mechanism is:
|
||||
|
||||
1. Packet arrives from a different address than expected
|
||||
|
||||
@@ -14,7 +14,7 @@ address, deliver the datagram to that address, and push inbound datagrams up
|
||||
to the FIPS Mesh Protocol (FMP) above.
|
||||
|
||||
The transport layer deals exclusively in **transport addresses** — IP:port
|
||||
tuples, MAC addresses, .onion identifiers, radio device addresses. These are
|
||||
or hostname:port addresses, MAC addresses, .onion identifiers, radio device addresses. These are
|
||||
opaque to every layer above FMP. The mapping from transport address to FIPS
|
||||
identity happens at the link layer after the Noise IK link handshake completes.
|
||||
The word "peer" belongs to the link layer and above; the transport layer
|
||||
@@ -115,8 +115,8 @@ for internet connectivity:
|
||||
|
||||
| Transport | Addressing | MTU | Reliability | Notes |
|
||||
| --------- | ---------- | --- | ----------- | ----- |
|
||||
| UDP/IP | IP:port | 1280–1472 | Unreliable | Primary internet transport |
|
||||
| TCP/IP | IP:port | Stream | Reliable | Requires length-prefix framing |
|
||||
| UDP/IP | host:port | 1280–1472 | Unreliable | Primary internet transport |
|
||||
| TCP/IP | host:port | Stream | Reliable | Requires length-prefix framing |
|
||||
| WebSocket | URL | Stream | Reliable | Browser-compatible |
|
||||
| Tor | .onion | Stream | Reliable | High latency, strong anonymity |
|
||||
|
||||
@@ -364,7 +364,7 @@ optional `TcpListener` for inbound connections.
|
||||
|
||||
| Property | Value |
|
||||
| -------- | ----- |
|
||||
| Addressing | IP:port (same as UDP) |
|
||||
| Addressing | host:port — IP address or DNS hostname |
|
||||
| Default MTU | 1400 bytes |
|
||||
| Per-link MTU | Derived from `TCP_MAXSEG` socket option |
|
||||
| Framing | FMP header-based (zero overhead) |
|
||||
@@ -480,7 +480,7 @@ X." FMP does not need to distinguish beacons from query responses.
|
||||
|
||||
For internet-reachable transports, a node publishes a signed Nostr event
|
||||
containing its FIPS discovery information — public key and reachable
|
||||
transport endpoints (UDP IP:port, TCP IP:port, .onion address). Other FIPS
|
||||
transport endpoints (UDP host:port, TCP host:port, .onion address). Other FIPS
|
||||
nodes subscribing on the same relays learn about available peers.
|
||||
|
||||
Nostr relay discovery is not a transport — it is a discovery service that
|
||||
@@ -588,7 +588,7 @@ on all forwarded datagrams.
|
||||
### Transport Addresses
|
||||
|
||||
Transport addresses (`TransportAddr`) are opaque byte vectors. The transport
|
||||
layer interprets them (e.g., UDP parses "ip:port" strings); all layers above
|
||||
layer interprets them (e.g., UDP/TCP resolve "host:port" strings (IP fast path, DNS fallback with 60s cache for UDP)); all layers above
|
||||
treat them as opaque handles passed back to the transport for sending.
|
||||
|
||||
### Transport State Machine
|
||||
|
||||
@@ -43,5 +43,5 @@ peers: []
|
||||
# alias: "gateway"
|
||||
# addresses:
|
||||
# - transport: udp
|
||||
# addr: "217.77.8.91:2121" # public FIPS testing node
|
||||
# addr: "217.77.8.91:2121" # IP or hostname (e.g., "peer.example.com:2121")
|
||||
# connect_policy: auto_connect
|
||||
|
||||
@@ -61,4 +61,4 @@ peers: []
|
||||
# alias: "gateway"
|
||||
# addresses:
|
||||
# - transport: udp
|
||||
# addr: "1.2.3.4:2121"
|
||||
# addr: "1.2.3.4:2121" # IP or hostname
|
||||
|
||||
@@ -72,7 +72,7 @@ peers:
|
||||
alias: "gateway"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "217.77.8.91:2121" # public FIPS testing node
|
||||
addr: "217.77.8.91:2121" # IP or hostname (e.g., "peer.example.com:2121")
|
||||
connect_policy: auto_connect
|
||||
```
|
||||
|
||||
|
||||
@@ -35,9 +35,9 @@ pub struct PeerAddress {
|
||||
/// Transport-specific address string.
|
||||
///
|
||||
/// Format depends on transport type:
|
||||
/// - UDP: "host:port" (e.g., "192.168.1.1:2121")
|
||||
/// - Tor: "onion_address:port" (e.g., "xyz...abc.onion:2121")
|
||||
/// - Ethernet: "interface/mac" (future)
|
||||
/// - UDP/TCP: "host:port" — IP address or DNS hostname
|
||||
/// (e.g., "192.168.1.1:2121" or "peer1.example.com:2121")
|
||||
/// - Ethernet: "interface/mac" (e.g., "eth0/aa:bb:cc:dd:ee:ff")
|
||||
pub addr: String,
|
||||
|
||||
/// Priority for address selection (lower = preferred).
|
||||
|
||||
@@ -16,6 +16,7 @@ use tcp::TcpTransport;
|
||||
#[cfg(target_os = "linux")]
|
||||
use ethernet::EthernetTransport;
|
||||
use std::fmt;
|
||||
use std::net::SocketAddr;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -362,9 +363,8 @@ impl fmt::Display for LinkDirection {
|
||||
/// Opaque transport-specific address.
|
||||
///
|
||||
/// Each transport type interprets this differently:
|
||||
/// - UDP: "ip:port"
|
||||
/// - UDP/TCP: "host:port" (IP address or DNS hostname)
|
||||
/// - Ethernet: MAC address (6 bytes)
|
||||
/// - Tor: ".onion:port"
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct TransportAddr(Vec<u8>);
|
||||
|
||||
@@ -1081,6 +1081,43 @@ impl TransportHandle {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DNS Resolution
|
||||
// ============================================================================
|
||||
|
||||
/// Resolve a TransportAddr to a SocketAddr.
|
||||
///
|
||||
/// Fast path: if the address parses as a numeric IP:port, returns
|
||||
/// immediately with no DNS lookup. Otherwise, treats the address as
|
||||
/// `hostname:port` and performs async DNS resolution via the system
|
||||
/// resolver.
|
||||
pub(crate) async fn resolve_socket_addr(
|
||||
addr: &TransportAddr,
|
||||
) -> Result<SocketAddr, TransportError> {
|
||||
let s = addr
|
||||
.as_str()
|
||||
.ok_or_else(|| TransportError::InvalidAddress("not valid UTF-8".into()))?;
|
||||
|
||||
// Fast path: numeric IP address — no DNS lookup
|
||||
if let Ok(sock_addr) = s.parse::<SocketAddr>() {
|
||||
return Ok(sock_addr);
|
||||
}
|
||||
|
||||
// Slow path: DNS resolution
|
||||
tokio::net::lookup_host(s)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
TransportError::InvalidAddress(format!("DNS resolution failed for {}: {}", s, e))
|
||||
})?
|
||||
.next()
|
||||
.ok_or_else(|| {
|
||||
TransportError::InvalidAddress(format!(
|
||||
"DNS resolution returned no addresses for {}",
|
||||
s
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Tests
|
||||
// ============================================================================
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
pub mod stats;
|
||||
pub mod stream;
|
||||
|
||||
use super::resolve_socket_addr;
|
||||
use super::{
|
||||
ConnectionState, DiscoveredPeer, PacketTx, ReceivedPacket, Transport, TransportAddr,
|
||||
TransportError, TransportId, TransportState, TransportType,
|
||||
@@ -339,7 +340,7 @@ impl TcpTransport {
|
||||
&self,
|
||||
addr: &TransportAddr,
|
||||
) -> Result<Arc<Mutex<OwnedWriteHalf>>, TransportError> {
|
||||
let socket_addr = parse_socket_addr(addr)?;
|
||||
let socket_addr = resolve_socket_addr(addr).await?;
|
||||
let timeout_ms = self.config.connect_timeout_ms();
|
||||
|
||||
// Connect with timeout
|
||||
@@ -453,7 +454,11 @@ impl TcpTransport {
|
||||
}
|
||||
}
|
||||
|
||||
let socket_addr = parse_socket_addr(addr)?;
|
||||
// Validate address is UTF-8 before spawning (fail fast on bad input)
|
||||
let addr_string = addr
|
||||
.as_str()
|
||||
.ok_or_else(|| TransportError::InvalidAddress("not valid UTF-8".into()))?
|
||||
.to_string();
|
||||
let timeout_ms = self.config.connect_timeout_ms();
|
||||
let config = self.config.clone();
|
||||
let transport_id = self.transport_id;
|
||||
@@ -467,6 +472,27 @@ impl TcpTransport {
|
||||
);
|
||||
|
||||
let task = tokio::spawn(async move {
|
||||
// Resolve address (may involve DNS for hostnames)
|
||||
let socket_addr: SocketAddr = if let Ok(sa) = addr_string.parse() {
|
||||
sa
|
||||
} else {
|
||||
tokio::net::lookup_host(&addr_string)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
TransportError::InvalidAddress(format!(
|
||||
"DNS resolution failed for {}: {}",
|
||||
addr_string, e
|
||||
))
|
||||
})?
|
||||
.next()
|
||||
.ok_or_else(|| {
|
||||
TransportError::InvalidAddress(format!(
|
||||
"DNS resolution returned no addresses for {}",
|
||||
addr_string
|
||||
))
|
||||
})?
|
||||
};
|
||||
|
||||
// Connect with timeout
|
||||
let stream = match tokio::time::timeout(
|
||||
Duration::from_millis(timeout_ms),
|
||||
@@ -903,14 +929,6 @@ async fn tcp_receive_loop(
|
||||
// Socket Configuration Helpers
|
||||
// ============================================================================
|
||||
|
||||
/// Parse a TransportAddr as SocketAddr.
|
||||
fn parse_socket_addr(addr: &TransportAddr) -> Result<SocketAddr, TransportError> {
|
||||
addr.as_str()
|
||||
.ok_or_else(|| TransportError::InvalidAddress("not valid UTF-8".into()))?
|
||||
.parse()
|
||||
.map_err(|e| TransportError::InvalidAddress(format!("{}", e)))
|
||||
}
|
||||
|
||||
/// Configure a TCP socket with the transport's settings.
|
||||
fn configure_socket(
|
||||
stream: &std::net::TcpStream,
|
||||
@@ -1511,4 +1529,88 @@ mod tests {
|
||||
);
|
||||
assert_eq!(state, ConnectionState::None);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_connect_ip_string() {
|
||||
let (tx1, _rx1) = packet_channel(100);
|
||||
let (tx2, mut rx2) = packet_channel(100);
|
||||
|
||||
let mut t1 = TcpTransport::new(TransportId::new(1), None, make_config(), tx1);
|
||||
let mut t2 = TcpTransport::new(
|
||||
TransportId::new(2),
|
||||
None,
|
||||
TcpConfig {
|
||||
bind_addr: Some("127.0.0.1:0".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
tx2,
|
||||
);
|
||||
|
||||
t1.start_async().await.unwrap();
|
||||
t2.start_async().await.unwrap();
|
||||
|
||||
let port2 = t2.local_addr().unwrap().port();
|
||||
|
||||
// Connect using IP string — build a valid FMP frame (114 bytes)
|
||||
let addr = TransportAddr::from_string(&format!("127.0.0.1:{}", port2));
|
||||
let mut frame = vec![0xAA; 114];
|
||||
frame[0] = 0x01; // ver=0, phase=1
|
||||
frame[1] = 0x00; // flags
|
||||
frame[2..4].copy_from_slice(&110u16.to_le_bytes()); // payload_len
|
||||
t1.send_async(&addr, &frame).await.unwrap();
|
||||
|
||||
// Receive on t2
|
||||
let packet = tokio::time::timeout(Duration::from_secs(5), rx2.recv())
|
||||
.await
|
||||
.expect("timeout")
|
||||
.expect("channel closed");
|
||||
|
||||
assert_eq!(packet.data, frame);
|
||||
|
||||
t1.stop_async().await.unwrap();
|
||||
t2.stop_async().await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_connect_async_ip_string() {
|
||||
let (tx1, _rx1) = packet_channel(100);
|
||||
let (tx2, _rx2) = packet_channel(100);
|
||||
|
||||
let mut t1 = TcpTransport::new(TransportId::new(1), None, make_config(), tx1);
|
||||
let mut t2 = TcpTransport::new(
|
||||
TransportId::new(2),
|
||||
None,
|
||||
TcpConfig {
|
||||
bind_addr: Some("127.0.0.1:0".to_string()),
|
||||
..Default::default()
|
||||
},
|
||||
tx2,
|
||||
);
|
||||
|
||||
t1.start_async().await.unwrap();
|
||||
t2.start_async().await.unwrap();
|
||||
|
||||
let port2 = t2.local_addr().unwrap().port();
|
||||
let addr = TransportAddr::from_string(&format!("127.0.0.1:{}", port2));
|
||||
|
||||
// Non-blocking connect via IP string
|
||||
t1.connect_async(&addr).await.unwrap();
|
||||
|
||||
// Poll until connected
|
||||
for _ in 0..50 {
|
||||
let state = t1.connection_state_sync(&addr);
|
||||
if state == ConnectionState::Connected {
|
||||
break;
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
t1.connection_state_sync(&addr),
|
||||
ConnectionState::Connected,
|
||||
);
|
||||
|
||||
t1.stop_async().await.unwrap();
|
||||
t2.stop_async().await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,12 +10,18 @@ mod socket;
|
||||
mod stats;
|
||||
use socket::{AsyncUdpSocket, UdpRawSocket};
|
||||
use stats::UdpStats;
|
||||
use super::resolve_socket_addr;
|
||||
use crate::config::UdpConfig;
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Mutex as StdMutex};
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::{debug, info, trace, warn};
|
||||
|
||||
/// DNS cache TTL for hostname resolution (60 seconds).
|
||||
const DNS_CACHE_TTL: Duration = Duration::from_secs(60);
|
||||
|
||||
/// UDP transport for FIPS.
|
||||
///
|
||||
/// Provides connectionless, unreliable packet delivery over UDP/IP.
|
||||
@@ -40,6 +46,8 @@ pub struct UdpTransport {
|
||||
local_addr: Option<SocketAddr>,
|
||||
/// Transport statistics.
|
||||
stats: Arc<UdpStats>,
|
||||
/// DNS resolution cache for hostname addresses.
|
||||
dns_cache: StdMutex<HashMap<TransportAddr, (SocketAddr, Instant)>>,
|
||||
}
|
||||
|
||||
impl UdpTransport {
|
||||
@@ -60,6 +68,7 @@ impl UdpTransport {
|
||||
recv_task: None,
|
||||
local_addr: None,
|
||||
stats: Arc::new(UdpStats::new()),
|
||||
dns_cache: StdMutex::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +87,41 @@ impl UdpTransport {
|
||||
&self.stats
|
||||
}
|
||||
|
||||
/// Resolve a transport address, using cached results for hostnames.
|
||||
///
|
||||
/// Numeric IP addresses bypass the cache entirely. Hostnames are
|
||||
/// resolved via DNS and cached for `DNS_CACHE_TTL` to avoid
|
||||
/// per-packet resolution overhead.
|
||||
async fn resolve_cached(&self, addr: &TransportAddr) -> Result<SocketAddr, TransportError> {
|
||||
// Fast path: try numeric IP parse (no cache, no DNS)
|
||||
if let Some(s) = addr.as_str()
|
||||
&& let Ok(sock_addr) = s.parse::<SocketAddr>()
|
||||
{
|
||||
return Ok(sock_addr);
|
||||
}
|
||||
|
||||
// Check cache
|
||||
{
|
||||
let cache = self.dns_cache.lock().unwrap();
|
||||
if let Some((resolved, cached_at)) = cache.get(addr)
|
||||
&& cached_at.elapsed() < DNS_CACHE_TTL
|
||||
{
|
||||
return Ok(*resolved);
|
||||
}
|
||||
}
|
||||
|
||||
// Cache miss or expired — resolve via DNS
|
||||
let resolved = resolve_socket_addr(addr).await?;
|
||||
|
||||
// Store in cache
|
||||
{
|
||||
let mut cache = self.dns_cache.lock().unwrap();
|
||||
cache.insert(addr.clone(), (resolved, Instant::now()));
|
||||
}
|
||||
|
||||
Ok(resolved)
|
||||
}
|
||||
|
||||
/// Query transport-local congestion indicators.
|
||||
pub fn congestion(&self) -> super::TransportCongestion {
|
||||
super::TransportCongestion {
|
||||
@@ -194,7 +238,7 @@ impl UdpTransport {
|
||||
});
|
||||
}
|
||||
|
||||
let socket_addr = parse_socket_addr(addr)?;
|
||||
let socket_addr = self.resolve_cached(addr).await?;
|
||||
let socket = self.socket.as_ref().ok_or(TransportError::NotStarted)?;
|
||||
|
||||
match socket.send_to(data, &socket_addr).await {
|
||||
@@ -261,14 +305,6 @@ impl Transport for UdpTransport {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a TransportAddr as SocketAddr.
|
||||
fn parse_socket_addr(addr: &TransportAddr) -> Result<SocketAddr, TransportError> {
|
||||
addr.as_str()
|
||||
.ok_or_else(|| TransportError::InvalidAddress("not valid UTF-8".into()))?
|
||||
.parse()
|
||||
.map_err(|e| TransportError::InvalidAddress(format!("{}", e)))
|
||||
}
|
||||
|
||||
/// UDP receive loop - runs as a spawned task.
|
||||
async fn udp_receive_loop(
|
||||
socket: AsyncUdpSocket,
|
||||
@@ -528,17 +564,29 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_socket_addr() {
|
||||
#[tokio::test]
|
||||
async fn test_resolve_socket_addr_ip() {
|
||||
let addr = TransportAddr::from_string("192.168.1.1:2121");
|
||||
let result = parse_socket_addr(&addr).unwrap();
|
||||
let result = resolve_socket_addr(&addr).await.unwrap();
|
||||
assert_eq!(result.to_string(), "192.168.1.1:2121");
|
||||
}
|
||||
|
||||
let invalid = TransportAddr::from_string("not_an_address");
|
||||
assert!(parse_socket_addr(&invalid).is_err());
|
||||
#[tokio::test]
|
||||
async fn test_resolve_socket_addr_invalid() {
|
||||
let invalid = TransportAddr::from_string("nonexistent.invalid:2121");
|
||||
assert!(resolve_socket_addr(&invalid).await.is_err());
|
||||
|
||||
let binary = TransportAddr::new(vec![0xff, 0x80]);
|
||||
assert!(parse_socket_addr(&binary).is_err());
|
||||
assert!(resolve_socket_addr(&binary).await.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_resolve_socket_addr_hostname() {
|
||||
let addr = TransportAddr::from_string("localhost:2121");
|
||||
let result = resolve_socket_addr(&addr).await.unwrap();
|
||||
// localhost should resolve to 127.0.0.1 or [::1]
|
||||
assert!(result.ip().is_loopback());
|
||||
assert_eq!(result.port(), 2121);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -550,4 +598,37 @@ mod tests {
|
||||
let cong = transport.congestion();
|
||||
assert_eq!(cong.recv_drops, Some(0));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_send_recv_ip_string() {
|
||||
let (tx1, _rx1) = packet_channel(100);
|
||||
let (tx2, mut rx2) = packet_channel(100);
|
||||
|
||||
let mut t1 = UdpTransport::new(TransportId::new(1), None, make_config(0), tx1);
|
||||
let mut t2 = UdpTransport::new(TransportId::new(2), None, make_config(0), tx2);
|
||||
|
||||
t1.start_async().await.unwrap();
|
||||
t2.start_async().await.unwrap();
|
||||
|
||||
let port2 = t2.local_addr().unwrap().port();
|
||||
|
||||
// Send using IP string address
|
||||
let data = b"hello via ip string";
|
||||
let bytes_sent = t1
|
||||
.send_async(&TransportAddr::from_string(&format!("127.0.0.1:{}", port2)), data)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(bytes_sent, data.len());
|
||||
|
||||
// Receive on t2
|
||||
let packet = timeout(Duration::from_secs(1), rx2.recv())
|
||||
.await
|
||||
.expect("timeout")
|
||||
.expect("channel closed");
|
||||
|
||||
assert_eq!(packet.data, data);
|
||||
|
||||
t1.stop_async().await.unwrap();
|
||||
t2.stop_async().await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user