Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Handle socket address parsing errors #8545

Merged
merged 7 commits into from
May 9, 2018
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions util/network-devp2p/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ error-chain = { version = "0.11", default-features = false }

[dev-dependencies]
tempdir = "0.3"
assert_matches = "1.2"

[features]
default = []
2 changes: 2 additions & 0 deletions util/network-devp2p/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ extern crate serde_derive;

#[cfg(test)]
extern crate tempdir;
#[cfg(test)] #[macro_use]
extern crate assert_matches;

mod host;
mod connection;
Expand Down
104 changes: 94 additions & 10 deletions util/network-devp2p/src/node_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,20 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.

use discovery::{TableUpdates, NodeEntry};
use ethereum_types::H512;
use ip_utils::*;
use network::{Error, ErrorKind, AllowIP, IpFilter};
use rlp::{Rlp, RlpStream, DecoderError};
use serde_json;
use std::collections::{HashMap, HashSet};
use std::fmt::{self, Display, Formatter};
use std::hash::{Hash, Hasher};
use std::io;
use std::net::{SocketAddr, ToSocketAddrs, SocketAddrV4, SocketAddrV6, Ipv4Addr, Ipv6Addr};
use std::path::PathBuf;
use std::str::FromStr;
use std::{fs, mem, slice};
use ethereum_types::H512;
use rlp::{Rlp, RlpStream, DecoderError};
use network::{Error, ErrorKind, AllowIP, IpFilter};
use discovery::{TableUpdates, NodeEntry};
use ip_utils::*;
use serde_json;

/// Node public key
pub type NodeId = H512;
Expand Down Expand Up @@ -122,18 +123,28 @@ impl FromStr for NodeEndpoint {
address: a,
udp_port: a.port()
}),
Ok(_) => Err(ErrorKind::AddressResolve(None).into()),
Err(e) => Err(ErrorKind::AddressResolve(Some(e)).into())
Ok(None) => bail!(ErrorKind::AddressResolve(None)),
Err(e) => {
match e.kind() {
io::ErrorKind::InvalidInput => {
Err(ErrorKind::AddressParse(Some(e)).into())
},
_ => {
Err(e.into())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will be transformed into Error::Io(...). Is it desired?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we return Err(ErrorKind::AddressParse()) for all error cases here? In light of your other comment below and https://doc.rust-lang.org/src/std/net/addr.rs.html#891 it seems kind of silly to cover other cases (there aren't any, to_socket_addrs always only return InvalidInput). This match arm would become:

Err(e) => Err(ErrorKind::AddressParse())

Thoughts?

}
}
}
}
}
}

#[derive(PartialEq, Eq, Copy, Clone)]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum PeerType {
_Required,
Optional
}

#[derive(Debug)]
pub struct Node {
pub id: NodeId,
pub endpoint: NodeEndpoint,
Expand Down Expand Up @@ -442,11 +453,59 @@ mod tests {
assert!(endpoint.is_ok());
let v4 = match endpoint.unwrap().address {
SocketAddr::V4(v4address) => v4address,
_ => panic!("should ve v4 address")
_ => panic!("should be v4 address")
};
assert_eq!(SocketAddrV4::new(Ipv4Addr::new(123, 99, 55, 44), 7770), v4);
}

#[test]
fn endpoint_parse_empty_ip_string_returns_error() {
let endpoint = NodeEndpoint::from_str("");
assert!(endpoint.is_err());
assert_matches!(
endpoint.unwrap_err().kind(),
// TODO: after 1.27 is stable, remove the `&`s and `ref`s, see https://github.com/rust-lang/rust/pull/49394
&ErrorKind::AddressParse(ref io_err) => {
assert!(io_err.is_some());
if let &Some(ref e) = io_err {
assert_eq!(e.to_string(), "invalid socket address");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if io_err is None? This assertion will not catch that

}
}
);
}

#[test]
fn endpoint_parse_invalid_ip_string_returns_error() {
let endpoint = NodeEndpoint::from_str("beef");
assert!(endpoint.is_err());
assert_matches!(
endpoint.unwrap_err().kind(),
&ErrorKind::AddressParse(ref io_err) => {
assert!(io_err.is_some());
if let &Some(ref e) = io_err {
assert_eq!(e.to_string(), "invalid socket address");
}
}
);
}

#[test]
fn endpoint_parse_valid_ip_without_port_returns_error() {
let endpoint = NodeEndpoint::from_str("123.123.123.123");
assert!(endpoint.is_err());
assert_matches!(
endpoint.unwrap_err().kind(),
&ErrorKind::AddressParse(ref io_err) => {
assert!(io_err.is_some());
if let &Some(ref e) = io_err {
assert_eq!(e.to_string(), "invalid socket address");
}
}
);
let endpoint = NodeEndpoint::from_str("123.123.123.123:123");
assert!(endpoint.is_ok())
}

#[test]
fn node_parse() {
assert!(validate_node_url("enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").is_none());
Expand All @@ -463,6 +522,31 @@ mod tests {
node.id);
}

#[test]
fn node_parse_fails_for_invalid_urls() {
let node = Node::from_str("foo");
assert!(node.is_err());
assert_matches!(
node.unwrap_err().kind(),
&ErrorKind::AddressParse(ref io_err) => {
if let &Some(ref e) = io_err {
assert_eq!(e.to_string(), "invalid socket address");
}
}
);

let node = Node::from_str("enode://foo@bar");
assert!(node.is_err());
assert_matches!(
node.unwrap_err().kind(),
&ErrorKind::AddressParse(ref io_err) => {
if let &Some(ref e) = io_err {
assert_eq!(e.to_string(), "invalid port value");
}
}
);
}

#[test]
fn table_failure_percentage_order() {
let node1 = Node::from_str("enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap();
Expand Down
13 changes: 12 additions & 1 deletion util/network/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,16 @@ error_chain! {
foreign_links {
SocketIo(IoError) #[doc = "Socket IO error."];
Io(io::Error) #[doc = "Error concerning the Rust standard library's IO subsystem."];
AddressParse(net::AddrParseError) #[doc = "Error concerning the network address parsing subsystem."];
Decompression(snappy::InvalidInput) #[doc = "Decompression error."];
}

errors {
#[doc = "Error concerning the network address parsing subsystem."]
AddressParse(err: Option<io::Error>) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if it's worth having Option<io::Error> here. Maybe it should be just AddressParse() ? io:Error is always the same https://doc.rust-lang.org/src/std/net/addr.rs.html#891

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The io::Error is always of the io::ErrorKind::InvalidInput kind but the message string changes. That said I had the same thought but thought I'd leave it similar to the foreign linked error type we had before.

description("Failed to parse network address"),
display("Failed to parse network address {}", err.as_ref().map_or("".to_string(), |e| e.to_string())),
}

#[doc = "Error concerning the network address resolution subsystem."]
AddressResolve(err: Option<io::Error>) {
description("Failed to resolve network address"),
Expand Down Expand Up @@ -163,6 +168,12 @@ impl From<crypto::error::SymmError> for Error {
}
}

impl From<net::AddrParseError> for Error {
fn from(_err: net::AddrParseError) -> Self {
ErrorKind::AddressParse(None).into()
}
}

#[test]
fn test_errors() {
assert_eq!(DisconnectReason::ClientQuit, DisconnectReason::from_u8(8));
Expand Down