diff --git a/Cargo.lock b/Cargo.lock index 359b2d2f9..52d0434a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -656,6 +656,7 @@ dependencies = [ "ethcrypto 0.1.0", "ethkey 0.2.0", "igd 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ipnetwork 0.12.6 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -862,6 +863,7 @@ dependencies = [ "ethcore-util 1.8.0", "ethkey 0.2.0", "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ipnetwork 0.12.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1149,6 +1151,11 @@ dependencies = [ "semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ipnetwork" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "isatty" version = "0.1.1" @@ -1763,6 +1770,7 @@ dependencies = [ "ethcore-ipc-tests 0.1.0", "ethcore-light 1.8.0", "ethcore-logger 1.8.0", + "ethcore-network 1.8.0", "ethcore-secretstore 1.0.0", "ethcore-stratum 1.8.0", "ethcore-util 1.8.0", @@ -1771,6 +1779,7 @@ dependencies = [ "fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "futures-cpupool 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "ipnetwork 0.12.6 (registry+https://github.com/rust-lang/crates.io-index)", "isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "jsonrpc-core 7.0.0 (git+https://github.com/paritytech/jsonrpc.git?branch=parity-1.7)", "log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3226,6 +3235,7 @@ dependencies = [ "checksum igd 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "356a0dc23a4fa0f8ce4777258085d00a01ea4923b2efd93538fc44bf5e1bda76" "checksum integer-encoding 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a053c9c7dcb7db1f2aa012c37dc176c62e4cdf14898dee0eecc606de835b8acb" "checksum iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "29d062ee61fccdf25be172e70f34c9f6efc597e1fb8f6526e8437b2046ab26be" +"checksum ipnetwork 0.12.6 (registry+https://github.com/rust-lang/crates.io-index)" = "232e76922883005380e831068f731ef0305541c9f77b30df3a1635047b16f370" "checksum isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7408a548dc0e406b7912d9f84c261cc533c1866e047644a811c133c56041ac0c" "checksum itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d95557e7ba6b71377b0f2c3b3ae96c53f1b75a926a6901a500f557a370af730a" "checksum itoa 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "91fd9dc2c587067de817fec4ad355e3818c3d893a78cab32a0a474c7a15bb8d5" diff --git a/Cargo.toml b/Cargo.toml index 21961776e..97746c082 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ ethcore-ipc-hypervisor = { path = "ipc/hypervisor" } ethcore-light = { path = "ethcore/light" } ethcore-logger = { path = "logger" } ethcore-stratum = { path = "stratum" } +ethcore-network = { path = "util/network" } ethkey = { path = "ethkey" } rlp = { path = "util/rlp" } rpc-cli = { path = "rpc_cli" } @@ -65,6 +66,7 @@ rustc_version = "0.2" [dev-dependencies] ethcore-ipc-tests = { path = "ipc/tests" } pretty_assertions = "0.1" +ipnetwork = "0.12.6" [target.'cfg(windows)'.dependencies] winapi = "0.2" diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index 1c9e1e22c..c1d1ab9de 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -148,11 +148,15 @@ Networking Options: These nodes will always have a reserved slot on top of the normal maximum peers. (default: {flag_reserved_peers:?}) --reserved-only Connect only to reserved nodes. (default: {flag_reserved_only}) - --allow-ips FILTER Filter outbound connections. Must be one of: + --allow-ips FILTER Filter outbound connections. FILTER can be one of: private - connect to private network IP addresses only; public - connect to public network IP addresses only; - all - connect to any IP address. - (default: {flag_allow_ips}) + all - connect to any IP address; + none - block all (for use with a custom filter as below); + a custom filter list in the format: "private ip_range1 -ip_range2 ...". + Where ip_range1 would be allowed and ip_range2 blocked; + Custom blocks ("-ip_range") override custom allows ("ip_range"); + (default: {flag_allow_ips}). --max-pending-peers NUM Allow up to NUM pending connections. (default: {flag_max_pending_peers}) --no-ancient-blocks Disable downloading old blocks after snapshot restoration or warp sync. (default: {flag_no_ancient_blocks}) diff --git a/parity/configuration.rs b/parity/configuration.rs index 40726eeca..f1399e47c 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -24,7 +24,7 @@ use cli::{Args, ArgsError}; use util::{Hashable, H256, U256, Bytes, version_data, Address}; use util::journaldb::Algorithm; use util::Colour; -use ethsync::{NetworkConfiguration, is_valid_node_url, AllowIP}; +use ethsync::{NetworkConfiguration, is_valid_node_url}; use ethcore::ethstore::ethkey::{Secret, Public}; use ethcore::client::{VMType}; use ethcore::miner::{MinerOptions, Banning, StratumOptions}; @@ -48,6 +48,7 @@ use blockchain::{BlockchainCmd, ImportBlockchain, ExportBlockchain, KillBlockcha use presale::ImportWallet; use account::{AccountCmd, NewAccount, ListAccounts, ImportAccounts, ImportFromGethAccounts}; use snapshot::{self, SnapshotCommand}; +use network::{IpFilter}; #[derive(Debug, PartialEq)] pub enum Cmd { @@ -461,12 +462,10 @@ impl Configuration { max(self.min_peers(), peers) } - fn allow_ips(&self) -> Result { - match self.args.flag_allow_ips.as_str() { - "all" => Ok(AllowIP::All), - "public" => Ok(AllowIP::Public), - "private" => Ok(AllowIP::Private), - _ => Err("Invalid IP filter value".to_owned()), + fn ip_filter(&self) -> Result { + match IpFilter::parse(self.args.flag_allow_ips.as_str()) { + Ok(allow_ip) => Ok(allow_ip), + Err(_) => Err("Invalid IP filter value".to_owned()), } } @@ -712,7 +711,7 @@ impl Configuration { ret.max_peers = self.max_peers(); ret.min_peers = self.min_peers(); ret.snapshot_peers = self.snapshot_peers(); - ret.allow_ips = self.allow_ips()?; + ret.ip_filter = self.ip_filter()?; ret.max_pending_peers = self.max_pending_peers(); let mut net_path = PathBuf::from(self.directories().base); net_path.push("network"); @@ -968,7 +967,6 @@ impl Configuration { }.into() } - fn ui_interface(&self) -> String { self.interface(&self.args.flag_ui_interface) } @@ -1080,6 +1078,7 @@ impl Configuration { mod tests { use std::io::Write; use std::fs::{File, create_dir}; + use std::str::FromStr; use devtools::{RandomTempPath}; use ethcore::client::{VMType, BlockId}; @@ -1097,6 +1096,11 @@ mod tests { use rpc::{WsConfiguration, UiConfiguration}; use run::RunCmd; + use network::{AllowIP, IpFilter}; + + extern crate ipnetwork; + use self::ipnetwork::IpNetwork; + use super::*; #[derive(Debug, PartialEq)] @@ -1752,4 +1756,50 @@ mod tests { assert_eq!(&conf0.ipfs_config().interface, "0.0.0.0"); assert_eq!(conf0.ipfs_config().hosts, None); } + + #[test] + fn allow_ips() { + let all = parse(&["parity", "--allow-ips", "all"]); + let private = parse(&["parity", "--allow-ips", "private"]); + let block_custom = parse(&["parity", "--allow-ips", "-10.0.0.0/8"]); + let combo = parse(&["parity", "--allow-ips", "public 10.0.0.0/8 -1.0.0.0/8"]); + let ipv6_custom_public = parse(&["parity", "--allow-ips", "public fc00::/7"]); + let ipv6_custom_private = parse(&["parity", "--allow-ips", "private -fc00::/7"]); + + assert_eq!(all.ip_filter().unwrap(), IpFilter { + predefined: AllowIP::All, + custom_allow: vec![], + custom_block: vec![], + }); + + assert_eq!(private.ip_filter().unwrap(), IpFilter { + predefined: AllowIP::Private, + custom_allow: vec![], + custom_block: vec![], + }); + + assert_eq!(block_custom.ip_filter().unwrap(), IpFilter { + predefined: AllowIP::All, + custom_allow: vec![], + custom_block: vec![IpNetwork::from_str("10.0.0.0/8").unwrap()], + }); + + assert_eq!(combo.ip_filter().unwrap(), IpFilter { + predefined: AllowIP::Public, + custom_allow: vec![IpNetwork::from_str("10.0.0.0/8").unwrap()], + custom_block: vec![IpNetwork::from_str("1.0.0.0/8").unwrap()], + }); + + assert_eq!(ipv6_custom_public.ip_filter().unwrap(), IpFilter { + predefined: AllowIP::Public, + custom_allow: vec![IpNetwork::from_str("fc00::/7").unwrap()], + custom_block: vec![], + }); + + assert_eq!(ipv6_custom_private.ip_filter().unwrap(), IpFilter { + predefined: AllowIP::Private, + custom_allow: vec![], + custom_block: vec![IpNetwork::from_str("fc00::/7").unwrap()], + }); + } } diff --git a/parity/helpers.rs b/parity/helpers.rs index 7d28f44fd..2a1b3156d 100644 --- a/parity/helpers.rs +++ b/parity/helpers.rs @@ -191,7 +191,8 @@ pub fn to_bootnodes(bootnodes: &Option) -> Result, String> { #[cfg(test)] pub fn default_network_config() -> ::ethsync::NetworkConfiguration { - use ethsync::{NetworkConfiguration, AllowIP}; + use ethsync::{NetworkConfiguration}; + use super::network::IpFilter; NetworkConfiguration { config_path: Some(replace_home(&::dir::default_data_path(), "$BASE/network")), net_config_path: None, @@ -206,7 +207,7 @@ pub fn default_network_config() -> ::ethsync::NetworkConfiguration { min_peers: 25, snapshot_peers: 0, max_pending_peers: 64, - allow_ips: AllowIP::All, + ip_filter: IpFilter::default(), reserved_nodes: Vec::new(), allow_non_reserved: true, } diff --git a/parity/main.rs b/parity/main.rs index 8bd6bf53f..311ce8b35 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -55,6 +55,7 @@ extern crate ethcore_ipc_nano as nanoipc; extern crate ethcore_light as light; extern crate ethcore_logger; extern crate ethcore_util as util; +extern crate ethcore_network as network; extern crate ethkey; extern crate ethsync; extern crate panic_hook; diff --git a/sync/Cargo.toml b/sync/Cargo.toml index 8e9a2a3eb..b9a411879 100644 --- a/sync/Cargo.toml +++ b/sync/Cargo.toml @@ -31,6 +31,7 @@ ethcore-ipc-nano = { path = "../ipc/nano" } ethcore-devtools = { path = "../devtools" } ethkey = { path = "../ethkey" } parking_lot = "0.4" +ipnetwork = "0.12.6" [features] default = [] diff --git a/sync/src/api.rs b/sync/src/api.rs index b8f495f9b..acbc26867 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -19,8 +19,7 @@ use std::collections::{HashMap, BTreeMap}; use std::io; use util::Bytes; use network::{NetworkProtocolHandler, NetworkService, NetworkContext, HostInfo, PeerId, ProtocolId, - NetworkConfiguration as BasicNetworkConfiguration, NonReservedPeerMode, NetworkError, - AllowIP as NetworkAllowIP}; + NetworkConfiguration as BasicNetworkConfiguration, NonReservedPeerMode, NetworkError}; use util::{U256, H256, H512}; use io::{TimerToken}; use ethcore::ethstore::ethkey::Secret; @@ -37,6 +36,7 @@ use chain::{ETH_PACKET_COUNT, SNAPSHOT_SYNC_PACKET_COUNT}; use light::client::AsLightClient; use light::Provider; use light::net::{self as light_net, LightProtocol, Params as LightParams, Capabilities, Handler as LightHandler, EventContext}; +use network::IpFilter; /// Parity sync protocol pub const WARP_SYNC_PROTOCOL_ID: ProtocolId = *b"par"; @@ -539,30 +539,6 @@ impl ManageNetwork for EthSync { } } -/// IP fiter -#[derive(Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "ipc", binary)] -pub enum AllowIP { - /// Connect to any address - All, - /// Connect to private network only - Private, - /// Connect to public network only - Public, -} - -impl AllowIP { - /// Attempt to parse the peer mode from a string. - pub fn parse(s: &str) -> Option { - match s { - "all" => Some(AllowIP::All), - "private" => Some(AllowIP::Private), - "public" => Some(AllowIP::Public), - _ => None, - } - } -} - #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "ipc", binary)] /// Network service configuration @@ -598,7 +574,7 @@ pub struct NetworkConfiguration { /// The non-reserved peer mode. pub allow_non_reserved: bool, /// IP Filtering - pub allow_ips: AllowIP, + pub ip_filter: IpFilter, } impl NetworkConfiguration { @@ -629,11 +605,7 @@ impl NetworkConfiguration { max_handshakes: self.max_pending_peers, reserved_protocols: hash_map![WARP_SYNC_PROTOCOL_ID => self.snapshot_peers], reserved_nodes: self.reserved_nodes, - allow_ips: match self.allow_ips { - AllowIP::All => NetworkAllowIP::All, - AllowIP::Private => NetworkAllowIP::Private, - AllowIP::Public => NetworkAllowIP::Public, - }, + ip_filter: self.ip_filter, non_reserved_mode: if self.allow_non_reserved { NonReservedPeerMode::Accept } else { NonReservedPeerMode::Deny }, }) } @@ -656,11 +628,7 @@ impl From for NetworkConfiguration { max_pending_peers: other.max_handshakes, snapshot_peers: *other.reserved_protocols.get(&WARP_SYNC_PROTOCOL_ID).unwrap_or(&0), reserved_nodes: other.reserved_nodes, - allow_ips: match other.allow_ips { - NetworkAllowIP::All => AllowIP::All, - NetworkAllowIP::Private => AllowIP::Private, - NetworkAllowIP::Public => AllowIP::Public, - }, + ip_filter: other.ip_filter, allow_non_reserved: match other.non_reserved_mode { NonReservedPeerMode::Accept => true, _ => false } , } } diff --git a/sync/src/lib.rs b/sync/src/lib.rs index b51c157be..e131bf901 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -37,6 +37,7 @@ extern crate semver; extern crate parking_lot; extern crate smallvec; extern crate rlp; +extern crate ipnetwork; extern crate ethcore_light as light; diff --git a/util/network/Cargo.toml b/util/network/Cargo.toml index 8abda59c5..7c44aff7f 100644 --- a/util/network/Cargo.toml +++ b/util/network/Cargo.toml @@ -30,6 +30,7 @@ ethcrypto = { path = "../../ethcrypto" } rlp = { path = "../rlp" } path = { path = "../path" } ethcore-logger = { path ="../../logger" } +ipnetwork = "0.12.6" [features] default = [] diff --git a/util/network/src/discovery.rs b/util/network/src/discovery.rs index 138ba23ba..3dcd8548d 100644 --- a/util/network/src/discovery.rs +++ b/util/network/src/discovery.rs @@ -30,7 +30,7 @@ use node_table::*; use error::NetworkError; use io::{StreamToken, IoContext}; use ethkey::{Secret, KeyPair, sign, recover}; -use AllowIP; +use IpFilter; use PROTOCOL_VERSION; @@ -99,7 +99,7 @@ pub struct Discovery { send_queue: VecDeque, check_timestamps: bool, adding_nodes: Vec, - allow_ips: AllowIP, + ip_filter: IpFilter, } pub struct TableUpdates { @@ -108,7 +108,7 @@ pub struct TableUpdates { } impl Discovery { - pub fn new(key: &KeyPair, listen: SocketAddr, public: NodeEndpoint, token: StreamToken, allow_ips: AllowIP) -> Discovery { + pub fn new(key: &KeyPair, listen: SocketAddr, public: NodeEndpoint, token: StreamToken, ip_filter: IpFilter) -> Discovery { let socket = UdpSocket::bind(&listen).expect("Error binding UDP socket"); Discovery { id: key.public().clone(), @@ -124,7 +124,7 @@ impl Discovery { send_queue: VecDeque::new(), check_timestamps: true, adding_nodes: Vec::new(), - allow_ips: allow_ips, + ip_filter: ip_filter, } } @@ -400,7 +400,7 @@ impl Discovery { } fn is_allowed(&self, entry: &NodeEntry) -> bool { - entry.endpoint.is_allowed(self.allow_ips) && entry.id != self.id + entry.endpoint.is_allowed(&self.ip_filter) && entry.id != self.id } fn on_ping(&mut self, rlp: &UntrustedRlp, node: &NodeId, from: &SocketAddr) -> Result, NetworkError> { @@ -561,7 +561,6 @@ mod tests { use std::str::FromStr; use rustc_hex::FromHex; use ethkey::{Random, Generator}; - use AllowIP; #[test] fn find_node() { @@ -586,8 +585,8 @@ mod tests { let key2 = Random.generate().unwrap(); let ep1 = NodeEndpoint { address: SocketAddr::from_str("127.0.0.1:40444").unwrap(), udp_port: 40444 }; let ep2 = NodeEndpoint { address: SocketAddr::from_str("127.0.0.1:40445").unwrap(), udp_port: 40445 }; - let mut discovery1 = Discovery::new(&key1, ep1.address.clone(), ep1.clone(), 0, AllowIP::All); - let mut discovery2 = Discovery::new(&key2, ep2.address.clone(), ep2.clone(), 0, AllowIP::All); + let mut discovery1 = Discovery::new(&key1, ep1.address.clone(), ep1.clone(), 0, IpFilter::default()); + let mut discovery2 = Discovery::new(&key2, ep2.address.clone(), ep2.clone(), 0, IpFilter::default()); let node1 = Node::from_str("enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@127.0.0.1:7770").unwrap(); let node2 = Node::from_str("enode://b979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@127.0.0.1:7771").unwrap(); @@ -619,7 +618,7 @@ mod tests { fn removes_expired() { let key = Random.generate().unwrap(); let ep = NodeEndpoint { address: SocketAddr::from_str("127.0.0.1:40446").unwrap(), udp_port: 40447 }; - let mut discovery = Discovery::new(&key, ep.address.clone(), ep.clone(), 0, AllowIP::All); + let mut discovery = Discovery::new(&key, ep.address.clone(), ep.clone(), 0, IpFilter::default()); for _ in 0..1200 { discovery.add_node(NodeEntry { id: NodeId::random(), endpoint: ep.clone() }); } @@ -648,7 +647,7 @@ mod tests { fn packets() { let key = Random.generate().unwrap(); let ep = NodeEndpoint { address: SocketAddr::from_str("127.0.0.1:40447").unwrap(), udp_port: 40447 }; - let mut discovery = Discovery::new(&key, ep.address.clone(), ep.clone(), 0, AllowIP::All); + let mut discovery = Discovery::new(&key, ep.address.clone(), ep.clone(), 0, IpFilter::default()); discovery.check_timestamps = false; let from = SocketAddr::from_str("99.99.99.99:40445").unwrap(); diff --git a/util/network/src/host.rs b/util/network/src/host.rs index b0de67499..8aea9184f 100644 --- a/util/network/src/host.rs +++ b/util/network/src/host.rs @@ -35,7 +35,7 @@ use rlp::*; use session::{Session, SessionInfo, SessionData}; use error::*; use io::*; -use {NetworkProtocolHandler, NonReservedPeerMode, AllowIP, PROTOCOL_VERSION}; +use {NetworkProtocolHandler, NonReservedPeerMode, PROTOCOL_VERSION, IpFilter}; use node_table::*; use stats::NetworkStats; use discovery::{Discovery, TableUpdates, NodeEntry}; @@ -106,7 +106,7 @@ pub struct NetworkConfiguration { /// The non-reserved peer mode. pub non_reserved_mode: NonReservedPeerMode, /// IP filter - pub allow_ips: AllowIP, + pub ip_filter: IpFilter, } impl Default for NetworkConfiguration { @@ -132,7 +132,7 @@ impl NetworkConfiguration { max_peers: 50, max_handshakes: 64, reserved_protocols: HashMap::new(), - allow_ips: AllowIP::All, + ip_filter: IpFilter::default(), reserved_nodes: Vec::new(), non_reserved_mode: NonReservedPeerMode::Accept, } @@ -566,7 +566,7 @@ impl Host { } let local_endpoint = self.info.read().local_endpoint.clone(); let public_address = self.info.read().config.public_address.clone(); - let allow_ips = self.info.read().config.allow_ips; + let allow_ips = self.info.read().config.ip_filter.clone(); let public_endpoint = match public_address { None => { let public_address = select_public_address(local_endpoint.address.port()); @@ -660,7 +660,7 @@ impl Host { } let config = &info.config; - (config.min_peers, config.non_reserved_mode == NonReservedPeerMode::Deny, config.max_handshakes as usize, config.allow_ips, info.id().clone()) + (config.min_peers, config.non_reserved_mode == NonReservedPeerMode::Deny, config.max_handshakes as usize, config.ip_filter.clone(), info.id().clone()) }; let session_count = self.session_count(); diff --git a/util/network/src/ip_utils.rs b/util/network/src/ip_utils.rs index a98cf1f88..d637dbee8 100644 --- a/util/network/src/ip_utils.rs +++ b/util/network/src/ip_utils.rs @@ -21,55 +21,193 @@ use std::io; use igd::{PortMappingProtocol, search_gateway_from_timeout}; use std::time::Duration; use node_table::{NodeEndpoint}; +use ipnetwork::{IpNetwork}; /// Socket address extension for rustc beta. To be replaces with now unstable API pub trait SocketAddrExt { - /// Returns true for the special 'unspecified' address 0.0.0.0. - fn is_unspecified_s(&self) -> bool; /// Returns true if the address appears to be globally routable. fn is_global_s(&self) -> bool; + + // Ipv4 specific + fn is_shared_space(&self) -> bool { false } + fn is_special_purpose(&self) -> bool { false } + fn is_benchmarking(&self) -> bool { false } + fn is_future_use(&self) -> bool { false } + + // Ipv6 specific + fn is_unique_local_s(&self) -> bool { false } + fn is_unicast_link_local_s(&self) -> bool { false } + fn is_documentation_s(&self) -> bool { false } + fn is_global_multicast(&self) -> bool { false } + fn is_other_multicast(&self) -> bool { false } + + fn is_reserved(&self) -> bool; + fn is_usable_public(&self) -> bool; + fn is_usable_private(&self) -> bool; + + fn is_within(&self, ipnet: &IpNetwork) -> bool; } impl SocketAddrExt for Ipv4Addr { - fn is_unspecified_s(&self) -> bool { - self.octets() == [0, 0, 0, 0] + fn is_global_s(&self) -> bool { + !self.is_private() && + !self.is_loopback() && + !self.is_link_local() && + !self.is_broadcast() && + !self.is_documentation() } - fn is_global_s(&self) -> bool { - !self.is_private() && !self.is_loopback() && !self.is_link_local() && - !self.is_broadcast() && !self.is_documentation() + // Used for communications between a service provider and its subscribers when using a carrier-grade NAT + // see: https://en.wikipedia.org/wiki/Reserved_IP_addresses + fn is_shared_space(&self) -> bool { + *self >= Ipv4Addr::new(100, 64, 0, 0) && + *self <= Ipv4Addr::new(100, 127, 255, 255) + } + + // Used for the IANA IPv4 Special Purpose Address Registry + // see: https://en.wikipedia.org/wiki/Reserved_IP_addresses + fn is_special_purpose(&self) -> bool { + *self >= Ipv4Addr::new(192, 0, 0, 0) && + *self <= Ipv4Addr::new(192, 0, 0, 255) + } + + // Used for testing of inter-network communications between two separate subnets + // see: https://en.wikipedia.org/wiki/Reserved_IP_addresses + fn is_benchmarking(&self) -> bool { + *self >= Ipv4Addr::new(198, 18, 0, 0) && + *self <= Ipv4Addr::new(198, 19, 255, 255) + } + + // Reserved for future use + // see: https://en.wikipedia.org/wiki/Reserved_IP_addresses + fn is_future_use(&self) -> bool { + *self >= Ipv4Addr::new(240, 0, 0, 0) && + *self <= Ipv4Addr::new(255, 255, 255, 254) + } + + fn is_reserved(&self) -> bool { + self.is_unspecified() || + self.is_loopback() || + self.is_link_local() || + self.is_broadcast() || + self.is_documentation() || + self.is_multicast() || + self.is_shared_space() || + self.is_special_purpose() || + self.is_benchmarking() || + self.is_future_use() + } + + fn is_usable_public(&self) -> bool { + !self.is_reserved() && + !self.is_private() + } + + fn is_usable_private(&self) -> bool { + self.is_private() + } + + fn is_within(&self, ipnet: &IpNetwork) -> bool { + match ipnet { + &IpNetwork::V4(ipnet) => ipnet.contains(*self), + _ => false + } } } impl SocketAddrExt for Ipv6Addr { - fn is_unspecified_s(&self) -> bool { - self.segments() == [0, 0, 0, 0, 0, 0, 0, 0] + fn is_global_s(&self) -> bool { + self.is_global_multicast() || + (!self.is_loopback() && + !self.is_unique_local_s() && + !self.is_unicast_link_local_s() && + !self.is_documentation_s() && + !self.is_other_multicast()) } - fn is_global_s(&self) -> bool { - if self.is_multicast() { - self.segments()[0] & 0x000f == 14 - } else { - !self.is_loopback() && !((self.segments()[0] & 0xffc0) == 0xfe80) && - !((self.segments()[0] & 0xffc0) == 0xfec0) && !((self.segments()[0] & 0xfe00) == 0xfc00) + // unique local address (fc00::/7). + fn is_unique_local_s(&self) -> bool { + (self.segments()[0] & 0xfe00) == 0xfc00 + } + + // unicast and link-local (fe80::/10). + fn is_unicast_link_local_s(&self) -> bool { + (self.segments()[0] & 0xffc0) == 0xfe80 + } + + // reserved for documentation (2001:db8::/32). + fn is_documentation_s(&self) -> bool { + (self.segments()[0] == 0x2001) && (self.segments()[1] == 0xdb8) + } + + fn is_global_multicast(&self) -> bool { + self.segments()[0] & 0x000f == 14 + } + + fn is_other_multicast(&self) -> bool { + self.is_multicast() && !self.is_global_multicast() + } + + fn is_reserved(&self) -> bool { + self.is_unspecified() || + self.is_loopback() || + self.is_unicast_link_local_s() || + self.is_documentation_s() || + self.is_other_multicast() + } + + fn is_usable_public(&self) -> bool { + !self.is_reserved() && + !self.is_unique_local_s() + } + + fn is_usable_private(&self) -> bool { + self.is_unique_local_s() + } + + fn is_within(&self, ipnet: &IpNetwork) -> bool { + match ipnet { + &IpNetwork::V6(ipnet) => ipnet.contains(*self), + _ => false } } } impl SocketAddrExt for IpAddr { - fn is_unspecified_s(&self) -> bool { - match *self { - IpAddr::V4(ref ip) => ip.is_unspecified_s(), - IpAddr::V6(ref ip) => ip.is_unspecified_s(), - } - } - fn is_global_s(&self) -> bool { match *self { IpAddr::V4(ref ip) => ip.is_global_s(), IpAddr::V6(ref ip) => ip.is_global_s(), } } + + fn is_reserved(&self) -> bool { + match *self { + IpAddr::V4(ref ip) => ip.is_reserved(), + IpAddr::V6(ref ip) => ip.is_reserved(), + } + } + + fn is_usable_public(&self) -> bool { + match *self { + IpAddr::V4(ref ip) => ip.is_usable_public(), + IpAddr::V6(ref ip) => ip.is_usable_public(), + } + } + + fn is_usable_private(&self) -> bool { + match *self { + IpAddr::V4(ref ip) => ip.is_usable_private(), + IpAddr::V6(ref ip) => ip.is_usable_private(), + } + } + + fn is_within(&self, ipnet: &IpNetwork) -> bool { + match *self { + IpAddr::V4(ref ip) => ip.is_within(ipnet), + IpAddr::V6(ref ip) => ip.is_within(ipnet) + } + } } #[cfg(not(windows))] @@ -79,7 +217,7 @@ mod getinterfaces { use libc::{getifaddrs, freeifaddrs, ifaddrs, sockaddr, sockaddr_in, sockaddr_in6}; use std::net::{Ipv4Addr, Ipv6Addr, IpAddr}; - fn convert_sockaddr (sa: *mut sockaddr) -> Option { + fn convert_sockaddr(sa: *mut sockaddr) -> Option { if sa == ptr::null_mut() { return None; } let (addr, _) = match unsafe { *sa }.sa_family as i32 { @@ -115,7 +253,7 @@ mod getinterfaces { Some(addr) } - fn convert_ifaddrs (ifa: *mut ifaddrs) -> Option { + fn convert_ifaddrs(ifa: *mut ifaddrs) -> Option { let ifa = unsafe { &mut *ifa }; convert_sockaddr(ifa.ifa_addr) } @@ -158,16 +296,16 @@ pub fn select_public_address(port: u16) -> SocketAddr { Ok(list) => { //prefer IPV4 bindings for addr in &list { //TODO: use better criteria than just the first in the list - match *addr { - IpAddr::V4(a) if !a.is_unspecified_s() && !a.is_loopback() && !a.is_link_local() => { + match addr { + &IpAddr::V4(a) if !a.is_reserved() => { return SocketAddr::V4(SocketAddrV4::new(a, port)); }, _ => {}, } } - for addr in list { + for addr in &list { match addr { - IpAddr::V6(a) if !a.is_unspecified_s() && !a.is_loopback() => { + &IpAddr::V6(a) if !a.is_reserved() => { return SocketAddr::V6(SocketAddrV6::new(a, port, 0, 0)); }, _ => {}, @@ -227,7 +365,6 @@ fn can_map_external_address_or_fail() { #[test] fn ipv4_properties() { - #![cfg_attr(feature="dev", allow(too_many_arguments))] fn check(octets: &[u8; 4], unspec: bool, loopback: bool, private: bool, link_local: bool, global: bool, @@ -235,7 +372,7 @@ fn ipv4_properties() { let ip = Ipv4Addr::new(octets[0], octets[1], octets[2], octets[3]); assert_eq!(octets, &ip.octets()); - assert_eq!(ip.is_unspecified_s(), unspec); + assert_eq!(ip.is_unspecified(), unspec); assert_eq!(ip.is_loopback(), loopback); assert_eq!(ip.is_private(), private); assert_eq!(ip.is_link_local(), link_local); @@ -264,13 +401,131 @@ fn ipv4_properties() { check(&[255, 255, 255, 255], false, false, false, false, false, false, true, false); } +#[test] +fn ipv4_shared_space() { + assert!(!Ipv4Addr::new(100, 63, 255, 255).is_shared_space()); + assert!(Ipv4Addr::new(100, 64, 0, 0).is_shared_space()); + assert!(Ipv4Addr::new(100, 127, 255, 255).is_shared_space()); + assert!(!Ipv4Addr::new(100, 128, 0, 0).is_shared_space()); +} + +#[test] +fn ipv4_special_purpose() { + assert!(!Ipv4Addr::new(191, 255, 255, 255).is_special_purpose()); + assert!(Ipv4Addr::new(192, 0, 0, 0).is_special_purpose()); + assert!(Ipv4Addr::new(192, 0, 0, 255).is_special_purpose()); + assert!(!Ipv4Addr::new(192, 0, 1, 255).is_special_purpose()); +} + +#[test] +fn ipv4_benchmarking() { + assert!(!Ipv4Addr::new(198, 17, 255, 255).is_benchmarking()); + assert!(Ipv4Addr::new(198, 18, 0, 0).is_benchmarking()); + assert!(Ipv4Addr::new(198, 19, 255, 255).is_benchmarking()); + assert!(!Ipv4Addr::new(198, 20, 0, 0).is_benchmarking()); +} + +#[test] +fn ipv4_future_use() { + assert!(!Ipv4Addr::new(239, 255, 255, 255).is_future_use()); + assert!(Ipv4Addr::new(240, 0, 0, 0).is_future_use()); + assert!(Ipv4Addr::new(255, 255, 255, 254).is_future_use()); + assert!(!Ipv4Addr::new(255, 255, 255, 255).is_future_use()); +} + +#[test] +fn ipv4_usable_public() { + assert!(!Ipv4Addr::new(0,0,0,0).is_usable_public()); // unspecified + assert!(Ipv4Addr::new(0,0,0,1).is_usable_public()); + + assert!(Ipv4Addr::new(9,255,255,255).is_usable_public()); + assert!(!Ipv4Addr::new(10,0,0,0).is_usable_public()); // private intra-network + assert!(!Ipv4Addr::new(10,255,255,255).is_usable_public()); // private intra-network + assert!(Ipv4Addr::new(11,0,0,0).is_usable_public()); + + assert!(Ipv4Addr::new(100, 63, 255, 255).is_usable_public()); + assert!(!Ipv4Addr::new(100, 64, 0, 0).is_usable_public()); // shared space + assert!(!Ipv4Addr::new(100, 127, 255, 255).is_usable_public()); // shared space + assert!(Ipv4Addr::new(100, 128, 0, 0).is_usable_public()); + + assert!(Ipv4Addr::new(126,255,255,255).is_usable_public()); + assert!(!Ipv4Addr::new(127,0,0,0).is_usable_public()); // loopback + assert!(!Ipv4Addr::new(127,255,255,255).is_usable_public()); // loopback + assert!(Ipv4Addr::new(128,0,0,0).is_usable_public()); + + assert!(Ipv4Addr::new(169,253,255,255).is_usable_public()); + assert!(!Ipv4Addr::new(169,254,0,0).is_usable_public()); // link-local + assert!(!Ipv4Addr::new(169,254,255,255).is_usable_public()); // link-local + assert!(Ipv4Addr::new(169,255,0,0).is_usable_public()); + + assert!(Ipv4Addr::new(172,15,255,255).is_usable_public()); + assert!(!Ipv4Addr::new(172,16,0,0).is_usable_public()); // private intra-network + assert!(!Ipv4Addr::new(172,31,255,255).is_usable_public()); // private intra-network + assert!(Ipv4Addr::new(172,32,255,255).is_usable_public()); + + assert!(Ipv4Addr::new(191,255,255,255).is_usable_public()); + assert!(!Ipv4Addr::new(192,0,0,0).is_usable_public()); // special purpose + assert!(!Ipv4Addr::new(192,0,0,255).is_usable_public()); // special purpose + assert!(Ipv4Addr::new(192,0,1,0).is_usable_public()); + + assert!(Ipv4Addr::new(192,0,1,255).is_usable_public()); + assert!(!Ipv4Addr::new(192,0,2,0).is_usable_public()); // documentation + assert!(!Ipv4Addr::new(192,0,2,255).is_usable_public()); // documentation + assert!(Ipv4Addr::new(192,0,3,0).is_usable_public()); + + assert!(Ipv4Addr::new(192,167,255,255).is_usable_public()); + assert!(!Ipv4Addr::new(192,168,0,0).is_usable_public()); // private intra-network + assert!(!Ipv4Addr::new(192,168,255,255).is_usable_public()); // private intra-network + assert!(Ipv4Addr::new(192,169,0,0).is_usable_public()); + + assert!(Ipv4Addr::new(198,17,255,255).is_usable_public()); + assert!(!Ipv4Addr::new(198,18,0,0).is_usable_public()); // benchmarking + assert!(!Ipv4Addr::new(198,19,255,255).is_usable_public()); // benchmarking + assert!(Ipv4Addr::new(198,20,0,0).is_usable_public()); + + assert!(Ipv4Addr::new(198,51,99,255).is_usable_public()); + assert!(!Ipv4Addr::new(198,51,100,0).is_usable_public()); // documentation + assert!(!Ipv4Addr::new(198,51,100,255).is_usable_public()); // documentation + assert!(Ipv4Addr::new(198,51,101,0).is_usable_public()); + + assert!(Ipv4Addr::new(203,0,112,255).is_usable_public()); + assert!(!Ipv4Addr::new(203,0,113,0).is_usable_public()); // documentation + assert!(!Ipv4Addr::new(203,0,113,255).is_usable_public()); // documentation + assert!(Ipv4Addr::new(203,0,114,0).is_usable_public()); + + assert!(Ipv4Addr::new(223,255,255,255).is_usable_public()); + assert!(!Ipv4Addr::new(224,0,0,0).is_usable_public()); // multicast + assert!(!Ipv4Addr::new(239, 255, 255, 255).is_usable_public()); // multicast + assert!(!Ipv4Addr::new(240, 0, 0, 0).is_usable_public()); // future use + assert!(!Ipv4Addr::new(255, 255, 255, 254).is_usable_public()); // future use + assert!(!Ipv4Addr::new(255, 255, 255, 255).is_usable_public()); // limited broadcast +} + +#[test] +fn ipv4_usable_private() { + assert!(!Ipv4Addr::new(9,255,255,255).is_usable_private()); + assert!(Ipv4Addr::new(10,0,0,0).is_usable_private()); // private intra-network + assert!(Ipv4Addr::new(10,255,255,255).is_usable_private()); // private intra-network + assert!(!Ipv4Addr::new(11,0,0,0).is_usable_private()); + + assert!(!Ipv4Addr::new(172,15,255,255).is_usable_private()); + assert!(Ipv4Addr::new(172,16,0,0).is_usable_private()); // private intra-network + assert!(Ipv4Addr::new(172,31,255,255).is_usable_private()); // private intra-network + assert!(!Ipv4Addr::new(172,32,255,255).is_usable_private()); + + assert!(!Ipv4Addr::new(192,167,255,255).is_usable_private()); + assert!(Ipv4Addr::new(192,168,0,0).is_usable_private()); // private intra-network + assert!(Ipv4Addr::new(192,168,255,255).is_usable_private()); // private intra-network + assert!(!Ipv4Addr::new(192,169,0,0).is_usable_private()); +} + #[test] fn ipv6_properties() { fn check(str_addr: &str, unspec: bool, loopback: bool, global: bool) { let ip: Ipv6Addr = str_addr.parse().unwrap(); assert_eq!(str_addr, ip.to_string()); - assert_eq!(ip.is_unspecified_s(), unspec); + assert_eq!(ip.is_unspecified(), unspec); assert_eq!(ip.is_loopback(), loopback); assert_eq!(ip.is_global_s(), global); } @@ -279,3 +534,5 @@ fn ipv6_properties() { check("::", true, false, true); check("::1", false, true, false); } + + diff --git a/util/network/src/lib.rs b/util/network/src/lib.rs index 5a3fdc1e6..74e30a750 100644 --- a/util/network/src/lib.rs +++ b/util/network/src/lib.rs @@ -77,6 +77,7 @@ extern crate rlp; extern crate bytes; extern crate path; extern crate ethcore_logger; +extern crate ipnetwork; #[macro_use] extern crate log; @@ -106,6 +107,8 @@ pub use session::SessionInfo; pub use io::TimerToken; pub use node_table::{is_valid_node_url, NodeId}; +use ipnetwork::{IpNetwork, IpNetworkError}; +use std::str::FromStr; const PROTOCOL_VERSION: u32 = 4; @@ -145,8 +148,49 @@ impl NonReservedPeerMode { } } +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "ipc", binary)] +pub struct IpFilter { + pub predefined: AllowIP, + pub custom_allow: Vec, + pub custom_block: Vec, +} + +impl Default for IpFilter { + fn default() -> Self { + IpFilter { + predefined: AllowIP::All, + custom_allow: vec![], + custom_block: vec![], + } + } +} + +impl IpFilter { + /// Attempt to parse the peer mode from a string. + pub fn parse(s: &str) -> Result { + let mut filter = IpFilter::default(); + for f in s.split_whitespace() { + match f { + "all" => filter.predefined = AllowIP::All, + "private" => filter.predefined = AllowIP::Private, + "public" => filter.predefined = AllowIP::Public, + "none" => filter.predefined = AllowIP::None, + custom => { + if custom.starts_with("-") { + filter.custom_block.push(IpNetwork::from_str(&custom.to_owned().split_off(1))?) + } else { + filter.custom_allow.push(IpNetwork::from_str(custom)?) + } + } + } + } + Ok(filter) + } +} + /// IP fiter -#[derive(Clone, Debug, PartialEq, Eq, Copy)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum AllowIP { /// Connect to any address All, @@ -154,5 +198,7 @@ pub enum AllowIP { Private, /// Connect to public network only Public, + /// Block all addresses + None, } diff --git a/util/network/src/node_table.rs b/util/network/src/node_table.rs index 1481187d4..f9d4e9589 100644 --- a/util/network/src/node_table.rs +++ b/util/network/src/node_table.rs @@ -30,7 +30,7 @@ use util::UtilError; use rlp::*; use time::Tm; use error::NetworkError; -use AllowIP; +use {AllowIP, IpFilter}; use discovery::{TableUpdates, NodeEntry}; use ip_utils::*; pub use rustc_serialize::json::Json; @@ -55,11 +55,21 @@ impl NodeEndpoint { } } - pub fn is_allowed(&self, filter: AllowIP) -> bool { + pub fn is_allowed(&self, filter: &IpFilter) -> bool { + (self.is_allowed_by_predefined(&filter.predefined) || filter.custom_allow.iter().any(|ipnet| { + self.address.ip().is_within(ipnet) + })) + && !filter.custom_block.iter().any(|ipnet| { + self.address.ip().is_within(ipnet) + }) + } + + pub fn is_allowed_by_predefined(&self, filter: &AllowIP) -> bool { match filter { - AllowIP::All => true, - AllowIP::Private => !self.address.ip().is_global_s(), - AllowIP::Public => self.address.ip().is_global_s(), + &AllowIP::All => true, + &AllowIP::Private => self.address.ip().is_usable_private(), + &AllowIP::Public => self.address.ip().is_usable_public(), + &AllowIP::None => false, } } @@ -101,8 +111,8 @@ impl NodeEndpoint { pub fn is_valid(&self) -> bool { self.udp_port != 0 && self.address.port() != 0 && match self.address { - SocketAddr::V4(a) => !a.ip().is_unspecified_s(), - SocketAddr::V6(a) => !a.ip().is_unspecified_s() + SocketAddr::V4(a) => !a.ip().is_unspecified(), + SocketAddr::V6(a) => !a.ip().is_unspecified() } } } @@ -219,8 +229,8 @@ impl NodeTable { } /// Returns node ids sorted by number of failures - pub fn nodes(&self, filter: AllowIP) -> Vec { - let mut refs: Vec<&Node> = self.nodes.values().filter(|n| !self.useless_nodes.contains(&n.id) && n.endpoint.is_allowed(filter)).collect(); + pub fn nodes(&self, filter: IpFilter) -> Vec { + let mut refs: Vec<&Node> = self.nodes.values().filter(|n| !self.useless_nodes.contains(&n.id) && n.endpoint.is_allowed(&filter)).collect(); refs.sort_by(|a, b| a.failures.cmp(&b.failures)); refs.iter().map(|n| n.id.clone()).collect() } @@ -283,7 +293,7 @@ impl NodeTable { let mut json = String::new(); json.push_str("{\n"); json.push_str("\"nodes\": [\n"); - let node_ids = self.nodes(AllowIP::All); + let node_ids = self.nodes(IpFilter::default()); for i in 0 .. node_ids.len() { let node = self.nodes.get(&node_ids[i]).expect("self.nodes() only returns node IDs from self.nodes"); json.push_str(&format!("\t{{ \"url\": \"{}\", \"failures\": {} }}{}\n", node, node.failures, if i == node_ids.len() - 1 {""} else {","})) @@ -366,7 +376,7 @@ mod tests { use util::H512; use std::str::FromStr; use devtools::*; - use AllowIP; + use ipnetwork::IpNetwork; #[test] fn endpoint_parse() { @@ -412,7 +422,7 @@ mod tests { table.note_failure(&id1); table.note_failure(&id2); - let r = table.nodes(AllowIP::All); + let r = table.nodes(IpFilter::default()); assert_eq!(r[0][..], id3[..]); assert_eq!(r[1][..], id2[..]); assert_eq!(r[2][..], id1[..]); @@ -434,9 +444,55 @@ mod tests { { let table = NodeTable::new(Some(temp_path.as_path().to_str().unwrap().to_owned())); - let r = table.nodes(AllowIP::All); + let r = table.nodes(IpFilter::default()); assert_eq!(r[0][..], id1[..]); assert_eq!(r[1][..], id2[..]); } } + + #[test] + fn custom_allow() { + let filter = IpFilter { + predefined: AllowIP::None, + custom_allow: vec![IpNetwork::from_str(&"10.0.0.0/8").unwrap(), IpNetwork::from_str(&"1.0.0.0/8").unwrap()], + custom_block: vec![], + }; + assert!(!NodeEndpoint::from_str("123.99.55.44:7770").unwrap().is_allowed(&filter)); + assert!(NodeEndpoint::from_str("10.0.0.1:7770").unwrap().is_allowed(&filter)); + assert!(NodeEndpoint::from_str("1.0.0.55:5550").unwrap().is_allowed(&filter)); + } + + #[test] + fn custom_block() { + let filter = IpFilter { + predefined: AllowIP::All, + custom_allow: vec![], + custom_block: vec![IpNetwork::from_str(&"10.0.0.0/8").unwrap(), IpNetwork::from_str(&"1.0.0.0/8").unwrap()], + }; + assert!(NodeEndpoint::from_str("123.99.55.44:7770").unwrap().is_allowed(&filter)); + assert!(!NodeEndpoint::from_str("10.0.0.1:7770").unwrap().is_allowed(&filter)); + assert!(!NodeEndpoint::from_str("1.0.0.55:5550").unwrap().is_allowed(&filter)); + } + + #[test] + fn custom_allow_ipv6() { + let filter = IpFilter { + predefined: AllowIP::None, + custom_allow: vec![IpNetwork::from_str(&"fc00::/8").unwrap()], + custom_block: vec![], + }; + assert!(NodeEndpoint::from_str("[fc00::]:5550").unwrap().is_allowed(&filter)); + assert!(!NodeEndpoint::from_str("[fd00::]:5550").unwrap().is_allowed(&filter)); + } + + #[test] + fn custom_block_ipv6() { + let filter = IpFilter { + predefined: AllowIP::All, + custom_allow: vec![], + custom_block: vec![IpNetwork::from_str(&"fc00::/8").unwrap()], + }; + assert!(!NodeEndpoint::from_str("[fc00::]:5550").unwrap().is_allowed(&filter)); + assert!(NodeEndpoint::from_str("[fd00::]:5550").unwrap().is_allowed(&filter)); + } }