diff --git a/src/options.rs b/src/options.rs index dc49b67..fde220a 100644 --- a/src/options.rs +++ b/src/options.rs @@ -21,7 +21,7 @@ use rand::{ thread_rng, }; -use std::fmt; +use std::{fmt, time::Duration}; /// Default port for UDP. pub(crate) const SAMV3_UDP_PORT: u16 = 7655; @@ -56,52 +56,256 @@ impl fmt::Debug for DestinationKind { /// Session options. #[derive(Debug, Clone, PartialEq, Eq)] pub struct SessionOptions { - /// Port where the datagram socket should be bound to. + /// Nickname. /// - /// Defaults to `0`. - pub datagram_port: u16, + /// Name that uniquely identifies the session. + /// + /// Defaults to a random alphanumeric string. + pub nickname: String, /// Destination kind. /// /// Defaults to [`DestinationKind::Transient`]. pub destination: DestinationKind, + /// Signature type. + /// + /// Default to `7`, i.e., EdDSA-SHA512-Ed25519 + pub signature_type: u16, + + /// Port where the datagram socket should be bound to. + /// + /// Defaults to `0`. + pub datagram_port: u16, + + /// Defaults to `0`. + pub from_port: u16, + + /// Defaults to `0`. + pub to_port: u16, + + /// Defaults to `18`. + pub protocol: u8, + + /// Defaults to `false`. + pub header: bool, + + /// Should the session's lease set be published to NetDb. + /// + /// Outbound-only sessions (clients) shouldn't be published whereas servers (accepting inbound + /// connections) need to be published. + /// + /// Corresponds to `i2cp.dontPublishLeaseSet`. + /// + /// Defaults to `true`. + pub publish: bool, + + /// Minimum number of ElGamal/AES Session Tags before we send more. Recommended: approximately + /// tagsToSend * 2/3 + /// + /// Defaults to `30`. + pub crypto_low_tag_threshold: usize, + + /// Inbound tag window for ECIES-X25519-AEAD-Ratchet. Local inbound tagset size. + /// + /// Defaults to `160`. + pub crypto_ratchet_inbound_tags: usize, + + /// Outbound tag window for ECIES-X25519-AEAD-Ratchet. Advisory to send to the far-end in the + /// options block. + /// + /// Defaults to `160`. + pub crypto_ratchet_outbound_tags: usize, + + /// Number of ElGamal/AES Session Tags to send at a time. + /// + /// Defaults to `40`. + pub crypto_tags_to_send: usize, + + /// For authorization, if required by the router. + /// + /// Defauts to `None`. + pub username: Option, + + /// For authorization, if required by the router. + /// + /// Defauts to `None`. + pub password: Option, + + /// If incoming zero hop tunnel is allowed + /// + /// Defaults to `false`. + pub inbound_allow_zero_hop: bool, + /// How many hops do the inbound tunnels of the session have. /// /// Defaults to `3`. pub inbound_len: usize, - /// Nickname. - /// - /// Name that uniquely identifies the session. + /// Random amount to add or subtract to the length of tunnels in. /// - /// Defaults to a random alphanumeric string. - pub nickname: String, + /// Defaults to `0`. + pub inbound_len_variance: isize, /// How many inbound tunnels does the tunnel pool of the session have. /// /// Defaults to `2`. - pub num_inbound: usize, + pub inbound_quantity: usize, - /// How many outbound tunnels does the tunnel pool of the session have. + /// Number of redundant, fail-over inbound tunnels /// - /// Defaults to `2`. - pub num_outbound: usize, + /// Defaults to `0`. + pub inbound_backup_quantity: usize, + + /// Number of IP bytes to match to determine if two routers should not be in the same inbound + /// tunnel. + /// + /// Defaults to `None`. + pub inbound_ip_restriction: Option, + + /// Used for consistent peer ordering across restarts. + /// + /// Defauts to `None`. + pub inbound_random_key: Option, + + /// Name of inbound tunnels - generally used in routerconsole, which will use + /// the first few characters of the Base64 hash of the destination by default. + /// + /// Defaults to `None`. + pub inbound_nickname: Option, + + /// If outgoing zero hop tunnel is allowed + /// + /// Defaults to `false`. + pub outbound_allow_zero_hop: bool, /// How many hops do the outbound tunnels of the session have. /// /// Defaults to `3`. pub outbound_len: usize, - /// Should the session's lease set be published to NetDb. + /// Random amount to add or subtract to the length of tunnels in. /// - /// Outbound-only sessions (clients) shouldn't be published whereas servers (accepting inbound - /// connections) need to be published. + /// Defaults to `0`. + pub outbound_len_variance: isize, + + /// How many outbound tunnels does the tunnel pool of the session have. /// - /// Corresponds to `i2cp.dontPublishLeaseSet`. + /// Defaults to `2`. + pub outbound_quantity: usize, + + /// Number of redundant, fail-over outbound tunnels + /// + /// Defaults to `0`. + pub outbound_backup_quantity: usize, + + /// Number of IP bytes to match to determine if two routers should not be in the same outbound + /// tunnel. + /// + /// Defaults to `None`. + pub outbound_ip_restriction: Option, + + /// Used for consistent peer ordering across restarts. + /// + /// Defauts to `None`. + pub outbound_random_key: Option, + + /// Name of outbound tunnels - generally ignored unless `inbound_nickname` is unset. + /// + /// Defauts to `None`. + pub outbound_nickname: Option, + + /// Priority adjustment for outbound messages. Higher is higher priority. + /// + /// Defaults to `0`. + pub outbound_priority: isize, + + /// Set to false to disable ever bundling a reply LeaseSet. /// /// Defaults to `true`. - pub publish: bool, + pub should_bundle_reply_info: bool, + + /// Reduce tunnel quantity when idle + /// + /// Defaults to `false`. + pub reduce_on_idle: bool, + + /// Idle time required before reducing tunnel quantity + /// + /// Defaults to 20 minutes. + pub reduce_idle_time: Duration, + + /// Tunnel quantity when reduced (applies to both inbound and outbound) + /// + /// Defauts to `1`. + pub reduce_quantity: usize, + + /// Close I2P session when idle + /// + /// Defaults to `false`. + pub close_on_idle: bool, + + /// Idle time required before closing session + /// + /// Defaults to 30 minutes. + pub close_idle_time: Duration, + + /// The type of authentication for encrypted LS2. 0 for no per-client authentication ; + /// 1 for DH per-client authentication; 2 for PSK per-client authentication. + /// + /// Defaults to `0`. + pub lease_set_auth_type: usize, + + /// The sig type of the blinded key for encrypted LS2. Default depends on the destination sig + /// type. + /// + /// Defaults to `0`. + pub lease_set_blinded_type: usize, + + /// The encryption type to be used. + /// + /// Defaults to `4`, i.e., ECIES-X25519. + pub lease_set_enc_type: usize, + + /// For encrypted leasesets. Base 64 SessionKey (44 characters) + /// + /// Defauts to `None`. + pub lease_set_key: Option, + + /// Base 64 private keys for encryption. + /// + /// Defauts to `None`. + pub lease_set_private_key: Option, + + /// Base 64 encoded UTF-8 secret used to blind the leaseset address. + /// + /// Defauts to `None`. + pub lease_set_secret: Option, + + /// Base 64 private key for signatures. + /// + /// Defauts to `None`. + pub lease_set_signing_private_key: Option, + + /// The type of leaseset to be sent in the CreateLeaseSet2 Message. + /// + /// Defaults to `1`. + pub lease_set_type: usize, + + /// Encrypt the lease + /// + /// Defaults to `false`. + pub encrypt_lease_set: bool, + + /// Gzip outbound data + /// + /// Defaults to `true`. + pub gzip: bool, + + /// Connect to the router using SSL. + /// + /// Defauts to `false`. + pub ssl: bool, /// TCP port of the listening SAMv3 server. /// @@ -125,17 +329,58 @@ pub struct SessionOptions { impl Default for SessionOptions { fn default() -> Self { Self { - datagram_port: 0u16, - destination: DestinationKind::Transient, nickname: Alphanumeric.sample_string(&mut thread_rng(), 16), + destination: DestinationKind::Transient, + signature_type: 7u16, + datagram_port: 0u16, + from_port: 0u16, + to_port: 0u16, + protocol: 18u8, + header: false, publish: true, + crypto_low_tag_threshold: 30usize, + crypto_ratchet_inbound_tags: 160usize, + crypto_ratchet_outbound_tags: 160usize, + crypto_tags_to_send: 40usize, + username: None, + password: None, + inbound_allow_zero_hop: false, + inbound_len: 3usize, + inbound_len_variance: 0isize, + inbound_quantity: 2usize, + inbound_backup_quantity: 0usize, + inbound_ip_restriction: None, + inbound_random_key: None, + inbound_nickname: None, + outbound_allow_zero_hop: false, + outbound_len: 3usize, + outbound_len_variance: 0isize, + outbound_quantity: 2usize, + outbound_backup_quantity: 0usize, + outbound_ip_restriction: None, + outbound_random_key: None, + outbound_nickname: None, + outbound_priority: 0isize, + should_bundle_reply_info: true, + reduce_on_idle: false, + reduce_idle_time: Duration::from_millis(1200000), + reduce_quantity: 1usize, + close_on_idle: false, + close_idle_time: Duration::from_millis(1800000), + lease_set_auth_type: 0usize, + lease_set_blinded_type: 0usize, + lease_set_enc_type: 4usize, + lease_set_key: None, + lease_set_private_key: None, + lease_set_secret: None, + lease_set_signing_private_key: None, + lease_set_type: 1usize, + encrypt_lease_set: false, + gzip: true, + ssl: false, samv3_tcp_port: SAMV3_TCP_PORT, samv3_udp_port: SAMV3_UDP_PORT, silent_forward: false, - num_inbound: 2usize, - inbound_len: 3usize, - num_outbound: 2usize, - outbound_len: 3usize, } } } diff --git a/src/proto/parser.rs b/src/proto/parser.rs index 8b441b5..69202d3 100644 --- a/src/proto/parser.rs +++ b/src/proto/parser.rs @@ -96,9 +96,9 @@ pub enum Response { impl<'a> TryFrom> for Response { type Error = (); - fn try_from(value: ParsedCommand<'a>) -> Result { - match (value.command, value.subcommand) { - ("HELLO", Some("REPLY")) => match value.key_value_pairs.get("VERSION") { + fn try_from(parsed_cmd: ParsedCommand<'a>) -> Result { + match (parsed_cmd.command, parsed_cmd.subcommand) { + ("HELLO", Some("REPLY")) => match parsed_cmd.key_value_pairs.get("VERSION") { Some(version) => Ok(Response::Hello { version: Ok(version.to_string()), }), @@ -106,27 +106,27 @@ impl<'a> TryFrom> for Response { // if `VERSION` doesn't exist, `RESULT` is expected to exist as `NOVERSION` // an unexpected error since reporting version is optional as of v3.1 and // `yosemite` doesn't send a version string to the router - let result = value.key_value_pairs.get("RESULT").ok_or(())?; - let message = value.key_value_pairs.get("MESSAGE"); + let result = parsed_cmd.key_value_pairs.get("RESULT").ok_or(())?; + let message = parsed_cmd.key_value_pairs.get("MESSAGE"); Ok(Response::Hello { version: Err(I2pError::try_from((*result, message.map(|value| *value)))?), }) } }, - ("SESSION", Some("STATUS")) => match value.key_value_pairs.get("DESTINATION") { + ("SESSION", Some("STATUS")) => match parsed_cmd.key_value_pairs.get("DESTINATION") { Some(destination) => Ok(Response::Session { destination: Ok(destination.to_string()), }), None => match ( - value.key_value_pairs.get("RESULT").ok_or(())?, - value.key_value_pairs.get("ID"), + parsed_cmd.key_value_pairs.get("RESULT").ok_or(())?, + parsed_cmd.key_value_pairs.get("ID"), ) { (&"OK", Some(session_id)) => Ok(Response::Subsession { session_id: Ok(session_id.to_string()), }), (result, _) => { - let message = value.key_value_pairs.get("MESSAGE"); + let message = parsed_cmd.key_value_pairs.get("MESSAGE"); Ok(Response::Session { destination: Err(I2pError::try_from(( @@ -137,10 +137,10 @@ impl<'a> TryFrom> for Response { } }, }, - ("STREAM", Some("STATUS")) => match value.key_value_pairs.get("RESULT") { + ("STREAM", Some("STATUS")) => match parsed_cmd.key_value_pairs.get("RESULT") { Some(value) if *value == "OK" => Ok(Response::Stream { result: Ok(()) }), Some(error) => { - let message = value.key_value_pairs.get("MESSAGE"); + let message = parsed_cmd.key_value_pairs.get("MESSAGE"); Ok(Response::Stream { result: Err(I2pError::try_from((*error, message.map(|value| *value)))?), @@ -148,16 +148,17 @@ impl<'a> TryFrom> for Response { } None => return Err(()), }, - ("NAMING", Some("REPLY")) => match value.key_value_pairs.get("RESULT") { + ("NAMING", Some("REPLY")) => match parsed_cmd.key_value_pairs.get("RESULT") { Some(result) if *result == "OK" => { - let destination = value.key_value_pairs.get("VALUE").ok_or(())?.to_string(); + let destination = + parsed_cmd.key_value_pairs.get("VALUE").ok_or(())?.to_string(); Ok(Response::NamingLookup { result: Ok(destination), }) } Some(error) => { - let message = value.key_value_pairs.get("MESSAGE"); + let message = parsed_cmd.key_value_pairs.get("MESSAGE"); Ok(Response::NamingLookup { result: Err(I2pError::try_from((*error, message.map(|value| *value)))?), @@ -166,8 +167,8 @@ impl<'a> TryFrom> for Response { None => return Err(()), }, ("DEST", Some("REPLY")) => { - let destination = value.key_value_pairs.get("PUB").ok_or(())?.to_string(); - let private_key = value.key_value_pairs.get("PRIV").ok_or(())?.to_string(); + let destination = parsed_cmd.key_value_pairs.get("PUB").ok_or(())?.to_string(); + let private_key = parsed_cmd.key_value_pairs.get("PRIV").ok_or(())?.to_string(); Ok(Response::DestinationGeneration { destination, @@ -183,7 +184,7 @@ impl Response { /// Attempt to parse `input` into `Response`. // // Non-public method returning `IResult` for cleaner error handling. - fn parse_inner<'a>(input: &'a str) -> IResult<&'a str, Self> { + fn parse_inner(input: &str) -> IResult<&str, Self> { let (rest, (command, _, subcommand, _, key_value_pairs)) = tuple(( alt(( tag("HELLO"), diff --git a/src/proto/session.rs b/src/proto/session.rs index c2758cb..60cd04a 100644 --- a/src/proto/session.rs +++ b/src/proto/session.rs @@ -185,19 +185,51 @@ impl SessionController { } } + match parameters.style.as_str() { + "PRIMARY" => {} + "STREAM" => {} + "DATAGRAM" => { + command += format!( + "FROM_PORT={} TO_PORT={} ", + self.options.from_port, self.options.to_port, + ) + .as_str(); + } + "DATAGRAM2" => {} + "DATAGRAM3" => {} + "RAW" => { + command += format!( + "FROM_PORT={} TO_PORT={} PROTOCOL={} HEADER={} ", + self.options.from_port, + self.options.to_port, + self.options.protocol, + self.options.header, + ) + .as_str(); + } + _ => { + tracing::warn!( + target: LOG_TARGET, + style = %parameters.style, + "cannot create session, non-supported session style", + ); + return Err(ProtocolError::InvalidMessage); + } + } + if !self.options.publish { command += "i2cp.dontPublishLeaseSet=true "; } command += format!( "inbound.length={} inbound.quantity={} ", - self.options.inbound_len, self.options.num_inbound + self.options.inbound_len, self.options.inbound_quantity ) .as_str(); command += format!( "outbound.length={} outbound.quantity={} ", - self.options.outbound_len, self.options.num_outbound + self.options.outbound_len, self.options.outbound_quantity ) .as_str(); @@ -211,7 +243,6 @@ impl SessionController { ?state, "cannot create session, invalid state", ); - debug_assert!(false); Err(ProtocolError::InvalidState) } @@ -384,8 +415,7 @@ impl SessionController { Ok(format!( "STREAM FORWARD ID={} PORT={port} SILENT={}\n", - self.options.nickname, - self.options.silent_forward.to_string(), + self.options.nickname, self.options.silent_forward, ) .into_bytes()) }