radicle: Warn less aggressively on IPv6 addresses
Parsing of IPv6 addresses not enclosed in square brackets causes a warning to be logged. This is too strict, since the user can not influence IPv6 addresses received over the network, but only those in their configuration. Remove the warning from parsing, and instead carry a boolean to warn the user later.
This commit is contained in:
parent
7ed9d50ab6
commit
ff8660d872
|
|
@ -0,0 +1,59 @@
|
||||||
|
```
|
||||||
|
$ rad config push preferredSeeds z6MkvUJtYD9dHDJfpevWRT98mzDDpdAtmUjwyDSkyqksUr7C@2001:db8::1:8776
|
||||||
|
z6MkvUJtYD9dHDJfpevWRT98mzDDpdAtmUjwyDSkyqksUr7C@2001:db8::1:8776
|
||||||
|
$ rad config push node.connect z6MkvUJtYD9dHDJfpevWRT98mzDDpdAtmUjwyDSkyqksUr7C@2001:db8::2:8776
|
||||||
|
z6MkvUJtYD9dHDJfpevWRT98mzDDpdAtmUjwyDSkyqksUr7C@2001:db8::2:8776
|
||||||
|
$ rad config push node.externalAddresses 2001:db8::3:8776
|
||||||
|
2001:db8::3:8776
|
||||||
|
```
|
||||||
|
|
||||||
|
Note the warnings that the above configuration causes:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rad debug
|
||||||
|
{
|
||||||
|
"radExe": "[..]",
|
||||||
|
"radVersion": "[..]",
|
||||||
|
"radicleNodeVersion": "radicle-node [..]",
|
||||||
|
"gitRemoteRadVersion": "git-remote-rad [..]",
|
||||||
|
"gitVersion": "git version [..]",
|
||||||
|
"sshVersion": "[..]",
|
||||||
|
"gitHead": "[..]",
|
||||||
|
"log": {
|
||||||
|
"filename": "[..]",
|
||||||
|
"exists": false,
|
||||||
|
"len": null
|
||||||
|
},
|
||||||
|
"oldLog": {
|
||||||
|
"filename": "[..]",
|
||||||
|
"exists": false,
|
||||||
|
"len": null
|
||||||
|
},
|
||||||
|
"operatingSystem": "[..]",
|
||||||
|
"arch": "[..]",
|
||||||
|
"env": {
|
||||||
|
"PATH": "[..]",
|
||||||
|
"RAD_HOME": "[..]",
|
||||||
|
"RAD_KEYGEN_SEED": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
"RAD_LOCAL_TIME": "[..]",
|
||||||
|
"RAD_PASSPHRASE": "<REDACTED>",
|
||||||
|
"RAD_RNG_SEED": "0"
|
||||||
|
},
|
||||||
|
"warnings": [
|
||||||
|
"Value of configuration option `preferredSeeds` at zero-based index 0 mentions IPv6 address '2001:db8::1' without square brackets. The address format will change, and this address will be rejected in the future. Please edit your configuration file to enclose the IPv6 address in square brackets. Combined with port information it should read '[2001:db8::1]:8776'. Refer to RFC 5926, Sec. 6 as well as RFC 3986, Sec. D.1. and RFC 2732, Sec. 2.",
|
||||||
|
"Value of configuration option `node.connect` at zero-based index 0 mentions IPv6 address '2001:db8::2' without square brackets. The address format will change, and this address will be rejected in the future. Please edit your configuration file to enclose the IPv6 address in square brackets. Combined with port information it should read '[2001:db8::2]:8776'. Refer to RFC 5926, Sec. 6 as well as RFC 3986, Sec. D.1. and RFC 2732, Sec. 2.",
|
||||||
|
"Value of configuration option `node.externalAddresses` at zero-based index 0 mentions IPv6 address '2001:db8::3' without square brackets. The address format will change, and this address will be rejected in the future. Please edit your configuration file to enclose the IPv6 address in square brackets. Combined with port information it should read '[2001:db8::3]:8776'. Refer to RFC 5926, Sec. 6 as well as RFC 3986, Sec. D.1. and RFC 2732, Sec. 2."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Also, `rad node status` will warn us:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rad node status
|
||||||
|
! Warning: Value of configuration option `preferredSeeds` at zero-based index 0 mentions IPv6 address '2001:db8::1' without square brackets. The address format will change, and this address will be rejected in the future. Please edit your configuration file to enclose the IPv6 address in square brackets. Combined with port information it should read '[2001:db8::1]:8776'. Refer to RFC 5926, Sec. 6 as well as RFC 3986, Sec. D.1. and RFC 2732, Sec. 2.
|
||||||
|
! Warning: Value of configuration option `node.connect` at zero-based index 0 mentions IPv6 address '2001:db8::2' without square brackets. The address format will change, and this address will be rejected in the future. Please edit your configuration file to enclose the IPv6 address in square brackets. Combined with port information it should read '[2001:db8::2]:8776'. Refer to RFC 5926, Sec. 6 as well as RFC 3986, Sec. D.1. and RFC 2732, Sec. 2.
|
||||||
|
! Warning: Value of configuration option `node.externalAddresses` at zero-based index 0 mentions IPv6 address '2001:db8::3' without square brackets. The address format will change, and this address will be rejected in the future. Please edit your configuration file to enclose the IPv6 address in square brackets. Combined with port information it should read '[2001:db8::3]:8776'. Refer to RFC 5926, Sec. 6 as well as RFC 3986, Sec. D.1. and RFC 2732, Sec. 2.
|
||||||
|
Node is stopped.
|
||||||
|
To start it, run `rad node start`.
|
||||||
|
```
|
||||||
|
|
@ -76,9 +76,49 @@ fn implicit_seeding_policy_allow_scope(config: &Config) -> Vec<String> {
|
||||||
)]
|
)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ipv6_without_square_brackets(config: &Config) -> Vec<String> {
|
||||||
|
fn zip(
|
||||||
|
option: &'static str,
|
||||||
|
iter: impl Iterator<Item = Address>,
|
||||||
|
) -> impl Iterator<Item = (&'static str, (usize, Address))> {
|
||||||
|
std::iter::zip(
|
||||||
|
std::iter::repeat(option),
|
||||||
|
iter.enumerate().filter_map(|(i, address)| {
|
||||||
|
#[allow(deprecated)]
|
||||||
|
address
|
||||||
|
.is_ipv6_without_square_brackets()
|
||||||
|
.then_some((i, address))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pick_addr<'a>(
|
||||||
|
iter: impl Iterator<Item = &'a ConnectAddress>,
|
||||||
|
) -> impl Iterator<Item = Address> {
|
||||||
|
iter.map(|connect_address| connect_address.addr.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
let chained = zip("preferredSeeds", pick_addr(config.preferred_seeds.iter()))
|
||||||
|
.chain(zip("node.connect", pick_addr(config.node.connect.iter())))
|
||||||
|
.chain(zip(
|
||||||
|
"node.externalAddresses",
|
||||||
|
config.node.external_addresses.iter().cloned(),
|
||||||
|
));
|
||||||
|
|
||||||
|
chained.map(|(option, (i, address))|
|
||||||
|
format!(
|
||||||
|
"Value of configuration option `{option}` at zero-based index {i} mentions IPv6 address '{}' without square brackets. The address format will change, and this address will be rejected in the future. Please edit your configuration file to enclose the IPv6 address in square brackets. Combined with port information it should read '[{}]:{}'. Refer to RFC 5926, Sec. 6 as well as RFC 3986, Sec. D.1. and RFC 2732, Sec. 2.",
|
||||||
|
address.host(),
|
||||||
|
address.host(),
|
||||||
|
address.port()
|
||||||
|
)
|
||||||
|
).collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn config_warnings(config: &Config) -> Vec<String> {
|
pub(crate) fn config_warnings(config: &Config) -> Vec<String> {
|
||||||
let mut warnings = nodes_renamed(config);
|
let mut warnings = nodes_renamed(config);
|
||||||
warnings.extend(implicit_seeding_policy_allow_scope(config));
|
warnings.extend(implicit_seeding_policy_allow_scope(config));
|
||||||
|
warnings.extend(ipv6_without_square_brackets(config));
|
||||||
|
|
||||||
warnings
|
warnings
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,11 @@ fn rad_warn_old_nodes() {
|
||||||
Environment::alice(["rad-warn-old-nodes"]);
|
Environment::alice(["rad-warn-old-nodes"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rad_warn_ipv6() {
|
||||||
|
Environment::alice(["rad-warn-ipv6"]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rad_clean() {
|
fn rad_clean() {
|
||||||
let mut environment = Environment::new();
|
let mut environment = Environment::new();
|
||||||
|
|
|
||||||
|
|
@ -460,7 +460,7 @@ impl TryFrom<&sqlite::Value> for Alias {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Peer public protocol address.
|
/// Peer public protocol address.
|
||||||
#[derive(Clone, Eq, PartialEq, Debug, Hash, From, Wrapper, WrapperMut, Serialize, Deserialize)]
|
#[derive(Clone, Eq, PartialEq, Debug, Hash, Wrapper, WrapperMut, Serialize, Deserialize)]
|
||||||
#[wrapper(Deref)]
|
#[wrapper(Deref)]
|
||||||
#[wrapper_mut(DerefMut)]
|
#[wrapper_mut(DerefMut)]
|
||||||
#[serde(try_from = "String", into = "String")]
|
#[serde(try_from = "String", into = "String")]
|
||||||
|
|
@ -479,12 +479,19 @@ impl TryFrom<&sqlite::Value> for Alias {
|
||||||
"pattern" = "^.+:((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0-9]{1,4}))$",
|
"pattern" = "^.+:((6553[0-5])|(655[0-2][0-9])|(65[0-4][0-9]{2})|(6[0-4][0-9]{3})|([1-5][0-9]{4})|([0-5]{0,5})|([0-9]{1,4}))$",
|
||||||
),
|
),
|
||||||
))]
|
))]
|
||||||
pub struct Address(NetAddr<HostName>);
|
pub struct Address {
|
||||||
|
#[wrap]
|
||||||
|
inner: NetAddr<HostName>,
|
||||||
|
|
||||||
|
/// See documentation of [`Address::is_ipv6_without_square_brackets`] for details.
|
||||||
|
#[deprecated]
|
||||||
|
is_ipv6_without_square_brackets: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl Address {
|
impl Address {
|
||||||
/// Check whether this address is from the local network.
|
/// Check whether this address is from the local network.
|
||||||
pub fn is_local(&self) -> bool {
|
pub fn is_local(&self) -> bool {
|
||||||
match &self.0.host {
|
match &self.inner.host {
|
||||||
HostName::Ip(ip) => address::is_local(ip),
|
HostName::Ip(ip) => address::is_local(ip),
|
||||||
HostName::Dns(name) => {
|
HostName::Dns(name) => {
|
||||||
let name = name.strip_suffix(".").unwrap_or(name);
|
let name = name.strip_suffix(".").unwrap_or(name);
|
||||||
|
|
@ -499,7 +506,7 @@ impl Address {
|
||||||
|
|
||||||
/// Check whether this address is globally routable.
|
/// Check whether this address is globally routable.
|
||||||
pub fn is_routable(&self) -> bool {
|
pub fn is_routable(&self) -> bool {
|
||||||
match self.0.host {
|
match self.inner.host {
|
||||||
HostName::Ip(ip) => address::is_routable(&ip),
|
HostName::Ip(ip) => address::is_routable(&ip),
|
||||||
HostName::Dns(_) => !self.is_local(),
|
HostName::Dns(_) => !self.is_local(),
|
||||||
_ => true,
|
_ => true,
|
||||||
|
|
@ -508,24 +515,24 @@ impl Address {
|
||||||
|
|
||||||
/// Return the [`HostName`] of the [`Address`].
|
/// Return the [`HostName`] of the [`Address`].
|
||||||
pub fn host(&self) -> &HostName {
|
pub fn host(&self) -> &HostName {
|
||||||
&self.0.host
|
&self.inner.host
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the [`HostName`] is a Tor onion address.
|
/// Returns `true` if the [`HostName`] is a Tor onion address.
|
||||||
#[cfg(feature = "tor")]
|
#[cfg(feature = "tor")]
|
||||||
pub fn is_onion(&self) -> bool {
|
pub fn is_onion(&self) -> bool {
|
||||||
matches!(self.0.host, HostName::Tor(_))
|
matches!(self.inner.host, HostName::Tor(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the [`HostName`] is an I2P address.
|
/// Returns `true` if the [`HostName`] is an I2P address.
|
||||||
#[cfg(feature = "i2p")]
|
#[cfg(feature = "i2p")]
|
||||||
pub fn is_i2p(&self) -> bool {
|
pub fn is_i2p(&self) -> bool {
|
||||||
matches!(self.0.host, HostName::I2p(_))
|
matches!(self.inner.host, HostName::I2p(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the port number of the [`Address`].
|
/// Return the port number of the [`Address`].
|
||||||
pub fn port(&self) -> u16 {
|
pub fn port(&self) -> u16 {
|
||||||
self.0.port
|
self.inner.port
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_compact(&self) -> impl Display + use<> {
|
pub fn display_compact(&self) -> impl Display + use<> {
|
||||||
|
|
@ -552,6 +559,17 @@ impl Address {
|
||||||
|
|
||||||
format!("{host}:{port}")
|
format!("{host}:{port}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the address was parsed from a string that
|
||||||
|
/// mentioned an IPv6 address without square brackets.
|
||||||
|
/// May be used to warn the user, since this format is deprecated.
|
||||||
|
/// This function itself is deprecated and will be removed without further
|
||||||
|
/// notice when the format is no longer accepted.
|
||||||
|
#[deprecated]
|
||||||
|
pub fn is_ipv6_without_square_brackets(&self) -> bool {
|
||||||
|
#[allow(deprecated)]
|
||||||
|
self.is_ipv6_without_square_brackets
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Address {
|
impl Display for Address {
|
||||||
|
|
@ -560,7 +578,17 @@ impl Display for Address {
|
||||||
HostName::Ip(IpAddr::V6(ip)) => {
|
HostName::Ip(IpAddr::V6(ip)) => {
|
||||||
write!(f, "[{ip}]:{}", self.port())
|
write!(f, "[{ip}]:{}", self.port())
|
||||||
}
|
}
|
||||||
_ => self.0.fmt(f),
|
_ => self.inner.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NetAddr<HostName>> for Address {
|
||||||
|
fn from(addr: NetAddr<HostName>) -> Self {
|
||||||
|
Address {
|
||||||
|
inner: addr,
|
||||||
|
#[allow(deprecated)]
|
||||||
|
is_ipv6_without_square_brackets: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -585,52 +613,55 @@ impl FromStr for Address {
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let (host, port) = s.rsplit_once(':').ok_or(AddrParseError::PortAbsent)?;
|
let (host, port) = s.rsplit_once(':').ok_or(AddrParseError::PortAbsent)?;
|
||||||
|
|
||||||
let host = if let Some(host) = host
|
let (host, is_ipv6_without_square_brackets) = if let Some(host) = host
|
||||||
.strip_prefix('[')
|
.strip_prefix('[')
|
||||||
.and_then(|host| host.strip_suffix(']'))
|
.and_then(|host| host.strip_suffix(']'))
|
||||||
{
|
{
|
||||||
HostName::Ip(host.parse::<Ipv6Addr>()?.into())
|
(HostName::Ip(host.parse::<Ipv6Addr>()?.into()), false)
|
||||||
} else {
|
} else {
|
||||||
// Warn on IPv6 addresses that are not enclosed in `[` and `]`.
|
let host = host.parse()?;
|
||||||
host.parse().map(|host| match host {
|
let is_ipv6_without_square_brackets = matches!(&host, HostName::Ip(IpAddr::V6(_)));
|
||||||
HostName::Ip(IpAddr::V6(addr)) => {
|
(host, is_ipv6_without_square_brackets)
|
||||||
log::warn!("Address format will change in the future. '{s}' should be changed to '[{addr}]:{port}' to stay compatible. Refer to RFC 5926, Sec. 6 as well as RFC 3986, Sec. D.1. and RFC 2732, Sec. 2.");
|
|
||||||
host
|
|
||||||
},
|
|
||||||
host => host,
|
|
||||||
})?
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let port = port.parse().map_err(|_| AddrParseError::InvalidPort)?;
|
let port = port.parse().map_err(|_| AddrParseError::InvalidPort)?;
|
||||||
|
|
||||||
Ok(Self(NetAddr::new(host, port)))
|
Ok(Self {
|
||||||
|
inner: NetAddr::new(host, port),
|
||||||
|
#[allow(deprecated)]
|
||||||
|
is_ipv6_without_square_brackets,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl cyphernet::addr::Host for Address {
|
impl cyphernet::addr::Host for Address {
|
||||||
fn requires_proxy(&self) -> bool {
|
fn requires_proxy(&self) -> bool {
|
||||||
self.0.requires_proxy()
|
self.inner.requires_proxy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl cyphernet::addr::Addr for Address {
|
impl cyphernet::addr::Addr for Address {
|
||||||
fn port(&self) -> u16 {
|
fn port(&self) -> u16 {
|
||||||
self.0.port()
|
self.inner.port()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<net::SocketAddr> for Address {
|
impl From<net::SocketAddr> for Address {
|
||||||
fn from(addr: net::SocketAddr) -> Self {
|
fn from(addr: net::SocketAddr) -> Self {
|
||||||
Address(NetAddr {
|
Address {
|
||||||
host: HostName::Ip(addr.ip()),
|
inner: NetAddr {
|
||||||
port: addr.port(),
|
host: HostName::Ip(addr.ip()),
|
||||||
})
|
port: addr.port(),
|
||||||
|
},
|
||||||
|
#[allow(deprecated)]
|
||||||
|
is_ipv6_without_square_brackets: false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Address> for HostName {
|
impl From<Address> for HostName {
|
||||||
fn from(addr: Address) -> Self {
|
fn from(addr: Address) -> Self {
|
||||||
addr.0.host
|
addr.inner.host
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue