Abstract devp2p (#8048)
* Rename ethcore-network to ethcore-network-devp2p * Fix typo * Extract generic traits into util/network * Simplify util/network * Fix devp2p tests * Remove old feature * Fix RPC tests
This commit is contained in:
759
util/network-devp2p/src/connection.rs
Normal file
759
util/network-devp2p/src/connection.rs
Normal file
@@ -0,0 +1,759 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::collections::VecDeque;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
|
||||
use hash::{keccak, write_keccak};
|
||||
use mio::{Token, Ready, PollOpt};
|
||||
use mio::deprecated::{Handler, EventLoop, TryRead, TryWrite};
|
||||
use mio::tcp::*;
|
||||
use ethereum_types::{H128, H256, H512};
|
||||
use ethcore_bytes::*;
|
||||
use rlp::*;
|
||||
use std::io::{self, Cursor, Read, Write};
|
||||
use io::{IoContext, StreamToken};
|
||||
use handshake::Handshake;
|
||||
use stats::NetworkStats;
|
||||
use rcrypto::blockmodes::*;
|
||||
use rcrypto::aessafe::*;
|
||||
use rcrypto::symmetriccipher::*;
|
||||
use rcrypto::buffer::*;
|
||||
use tiny_keccak::Keccak;
|
||||
use bytes::{Buf, BufMut};
|
||||
use crypto;
|
||||
use network::{Error, ErrorKind};
|
||||
|
||||
const ENCRYPTED_HEADER_LEN: usize = 32;
|
||||
const RECIEVE_PAYLOAD_TIMEOUT: u64 = 30000;
|
||||
pub const MAX_PAYLOAD_SIZE: usize = (1 << 24) - 1;
|
||||
|
||||
pub trait GenericSocket : Read + Write {
|
||||
}
|
||||
|
||||
impl GenericSocket for TcpStream {
|
||||
}
|
||||
|
||||
pub struct GenericConnection<Socket: GenericSocket> {
|
||||
/// Connection id (token)
|
||||
pub token: StreamToken,
|
||||
/// Network socket
|
||||
pub socket: Socket,
|
||||
/// Receive buffer
|
||||
rec_buf: Bytes,
|
||||
/// Expected size
|
||||
rec_size: usize,
|
||||
/// Send out packets FIFO
|
||||
send_queue: VecDeque<Cursor<Bytes>>,
|
||||
/// Event flags this connection expects
|
||||
interest: Ready,
|
||||
/// Shared network statistics
|
||||
stats: Arc<NetworkStats>,
|
||||
/// Registered flag
|
||||
registered: AtomicBool,
|
||||
}
|
||||
|
||||
impl<Socket: GenericSocket> GenericConnection<Socket> {
|
||||
pub fn expect(&mut self, size: usize) {
|
||||
trace!(target:"network", "Expect to read {} bytes", size);
|
||||
if self.rec_size != self.rec_buf.len() {
|
||||
warn!(target:"network", "Unexpected connection read start");
|
||||
}
|
||||
self.rec_size = size;
|
||||
}
|
||||
|
||||
/// Readable IO handler. Called when there is some data to be read.
|
||||
pub fn readable(&mut self) -> io::Result<Option<Bytes>> {
|
||||
if self.rec_size == 0 || self.rec_buf.len() >= self.rec_size {
|
||||
return Ok(None);
|
||||
}
|
||||
let sock_ref = <Socket as Read>::by_ref(&mut self.socket);
|
||||
loop {
|
||||
let max = self.rec_size - self.rec_buf.len();
|
||||
match sock_ref.take(max as u64).try_read(unsafe { self.rec_buf.bytes_mut() }) {
|
||||
Ok(Some(size)) if size != 0 => {
|
||||
unsafe { self.rec_buf.advance_mut(size); }
|
||||
self.stats.inc_recv(size);
|
||||
trace!(target:"network", "{}: Read {} of {} bytes", self.token, self.rec_buf.len(), self.rec_size);
|
||||
if self.rec_size != 0 && self.rec_buf.len() == self.rec_size {
|
||||
self.rec_size = 0;
|
||||
return Ok(Some(::std::mem::replace(&mut self.rec_buf, Bytes::new())))
|
||||
}
|
||||
else if self.rec_buf.len() > self.rec_size {
|
||||
warn!(target:"network", "Read past buffer {} bytes", self.rec_buf.len() - self.rec_size);
|
||||
return Ok(Some(::std::mem::replace(&mut self.rec_buf, Bytes::new())))
|
||||
}
|
||||
},
|
||||
Ok(_) => return Ok(None),
|
||||
Err(e) => {
|
||||
debug!(target:"network", "Read error {} ({})", self.token, e);
|
||||
return Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a packet to send queue.
|
||||
pub fn send<Message>(&mut self, io: &IoContext<Message>, data: Bytes) where Message: Send + Clone + Sync + 'static {
|
||||
if !data.is_empty() {
|
||||
trace!(target:"network", "{}: Sending {} bytes", self.token, data.len());
|
||||
self.send_queue.push_back(Cursor::new(data));
|
||||
if !self.interest.is_writable() {
|
||||
self.interest.insert(Ready::writable());
|
||||
}
|
||||
io.update_registration(self.token).ok();
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if this connection has data to be sent.
|
||||
pub fn is_sending(&self) -> bool {
|
||||
self.interest.is_writable()
|
||||
}
|
||||
|
||||
/// Writable IO handler. Called when the socket is ready to send.
|
||||
pub fn writable<Message>(&mut self, io: &IoContext<Message>) -> Result<WriteStatus, Error> where Message: Send + Clone + Sync + 'static {
|
||||
{
|
||||
let buf = match self.send_queue.front_mut() {
|
||||
Some(buf) => buf,
|
||||
None => return Ok(WriteStatus::Complete),
|
||||
};
|
||||
let send_size = buf.get_ref().len();
|
||||
let pos = buf.position() as usize;
|
||||
if (pos as usize) >= send_size {
|
||||
warn!(target:"net", "Unexpected connection data");
|
||||
return Ok(WriteStatus::Complete)
|
||||
}
|
||||
|
||||
match self.socket.try_write(Buf::bytes(&buf)) {
|
||||
Ok(Some(size)) if (pos + size) < send_size => {
|
||||
buf.advance(size);
|
||||
self.stats.inc_send(size);
|
||||
Ok(WriteStatus::Ongoing)
|
||||
},
|
||||
Ok(Some(size)) if (pos + size) == send_size => {
|
||||
self.stats.inc_send(size);
|
||||
trace!(target:"network", "{}: Wrote {} bytes", self.token, send_size);
|
||||
Ok(WriteStatus::Complete)
|
||||
},
|
||||
Ok(Some(_)) => { panic!("Wrote past buffer");},
|
||||
Ok(None) => Ok(WriteStatus::Ongoing),
|
||||
Err(e) => Err(e)?
|
||||
}
|
||||
}.and_then(|r| {
|
||||
if r == WriteStatus::Complete {
|
||||
self.send_queue.pop_front();
|
||||
}
|
||||
if self.send_queue.is_empty() {
|
||||
self.interest.remove(Ready::writable());
|
||||
}
|
||||
io.update_registration(self.token)?;
|
||||
Ok(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Low level tcp connection
|
||||
pub type Connection = GenericConnection<TcpStream>;
|
||||
|
||||
impl Connection {
|
||||
/// Create a new connection with given id and socket.
|
||||
pub fn new(token: StreamToken, socket: TcpStream, stats: Arc<NetworkStats>) -> Connection {
|
||||
Connection {
|
||||
token: token,
|
||||
socket: socket,
|
||||
send_queue: VecDeque::new(),
|
||||
rec_buf: Bytes::new(),
|
||||
rec_size: 0,
|
||||
interest: Ready::hup() | Ready::readable(),
|
||||
stats: stats,
|
||||
registered: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get socket token
|
||||
pub fn token(&self) -> StreamToken {
|
||||
self.token
|
||||
}
|
||||
|
||||
/// Get remote peer address
|
||||
pub fn remote_addr(&self) -> io::Result<SocketAddr> {
|
||||
self.socket.peer_addr()
|
||||
}
|
||||
|
||||
/// Get remote peer address string
|
||||
pub fn remote_addr_str(&self) -> String {
|
||||
self.socket.peer_addr().map(|a| a.to_string()).unwrap_or_else(|_| "Unknown".to_owned())
|
||||
}
|
||||
|
||||
/// Get local peer address string
|
||||
pub fn local_addr_str(&self) -> String {
|
||||
self.socket.local_addr().map(|a| a.to_string()).unwrap_or_else(|_| "Unknown".to_owned())
|
||||
}
|
||||
|
||||
/// Clone this connection. Clears the receiving buffer of the returned connection.
|
||||
pub fn try_clone(&self) -> io::Result<Self> {
|
||||
Ok(Connection {
|
||||
token: self.token,
|
||||
socket: self.socket.try_clone()?,
|
||||
rec_buf: Vec::new(),
|
||||
rec_size: 0,
|
||||
send_queue: self.send_queue.clone(),
|
||||
interest: Ready::hup(),
|
||||
stats: self.stats.clone(),
|
||||
registered: AtomicBool::new(false),
|
||||
})
|
||||
}
|
||||
|
||||
/// Register this connection with the IO event loop.
|
||||
pub fn register_socket<Host: Handler>(&self, reg: Token, event_loop: &mut EventLoop<Host>) -> io::Result<()> {
|
||||
if self.registered.load(AtomicOrdering::SeqCst) {
|
||||
return Ok(());
|
||||
}
|
||||
trace!(target: "network", "connection register; token={:?}", reg);
|
||||
if let Err(e) = event_loop.register(&self.socket, reg, self.interest, PollOpt::edge() /* | PollOpt::oneshot() */) { // TODO: oneshot is broken on windows
|
||||
trace!(target: "network", "Failed to register {:?}, {:?}", reg, e);
|
||||
}
|
||||
self.registered.store(true, AtomicOrdering::SeqCst);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update connection registration. Should be called at the end of the IO handler.
|
||||
pub fn update_socket<Host: Handler>(&self, reg: Token, event_loop: &mut EventLoop<Host>) -> io::Result<()> {
|
||||
trace!(target: "network", "connection reregister; token={:?}", reg);
|
||||
if !self.registered.load(AtomicOrdering::SeqCst) {
|
||||
self.register_socket(reg, event_loop)
|
||||
} else {
|
||||
event_loop.reregister(&self.socket, reg, self.interest, PollOpt::edge() /* | PollOpt::oneshot() */ ).unwrap_or_else(|e| { // TODO: oneshot is broken on windows
|
||||
trace!(target: "network", "Failed to reregister {:?}, {:?}", reg, e);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete connection registration. Should be called at the end of the IO handler.
|
||||
pub fn deregister_socket<Host: Handler>(&self, event_loop: &mut EventLoop<Host>) -> io::Result<()> {
|
||||
trace!(target: "network", "connection deregister; token={:?}", self.token);
|
||||
event_loop.deregister(&self.socket).ok(); // ignore errors here
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Connection write status.
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub enum WriteStatus {
|
||||
/// Some data is still pending for current packet
|
||||
Ongoing,
|
||||
/// All data sent.
|
||||
Complete
|
||||
}
|
||||
|
||||
/// `RLPx` packet
|
||||
pub struct Packet {
|
||||
pub protocol: u16,
|
||||
pub data: Bytes,
|
||||
}
|
||||
|
||||
/// Encrypted connection receiving state.
|
||||
enum EncryptedConnectionState {
|
||||
/// Reading a header.
|
||||
Header,
|
||||
/// Reading the rest of the packet.
|
||||
Payload,
|
||||
}
|
||||
|
||||
/// Connection implementing `RLPx` framing
|
||||
/// https://github.com/ethereum/devp2p/blob/master/rlpx.md#framing
|
||||
pub struct EncryptedConnection {
|
||||
/// Underlying tcp connection
|
||||
pub connection: Connection,
|
||||
/// Egress data encryptor
|
||||
encoder: CtrMode<AesSafe256Encryptor>,
|
||||
/// Ingress data decryptor
|
||||
decoder: CtrMode<AesSafe256Encryptor>,
|
||||
/// Ingress data decryptor
|
||||
mac_encoder: EcbEncryptor<AesSafe256Encryptor, EncPadding<NoPadding>>,
|
||||
/// MAC for egress data
|
||||
egress_mac: Keccak,
|
||||
/// MAC for ingress data
|
||||
ingress_mac: Keccak,
|
||||
/// Read state
|
||||
read_state: EncryptedConnectionState,
|
||||
/// Protocol id for the last received packet
|
||||
protocol_id: u16,
|
||||
/// Payload expected to be received for the last header.
|
||||
payload_len: usize,
|
||||
}
|
||||
|
||||
impl EncryptedConnection {
|
||||
/// Create an encrypted connection out of the handshake.
|
||||
pub fn new(handshake: &mut Handshake) -> Result<EncryptedConnection, Error> {
|
||||
let shared = crypto::ecdh::agree(handshake.ecdhe.secret(), &handshake.remote_ephemeral)?;
|
||||
let mut nonce_material = H512::new();
|
||||
if handshake.originated {
|
||||
handshake.remote_nonce.copy_to(&mut nonce_material[0..32]);
|
||||
handshake.nonce.copy_to(&mut nonce_material[32..64]);
|
||||
}
|
||||
else {
|
||||
handshake.nonce.copy_to(&mut nonce_material[0..32]);
|
||||
handshake.remote_nonce.copy_to(&mut nonce_material[32..64]);
|
||||
}
|
||||
let mut key_material = H512::new();
|
||||
shared.copy_to(&mut key_material[0..32]);
|
||||
write_keccak(&nonce_material, &mut key_material[32..64]);
|
||||
keccak(&key_material).copy_to(&mut key_material[32..64]);
|
||||
keccak(&key_material).copy_to(&mut key_material[32..64]);
|
||||
|
||||
let iv = vec![0u8; 16];
|
||||
let encoder = CtrMode::new(AesSafe256Encryptor::new(&key_material[32..64]), iv);
|
||||
let iv = vec![0u8; 16];
|
||||
let decoder = CtrMode::new(AesSafe256Encryptor::new(&key_material[32..64]), iv);
|
||||
|
||||
keccak(&key_material).copy_to(&mut key_material[32..64]);
|
||||
let mac_encoder = EcbEncryptor::new(AesSafe256Encryptor::new(&key_material[32..64]), NoPadding);
|
||||
|
||||
let mut egress_mac = Keccak::new_keccak256();
|
||||
let mut mac_material = &H256::from_slice(&key_material[32..64]) ^ &handshake.remote_nonce;
|
||||
egress_mac.update(&mac_material);
|
||||
egress_mac.update(if handshake.originated { &handshake.auth_cipher } else { &handshake.ack_cipher });
|
||||
|
||||
let mut ingress_mac = Keccak::new_keccak256();
|
||||
mac_material = &H256::from_slice(&key_material[32..64]) ^ &handshake.nonce;
|
||||
ingress_mac.update(&mac_material);
|
||||
ingress_mac.update(if handshake.originated { &handshake.ack_cipher } else { &handshake.auth_cipher });
|
||||
|
||||
let old_connection = handshake.connection.try_clone()?;
|
||||
let connection = ::std::mem::replace(&mut handshake.connection, old_connection);
|
||||
let mut enc = EncryptedConnection {
|
||||
connection: connection,
|
||||
encoder: encoder,
|
||||
decoder: decoder,
|
||||
mac_encoder: mac_encoder,
|
||||
egress_mac: egress_mac,
|
||||
ingress_mac: ingress_mac,
|
||||
read_state: EncryptedConnectionState::Header,
|
||||
protocol_id: 0,
|
||||
payload_len: 0,
|
||||
};
|
||||
enc.connection.expect(ENCRYPTED_HEADER_LEN);
|
||||
Ok(enc)
|
||||
}
|
||||
|
||||
/// Send a packet
|
||||
pub fn send_packet<Message>(&mut self, io: &IoContext<Message>, payload: &[u8]) -> Result<(), Error> where Message: Send + Clone + Sync + 'static {
|
||||
let mut header = RlpStream::new();
|
||||
let len = payload.len();
|
||||
if len > MAX_PAYLOAD_SIZE {
|
||||
bail!(ErrorKind::OversizedPacket);
|
||||
}
|
||||
header.append_raw(&[(len >> 16) as u8, (len >> 8) as u8, len as u8], 1);
|
||||
header.append_raw(&[0xc2u8, 0x80u8, 0x80u8], 1);
|
||||
//TODO: ger rid of vectors here
|
||||
let mut header = header.out();
|
||||
let padding = (16 - (payload.len() % 16)) % 16;
|
||||
header.resize(16, 0u8);
|
||||
|
||||
let mut packet = vec![0u8; 32 + payload.len() + padding + 16];
|
||||
self.encoder.encrypt(&mut RefReadBuffer::new(&header), &mut RefWriteBuffer::new(&mut packet), false).expect("Invalid length or padding");
|
||||
EncryptedConnection::update_mac(&mut self.egress_mac, &mut self.mac_encoder, &packet[0..16]);
|
||||
self.egress_mac.clone().finalize(&mut packet[16..32]);
|
||||
self.encoder.encrypt(&mut RefReadBuffer::new(payload), &mut RefWriteBuffer::new(&mut packet[32..(32 + len)]), padding == 0).expect("Invalid length or padding");
|
||||
if padding != 0 {
|
||||
let pad = [0u8; 16];
|
||||
self.encoder.encrypt(&mut RefReadBuffer::new(&pad[0..padding]), &mut RefWriteBuffer::new(&mut packet[(32 + len)..(32 + len + padding)]), true).expect("Invalid length or padding");
|
||||
}
|
||||
self.egress_mac.update(&packet[32..(32 + len + padding)]);
|
||||
EncryptedConnection::update_mac(&mut self.egress_mac, &mut self.mac_encoder, &[0u8; 0]);
|
||||
self.egress_mac.clone().finalize(&mut packet[(32 + len + padding)..]);
|
||||
self.connection.send(io, packet);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Decrypt and authenticate an incoming packet header. Prepare for receiving payload.
|
||||
fn read_header(&mut self, header: &[u8]) -> Result<(), Error> {
|
||||
if header.len() != ENCRYPTED_HEADER_LEN {
|
||||
return Err(ErrorKind::Auth.into());
|
||||
}
|
||||
EncryptedConnection::update_mac(&mut self.ingress_mac, &mut self.mac_encoder, &header[0..16]);
|
||||
let mac = &header[16..];
|
||||
let mut expected = H256::new();
|
||||
self.ingress_mac.clone().finalize(&mut expected);
|
||||
if mac != &expected[0..16] {
|
||||
return Err(ErrorKind::Auth.into());
|
||||
}
|
||||
|
||||
let mut hdec = H128::new();
|
||||
self.decoder.decrypt(&mut RefReadBuffer::new(&header[0..16]), &mut RefWriteBuffer::new(&mut hdec), false).expect("Invalid length or padding");
|
||||
|
||||
let length = ((((hdec[0] as u32) << 8) + (hdec[1] as u32)) << 8) + (hdec[2] as u32);
|
||||
let header_rlp = UntrustedRlp::new(&hdec[3..6]);
|
||||
let protocol_id = header_rlp.val_at::<u16>(0)?;
|
||||
|
||||
self.payload_len = length as usize;
|
||||
self.protocol_id = protocol_id;
|
||||
self.read_state = EncryptedConnectionState::Payload;
|
||||
|
||||
let padding = (16 - (length % 16)) % 16;
|
||||
let full_length = length + padding + 16;
|
||||
self.connection.expect(full_length as usize);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Decrypt and authenticate packet payload.
|
||||
fn read_payload(&mut self, payload: &[u8]) -> Result<Packet, Error> {
|
||||
let padding = (16 - (self.payload_len % 16)) % 16;
|
||||
let full_length = self.payload_len + padding + 16;
|
||||
if payload.len() != full_length {
|
||||
return Err(ErrorKind::Auth.into());
|
||||
}
|
||||
self.ingress_mac.update(&payload[0..payload.len() - 16]);
|
||||
EncryptedConnection::update_mac(&mut self.ingress_mac, &mut self.mac_encoder, &[0u8; 0]);
|
||||
let mac = &payload[(payload.len() - 16)..];
|
||||
let mut expected = H128::new();
|
||||
self.ingress_mac.clone().finalize(&mut expected);
|
||||
if mac != &expected[..] {
|
||||
return Err(ErrorKind::Auth.into());
|
||||
}
|
||||
|
||||
let mut packet = vec![0u8; self.payload_len];
|
||||
self.decoder.decrypt(&mut RefReadBuffer::new(&payload[0..self.payload_len]), &mut RefWriteBuffer::new(&mut packet), false).expect("Invalid length or padding");
|
||||
let mut pad_buf = [0u8; 16];
|
||||
self.decoder.decrypt(&mut RefReadBuffer::new(&payload[self.payload_len..(payload.len() - 16)]), &mut RefWriteBuffer::new(&mut pad_buf), false).expect("Invalid length or padding");
|
||||
Ok(Packet {
|
||||
protocol: self.protocol_id,
|
||||
data: packet
|
||||
})
|
||||
}
|
||||
|
||||
/// Update MAC after reading or writing any data.
|
||||
fn update_mac(mac: &mut Keccak, mac_encoder: &mut EcbEncryptor<AesSafe256Encryptor, EncPadding<NoPadding>>, seed: &[u8]) {
|
||||
let mut prev = H128::new();
|
||||
mac.clone().finalize(&mut prev);
|
||||
let mut enc = H128::new();
|
||||
mac_encoder.encrypt(&mut RefReadBuffer::new(&prev), &mut RefWriteBuffer::new(&mut enc), true).expect("Error updating MAC");
|
||||
mac_encoder.reset();
|
||||
|
||||
enc = enc ^ if seed.is_empty() { prev } else { H128::from_slice(seed) };
|
||||
mac.update(&enc);
|
||||
}
|
||||
|
||||
/// Readable IO handler. Tracker receive status and returns decoded packet if avaialable.
|
||||
pub fn readable<Message>(&mut self, io: &IoContext<Message>) -> Result<Option<Packet>, Error> where Message: Send + Clone + Sync + 'static {
|
||||
io.clear_timer(self.connection.token)?;
|
||||
if let EncryptedConnectionState::Header = self.read_state {
|
||||
if let Some(data) = self.connection.readable()? {
|
||||
self.read_header(&data)?;
|
||||
io.register_timer(self.connection.token, RECIEVE_PAYLOAD_TIMEOUT)?;
|
||||
}
|
||||
};
|
||||
if let EncryptedConnectionState::Payload = self.read_state {
|
||||
match self.connection.readable()? {
|
||||
Some(data) => {
|
||||
self.read_state = EncryptedConnectionState::Header;
|
||||
self.connection.expect(ENCRYPTED_HEADER_LEN);
|
||||
Ok(Some(self.read_payload(&data)?))
|
||||
},
|
||||
None => Ok(None)
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Writable IO handler. Processes send queeue.
|
||||
pub fn writable<Message>(&mut self, io: &IoContext<Message>) -> Result<(), Error> where Message: Send + Clone + Sync + 'static {
|
||||
self.connection.writable(io)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_encryption() {
|
||||
use ethereum_types::{H256, H128};
|
||||
use std::str::FromStr;
|
||||
let key = H256::from_str("2212767d793a7a3d66f869ae324dd11bd17044b82c9f463b8a541a4d089efec5").unwrap();
|
||||
let before = H128::from_str("12532abaec065082a3cf1da7d0136f15").unwrap();
|
||||
let before2 = H128::from_str("7e99f682356fdfbc6b67a9562787b18a").unwrap();
|
||||
let after = H128::from_str("89464c6b04e7c99e555c81d3f7266a05").unwrap();
|
||||
let after2 = H128::from_str("85c070030589ef9c7a2879b3a8489316").unwrap();
|
||||
|
||||
let mut got = H128::new();
|
||||
|
||||
let mut encoder = EcbEncryptor::new(AesSafe256Encryptor::new(&key), NoPadding);
|
||||
encoder.encrypt(&mut RefReadBuffer::new(&before), &mut RefWriteBuffer::new(&mut got), true).unwrap();
|
||||
encoder.reset();
|
||||
assert_eq!(got, after);
|
||||
got = H128::new();
|
||||
encoder.encrypt(&mut RefReadBuffer::new(&before2), &mut RefWriteBuffer::new(&mut got), true).unwrap();
|
||||
encoder.reset();
|
||||
assert_eq!(got, after2);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::cmp;
|
||||
use std::collections::VecDeque;
|
||||
use std::io::{Read, Write, Cursor, ErrorKind, Result, Error};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
use mio::{Ready};
|
||||
use ethcore_bytes::Bytes;
|
||||
use io::*;
|
||||
use super::super::stats::*;
|
||||
use super::*;
|
||||
|
||||
pub struct TestSocket {
|
||||
pub read_buffer: Vec<u8>,
|
||||
pub write_buffer: Vec<u8>,
|
||||
pub cursor: usize,
|
||||
pub buf_size: usize,
|
||||
}
|
||||
|
||||
impl Default for TestSocket {
|
||||
fn default() -> Self {
|
||||
TestSocket::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TestSocket {
|
||||
pub fn new() -> Self {
|
||||
TestSocket {
|
||||
read_buffer: vec![],
|
||||
write_buffer: vec![],
|
||||
cursor: 0,
|
||||
buf_size: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_buf(buf_size: usize) -> TestSocket {
|
||||
TestSocket {
|
||||
read_buffer: vec![],
|
||||
write_buffer: vec![],
|
||||
cursor: 0,
|
||||
buf_size: buf_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for TestSocket {
|
||||
fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
|
||||
let end_position = cmp::min(self.read_buffer.len(), self.cursor+buf.len());
|
||||
if self.cursor > end_position { return Ok(0) }
|
||||
let len = cmp::max(end_position - self.cursor, 0);
|
||||
match len {
|
||||
0 => Ok(0),
|
||||
_ => {
|
||||
for i in self.cursor..end_position {
|
||||
buf[i-self.cursor] = self.read_buffer[i];
|
||||
}
|
||||
self.cursor = end_position;
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for TestSocket {
|
||||
fn write(&mut self, buf: &[u8]) -> Result<usize> {
|
||||
if self.buf_size == 0 || buf.len() < self.buf_size {
|
||||
self.write_buffer.extend(buf.iter().cloned());
|
||||
Ok(buf.len())
|
||||
}
|
||||
else {
|
||||
self.write_buffer.extend(buf.iter().take(self.buf_size).cloned());
|
||||
Ok(self.buf_size)
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<()> {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
impl GenericSocket for TestSocket {}
|
||||
|
||||
struct TestBrokenSocket {
|
||||
error: String
|
||||
}
|
||||
|
||||
impl Read for TestBrokenSocket {
|
||||
fn read(&mut self, _: &mut [u8]) -> Result<usize> {
|
||||
Err(Error::new(ErrorKind::Other, self.error.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for TestBrokenSocket {
|
||||
fn write(&mut self, _: &[u8]) -> Result<usize> {
|
||||
Err(Error::new(ErrorKind::Other, self.error.clone()))
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<()> {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
impl GenericSocket for TestBrokenSocket {}
|
||||
|
||||
type TestConnection = GenericConnection<TestSocket>;
|
||||
|
||||
impl Default for TestConnection {
|
||||
fn default() -> Self {
|
||||
TestConnection::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TestConnection {
|
||||
pub fn new() -> Self {
|
||||
TestConnection {
|
||||
token: 999998888usize,
|
||||
socket: TestSocket::new(),
|
||||
send_queue: VecDeque::new(),
|
||||
rec_buf: Bytes::new(),
|
||||
rec_size: 0,
|
||||
interest: Ready::hup() | Ready::readable(),
|
||||
stats: Arc::<NetworkStats>::new(NetworkStats::new()),
|
||||
registered: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type TestBrokenConnection = GenericConnection<TestBrokenSocket>;
|
||||
|
||||
impl Default for TestBrokenConnection {
|
||||
fn default() -> Self {
|
||||
TestBrokenConnection::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TestBrokenConnection {
|
||||
pub fn new() -> Self {
|
||||
TestBrokenConnection {
|
||||
token: 999998888usize,
|
||||
socket: TestBrokenSocket { error: "test broken socket".to_owned() },
|
||||
send_queue: VecDeque::new(),
|
||||
rec_buf: Bytes::new(),
|
||||
rec_size: 0,
|
||||
interest: Ready::hup() | Ready::readable(),
|
||||
stats: Arc::<NetworkStats>::new(NetworkStats::new()),
|
||||
registered: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_io() -> IoContext<i32> {
|
||||
IoContext::new(IoChannel::disconnected(), 0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connection_expect() {
|
||||
let mut connection = TestConnection::new();
|
||||
connection.expect(1024);
|
||||
assert_eq!(1024, connection.rec_size);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connection_write_empty() {
|
||||
let mut connection = TestConnection::new();
|
||||
let status = connection.writable(&test_io());
|
||||
assert!(status.is_ok());
|
||||
assert!(WriteStatus::Complete == status.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connection_write() {
|
||||
let mut connection = TestConnection::new();
|
||||
let data = Cursor::new(vec![0; 10240]);
|
||||
connection.send_queue.push_back(data);
|
||||
|
||||
let status = connection.writable(&test_io());
|
||||
assert!(status.is_ok());
|
||||
assert!(WriteStatus::Complete == status.unwrap());
|
||||
assert_eq!(10240, connection.socket.write_buffer.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connection_write_is_buffered() {
|
||||
let mut connection = TestConnection::new();
|
||||
connection.socket = TestSocket::new_buf(1024);
|
||||
let data = Cursor::new(vec![0; 10240]);
|
||||
connection.send_queue.push_back(data);
|
||||
|
||||
let status = connection.writable(&test_io());
|
||||
|
||||
assert!(status.is_ok());
|
||||
assert!(WriteStatus::Ongoing == status.unwrap());
|
||||
assert_eq!(1024, connection.socket.write_buffer.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connection_write_to_broken() {
|
||||
let mut connection = TestBrokenConnection::new();
|
||||
let data = Cursor::new(vec![0; 10240]);
|
||||
connection.send_queue.push_back(data);
|
||||
|
||||
let status = connection.writable(&test_io());
|
||||
|
||||
assert!(!status.is_ok());
|
||||
assert_eq!(1, connection.send_queue.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connection_read() {
|
||||
let mut connection = TestConnection::new();
|
||||
connection.rec_size = 2048;
|
||||
connection.rec_buf = vec![10; 1024];
|
||||
connection.socket.read_buffer = vec![99; 2048];
|
||||
|
||||
let status = connection.readable();
|
||||
|
||||
assert!(status.is_ok());
|
||||
assert_eq!(1024, connection.socket.cursor);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connection_read_from_broken() {
|
||||
let mut connection = TestBrokenConnection::new();
|
||||
connection.rec_size = 2048;
|
||||
|
||||
let status = connection.readable();
|
||||
assert!(!status.is_ok());
|
||||
assert_eq!(0, connection.rec_buf.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connection_read_nothing() {
|
||||
let mut connection = TestConnection::new();
|
||||
connection.rec_size = 2048;
|
||||
|
||||
let status = connection.readable();
|
||||
|
||||
assert!(status.is_ok());
|
||||
assert_eq!(0, connection.rec_buf.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connection_read_full() {
|
||||
let mut connection = TestConnection::new();
|
||||
connection.rec_size = 1024;
|
||||
connection.rec_buf = vec![76;1024];
|
||||
|
||||
let status = connection.readable();
|
||||
|
||||
assert!(status.is_ok());
|
||||
assert_eq!(0, connection.socket.cursor);
|
||||
}
|
||||
}
|
||||
31
util/network-devp2p/src/connection_filter.rs
Normal file
31
util/network-devp2p/src/connection_filter.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Connection filter trait.
|
||||
|
||||
use super::NodeId;
|
||||
|
||||
/// Filtered connection direction.
|
||||
pub enum ConnectionDirection {
|
||||
Inbound,
|
||||
Outbound,
|
||||
}
|
||||
|
||||
/// Connection filter. Each connection is checked against `connection_allowed`.
|
||||
pub trait ConnectionFilter : Send + Sync {
|
||||
/// Filter a connection. Returns `true` if connection should be allowed. `false` if rejected.
|
||||
fn connection_allowed(&self, own_id: &NodeId, connecting_id: &NodeId, direction: ConnectionDirection) -> bool;
|
||||
}
|
||||
710
util/network-devp2p/src/discovery.rs
Normal file
710
util/network-devp2p/src/discovery.rs
Normal file
@@ -0,0 +1,710 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use ethcore_bytes::Bytes;
|
||||
use std::net::SocketAddr;
|
||||
use std::collections::{HashSet, HashMap, BTreeMap, VecDeque};
|
||||
use std::mem;
|
||||
use std::default::Default;
|
||||
use mio::*;
|
||||
use mio::deprecated::{Handler, EventLoop};
|
||||
use mio::udp::*;
|
||||
use hash::keccak;
|
||||
use time;
|
||||
use ethereum_types::{H256, H520};
|
||||
use rlp::*;
|
||||
use node_table::*;
|
||||
use network::{Error, ErrorKind};
|
||||
use io::{StreamToken, IoContext};
|
||||
use ethkey::{Secret, KeyPair, sign, recover};
|
||||
use network::IpFilter;
|
||||
|
||||
use PROTOCOL_VERSION;
|
||||
|
||||
const ADDRESS_BYTES_SIZE: u32 = 32; // Size of address type in bytes.
|
||||
const ADDRESS_BITS: u32 = 8 * ADDRESS_BYTES_SIZE; // Denoted by n in [Kademlia].
|
||||
const NODE_BINS: u32 = ADDRESS_BITS - 1; // Size of m_state (excludes root, which is us).
|
||||
const DISCOVERY_MAX_STEPS: u16 = 8; // Max iterations of discovery. (discover)
|
||||
const BUCKET_SIZE: usize = 16; // Denoted by k in [Kademlia]. Number of nodes stored in each bucket.
|
||||
const ALPHA: usize = 3; // Denoted by \alpha in [Kademlia]. Number of concurrent FindNode requests.
|
||||
const MAX_DATAGRAM_SIZE: usize = 1280;
|
||||
|
||||
const PACKET_PING: u8 = 1;
|
||||
const PACKET_PONG: u8 = 2;
|
||||
const PACKET_FIND_NODE: u8 = 3;
|
||||
const PACKET_NEIGHBOURS: u8 = 4;
|
||||
|
||||
const PING_TIMEOUT_MS: u64 = 300;
|
||||
const MAX_NODES_PING: usize = 32; // Max nodes to add/ping at once
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NodeEntry {
|
||||
pub id: NodeId,
|
||||
pub endpoint: NodeEndpoint,
|
||||
}
|
||||
|
||||
pub struct BucketEntry {
|
||||
pub address: NodeEntry,
|
||||
pub id_hash: H256,
|
||||
pub timeout: Option<u64>,
|
||||
}
|
||||
|
||||
pub struct NodeBucket {
|
||||
nodes: VecDeque<BucketEntry>, //sorted by last active
|
||||
}
|
||||
|
||||
impl Default for NodeBucket {
|
||||
fn default() -> Self {
|
||||
NodeBucket::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeBucket {
|
||||
fn new() -> Self {
|
||||
NodeBucket {
|
||||
nodes: VecDeque::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Datagramm {
|
||||
payload: Bytes,
|
||||
address: SocketAddr,
|
||||
}
|
||||
|
||||
pub struct Discovery {
|
||||
id: NodeId,
|
||||
id_hash: H256,
|
||||
secret: Secret,
|
||||
public_endpoint: NodeEndpoint,
|
||||
udp_socket: UdpSocket,
|
||||
token: StreamToken,
|
||||
discovery_round: u16,
|
||||
discovery_id: NodeId,
|
||||
discovery_nodes: HashSet<NodeId>,
|
||||
node_buckets: Vec<NodeBucket>,
|
||||
send_queue: VecDeque<Datagramm>,
|
||||
check_timestamps: bool,
|
||||
adding_nodes: Vec<NodeEntry>,
|
||||
ip_filter: IpFilter,
|
||||
}
|
||||
|
||||
pub struct TableUpdates {
|
||||
pub added: HashMap<NodeId, NodeEntry>,
|
||||
pub removed: HashSet<NodeId>,
|
||||
}
|
||||
|
||||
impl 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(),
|
||||
id_hash: keccak(key.public()),
|
||||
secret: key.secret().clone(),
|
||||
public_endpoint: public,
|
||||
token: token,
|
||||
discovery_round: 0,
|
||||
discovery_id: NodeId::new(),
|
||||
discovery_nodes: HashSet::new(),
|
||||
node_buckets: (0..NODE_BINS).map(|_| NodeBucket::new()).collect(),
|
||||
udp_socket: socket,
|
||||
send_queue: VecDeque::new(),
|
||||
check_timestamps: true,
|
||||
adding_nodes: Vec::new(),
|
||||
ip_filter: ip_filter,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a new node to discovery table. Pings the node.
|
||||
pub fn add_node(&mut self, e: NodeEntry) {
|
||||
if self.is_allowed(&e) {
|
||||
let endpoint = e.endpoint.clone();
|
||||
self.update_node(e);
|
||||
self.ping(&endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a list of nodes. Pings a few nodes each round
|
||||
pub fn add_node_list(&mut self, nodes: Vec<NodeEntry>) {
|
||||
self.adding_nodes = nodes;
|
||||
self.update_new_nodes();
|
||||
}
|
||||
|
||||
/// Add a list of known nodes to the table.
|
||||
pub fn init_node_list(&mut self, mut nodes: Vec<NodeEntry>) {
|
||||
for n in nodes.drain(..) {
|
||||
if self.is_allowed(&n) {
|
||||
self.update_node(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_node(&mut self, e: NodeEntry) {
|
||||
trace!(target: "discovery", "Inserting {:?}", &e);
|
||||
let id_hash = keccak(e.id);
|
||||
let ping = {
|
||||
let bucket = &mut self.node_buckets[Discovery::distance(&self.id_hash, &id_hash) as usize];
|
||||
let updated = if let Some(node) = bucket.nodes.iter_mut().find(|n| n.address.id == e.id) {
|
||||
node.address = e.clone();
|
||||
node.timeout = None;
|
||||
true
|
||||
} else { false };
|
||||
|
||||
if !updated {
|
||||
bucket.nodes.push_front(BucketEntry { address: e, timeout: None, id_hash: id_hash, });
|
||||
}
|
||||
|
||||
if bucket.nodes.len() > BUCKET_SIZE {
|
||||
//ping least active node
|
||||
let last = bucket.nodes.back_mut().expect("Last item is always present when len() > 0");
|
||||
last.timeout = Some(time::precise_time_ns());
|
||||
Some(last.address.endpoint.clone())
|
||||
} else { None }
|
||||
};
|
||||
if let Some(endpoint) = ping {
|
||||
self.ping(&endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_ping(&mut self, id: &NodeId) {
|
||||
let bucket = &mut self.node_buckets[Discovery::distance(&self.id_hash, &keccak(id)) as usize];
|
||||
if let Some(node) = bucket.nodes.iter_mut().find(|n| &n.address.id == id) {
|
||||
node.timeout = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn start(&mut self) {
|
||||
trace!(target: "discovery", "Starting discovery");
|
||||
self.discovery_round = 0;
|
||||
self.discovery_id.randomize(); //TODO: use cryptographic nonce
|
||||
self.discovery_nodes.clear();
|
||||
}
|
||||
|
||||
fn update_new_nodes(&mut self) {
|
||||
let mut count = 0usize;
|
||||
while !self.adding_nodes.is_empty() && count < MAX_NODES_PING {
|
||||
let node = self.adding_nodes.pop().expect("pop is always Some if not empty; qed");
|
||||
self.add_node(node);
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn discover(&mut self) {
|
||||
self.update_new_nodes();
|
||||
if self.discovery_round == DISCOVERY_MAX_STEPS {
|
||||
return;
|
||||
}
|
||||
trace!(target: "discovery", "Starting round {:?}", self.discovery_round);
|
||||
let mut tried_count = 0;
|
||||
{
|
||||
let nearest = Discovery::nearest_node_entries(&self.discovery_id, &self.node_buckets).into_iter();
|
||||
let nearest = nearest.filter(|x| !self.discovery_nodes.contains(&x.id)).take(ALPHA).collect::<Vec<_>>();
|
||||
for r in nearest {
|
||||
let rlp = encode_list(&(&[self.discovery_id.clone()][..]));
|
||||
self.send_packet(PACKET_FIND_NODE, &r.endpoint.udp_address(), &rlp);
|
||||
self.discovery_nodes.insert(r.id.clone());
|
||||
tried_count += 1;
|
||||
trace!(target: "discovery", "Sent FindNode to {:?}", &r.endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
if tried_count == 0 {
|
||||
trace!(target: "discovery", "Completing discovery");
|
||||
self.discovery_round = DISCOVERY_MAX_STEPS;
|
||||
self.discovery_nodes.clear();
|
||||
return;
|
||||
}
|
||||
self.discovery_round += 1;
|
||||
}
|
||||
|
||||
fn distance(a: &H256, b: &H256) -> u32 {
|
||||
let d = *a ^ *b;
|
||||
let mut ret:u32 = 0;
|
||||
for i in 0..32 {
|
||||
let mut v: u8 = d[i];
|
||||
while v != 0 {
|
||||
v >>= 1;
|
||||
ret += 1;
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn ping(&mut self, node: &NodeEndpoint) {
|
||||
let mut rlp = RlpStream::new_list(3);
|
||||
rlp.append(&PROTOCOL_VERSION);
|
||||
self.public_endpoint.to_rlp_list(&mut rlp);
|
||||
node.to_rlp_list(&mut rlp);
|
||||
trace!(target: "discovery", "Sent Ping to {:?}", &node);
|
||||
self.send_packet(PACKET_PING, &node.udp_address(), &rlp.drain());
|
||||
}
|
||||
|
||||
fn send_packet(&mut self, packet_id: u8, address: &SocketAddr, payload: &[u8]) {
|
||||
let mut rlp = RlpStream::new();
|
||||
rlp.append_raw(&[packet_id], 1);
|
||||
let source = Rlp::new(payload);
|
||||
rlp.begin_list(source.item_count() + 1);
|
||||
for i in 0 .. source.item_count() {
|
||||
rlp.append_raw(source.at(i).as_raw(), 1);
|
||||
}
|
||||
let timestamp = time::get_time().sec as u32 + 60;
|
||||
rlp.append(×tamp);
|
||||
|
||||
let bytes = rlp.drain();
|
||||
let hash = keccak(bytes.as_ref());
|
||||
let signature = match sign(&self.secret, &hash) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
warn!("Error signing UDP packet");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mut packet = Bytes::with_capacity(bytes.len() + 32 + 65);
|
||||
packet.extend(hash.iter());
|
||||
packet.extend(signature.iter());
|
||||
packet.extend(bytes.iter());
|
||||
let signed_hash = keccak(&packet[32..]);
|
||||
packet[0..32].clone_from_slice(&signed_hash);
|
||||
self.send_to(packet, address.clone());
|
||||
}
|
||||
|
||||
fn nearest_node_entries(target: &NodeId, buckets: &[NodeBucket]) -> Vec<NodeEntry> {
|
||||
let mut found: BTreeMap<u32, Vec<&NodeEntry>> = BTreeMap::new();
|
||||
let mut count = 0;
|
||||
let target_hash = keccak(target);
|
||||
|
||||
// Sort nodes by distance to target
|
||||
for bucket in buckets {
|
||||
for node in &bucket.nodes {
|
||||
let distance = Discovery::distance(&target_hash, &node.id_hash);
|
||||
found.entry(distance).or_insert_with(Vec::new).push(&node.address);
|
||||
if count == BUCKET_SIZE {
|
||||
// delete the most distant element
|
||||
let remove = {
|
||||
let (key, last) = found.iter_mut().next_back().expect("Last element is always Some when count > 0");
|
||||
last.pop();
|
||||
if last.is_empty() { Some(key.clone()) } else { None }
|
||||
};
|
||||
if let Some(remove) = remove {
|
||||
found.remove(&remove);
|
||||
}
|
||||
}
|
||||
else {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut ret:Vec<NodeEntry> = Vec::new();
|
||||
for nodes in found.values() {
|
||||
ret.extend(nodes.iter().map(|&n| n.clone()));
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn writable<Message>(&mut self, io: &IoContext<Message>) where Message: Send + Sync + Clone {
|
||||
while let Some(data) = self.send_queue.pop_front() {
|
||||
match self.udp_socket.send_to(&data.payload, &data.address) {
|
||||
Ok(Some(size)) if size == data.payload.len() => {
|
||||
},
|
||||
Ok(Some(_)) => {
|
||||
warn!("UDP sent incomplete datagramm");
|
||||
},
|
||||
Ok(None) => {
|
||||
self.send_queue.push_front(data);
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("UDP send error: {:?}, address: {:?}", e, &data.address);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
io.update_registration(self.token).unwrap_or_else(|e| debug!("Error updating discovery registration: {:?}", e));
|
||||
}
|
||||
|
||||
fn send_to(&mut self, payload: Bytes, address: SocketAddr) {
|
||||
self.send_queue.push_back(Datagramm { payload: payload, address: address });
|
||||
}
|
||||
|
||||
pub fn readable<Message>(&mut self, io: &IoContext<Message>) -> Option<TableUpdates> where Message: Send + Sync + Clone {
|
||||
let mut buf: [u8; MAX_DATAGRAM_SIZE] = unsafe { mem::uninitialized() };
|
||||
let writable = !self.send_queue.is_empty();
|
||||
let res = match self.udp_socket.recv_from(&mut buf) {
|
||||
Ok(Some((len, address))) => self.on_packet(&buf[0..len], address).unwrap_or_else(|e| {
|
||||
debug!("Error processing UDP packet: {:?}", e);
|
||||
None
|
||||
}),
|
||||
Ok(_) => None,
|
||||
Err(e) => {
|
||||
debug!("Error reading UPD socket: {:?}", e);
|
||||
None
|
||||
}
|
||||
};
|
||||
let new_writable = !self.send_queue.is_empty();
|
||||
if writable != new_writable {
|
||||
io.update_registration(self.token).unwrap_or_else(|e| debug!("Error updating discovery registration: {:?}", e));
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn on_packet(&mut self, packet: &[u8], from: SocketAddr) -> Result<Option<TableUpdates>, Error> {
|
||||
// validate packet
|
||||
if packet.len() < 32 + 65 + 4 + 1 {
|
||||
return Err(ErrorKind::BadProtocol.into());
|
||||
}
|
||||
|
||||
let hash_signed = keccak(&packet[32..]);
|
||||
if hash_signed[..] != packet[0..32] {
|
||||
return Err(ErrorKind::BadProtocol.into());
|
||||
}
|
||||
|
||||
let signed = &packet[(32 + 65)..];
|
||||
let signature = H520::from_slice(&packet[32..(32 + 65)]);
|
||||
let node_id = recover(&signature.into(), &keccak(signed))?;
|
||||
|
||||
let packet_id = signed[0];
|
||||
let rlp = UntrustedRlp::new(&signed[1..]);
|
||||
match packet_id {
|
||||
PACKET_PING => self.on_ping(&rlp, &node_id, &from),
|
||||
PACKET_PONG => self.on_pong(&rlp, &node_id, &from),
|
||||
PACKET_FIND_NODE => self.on_find_node(&rlp, &node_id, &from),
|
||||
PACKET_NEIGHBOURS => self.on_neighbours(&rlp, &node_id, &from),
|
||||
_ => {
|
||||
debug!("Unknown UDP packet: {}", packet_id);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_timestamp(&self, timestamp: u64) -> Result<(), Error> {
|
||||
if self.check_timestamps && timestamp < time::get_time().sec as u64{
|
||||
debug!(target: "discovery", "Expired packet");
|
||||
return Err(ErrorKind::Expired.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_allowed(&self, entry: &NodeEntry) -> bool {
|
||||
entry.endpoint.is_allowed(&self.ip_filter) && entry.id != self.id
|
||||
}
|
||||
|
||||
fn on_ping(&mut self, rlp: &UntrustedRlp, node: &NodeId, from: &SocketAddr) -> Result<Option<TableUpdates>, Error> {
|
||||
trace!(target: "discovery", "Got Ping from {:?}", &from);
|
||||
let source = NodeEndpoint::from_rlp(&rlp.at(1)?)?;
|
||||
let dest = NodeEndpoint::from_rlp(&rlp.at(2)?)?;
|
||||
let timestamp: u64 = rlp.val_at(3)?;
|
||||
self.check_timestamp(timestamp)?;
|
||||
let mut added_map = HashMap::new();
|
||||
let entry = NodeEntry { id: node.clone(), endpoint: source.clone() };
|
||||
if !entry.endpoint.is_valid() {
|
||||
debug!(target: "discovery", "Got bad address: {:?}", entry);
|
||||
} else if !self.is_allowed(&entry) {
|
||||
debug!(target: "discovery", "Address not allowed: {:?}", entry);
|
||||
} else {
|
||||
self.update_node(entry.clone());
|
||||
added_map.insert(node.clone(), entry);
|
||||
}
|
||||
let hash = keccak(rlp.as_raw());
|
||||
let mut response = RlpStream::new_list(2);
|
||||
dest.to_rlp_list(&mut response);
|
||||
response.append(&hash);
|
||||
self.send_packet(PACKET_PONG, from, &response.drain());
|
||||
|
||||
Ok(Some(TableUpdates { added: added_map, removed: HashSet::new() }))
|
||||
}
|
||||
|
||||
fn on_pong(&mut self, rlp: &UntrustedRlp, node: &NodeId, from: &SocketAddr) -> Result<Option<TableUpdates>, Error> {
|
||||
trace!(target: "discovery", "Got Pong from {:?}", &from);
|
||||
// TODO: validate pong packet
|
||||
let dest = NodeEndpoint::from_rlp(&rlp.at(0)?)?;
|
||||
let timestamp: u64 = rlp.val_at(2)?;
|
||||
self.check_timestamp(timestamp)?;
|
||||
let mut entry = NodeEntry { id: node.clone(), endpoint: dest };
|
||||
if !entry.endpoint.is_valid() {
|
||||
debug!(target: "discovery", "Bad address: {:?}", entry);
|
||||
entry.endpoint.address = from.clone();
|
||||
}
|
||||
self.clear_ping(node);
|
||||
let mut added_map = HashMap::new();
|
||||
added_map.insert(node.clone(), entry);
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn on_find_node(&mut self, rlp: &UntrustedRlp, _node: &NodeId, from: &SocketAddr) -> Result<Option<TableUpdates>, Error> {
|
||||
trace!(target: "discovery", "Got FindNode from {:?}", &from);
|
||||
let target: NodeId = rlp.val_at(0)?;
|
||||
let timestamp: u64 = rlp.val_at(1)?;
|
||||
self.check_timestamp(timestamp)?;
|
||||
let nearest = Discovery::nearest_node_entries(&target, &self.node_buckets);
|
||||
if nearest.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
let mut packets = Discovery::prepare_neighbours_packets(&nearest);
|
||||
for p in packets.drain(..) {
|
||||
self.send_packet(PACKET_NEIGHBOURS, from, &p);
|
||||
}
|
||||
trace!(target: "discovery", "Sent {} Neighbours to {:?}", nearest.len(), &from);
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn prepare_neighbours_packets(nearest: &[NodeEntry]) -> Vec<Bytes> {
|
||||
let limit = (MAX_DATAGRAM_SIZE - 109) / 90;
|
||||
let chunks = nearest.chunks(limit);
|
||||
let packets = chunks.map(|c| {
|
||||
let mut rlp = RlpStream::new_list(1);
|
||||
rlp.begin_list(c.len());
|
||||
for n in 0 .. c.len() {
|
||||
rlp.begin_list(4);
|
||||
c[n].endpoint.to_rlp(&mut rlp);
|
||||
rlp.append(&c[n].id);
|
||||
}
|
||||
rlp.out()
|
||||
});
|
||||
packets.collect()
|
||||
}
|
||||
|
||||
fn on_neighbours(&mut self, rlp: &UntrustedRlp, _node: &NodeId, from: &SocketAddr) -> Result<Option<TableUpdates>, Error> {
|
||||
// TODO: validate packet
|
||||
let mut added = HashMap::new();
|
||||
trace!(target: "discovery", "Got {} Neighbours from {:?}", rlp.at(0)?.item_count()?, &from);
|
||||
for r in rlp.at(0)?.iter() {
|
||||
let endpoint = NodeEndpoint::from_rlp(&r)?;
|
||||
if !endpoint.is_valid() {
|
||||
debug!(target: "discovery", "Bad address: {:?}", endpoint);
|
||||
continue;
|
||||
}
|
||||
let node_id: NodeId = r.val_at(3)?;
|
||||
if node_id == self.id {
|
||||
continue;
|
||||
}
|
||||
let entry = NodeEntry { id: node_id.clone(), endpoint: endpoint };
|
||||
if !self.is_allowed(&entry) {
|
||||
debug!(target: "discovery", "Address not allowed: {:?}", entry);
|
||||
continue;
|
||||
}
|
||||
added.insert(node_id, entry.clone());
|
||||
self.ping(&entry.endpoint);
|
||||
self.update_node(entry);
|
||||
}
|
||||
Ok(Some(TableUpdates { added: added, removed: HashSet::new() }))
|
||||
}
|
||||
|
||||
fn check_expired(&mut self, force: bool) -> HashSet<NodeId> {
|
||||
let now = time::precise_time_ns();
|
||||
let mut removed: HashSet<NodeId> = HashSet::new();
|
||||
for bucket in &mut self.node_buckets {
|
||||
bucket.nodes.retain(|node| {
|
||||
if let Some(timeout) = node.timeout {
|
||||
if !force && now - timeout < PING_TIMEOUT_MS * 1000_0000 {
|
||||
true
|
||||
}
|
||||
else {
|
||||
trace!(target: "discovery", "Removed expired node {:?}", &node.address);
|
||||
removed.insert(node.address.id.clone());
|
||||
false
|
||||
}
|
||||
} else { true }
|
||||
});
|
||||
}
|
||||
removed
|
||||
}
|
||||
|
||||
pub fn round(&mut self) -> Option<TableUpdates> {
|
||||
let removed = self.check_expired(false);
|
||||
self.discover();
|
||||
if !removed.is_empty() {
|
||||
Some(TableUpdates { added: HashMap::new(), removed: removed })
|
||||
} else { None }
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self) {
|
||||
self.start();
|
||||
}
|
||||
|
||||
pub fn register_socket<Host:Handler>(&self, event_loop: &mut EventLoop<Host>) -> Result<(), Error> {
|
||||
event_loop.register(&self.udp_socket, Token(self.token), Ready::all(), PollOpt::edge()).expect("Error registering UDP socket");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_registration<Host:Handler>(&self, event_loop: &mut EventLoop<Host>) -> Result<(), Error> {
|
||||
let registration = if !self.send_queue.is_empty() {
|
||||
Ready::readable() | Ready::writable()
|
||||
} else {
|
||||
Ready::readable()
|
||||
};
|
||||
event_loop.reregister(&self.udp_socket, Token(self.token), registration, PollOpt::edge()).expect("Error reregistering UDP socket");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::net::{SocketAddr};
|
||||
use node_table::{Node, NodeId, NodeEndpoint};
|
||||
|
||||
use std::str::FromStr;
|
||||
use rustc_hex::FromHex;
|
||||
use ethkey::{Random, Generator};
|
||||
|
||||
#[test]
|
||||
fn find_node() {
|
||||
let mut nearest = Vec::new();
|
||||
let node = Node::from_str("enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@127.0.0.1:7770").unwrap();
|
||||
for _ in 0..1000 {
|
||||
nearest.push( NodeEntry { id: node.id.clone(), endpoint: node.endpoint.clone() });
|
||||
}
|
||||
|
||||
let packets = Discovery::prepare_neighbours_packets(&nearest);
|
||||
assert_eq!(packets.len(), 77);
|
||||
for p in &packets[0..76] {
|
||||
assert!(p.len() > 1280/2);
|
||||
assert!(p.len() <= 1280);
|
||||
}
|
||||
assert!(packets.last().unwrap().len() > 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn discovery() {
|
||||
let key1 = Random.generate().unwrap();
|
||||
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, 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();
|
||||
discovery1.add_node(NodeEntry { id: node1.id.clone(), endpoint: node1.endpoint.clone() });
|
||||
discovery1.add_node(NodeEntry { id: node2.id.clone(), endpoint: node2.endpoint.clone() });
|
||||
|
||||
discovery2.add_node(NodeEntry { id: key1.public().clone(), endpoint: ep1.clone() });
|
||||
discovery2.refresh();
|
||||
|
||||
for _ in 0 .. 10 {
|
||||
while !discovery1.send_queue.is_empty() {
|
||||
let datagramm = discovery1.send_queue.pop_front().unwrap();
|
||||
if datagramm.address == ep2.address {
|
||||
discovery2.on_packet(&datagramm.payload, ep1.address.clone()).ok();
|
||||
}
|
||||
}
|
||||
while !discovery2.send_queue.is_empty() {
|
||||
let datagramm = discovery2.send_queue.pop_front().unwrap();
|
||||
if datagramm.address == ep1.address {
|
||||
discovery1.on_packet(&datagramm.payload, ep2.address.clone()).ok();
|
||||
}
|
||||
}
|
||||
discovery2.round();
|
||||
}
|
||||
assert_eq!(Discovery::nearest_node_entries(&NodeId::new(), &discovery2.node_buckets).len(), 3)
|
||||
}
|
||||
|
||||
#[test]
|
||||
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, IpFilter::default());
|
||||
for _ in 0..1200 {
|
||||
discovery.add_node(NodeEntry { id: NodeId::random(), endpoint: ep.clone() });
|
||||
}
|
||||
assert!(Discovery::nearest_node_entries(&NodeId::new(), &discovery.node_buckets).len() <= 16);
|
||||
let removed = discovery.check_expired(true).len();
|
||||
assert!(removed > 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_nearest_saturated() {
|
||||
use super::*;
|
||||
let mut buckets: Vec<_> = (0..256).map(|_| NodeBucket::new()).collect();
|
||||
let ep = NodeEndpoint { address: SocketAddr::from_str("127.0.0.1:40447").unwrap(), udp_port: 40447 };
|
||||
for _ in 0..(16 + 10) {
|
||||
buckets[0].nodes.push_back(BucketEntry {
|
||||
address: NodeEntry { id: NodeId::new(), endpoint: ep.clone() },
|
||||
timeout: None,
|
||||
id_hash: keccak(NodeId::new()),
|
||||
});
|
||||
}
|
||||
let nearest = Discovery::nearest_node_entries(&NodeId::new(), &buckets);
|
||||
assert_eq!(nearest.len(), 16)
|
||||
}
|
||||
|
||||
#[test]
|
||||
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, IpFilter::default());
|
||||
discovery.check_timestamps = false;
|
||||
let from = SocketAddr::from_str("99.99.99.99:40445").unwrap();
|
||||
|
||||
let packet = "\
|
||||
e9614ccfd9fc3e74360018522d30e1419a143407ffcce748de3e22116b7e8dc92ff74788c0b6663a\
|
||||
aa3d67d641936511c8f8d6ad8698b820a7cf9e1be7155e9a241f556658c55428ec0563514365799a\
|
||||
4be2be5a685a80971ddcfa80cb422cdd0101ec04cb847f000001820cfa8215a8d790000000000000\
|
||||
000000000000000000018208ae820d058443b9a3550102\
|
||||
".from_hex().unwrap();
|
||||
assert!(discovery.on_packet(&packet, from.clone()).is_ok());
|
||||
|
||||
let packet = "\
|
||||
577be4349c4dd26768081f58de4c6f375a7a22f3f7adda654d1428637412c3d7fe917cadc56d4e5e\
|
||||
7ffae1dbe3efffb9849feb71b262de37977e7c7a44e677295680e9e38ab26bee2fcbae207fba3ff3\
|
||||
d74069a50b902a82c9903ed37cc993c50001f83e82022bd79020010db83c4d001500000000abcdef\
|
||||
12820cfa8215a8d79020010db885a308d313198a2e037073488208ae82823a8443b9a355c5010203\
|
||||
040531b9019afde696e582a78fa8d95ea13ce3297d4afb8ba6433e4154caa5ac6431af1b80ba7602\
|
||||
3fa4090c408f6b4bc3701562c031041d4702971d102c9ab7fa5eed4cd6bab8f7af956f7d565ee191\
|
||||
7084a95398b6a21eac920fe3dd1345ec0a7ef39367ee69ddf092cbfe5b93e5e568ebc491983c09c7\
|
||||
6d922dc3\
|
||||
".from_hex().unwrap();
|
||||
assert!(discovery.on_packet(&packet, from.clone()).is_ok());
|
||||
|
||||
let packet = "\
|
||||
09b2428d83348d27cdf7064ad9024f526cebc19e4958f0fdad87c15eb598dd61d08423e0bf66b206\
|
||||
9869e1724125f820d851c136684082774f870e614d95a2855d000f05d1648b2d5945470bc187c2d2\
|
||||
216fbe870f43ed0909009882e176a46b0102f846d79020010db885a308d313198a2e037073488208\
|
||||
ae82823aa0fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c9548443b9\
|
||||
a355c6010203c2040506a0c969a58f6f9095004c0177a6b47f451530cab38966a25cca5cb58f0555
|
||||
42124e\
|
||||
".from_hex().unwrap();
|
||||
assert!(discovery.on_packet(&packet, from.clone()).is_ok());
|
||||
|
||||
let packet = "\
|
||||
c7c44041b9f7c7e41934417ebac9a8e1a4c6298f74553f2fcfdcae6ed6fe53163eb3d2b52e39fe91\
|
||||
831b8a927bf4fc222c3902202027e5e9eb812195f95d20061ef5cd31d502e47ecb61183f74a504fe\
|
||||
04c51e73df81f25c4d506b26db4517490103f84eb840ca634cae0d49acb401d8a4c6b6fe8c55b70d\
|
||||
115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be0081290476\
|
||||
7bf5ccd1fc7f8443b9a35582999983999999280dc62cc8255c73471e0a61da0c89acdc0e035e260a\
|
||||
dd7fc0c04ad9ebf3919644c91cb247affc82b69bd2ca235c71eab8e49737c937a2c396\
|
||||
".from_hex().unwrap();
|
||||
assert!(discovery.on_packet(&packet, from.clone()).is_ok());
|
||||
|
||||
let packet = "\
|
||||
c679fc8fe0b8b12f06577f2e802d34f6fa257e6137a995f6f4cbfc9ee50ed3710faf6e66f932c4c8\
|
||||
d81d64343f429651328758b47d3dbc02c4042f0fff6946a50f4a49037a72bb550f3a7872363a83e1\
|
||||
b9ee6469856c24eb4ef80b7535bcf99c0004f9015bf90150f84d846321163782115c82115db84031\
|
||||
55e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa8291\
|
||||
15d224c523596b401065a97f74010610fce76382c0bf32f84984010203040101b840312c55512422\
|
||||
cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e82\
|
||||
9f04c2d314fc2d4e255e0d3bc08792b069dbf8599020010db83c4d001500000000abcdef12820d05\
|
||||
820d05b84038643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2\
|
||||
d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aacf8599020010db885a308d3\
|
||||
13198a2e037073488203e78203e8b8408dcab8618c3253b558d459da53bd8fa68935a719aff8b811\
|
||||
197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73\
|
||||
8443b9a355010203b525a138aa34383fec3d2719a0\
|
||||
".from_hex().unwrap();
|
||||
assert!(discovery.on_packet(&packet, from.clone()).is_ok());
|
||||
}
|
||||
|
||||
}
|
||||
520
util/network-devp2p/src/handshake.rs
Normal file
520
util/network-devp2p/src/handshake.rs
Normal file
@@ -0,0 +1,520 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::sync::Arc;
|
||||
use rand::random;
|
||||
use hash::write_keccak;
|
||||
use mio::tcp::*;
|
||||
use ethereum_types::{H256, H520};
|
||||
use ethcore_bytes::Bytes;
|
||||
use rlp::*;
|
||||
use connection::{Connection};
|
||||
use node_table::NodeId;
|
||||
use stats::NetworkStats;
|
||||
use io::{IoContext, StreamToken};
|
||||
use ethkey::{KeyPair, Public, Secret, recover, sign, Generator, Random};
|
||||
use crypto::{ecdh, ecies};
|
||||
use network::{Error, ErrorKind, HostInfo};
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
enum HandshakeState {
|
||||
/// Just created
|
||||
New,
|
||||
/// Waiting for auth packet
|
||||
ReadingAuth,
|
||||
/// Waiting for extended auth packet
|
||||
ReadingAuthEip8,
|
||||
/// Waiting for ack packet
|
||||
ReadingAck,
|
||||
/// Waiting for extended ack packet
|
||||
ReadingAckEip8,
|
||||
/// Ready to start a session
|
||||
StartSession,
|
||||
}
|
||||
|
||||
/// `RLPx` protocol handhake. See https://github.com/ethereum/devp2p/blob/master/rlpx.md#encrypted-handshake
|
||||
pub struct Handshake {
|
||||
/// Remote node public key
|
||||
pub id: NodeId,
|
||||
/// Underlying connection
|
||||
pub connection: Connection,
|
||||
/// Handshake state
|
||||
state: HandshakeState,
|
||||
/// Outgoing or incoming connection
|
||||
pub originated: bool,
|
||||
/// ECDH ephemeral
|
||||
pub ecdhe: KeyPair,
|
||||
/// Connection nonce
|
||||
pub nonce: H256,
|
||||
/// Handshake public key
|
||||
pub remote_ephemeral: Public,
|
||||
/// Remote connection nonce.
|
||||
pub remote_nonce: H256,
|
||||
/// Remote `RLPx` protocol version.
|
||||
pub remote_version: u64,
|
||||
/// A copy of received encryped auth packet
|
||||
pub auth_cipher: Bytes,
|
||||
/// A copy of received encryped ack packet
|
||||
pub ack_cipher: Bytes,
|
||||
/// This Handshake is marked for deleteion flag
|
||||
pub expired: bool,
|
||||
}
|
||||
|
||||
const V4_AUTH_PACKET_SIZE: usize = 307;
|
||||
const V4_ACK_PACKET_SIZE: usize = 210;
|
||||
const HANDSHAKE_TIMEOUT: u64 = 5000;
|
||||
const PROTOCOL_VERSION: u64 = 4;
|
||||
// Amount of bytes added when encrypting with encryptECIES.
|
||||
const ECIES_OVERHEAD: usize = 113;
|
||||
|
||||
impl Handshake {
|
||||
/// Create a new handshake object
|
||||
pub fn new(token: StreamToken, id: Option<&NodeId>, socket: TcpStream, nonce: &H256, stats: Arc<NetworkStats>) -> Result<Handshake, Error> {
|
||||
Ok(Handshake {
|
||||
id: if let Some(id) = id { id.clone()} else { NodeId::new() },
|
||||
connection: Connection::new(token, socket, stats),
|
||||
originated: false,
|
||||
state: HandshakeState::New,
|
||||
ecdhe: Random.generate()?,
|
||||
nonce: nonce.clone(),
|
||||
remote_ephemeral: Public::new(),
|
||||
remote_nonce: H256::new(),
|
||||
remote_version: PROTOCOL_VERSION,
|
||||
auth_cipher: Bytes::new(),
|
||||
ack_cipher: Bytes::new(),
|
||||
expired: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if this handshake is expired.
|
||||
pub fn expired(&self) -> bool {
|
||||
self.expired
|
||||
}
|
||||
|
||||
/// Start a handhsake
|
||||
pub fn start<Message>(&mut self, io: &IoContext<Message>, host: &HostInfo, originated: bool) -> Result<(), Error> where Message: Send + Clone+ Sync + 'static {
|
||||
self.originated = originated;
|
||||
io.register_timer(self.connection.token, HANDSHAKE_TIMEOUT).ok();
|
||||
if originated {
|
||||
self.write_auth(io, host.secret(), host.id())?;
|
||||
}
|
||||
else {
|
||||
self.state = HandshakeState::ReadingAuth;
|
||||
self.connection.expect(V4_AUTH_PACKET_SIZE);
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if handshake is complete
|
||||
pub fn done(&self) -> bool {
|
||||
self.state == HandshakeState::StartSession
|
||||
}
|
||||
|
||||
/// Readable IO handler. Drives the state change.
|
||||
pub fn readable<Message>(&mut self, io: &IoContext<Message>, host: &HostInfo) -> Result<(), Error> where Message: Send + Clone + Sync + 'static {
|
||||
if !self.expired() {
|
||||
while let Some(data) = self.connection.readable()? {
|
||||
match self.state {
|
||||
HandshakeState::New => {},
|
||||
HandshakeState::StartSession => {},
|
||||
HandshakeState::ReadingAuth => {
|
||||
self.read_auth(io, host.secret(), &data)?;
|
||||
},
|
||||
HandshakeState::ReadingAuthEip8 => {
|
||||
self.read_auth_eip8(io, host.secret(), &data)?;
|
||||
},
|
||||
HandshakeState::ReadingAck => {
|
||||
self.read_ack(host.secret(), &data)?;
|
||||
},
|
||||
HandshakeState::ReadingAckEip8 => {
|
||||
self.read_ack_eip8(host.secret(), &data)?;
|
||||
},
|
||||
}
|
||||
if self.state == HandshakeState::StartSession {
|
||||
io.clear_timer(self.connection.token).ok();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writabe IO handler.
|
||||
pub fn writable<Message>(&mut self, io: &IoContext<Message>) -> Result<(), Error> where Message: Send + Clone + Sync + 'static {
|
||||
if !self.expired() {
|
||||
self.connection.writable(io)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_auth(&mut self, host_secret: &Secret, sig: &[u8], remote_public: &[u8], remote_nonce: &[u8], remote_version: u64) -> Result<(), Error> {
|
||||
self.id.clone_from_slice(remote_public);
|
||||
self.remote_nonce.clone_from_slice(remote_nonce);
|
||||
self.remote_version = remote_version;
|
||||
let shared = *ecdh::agree(host_secret, &self.id)?;
|
||||
let signature = H520::from_slice(sig);
|
||||
self.remote_ephemeral = recover(&signature.into(), &(&shared ^ &self.remote_nonce))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse, validate and confirm auth message
|
||||
fn read_auth<Message>(&mut self, io: &IoContext<Message>, secret: &Secret, data: &[u8]) -> Result<(), Error> where Message: Send + Clone + Sync + 'static {
|
||||
trace!(target: "network", "Received handshake auth from {:?}", self.connection.remote_addr_str());
|
||||
if data.len() != V4_AUTH_PACKET_SIZE {
|
||||
debug!(target: "network", "Wrong auth packet size");
|
||||
return Err(ErrorKind::BadProtocol.into());
|
||||
}
|
||||
self.auth_cipher = data.to_vec();
|
||||
match ecies::decrypt(secret, &[], data) {
|
||||
Ok(auth) => {
|
||||
let (sig, rest) = auth.split_at(65);
|
||||
let (_, rest) = rest.split_at(32);
|
||||
let (pubk, rest) = rest.split_at(64);
|
||||
let (nonce, _) = rest.split_at(32);
|
||||
self.set_auth(secret, sig, pubk, nonce, PROTOCOL_VERSION)?;
|
||||
self.write_ack(io)?;
|
||||
}
|
||||
Err(_) => {
|
||||
// Try to interpret as EIP-8 packet
|
||||
let total = (((data[0] as u16) << 8 | (data[1] as u16)) as usize) + 2;
|
||||
if total < V4_AUTH_PACKET_SIZE {
|
||||
debug!(target: "network", "Wrong EIP8 auth packet size");
|
||||
return Err(ErrorKind::BadProtocol.into());
|
||||
}
|
||||
let rest = total - data.len();
|
||||
self.state = HandshakeState::ReadingAuthEip8;
|
||||
self.connection.expect(rest);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_auth_eip8<Message>(&mut self, io: &IoContext<Message>, secret: &Secret, data: &[u8]) -> Result<(), Error> where Message: Send + Clone + Sync + 'static {
|
||||
trace!(target: "network", "Received EIP8 handshake auth from {:?}", self.connection.remote_addr_str());
|
||||
self.auth_cipher.extend_from_slice(data);
|
||||
let auth = ecies::decrypt(secret, &self.auth_cipher[0..2], &self.auth_cipher[2..])?;
|
||||
let rlp = UntrustedRlp::new(&auth);
|
||||
let signature: H520 = rlp.val_at(0)?;
|
||||
let remote_public: Public = rlp.val_at(1)?;
|
||||
let remote_nonce: H256 = rlp.val_at(2)?;
|
||||
let remote_version: u64 = rlp.val_at(3)?;
|
||||
self.set_auth(secret, &signature, &remote_public, &remote_nonce, remote_version)?;
|
||||
self.write_ack_eip8(io)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse and validate ack message
|
||||
fn read_ack(&mut self, secret: &Secret, data: &[u8]) -> Result<(), Error> {
|
||||
trace!(target: "network", "Received handshake ack from {:?}", self.connection.remote_addr_str());
|
||||
if data.len() != V4_ACK_PACKET_SIZE {
|
||||
debug!(target: "network", "Wrong ack packet size");
|
||||
return Err(ErrorKind::BadProtocol.into());
|
||||
}
|
||||
self.ack_cipher = data.to_vec();
|
||||
match ecies::decrypt(secret, &[], data) {
|
||||
Ok(ack) => {
|
||||
self.remote_ephemeral.clone_from_slice(&ack[0..64]);
|
||||
self.remote_nonce.clone_from_slice(&ack[64..(64+32)]);
|
||||
self.state = HandshakeState::StartSession;
|
||||
}
|
||||
Err(_) => {
|
||||
// Try to interpret as EIP-8 packet
|
||||
let total = (((data[0] as u16) << 8 | (data[1] as u16)) as usize) + 2;
|
||||
if total < V4_ACK_PACKET_SIZE {
|
||||
debug!(target: "network", "Wrong EIP8 ack packet size");
|
||||
return Err(ErrorKind::BadProtocol.into());
|
||||
}
|
||||
let rest = total - data.len();
|
||||
self.state = HandshakeState::ReadingAckEip8;
|
||||
self.connection.expect(rest);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_ack_eip8(&mut self, secret: &Secret, data: &[u8]) -> Result<(), Error> {
|
||||
trace!(target: "network", "Received EIP8 handshake auth from {:?}", self.connection.remote_addr_str());
|
||||
self.ack_cipher.extend_from_slice(data);
|
||||
let ack = ecies::decrypt(secret, &self.ack_cipher[0..2], &self.ack_cipher[2..])?;
|
||||
let rlp = UntrustedRlp::new(&ack);
|
||||
self.remote_ephemeral = rlp.val_at(0)?;
|
||||
self.remote_nonce = rlp.val_at(1)?;
|
||||
self.remote_version = rlp.val_at(2)?;
|
||||
self.state = HandshakeState::StartSession;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends auth message
|
||||
fn write_auth<Message>(&mut self, io: &IoContext<Message>, secret: &Secret, public: &Public) -> Result<(), Error> where Message: Send + Clone + Sync + 'static {
|
||||
trace!(target: "network", "Sending handshake auth to {:?}", self.connection.remote_addr_str());
|
||||
let mut data = [0u8; /*Signature::SIZE*/ 65 + /*H256::SIZE*/ 32 + /*Public::SIZE*/ 64 + /*H256::SIZE*/ 32 + 1]; //TODO: use associated constants
|
||||
let len = data.len();
|
||||
{
|
||||
data[len - 1] = 0x0;
|
||||
let (sig, rest) = data.split_at_mut(65);
|
||||
let (hepubk, rest) = rest.split_at_mut(32);
|
||||
let (pubk, rest) = rest.split_at_mut(64);
|
||||
let (nonce, _) = rest.split_at_mut(32);
|
||||
|
||||
// E(remote-pubk, S(ecdhe-random, ecdh-shared-secret^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x0)
|
||||
let shared = *ecdh::agree(secret, &self.id)?;
|
||||
sig.copy_from_slice(&*sign(self.ecdhe.secret(), &(&shared ^ &self.nonce))?);
|
||||
write_keccak(self.ecdhe.public(), hepubk);
|
||||
pubk.copy_from_slice(public);
|
||||
nonce.copy_from_slice(&self.nonce);
|
||||
}
|
||||
let message = ecies::encrypt(&self.id, &[], &data)?;
|
||||
self.auth_cipher = message.clone();
|
||||
self.connection.send(io, message);
|
||||
self.connection.expect(V4_ACK_PACKET_SIZE);
|
||||
self.state = HandshakeState::ReadingAck;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends ack message
|
||||
fn write_ack<Message>(&mut self, io: &IoContext<Message>) -> Result<(), Error> where Message: Send + Clone + Sync + 'static {
|
||||
trace!(target: "network", "Sending handshake ack to {:?}", self.connection.remote_addr_str());
|
||||
let mut data = [0u8; 1 + /*Public::SIZE*/ 64 + /*H256::SIZE*/ 32]; //TODO: use associated constants
|
||||
let len = data.len();
|
||||
{
|
||||
data[len - 1] = 0x0;
|
||||
let (epubk, rest) = data.split_at_mut(64);
|
||||
let (nonce, _) = rest.split_at_mut(32);
|
||||
self.ecdhe.public().copy_to(epubk);
|
||||
self.nonce.copy_to(nonce);
|
||||
}
|
||||
let message = ecies::encrypt(&self.id, &[], &data)?;
|
||||
self.ack_cipher = message.clone();
|
||||
self.connection.send(io, message);
|
||||
self.state = HandshakeState::StartSession;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends EIP8 ack message
|
||||
fn write_ack_eip8<Message>(&mut self, io: &IoContext<Message>) -> Result<(), Error> where Message: Send + Clone + Sync + 'static {
|
||||
trace!(target: "network", "Sending EIP8 handshake ack to {:?}", self.connection.remote_addr_str());
|
||||
let mut rlp = RlpStream::new_list(3);
|
||||
rlp.append(self.ecdhe.public());
|
||||
rlp.append(&self.nonce);
|
||||
rlp.append(&PROTOCOL_VERSION);
|
||||
|
||||
let pad_array = [0u8; 200];
|
||||
let pad = &pad_array[0 .. 100 + random::<usize>() % 100];
|
||||
rlp.append_raw(pad, 0);
|
||||
|
||||
let encoded = rlp.drain();
|
||||
let len = (encoded.len() + ECIES_OVERHEAD) as u16;
|
||||
let prefix = [ (len >> 8) as u8, (len & 0xff) as u8 ];
|
||||
let message = ecies::encrypt(&self.id, &prefix, &encoded)?;
|
||||
self.ack_cipher.extend_from_slice(&prefix);
|
||||
self.ack_cipher.extend_from_slice(&message);
|
||||
self.connection.send(io, self.ack_cipher.clone());
|
||||
self.state = HandshakeState::StartSession;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::sync::Arc;
|
||||
use rustc_hex::FromHex;
|
||||
use super::*;
|
||||
use ethereum_types::H256;
|
||||
use io::*;
|
||||
use mio::tcp::TcpStream;
|
||||
use stats::NetworkStats;
|
||||
use ethkey::Public;
|
||||
|
||||
fn check_auth(h: &Handshake, version: u64) {
|
||||
assert_eq!(h.id, "fda1cff674c90c9a197539fe3dfb53086ace64f83ed7c6eabec741f7f381cc803e52ab2cd55d5569bce4347107a310dfd5f88a010cd2ffd1005ca406f1842877".into());
|
||||
assert_eq!(h.remote_nonce, "7e968bba13b6c50e2c4cd7f241cc0d64d1ac25c7f5952df231ac6a2bda8ee5d6".into());
|
||||
assert_eq!(h.remote_ephemeral, "654d1044b69c577a44e5f01a1209523adb4026e70c62d1c13a067acabc09d2667a49821a0ad4b634554d330a15a58fe61f8a8e0544b310c6de7b0c8da7528a8d".into());
|
||||
assert_eq!(h.remote_version, version);
|
||||
}
|
||||
|
||||
fn check_ack(h: &Handshake, version: u64) {
|
||||
assert_eq!(h.remote_nonce, "559aead08264d5795d3909718cdd05abd49572e84fe55590eef31a88a08fdffd".into());
|
||||
assert_eq!(h.remote_ephemeral, "b6d82fa3409da933dbf9cb0140c5dde89f4e64aec88d476af648880f4a10e1e49fe35ef3e69e93dd300b4797765a747c6384a6ecf5db9c2690398607a86181e4".into());
|
||||
assert_eq!(h.remote_version, version);
|
||||
}
|
||||
|
||||
fn create_handshake(to: Option<&Public>) -> Handshake {
|
||||
let addr = "127.0.0.1:50556".parse().unwrap();
|
||||
let socket = TcpStream::connect(&addr).unwrap();
|
||||
let nonce = H256::new();
|
||||
Handshake::new(0, to, socket, &nonce, Arc::new(NetworkStats::new())).unwrap()
|
||||
}
|
||||
|
||||
fn test_io() -> IoContext<i32> {
|
||||
IoContext::new(IoChannel::disconnected(), 0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handshake_auth_plain() {
|
||||
let mut h = create_handshake(None);
|
||||
let secret = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291".parse().unwrap();
|
||||
let auth =
|
||||
"\
|
||||
048ca79ad18e4b0659fab4853fe5bc58eb83992980f4c9cc147d2aa31532efd29a3d3dc6a3d89eaf\
|
||||
913150cfc777ce0ce4af2758bf4810235f6e6ceccfee1acc6b22c005e9e3a49d6448610a58e98744\
|
||||
ba3ac0399e82692d67c1f58849050b3024e21a52c9d3b01d871ff5f210817912773e610443a9ef14\
|
||||
2e91cdba0bd77b5fdf0769b05671fc35f83d83e4d3b0b000c6b2a1b1bba89e0fc51bf4e460df3105\
|
||||
c444f14be226458940d6061c296350937ffd5e3acaceeaaefd3c6f74be8e23e0f45163cc7ebd7622\
|
||||
0f0128410fd05250273156d548a414444ae2f7dea4dfca2d43c057adb701a715bf59f6fb66b2d1d2\
|
||||
0f2c703f851cbf5ac47396d9ca65b6260bd141ac4d53e2de585a73d1750780db4c9ee4cd4d225173\
|
||||
a4592ee77e2bd94d0be3691f3b406f9bba9b591fc63facc016bfa8\
|
||||
".from_hex().unwrap();
|
||||
|
||||
h.read_auth(&test_io(), &secret, &auth).unwrap();
|
||||
assert_eq!(h.state, super::HandshakeState::StartSession);
|
||||
check_auth(&h, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handshake_auth_eip8() {
|
||||
let mut h = create_handshake(None);
|
||||
let secret = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291".parse().unwrap();
|
||||
let auth =
|
||||
"\
|
||||
01b304ab7578555167be8154d5cc456f567d5ba302662433674222360f08d5f1534499d3678b513b\
|
||||
0fca474f3a514b18e75683032eb63fccb16c156dc6eb2c0b1593f0d84ac74f6e475f1b8d56116b84\
|
||||
9634a8c458705bf83a626ea0384d4d7341aae591fae42ce6bd5c850bfe0b999a694a49bbbaf3ef6c\
|
||||
da61110601d3b4c02ab6c30437257a6e0117792631a4b47c1d52fc0f8f89caadeb7d02770bf999cc\
|
||||
147d2df3b62e1ffb2c9d8c125a3984865356266bca11ce7d3a688663a51d82defaa8aad69da39ab6\
|
||||
d5470e81ec5f2a7a47fb865ff7cca21516f9299a07b1bc63ba56c7a1a892112841ca44b6e0034dee\
|
||||
70c9adabc15d76a54f443593fafdc3b27af8059703f88928e199cb122362a4b35f62386da7caad09\
|
||||
c001edaeb5f8a06d2b26fb6cb93c52a9fca51853b68193916982358fe1e5369e249875bb8d0d0ec3\
|
||||
6f917bc5e1eafd5896d46bd61ff23f1a863a8a8dcd54c7b109b771c8e61ec9c8908c733c0263440e\
|
||||
2aa067241aaa433f0bb053c7b31a838504b148f570c0ad62837129e547678c5190341e4f1693956c\
|
||||
3bf7678318e2d5b5340c9e488eefea198576344afbdf66db5f51204a6961a63ce072c8926c\
|
||||
".from_hex().unwrap();
|
||||
|
||||
h.read_auth(&test_io(), &secret, &auth[0..super::V4_AUTH_PACKET_SIZE]).unwrap();
|
||||
assert_eq!(h.state, super::HandshakeState::ReadingAuthEip8);
|
||||
h.read_auth_eip8(&test_io(), &secret, &auth[super::V4_AUTH_PACKET_SIZE..]).unwrap();
|
||||
assert_eq!(h.state, super::HandshakeState::StartSession);
|
||||
check_auth(&h, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handshake_auth_eip8_2() {
|
||||
let mut h = create_handshake(None);
|
||||
let secret = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291".parse().unwrap();
|
||||
let auth =
|
||||
"\
|
||||
01b8044c6c312173685d1edd268aa95e1d495474c6959bcdd10067ba4c9013df9e40ff45f5bfd6f7\
|
||||
2471f93a91b493f8e00abc4b80f682973de715d77ba3a005a242eb859f9a211d93a347fa64b597bf\
|
||||
280a6b88e26299cf263b01b8dfdb712278464fd1c25840b995e84d367d743f66c0e54a586725b7bb\
|
||||
f12acca27170ae3283c1073adda4b6d79f27656993aefccf16e0d0409fe07db2dc398a1b7e8ee93b\
|
||||
cd181485fd332f381d6a050fba4c7641a5112ac1b0b61168d20f01b479e19adf7fdbfa0905f63352\
|
||||
bfc7e23cf3357657455119d879c78d3cf8c8c06375f3f7d4861aa02a122467e069acaf513025ff19\
|
||||
6641f6d2810ce493f51bee9c966b15c5043505350392b57645385a18c78f14669cc4d960446c1757\
|
||||
1b7c5d725021babbcd786957f3d17089c084907bda22c2b2675b4378b114c601d858802a55345a15\
|
||||
116bc61da4193996187ed70d16730e9ae6b3bb8787ebcaea1871d850997ddc08b4f4ea668fbf3740\
|
||||
7ac044b55be0908ecb94d4ed172ece66fd31bfdadf2b97a8bc690163ee11f5b575a4b44e36e2bfb2\
|
||||
f0fce91676fd64c7773bac6a003f481fddd0bae0a1f31aa27504e2a533af4cef3b623f4791b2cca6\
|
||||
d490\
|
||||
".from_hex().unwrap();
|
||||
|
||||
h.read_auth(&test_io(), &secret, &auth[0..super::V4_AUTH_PACKET_SIZE]).unwrap();
|
||||
assert_eq!(h.state, super::HandshakeState::ReadingAuthEip8);
|
||||
h.read_auth_eip8(&test_io(), &secret, &auth[super::V4_AUTH_PACKET_SIZE..]).unwrap();
|
||||
assert_eq!(h.state, super::HandshakeState::StartSession);
|
||||
check_auth(&h, 56);
|
||||
let ack = h.ack_cipher.clone();
|
||||
let total = (((ack[0] as u16) << 8 | (ack[1] as u16)) as usize) + 2;
|
||||
assert_eq!(ack.len(), total);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handshake_ack_plain() {
|
||||
let remote = "fda1cff674c90c9a197539fe3dfb53086ace64f83ed7c6eabec741f7f381cc803e52ab2cd55d5569bce4347107a310dfd5f88a010cd2ffd1005ca406f1842877".into();
|
||||
let mut h = create_handshake(Some(&remote));
|
||||
let secret = "49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee".parse().unwrap();
|
||||
let ack =
|
||||
"\
|
||||
049f8abcfa9c0dc65b982e98af921bc0ba6e4243169348a236abe9df5f93aa69d99cadddaa387662\
|
||||
b0ff2c08e9006d5a11a278b1b3331e5aaabf0a32f01281b6f4ede0e09a2d5f585b26513cb794d963\
|
||||
5a57563921c04a9090b4f14ee42be1a5461049af4ea7a7f49bf4c97a352d39c8d02ee4acc416388c\
|
||||
1c66cec761d2bc1c72da6ba143477f049c9d2dde846c252c111b904f630ac98e51609b3b1f58168d\
|
||||
dca6505b7196532e5f85b259a20c45e1979491683fee108e9660edbf38f3add489ae73e3dda2c71b\
|
||||
d1497113d5c755e942d1\
|
||||
".from_hex().unwrap();
|
||||
|
||||
h.read_ack(&secret, &ack).unwrap();
|
||||
assert_eq!(h.state, super::HandshakeState::StartSession);
|
||||
check_ack(&h, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handshake_ack_eip8() {
|
||||
let remote = "fda1cff674c90c9a197539fe3dfb53086ace64f83ed7c6eabec741f7f381cc803e52ab2cd55d5569bce4347107a310dfd5f88a010cd2ffd1005ca406f1842877".into();
|
||||
let mut h = create_handshake(Some(&remote));
|
||||
let secret = "49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee".parse().unwrap();
|
||||
let ack =
|
||||
"\
|
||||
01ea0451958701280a56482929d3b0757da8f7fbe5286784beead59d95089c217c9b917788989470\
|
||||
b0e330cc6e4fb383c0340ed85fab836ec9fb8a49672712aeabbdfd1e837c1ff4cace34311cd7f4de\
|
||||
05d59279e3524ab26ef753a0095637ac88f2b499b9914b5f64e143eae548a1066e14cd2f4bd7f814\
|
||||
c4652f11b254f8a2d0191e2f5546fae6055694aed14d906df79ad3b407d94692694e259191cde171\
|
||||
ad542fc588fa2b7333313d82a9f887332f1dfc36cea03f831cb9a23fea05b33deb999e85489e645f\
|
||||
6aab1872475d488d7bd6c7c120caf28dbfc5d6833888155ed69d34dbdc39c1f299be1057810f34fb\
|
||||
e754d021bfca14dc989753d61c413d261934e1a9c67ee060a25eefb54e81a4d14baff922180c395d\
|
||||
3f998d70f46f6b58306f969627ae364497e73fc27f6d17ae45a413d322cb8814276be6ddd13b885b\
|
||||
201b943213656cde498fa0e9ddc8e0b8f8a53824fbd82254f3e2c17e8eaea009c38b4aa0a3f306e8\
|
||||
797db43c25d68e86f262e564086f59a2fc60511c42abfb3057c247a8a8fe4fb3ccbadde17514b7ac\
|
||||
8000cdb6a912778426260c47f38919a91f25f4b5ffb455d6aaaf150f7e5529c100ce62d6d92826a7\
|
||||
1778d809bdf60232ae21ce8a437eca8223f45ac37f6487452ce626f549b3b5fdee26afd2072e4bc7\
|
||||
5833c2464c805246155289f4\
|
||||
".from_hex().unwrap();
|
||||
|
||||
h.read_ack(&secret, &ack[0..super::V4_ACK_PACKET_SIZE]).unwrap();
|
||||
assert_eq!(h.state, super::HandshakeState::ReadingAckEip8);
|
||||
h.read_ack_eip8(&secret, &ack[super::V4_ACK_PACKET_SIZE..]).unwrap();
|
||||
assert_eq!(h.state, super::HandshakeState::StartSession);
|
||||
check_ack(&h, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handshake_ack_eip8_2() {
|
||||
let remote = "fda1cff674c90c9a197539fe3dfb53086ace64f83ed7c6eabec741f7f381cc803e52ab2cd55d5569bce4347107a310dfd5f88a010cd2ffd1005ca406f1842877".into();
|
||||
let mut h = create_handshake(Some(&remote));
|
||||
let secret = "49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee".parse().unwrap();
|
||||
let ack =
|
||||
"\
|
||||
01f004076e58aae772bb101ab1a8e64e01ee96e64857ce82b1113817c6cdd52c09d26f7b90981cd7\
|
||||
ae835aeac72e1573b8a0225dd56d157a010846d888dac7464baf53f2ad4e3d584531fa203658fab0\
|
||||
3a06c9fd5e35737e417bc28c1cbf5e5dfc666de7090f69c3b29754725f84f75382891c561040ea1d\
|
||||
dc0d8f381ed1b9d0d4ad2a0ec021421d847820d6fa0ba66eaf58175f1b235e851c7e2124069fbc20\
|
||||
2888ddb3ac4d56bcbd1b9b7eab59e78f2e2d400905050f4a92dec1c4bdf797b3fc9b2f8e84a482f3\
|
||||
d800386186712dae00d5c386ec9387a5e9c9a1aca5a573ca91082c7d68421f388e79127a5177d4f8\
|
||||
590237364fd348c9611fa39f78dcdceee3f390f07991b7b47e1daa3ebcb6ccc9607811cb17ce51f1\
|
||||
c8c2c5098dbdd28fca547b3f58c01a424ac05f869f49c6a34672ea2cbbc558428aa1fe48bbfd6115\
|
||||
8b1b735a65d99f21e70dbc020bfdface9f724a0d1fb5895db971cc81aa7608baa0920abb0a565c9c\
|
||||
436e2fd13323428296c86385f2384e408a31e104670df0791d93e743a3a5194ee6b076fb6323ca59\
|
||||
3011b7348c16cf58f66b9633906ba54a2ee803187344b394f75dd2e663a57b956cb830dd7a908d4f\
|
||||
39a2336a61ef9fda549180d4ccde21514d117b6c6fd07a9102b5efe710a32af4eeacae2cb3b1dec0\
|
||||
35b9593b48b9d3ca4c13d245d5f04169b0b1\
|
||||
".from_hex().unwrap();
|
||||
|
||||
h.read_ack(&secret, &ack[0..super::V4_ACK_PACKET_SIZE]).unwrap();
|
||||
assert_eq!(h.state, super::HandshakeState::ReadingAckEip8);
|
||||
h.read_ack_eip8(&secret, &ack[super::V4_ACK_PACKET_SIZE..]).unwrap();
|
||||
assert_eq!(h.state, super::HandshakeState::StartSession);
|
||||
check_ack(&h, 57);
|
||||
}
|
||||
}
|
||||
|
||||
1148
util/network-devp2p/src/host.rs
Normal file
1148
util/network-devp2p/src/host.rs
Normal file
File diff suppressed because it is too large
Load Diff
537
util/network-devp2p/src/ip_utils.rs
Normal file
537
util/network-devp2p/src/ip_utils.rs
Normal file
@@ -0,0 +1,537 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Based on original work by David Levy https://raw.githubusercontent.com/dlevy47/rust-interfaces
|
||||
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
|
||||
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 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_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_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())
|
||||
}
|
||||
|
||||
// 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_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))]
|
||||
mod getinterfaces {
|
||||
use std::{mem, io, ptr};
|
||||
use libc::{AF_INET, AF_INET6};
|
||||
use libc::{getifaddrs, freeifaddrs, ifaddrs, sockaddr, sockaddr_in, sockaddr_in6};
|
||||
use std::net::{Ipv4Addr, Ipv6Addr, IpAddr};
|
||||
|
||||
fn convert_sockaddr(sa: *mut sockaddr) -> Option<IpAddr> {
|
||||
if sa == ptr::null_mut() { return None; }
|
||||
|
||||
let (addr, _) = match unsafe { *sa }.sa_family as i32 {
|
||||
AF_INET => {
|
||||
let sa: *const sockaddr_in = unsafe { mem::transmute(sa) };
|
||||
let sa = & unsafe { *sa };
|
||||
let (addr, port) = (sa.sin_addr.s_addr, sa.sin_port);
|
||||
(IpAddr::V4(Ipv4Addr::new(
|
||||
(addr & 0x000000FF) as u8,
|
||||
((addr & 0x0000FF00) >> 8) as u8,
|
||||
((addr & 0x00FF0000) >> 16) as u8,
|
||||
((addr & 0xFF000000) >> 24) as u8)),
|
||||
port)
|
||||
},
|
||||
AF_INET6 => {
|
||||
let sa: *const sockaddr_in6 = unsafe { mem::transmute(sa) };
|
||||
let sa = & unsafe { *sa };
|
||||
let (addr, port) = (sa.sin6_addr.s6_addr, sa.sin6_port);
|
||||
let addr: [u16; 8] = unsafe { mem::transmute(addr) };
|
||||
(IpAddr::V6(Ipv6Addr::new(
|
||||
addr[0],
|
||||
addr[1],
|
||||
addr[2],
|
||||
addr[3],
|
||||
addr[4],
|
||||
addr[5],
|
||||
addr[6],
|
||||
addr[7])),
|
||||
port)
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
Some(addr)
|
||||
}
|
||||
|
||||
fn convert_ifaddrs(ifa: *mut ifaddrs) -> Option<IpAddr> {
|
||||
let ifa = unsafe { &mut *ifa };
|
||||
convert_sockaddr(ifa.ifa_addr)
|
||||
}
|
||||
|
||||
pub fn get_all() -> io::Result<Vec<IpAddr>> {
|
||||
let mut ifap: *mut ifaddrs = unsafe { mem::zeroed() };
|
||||
if unsafe { getifaddrs(&mut ifap as *mut _) } != 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
let mut ret = Vec::new();
|
||||
let mut cur: *mut ifaddrs = ifap;
|
||||
while cur != ptr::null_mut() {
|
||||
if let Some(ip_addr) = convert_ifaddrs(cur) {
|
||||
ret.push(ip_addr);
|
||||
}
|
||||
|
||||
//TODO: do something else maybe?
|
||||
cur = unsafe { (*cur).ifa_next };
|
||||
}
|
||||
|
||||
unsafe { freeifaddrs(ifap) };
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn get_if_addrs() -> io::Result<Vec<IpAddr>> {
|
||||
getinterfaces::get_all()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn get_if_addrs() -> io::Result<Vec<IpAddr>> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
/// Select the best available public address
|
||||
pub fn select_public_address(port: u16) -> SocketAddr {
|
||||
match get_if_addrs() {
|
||||
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_reserved() => {
|
||||
return SocketAddr::V4(SocketAddrV4::new(a, port));
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
for addr in &list {
|
||||
match addr {
|
||||
&IpAddr::V6(a) if !a.is_reserved() => {
|
||||
return SocketAddr::V6(SocketAddrV6::new(a, port, 0, 0));
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => debug!("Error listing public interfaces: {:?}", e)
|
||||
}
|
||||
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), port))
|
||||
}
|
||||
|
||||
pub fn map_external_address(local: &NodeEndpoint) -> Option<NodeEndpoint> {
|
||||
if let SocketAddr::V4(ref local_addr) = local.address {
|
||||
match search_gateway_from_timeout(local_addr.ip().clone(), Duration::new(5, 0)) {
|
||||
Err(ref err) => debug!("Gateway search error: {}", err),
|
||||
Ok(gateway) => {
|
||||
match gateway.get_external_ip() {
|
||||
Err(ref err) => {
|
||||
debug!("IP request error: {}", err);
|
||||
},
|
||||
Ok(external_addr) => {
|
||||
match gateway.add_any_port(PortMappingProtocol::TCP, SocketAddrV4::new(local_addr.ip().clone(), local_addr.port()), 0, "Parity Node/TCP") {
|
||||
Err(ref err) => {
|
||||
debug!("Port mapping error: {}", err);
|
||||
},
|
||||
Ok(tcp_port) => {
|
||||
match gateway.add_any_port(PortMappingProtocol::UDP, SocketAddrV4::new(local_addr.ip().clone(), local.udp_port), 0, "Parity Node/UDP") {
|
||||
Err(ref err) => {
|
||||
debug!("Port mapping error: {}", err);
|
||||
},
|
||||
Ok(udp_port) => {
|
||||
return Some(NodeEndpoint { address: SocketAddr::V4(SocketAddrV4::new(external_addr, tcp_port)), udp_port: udp_port });
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_select_public_address() {
|
||||
let pub_address = select_public_address(40477);
|
||||
assert!(pub_address.port() == 40477);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn can_map_external_address_or_fail() {
|
||||
let pub_address = select_public_address(40478);
|
||||
let _ = map_external_address(&NodeEndpoint { address: pub_address, udp_port: 40478 });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ipv4_properties() {
|
||||
fn check(octets: &[u8; 4], unspec: bool, loopback: bool,
|
||||
private: bool, link_local: bool, global: bool,
|
||||
multicast: bool, broadcast: bool, documentation: bool) {
|
||||
let ip = Ipv4Addr::new(octets[0], octets[1], octets[2], octets[3]);
|
||||
assert_eq!(octets, &ip.octets());
|
||||
|
||||
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);
|
||||
assert_eq!(ip.is_global_s(), global);
|
||||
assert_eq!(ip.is_multicast(), multicast);
|
||||
assert_eq!(ip.is_broadcast(), broadcast);
|
||||
assert_eq!(ip.is_documentation(), documentation);
|
||||
}
|
||||
|
||||
// address unspec loopbk privt linloc global multicast brdcast doc
|
||||
check(&[0, 0, 0, 0], true, false, false, false, true, false, false, false);
|
||||
check(&[0, 0, 0, 1], false, false, false, false, true, false, false, false);
|
||||
check(&[1, 0, 0, 0], false, false, false, false, true, false, false, false);
|
||||
check(&[10, 9, 8, 7], false, false, true, false, false, false, false, false);
|
||||
check(&[127, 1, 2, 3], false, true, false, false, false, false, false, false);
|
||||
check(&[172, 31, 254, 253], false, false, true, false, false, false, false, false);
|
||||
check(&[169, 254, 253, 242], false, false, false, true, false, false, false, false);
|
||||
check(&[192, 0, 2, 183], false, false, false, false, false, false, false, true);
|
||||
check(&[192, 1, 2, 183], false, false, false, false, true, false, false, false);
|
||||
check(&[192, 168, 254, 253], false, false, true, false, false, false, false, false);
|
||||
check(&[198, 51, 100, 0], false, false, false, false, false, false, false, true);
|
||||
check(&[203, 0, 113, 0], false, false, false, false, false, false, false, true);
|
||||
check(&[203, 2, 113, 0], false, false, false, false, true, false, false, false);
|
||||
check(&[224, 0, 0, 0], false, false, false, false, true, true, false, false);
|
||||
check(&[239, 255, 255, 255], false, false, false, false, true, true, false, false);
|
||||
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(), unspec);
|
||||
assert_eq!(ip.is_loopback(), loopback);
|
||||
assert_eq!(ip.is_global_s(), global);
|
||||
}
|
||||
|
||||
// unspec loopbk global
|
||||
check("::", true, false, true);
|
||||
check("::1", false, true, false);
|
||||
}
|
||||
|
||||
|
||||
118
util/network-devp2p/src/lib.rs
Normal file
118
util/network-devp2p/src/lib.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Network and general IO module.
|
||||
//!
|
||||
//! Example usage for creating a network service and adding an IO handler:
|
||||
//!
|
||||
//! ```rust
|
||||
//! extern crate ethcore_network as net;
|
||||
//! extern crate ethcore_network_devp2p as devp2p;
|
||||
//! use net::*;
|
||||
//! use devp2p::NetworkService;
|
||||
//! use std::sync::Arc;
|
||||
//!
|
||||
//! struct MyHandler;
|
||||
//!
|
||||
//! impl NetworkProtocolHandler for MyHandler {
|
||||
//! fn initialize(&self, io: &NetworkContext, _host_info: &HostInfo) {
|
||||
//! io.register_timer(0, 1000);
|
||||
//! }
|
||||
//!
|
||||
//! fn read(&self, io: &NetworkContext, peer: &PeerId, packet_id: u8, data: &[u8]) {
|
||||
//! println!("Received {} ({} bytes) from {}", packet_id, data.len(), peer);
|
||||
//! }
|
||||
//!
|
||||
//! fn connected(&self, io: &NetworkContext, peer: &PeerId) {
|
||||
//! println!("Connected {}", peer);
|
||||
//! }
|
||||
//!
|
||||
//! fn disconnected(&self, io: &NetworkContext, peer: &PeerId) {
|
||||
//! println!("Disconnected {}", peer);
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn main () {
|
||||
//! let mut service = NetworkService::new(NetworkConfiguration::new_local(), None).expect("Error creating network service");
|
||||
//! service.start().expect("Error starting service");
|
||||
//! service.register_protocol(Arc::new(MyHandler), *b"myp", 1, &[1u8]);
|
||||
//!
|
||||
//! // Wait for quit condition
|
||||
//! // ...
|
||||
//! // Drop the service
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
//TODO: use Poll from mio
|
||||
#![allow(deprecated)]
|
||||
|
||||
extern crate ethcore_io as io;
|
||||
extern crate ethcore_bytes;
|
||||
extern crate ethereum_types;
|
||||
extern crate parking_lot;
|
||||
extern crate mio;
|
||||
extern crate tiny_keccak;
|
||||
extern crate crypto as rcrypto;
|
||||
extern crate rand;
|
||||
extern crate time;
|
||||
extern crate ansi_term; //TODO: remove this
|
||||
extern crate rustc_hex;
|
||||
extern crate igd;
|
||||
extern crate libc;
|
||||
extern crate slab;
|
||||
extern crate ethkey;
|
||||
extern crate ethcrypto as crypto;
|
||||
extern crate rlp;
|
||||
extern crate bytes;
|
||||
extern crate path;
|
||||
extern crate ethcore_logger;
|
||||
extern crate ethcore_network as network;
|
||||
extern crate ipnetwork;
|
||||
extern crate keccak_hash as hash;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
extern crate snappy;
|
||||
|
||||
#[macro_use]
|
||||
extern crate error_chain;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
#[cfg(test)]
|
||||
extern crate tempdir;
|
||||
|
||||
mod host;
|
||||
mod connection;
|
||||
mod handshake;
|
||||
mod session;
|
||||
mod discovery;
|
||||
mod service;
|
||||
mod node_table;
|
||||
mod stats;
|
||||
mod ip_utils;
|
||||
mod connection_filter;
|
||||
|
||||
pub use service::NetworkService;
|
||||
pub use stats::NetworkStats;
|
||||
pub use connection_filter::{ConnectionFilter, ConnectionDirection};
|
||||
pub use host::NetworkContext;
|
||||
|
||||
pub use io::TimerToken;
|
||||
pub use node_table::{validate_node_url, NodeId};
|
||||
|
||||
const PROTOCOL_VERSION: u32 = 5;
|
||||
565
util/network-devp2p/src/node_table.rs
Normal file
565
util/network-devp2p/src/node_table.rs
Normal file
@@ -0,0 +1,565 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
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::*;
|
||||
use network::{Error, ErrorKind, AllowIP, IpFilter};
|
||||
use discovery::{TableUpdates, NodeEntry};
|
||||
use ip_utils::*;
|
||||
use serde_json;
|
||||
|
||||
/// Node public key
|
||||
pub type NodeId = H512;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// Node address info
|
||||
pub struct NodeEndpoint {
|
||||
/// IP(V4 or V6) address
|
||||
pub address: SocketAddr,
|
||||
/// Conneciton port.
|
||||
pub udp_port: u16
|
||||
}
|
||||
|
||||
impl NodeEndpoint {
|
||||
pub fn udp_address(&self) -> SocketAddr {
|
||||
match self.address {
|
||||
SocketAddr::V4(a) => SocketAddr::V4(SocketAddrV4::new(a.ip().clone(), self.udp_port)),
|
||||
SocketAddr::V6(a) => SocketAddr::V6(SocketAddrV6::new(a.ip().clone(), self.udp_port, a.flowinfo(), a.scope_id())),
|
||||
}
|
||||
}
|
||||
|
||||
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_usable_private(),
|
||||
&AllowIP::Public => self.address.ip().is_usable_public(),
|
||||
&AllowIP::None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_rlp(rlp: &UntrustedRlp) -> Result<Self, DecoderError> {
|
||||
let tcp_port = rlp.val_at::<u16>(2)?;
|
||||
let udp_port = rlp.val_at::<u16>(1)?;
|
||||
let addr_bytes = rlp.at(0)?.data()?;
|
||||
let address = match addr_bytes.len() {
|
||||
4 => Ok(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(addr_bytes[0], addr_bytes[1], addr_bytes[2], addr_bytes[3]), tcp_port))),
|
||||
16 => unsafe {
|
||||
let o: *const u16 = mem::transmute(addr_bytes.as_ptr());
|
||||
let o = slice::from_raw_parts(o, 8);
|
||||
Ok(SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::new(o[0], o[1], o[2], o[3], o[4], o[5], o[6], o[7]), tcp_port, 0, 0)))
|
||||
},
|
||||
_ => Err(DecoderError::RlpInconsistentLengthAndData)
|
||||
}?;
|
||||
Ok(NodeEndpoint { address: address, udp_port: udp_port })
|
||||
}
|
||||
|
||||
pub fn to_rlp(&self, rlp: &mut RlpStream) {
|
||||
match self.address {
|
||||
SocketAddr::V4(a) => {
|
||||
rlp.append(&(&a.ip().octets()[..]));
|
||||
}
|
||||
SocketAddr::V6(a) => unsafe {
|
||||
let o: *const u8 = mem::transmute(a.ip().segments().as_ptr());
|
||||
rlp.append(&slice::from_raw_parts(o, 16));
|
||||
}
|
||||
};
|
||||
rlp.append(&self.udp_port);
|
||||
rlp.append(&self.address.port());
|
||||
}
|
||||
|
||||
pub fn to_rlp_list(&self, rlp: &mut RlpStream) {
|
||||
rlp.begin_list(3);
|
||||
self.to_rlp(rlp);
|
||||
}
|
||||
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.udp_port != 0 && self.address.port() != 0 &&
|
||||
match self.address {
|
||||
SocketAddr::V4(a) => !a.ip().is_unspecified(),
|
||||
SocketAddr::V6(a) => !a.ip().is_unspecified()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for NodeEndpoint {
|
||||
type Err = Error;
|
||||
|
||||
/// Create endpoint from string. Performs name resolution if given a host name.
|
||||
fn from_str(s: &str) -> Result<NodeEndpoint, Error> {
|
||||
let address = s.to_socket_addrs().map(|mut i| i.next());
|
||||
match address {
|
||||
Ok(Some(a)) => Ok(NodeEndpoint {
|
||||
address: a,
|
||||
udp_port: a.port()
|
||||
}),
|
||||
Ok(_) => Err(ErrorKind::AddressResolve(None).into()),
|
||||
Err(e) => Err(ErrorKind::AddressResolve(Some(e)).into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
pub enum PeerType {
|
||||
_Required,
|
||||
Optional
|
||||
}
|
||||
|
||||
pub struct Node {
|
||||
pub id: NodeId,
|
||||
pub endpoint: NodeEndpoint,
|
||||
pub peer_type: PeerType,
|
||||
pub attempts: u32,
|
||||
pub failures: u32,
|
||||
}
|
||||
|
||||
const DEFAULT_FAILURE_PERCENTAGE: usize = 50;
|
||||
|
||||
impl Node {
|
||||
pub fn new(id: NodeId, endpoint: NodeEndpoint) -> Node {
|
||||
Node {
|
||||
id: id,
|
||||
endpoint: endpoint,
|
||||
peer_type: PeerType::Optional,
|
||||
attempts: 0,
|
||||
failures: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the node's failure percentage (0..100) in buckets of 5%. If there are 0 connection attempts for this
|
||||
/// node the default failure percentage is returned (50%).
|
||||
pub fn failure_percentage(&self) -> usize {
|
||||
if self.attempts == 0 {
|
||||
DEFAULT_FAILURE_PERCENTAGE
|
||||
} else {
|
||||
(self.failures * 100 / self.attempts / 5 * 5) as usize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Node {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
if self.endpoint.udp_port != self.endpoint.address.port() {
|
||||
write!(f, "enode://{}@{}+{}", self.id.hex(), self.endpoint.address, self.endpoint.udp_port)?;
|
||||
} else {
|
||||
write!(f, "enode://{}@{}", self.id.hex(), self.endpoint.address)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Node {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let (id, endpoint) = if s.len() > 136 && &s[0..8] == "enode://" && &s[136..137] == "@" {
|
||||
(s[8..136].parse().map_err(|_| ErrorKind::InvalidNodeId)?, NodeEndpoint::from_str(&s[137..])?)
|
||||
}
|
||||
else {
|
||||
(NodeId::new(), NodeEndpoint::from_str(s)?)
|
||||
};
|
||||
|
||||
Ok(Node {
|
||||
id: id,
|
||||
endpoint: endpoint,
|
||||
peer_type: PeerType::Optional,
|
||||
attempts: 0,
|
||||
failures: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Node {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
impl Eq for Node {}
|
||||
|
||||
impl Hash for Node {
|
||||
fn hash<H>(&self, state: &mut H) where H: Hasher {
|
||||
self.id.hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_NODES: usize = 1024;
|
||||
const NODES_FILE: &str = "nodes.json";
|
||||
|
||||
/// Node table backed by disk file.
|
||||
pub struct NodeTable {
|
||||
nodes: HashMap<NodeId, Node>,
|
||||
useless_nodes: HashSet<NodeId>,
|
||||
path: Option<String>,
|
||||
}
|
||||
|
||||
impl NodeTable {
|
||||
pub fn new(path: Option<String>) -> NodeTable {
|
||||
NodeTable {
|
||||
path: path.clone(),
|
||||
nodes: NodeTable::load(path),
|
||||
useless_nodes: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a node to table
|
||||
pub fn add_node(&mut self, mut node: Node) {
|
||||
// preserve attempts and failure counter
|
||||
let (attempts, failures) =
|
||||
self.nodes.get(&node.id).map_or((0, 0), |n| (n.attempts, n.failures));
|
||||
|
||||
node.attempts = attempts;
|
||||
node.failures = failures;
|
||||
|
||||
self.nodes.insert(node.id.clone(), node);
|
||||
}
|
||||
|
||||
/// Returns node ids sorted by failure percentage, for nodes with the same failure percentage the absolute number of
|
||||
/// failures is considered.
|
||||
pub fn nodes(&self, filter: IpFilter) -> Vec<NodeId> {
|
||||
let mut refs: Vec<&Node> = self.nodes.values()
|
||||
.filter(|n| !self.useless_nodes.contains(&n.id))
|
||||
.filter(|n| n.endpoint.is_allowed(&filter))
|
||||
.collect();
|
||||
refs.sort_by(|a, b| {
|
||||
a.failure_percentage().cmp(&b.failure_percentage())
|
||||
.then_with(|| a.failures.cmp(&b.failures))
|
||||
.then_with(|| b.attempts.cmp(&a.attempts)) // we use reverse ordering for number of attempts
|
||||
});
|
||||
refs.into_iter().map(|n| n.id).collect()
|
||||
}
|
||||
|
||||
/// Unordered list of all entries
|
||||
pub fn unordered_entries(&self) -> Vec<NodeEntry> {
|
||||
self.nodes.values().map(|n| NodeEntry {
|
||||
endpoint: n.endpoint.clone(),
|
||||
id: n.id.clone(),
|
||||
}).collect()
|
||||
}
|
||||
|
||||
/// Get particular node
|
||||
pub fn get_mut(&mut self, id: &NodeId) -> Option<&mut Node> {
|
||||
self.nodes.get_mut(id)
|
||||
}
|
||||
|
||||
/// Check if a node exists in the table.
|
||||
pub fn contains(&self, id: &NodeId) -> bool {
|
||||
self.nodes.contains_key(id)
|
||||
}
|
||||
|
||||
/// Apply table changes coming from discovery
|
||||
pub fn update(&mut self, mut update: TableUpdates, reserved: &HashSet<NodeId>) {
|
||||
for (_, node) in update.added.drain() {
|
||||
let entry = self.nodes.entry(node.id.clone()).or_insert_with(|| Node::new(node.id.clone(), node.endpoint.clone()));
|
||||
entry.endpoint = node.endpoint;
|
||||
}
|
||||
for r in update.removed {
|
||||
if !reserved.contains(&r) {
|
||||
self.nodes.remove(&r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Increase failure counte for a node
|
||||
pub fn note_failure(&mut self, id: &NodeId) {
|
||||
if let Some(node) = self.nodes.get_mut(id) {
|
||||
node.failures += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark as useless, no further attempts to connect until next call to `clear_useless`.
|
||||
pub fn mark_as_useless(&mut self, id: &NodeId) {
|
||||
self.useless_nodes.insert(id.clone());
|
||||
}
|
||||
|
||||
/// Atempt to connect to useless nodes again.
|
||||
pub fn clear_useless(&mut self) {
|
||||
self.useless_nodes.clear();
|
||||
}
|
||||
|
||||
/// Save the nodes.json file.
|
||||
pub fn save(&self) {
|
||||
let mut path = match self.path {
|
||||
Some(ref path) => PathBuf::from(path),
|
||||
None => return,
|
||||
};
|
||||
if let Err(e) = fs::create_dir_all(&path) {
|
||||
warn!("Error creating node table directory: {:?}", e);
|
||||
return;
|
||||
}
|
||||
path.push(NODES_FILE);
|
||||
let node_ids = self.nodes(IpFilter::default());
|
||||
let nodes = node_ids.into_iter()
|
||||
.map(|id| self.nodes.get(&id).expect("self.nodes() only returns node IDs from self.nodes"))
|
||||
.take(MAX_NODES)
|
||||
.map(|node| node.clone())
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
let table = json::NodeTable { nodes };
|
||||
|
||||
match fs::File::create(&path) {
|
||||
Ok(file) => {
|
||||
if let Err(e) = serde_json::to_writer_pretty(file, &table) {
|
||||
warn!("Error writing node table file: {:?}", e);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
warn!("Error creating node table file: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn load(path: Option<String>) -> HashMap<NodeId, Node> {
|
||||
let path = match path {
|
||||
Some(path) => PathBuf::from(path).join(NODES_FILE),
|
||||
None => return Default::default(),
|
||||
};
|
||||
|
||||
let file = match fs::File::open(&path) {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
debug!("Error opening node table file: {:?}", e);
|
||||
return Default::default();
|
||||
},
|
||||
};
|
||||
let res: Result<json::NodeTable, _> = serde_json::from_reader(file);
|
||||
match res {
|
||||
Ok(table) => {
|
||||
table.nodes.into_iter()
|
||||
.filter_map(|n| n.into_node())
|
||||
.map(|n| (n.id.clone(), n))
|
||||
.collect()
|
||||
},
|
||||
Err(e) => {
|
||||
warn!("Error reading node table file: {:?}", e);
|
||||
Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for NodeTable {
|
||||
fn drop(&mut self) {
|
||||
self.save();
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if node url is valid
|
||||
pub fn validate_node_url(url: &str) -> Option<Error> {
|
||||
match Node::from_str(url) {
|
||||
Ok(_) => None,
|
||||
Err(e) => Some(e)
|
||||
}
|
||||
}
|
||||
|
||||
mod json {
|
||||
use super::*;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct NodeTable {
|
||||
pub nodes: Vec<Node>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Node {
|
||||
pub url: String,
|
||||
pub attempts: u32,
|
||||
pub failures: u32,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn into_node(self) -> Option<super::Node> {
|
||||
match super::Node::from_str(&self.url) {
|
||||
Ok(mut node) => {
|
||||
node.attempts = self.attempts;
|
||||
node.failures = self.failures;
|
||||
Some(node)
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a super::Node> for Node {
|
||||
fn from(node: &'a super::Node) -> Self {
|
||||
Node {
|
||||
url: format!("{}", node),
|
||||
attempts: node.attempts,
|
||||
failures: node.failures,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::net::{SocketAddr, SocketAddrV4, Ipv4Addr};
|
||||
use ethereum_types::H512;
|
||||
use std::str::FromStr;
|
||||
use tempdir::TempDir;
|
||||
use ipnetwork::IpNetwork;
|
||||
|
||||
#[test]
|
||||
fn endpoint_parse() {
|
||||
let endpoint = NodeEndpoint::from_str("123.99.55.44:7770");
|
||||
assert!(endpoint.is_ok());
|
||||
let v4 = match endpoint.unwrap().address {
|
||||
SocketAddr::V4(v4address) => v4address,
|
||||
_ => panic!("should ve v4 address")
|
||||
};
|
||||
assert_eq!(SocketAddrV4::new(Ipv4Addr::new(123, 99, 55, 44), 7770), v4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn node_parse() {
|
||||
assert!(validate_node_url("enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").is_none());
|
||||
let node = Node::from_str("enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770");
|
||||
assert!(node.is_ok());
|
||||
let node = node.unwrap();
|
||||
let v4 = match node.endpoint.address {
|
||||
SocketAddr::V4(v4address) => v4address,
|
||||
_ => panic!("should ve v4 address")
|
||||
};
|
||||
assert_eq!(SocketAddrV4::new(Ipv4Addr::new(22, 99, 55, 44), 7770), v4);
|
||||
assert_eq!(
|
||||
H512::from_str("a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap(),
|
||||
node.id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_failure_percentage_order() {
|
||||
let node1 = Node::from_str("enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap();
|
||||
let node2 = Node::from_str("enode://b979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap();
|
||||
let node3 = Node::from_str("enode://c979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap();
|
||||
let node4 = Node::from_str("enode://d979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap();
|
||||
let id1 = H512::from_str("a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap();
|
||||
let id2 = H512::from_str("b979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap();
|
||||
let id3 = H512::from_str("c979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap();
|
||||
let id4 = H512::from_str("d979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap();
|
||||
let mut table = NodeTable::new(None);
|
||||
|
||||
table.add_node(node1);
|
||||
table.add_node(node2);
|
||||
table.add_node(node3);
|
||||
table.add_node(node4);
|
||||
|
||||
// node 1 - failure percentage 100%
|
||||
table.get_mut(&id1).unwrap().attempts = 2;
|
||||
table.note_failure(&id1);
|
||||
table.note_failure(&id1);
|
||||
|
||||
// node2 - failure percentage 33%
|
||||
table.get_mut(&id2).unwrap().attempts = 3;
|
||||
table.note_failure(&id2);
|
||||
|
||||
// node3 - failure percentage 0%
|
||||
table.get_mut(&id3).unwrap().attempts = 1;
|
||||
|
||||
// node4 - failure percentage 50% (default when no attempts)
|
||||
|
||||
let r = table.nodes(IpFilter::default());
|
||||
|
||||
assert_eq!(r[0][..], id3[..]);
|
||||
assert_eq!(r[1][..], id2[..]);
|
||||
assert_eq!(r[2][..], id4[..]);
|
||||
assert_eq!(r[3][..], id1[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_save_load() {
|
||||
let tempdir = TempDir::new("").unwrap();
|
||||
let node1 = Node::from_str("enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap();
|
||||
let node2 = Node::from_str("enode://b979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@22.99.55.44:7770").unwrap();
|
||||
let id1 = H512::from_str("a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap();
|
||||
let id2 = H512::from_str("b979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c").unwrap();
|
||||
{
|
||||
let mut table = NodeTable::new(Some(tempdir.path().to_str().unwrap().to_owned()));
|
||||
table.add_node(node1);
|
||||
table.add_node(node2);
|
||||
|
||||
table.get_mut(&id1).unwrap().attempts = 1;
|
||||
table.get_mut(&id2).unwrap().attempts = 1;
|
||||
table.note_failure(&id2);
|
||||
}
|
||||
|
||||
{
|
||||
let table = NodeTable::new(Some(tempdir.path().to_str().unwrap().to_owned()));
|
||||
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));
|
||||
}
|
||||
}
|
||||
192
util/network-devp2p/src/service.rs
Normal file
192
util/network-devp2p/src/service.rs
Normal file
@@ -0,0 +1,192 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use network::{Error, NetworkConfiguration, NetworkProtocolHandler, NonReservedPeerMode};
|
||||
use network::{NetworkContext, PeerId, ProtocolId, NetworkIoMessage};
|
||||
use host::Host;
|
||||
use stats::NetworkStats;
|
||||
use io::*;
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
use ansi_term::Colour;
|
||||
use connection_filter::ConnectionFilter;
|
||||
|
||||
struct HostHandler {
|
||||
public_url: RwLock<Option<String>>
|
||||
}
|
||||
|
||||
impl IoHandler<NetworkIoMessage> for HostHandler {
|
||||
fn message(&self, _io: &IoContext<NetworkIoMessage>, message: &NetworkIoMessage) {
|
||||
if let NetworkIoMessage::NetworkStarted(ref public_url) = *message {
|
||||
let mut url = self.public_url.write();
|
||||
if url.as_ref().map_or(true, |uref| uref != public_url) {
|
||||
info!(target: "network", "Public node URL: {}", Colour::White.bold().paint(public_url.as_ref()));
|
||||
}
|
||||
*url = Some(public_url.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// IO Service with networking
|
||||
/// `Message` defines a notification data type.
|
||||
pub struct NetworkService {
|
||||
io_service: IoService<NetworkIoMessage>,
|
||||
host_info: String,
|
||||
host: RwLock<Option<Arc<Host>>>,
|
||||
stats: Arc<NetworkStats>,
|
||||
host_handler: Arc<HostHandler>,
|
||||
config: NetworkConfiguration,
|
||||
filter: Option<Arc<ConnectionFilter>>,
|
||||
}
|
||||
|
||||
impl NetworkService {
|
||||
/// Starts IO event loop
|
||||
pub fn new(config: NetworkConfiguration, filter: Option<Arc<ConnectionFilter>>) -> Result<NetworkService, Error> {
|
||||
let host_handler = Arc::new(HostHandler { public_url: RwLock::new(None) });
|
||||
let io_service = IoService::<NetworkIoMessage>::start()?;
|
||||
|
||||
let stats = Arc::new(NetworkStats::new());
|
||||
Ok(NetworkService {
|
||||
io_service: io_service,
|
||||
host_info: config.client_version.clone(),
|
||||
stats: stats,
|
||||
host: RwLock::new(None),
|
||||
config: config,
|
||||
host_handler: host_handler,
|
||||
filter: filter,
|
||||
})
|
||||
}
|
||||
|
||||
/// Regiter a new protocol handler with the event loop.
|
||||
pub fn register_protocol(&self, handler: Arc<NetworkProtocolHandler + Send + Sync>, protocol: ProtocolId, packet_count: u8, versions: &[u8]) -> Result<(), Error> {
|
||||
self.io_service.send_message(NetworkIoMessage::AddHandler {
|
||||
handler: handler,
|
||||
protocol: protocol,
|
||||
versions: versions.to_vec(),
|
||||
packet_count: packet_count,
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns host identifier string as advertised to other peers
|
||||
pub fn host_info(&self) -> String {
|
||||
self.host_info.clone()
|
||||
}
|
||||
|
||||
/// Returns underlying io service.
|
||||
pub fn io(&self) -> &IoService<NetworkIoMessage> {
|
||||
&self.io_service
|
||||
}
|
||||
|
||||
/// Returns network statistics.
|
||||
pub fn stats(&self) -> &NetworkStats {
|
||||
&self.stats
|
||||
}
|
||||
|
||||
/// Returns network configuration.
|
||||
pub fn config(&self) -> &NetworkConfiguration {
|
||||
&self.config
|
||||
}
|
||||
|
||||
/// Returns external url if available.
|
||||
pub fn external_url(&self) -> Option<String> {
|
||||
let host = self.host.read();
|
||||
host.as_ref().and_then(|h| h.external_url())
|
||||
}
|
||||
|
||||
/// Returns external url if available.
|
||||
pub fn local_url(&self) -> Option<String> {
|
||||
let host = self.host.read();
|
||||
host.as_ref().map(|h| h.local_url())
|
||||
}
|
||||
|
||||
/// Start network IO
|
||||
pub fn start(&self) -> Result<(), Error> {
|
||||
let mut host = self.host.write();
|
||||
if host.is_none() {
|
||||
let h = Arc::new(Host::new(self.config.clone(), self.stats.clone(), self.filter.clone())?);
|
||||
self.io_service.register_handler(h.clone())?;
|
||||
*host = Some(h);
|
||||
}
|
||||
|
||||
if self.host_handler.public_url.read().is_none() {
|
||||
self.io_service.register_handler(self.host_handler.clone())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stop network IO
|
||||
pub fn stop(&self) -> Result<(), Error> {
|
||||
let mut host = self.host.write();
|
||||
if let Some(ref host) = *host {
|
||||
let io = IoContext::new(self.io_service.channel(), 0); //TODO: take token id from host
|
||||
host.stop(&io)?;
|
||||
}
|
||||
*host = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a list of all connected peers by id.
|
||||
pub fn connected_peers(&self) -> Vec<PeerId> {
|
||||
self.host.read().as_ref().map(|h| h.connected_peers()).unwrap_or_else(Vec::new)
|
||||
}
|
||||
|
||||
/// Try to add a reserved peer.
|
||||
pub fn add_reserved_peer(&self, peer: &str) -> Result<(), Error> {
|
||||
let host = self.host.read();
|
||||
if let Some(ref host) = *host {
|
||||
host.add_reserved_node(peer)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to remove a reserved peer.
|
||||
pub fn remove_reserved_peer(&self, peer: &str) -> Result<(), Error> {
|
||||
let host = self.host.read();
|
||||
if let Some(ref host) = *host {
|
||||
host.remove_reserved_node(peer)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the non-reserved peer mode.
|
||||
pub fn set_non_reserved_mode(&self, mode: NonReservedPeerMode) {
|
||||
let host = self.host.read();
|
||||
if let Some(ref host) = *host {
|
||||
let io_ctxt = IoContext::new(self.io_service.channel(), 0);
|
||||
host.set_non_reserved_mode(mode, &io_ctxt);
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes action in the network context
|
||||
pub fn with_context<F>(&self, protocol: ProtocolId, action: F) where F: FnOnce(&NetworkContext) {
|
||||
let io = IoContext::new(self.io_service.channel(), 0);
|
||||
let host = self.host.read();
|
||||
if let Some(ref host) = host.as_ref() {
|
||||
host.with_context(protocol, &io, action);
|
||||
};
|
||||
}
|
||||
|
||||
/// Evaluates function in the network context
|
||||
pub fn with_context_eval<F, T>(&self, protocol: ProtocolId, action: F) -> Option<T> where F: FnOnce(&NetworkContext) -> T {
|
||||
let io = IoContext::new(self.io_service.channel(), 0);
|
||||
let host = self.host.read();
|
||||
host.as_ref().map(|ref host| host.with_context_eval(protocol, &io, action))
|
||||
}
|
||||
}
|
||||
520
util/network-devp2p/src/session.rs
Normal file
520
util/network-devp2p/src/session.rs
Normal file
@@ -0,0 +1,520 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{str, io};
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use mio::*;
|
||||
use mio::deprecated::{Handler, EventLoop};
|
||||
use mio::tcp::*;
|
||||
use ethereum_types::H256;
|
||||
use rlp::*;
|
||||
use connection::{EncryptedConnection, Packet, Connection, MAX_PAYLOAD_SIZE};
|
||||
use handshake::Handshake;
|
||||
use io::{IoContext, StreamToken};
|
||||
use network::{Error, ErrorKind, DisconnectReason, SessionInfo, ProtocolId, PeerCapabilityInfo};
|
||||
use network::{SessionCapabilityInfo, HostInfo as HostInfoTrait};
|
||||
use host::*;
|
||||
use node_table::NodeId;
|
||||
use stats::NetworkStats;
|
||||
use time;
|
||||
use snappy;
|
||||
|
||||
// Timeout must be less than (interval - 1).
|
||||
const PING_TIMEOUT_SEC: u64 = 60;
|
||||
const PING_INTERVAL_SEC: u64 = 120;
|
||||
const MIN_PROTOCOL_VERSION: u32 = 4;
|
||||
const MIN_COMPRESSION_PROTOCOL_VERSION: u32 = 5;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum ProtocolState {
|
||||
// Packets pending protocol on_connect event return.
|
||||
Pending(Vec<(Vec<u8>, u8)>),
|
||||
// Protocol connected.
|
||||
Connected,
|
||||
}
|
||||
|
||||
/// Peer session over encrypted connection.
|
||||
/// When created waits for Hello packet exchange and signals ready state.
|
||||
/// Sends and receives protocol packets and handles basic packes such as ping/pong and disconnect.
|
||||
pub struct Session {
|
||||
/// Shared session information
|
||||
pub info: SessionInfo,
|
||||
/// Session ready flag. Set after successfull Hello packet exchange
|
||||
had_hello: bool,
|
||||
/// Session is no longer active flag.
|
||||
expired: bool,
|
||||
ping_time_ns: u64,
|
||||
pong_time_ns: Option<u64>,
|
||||
state: State,
|
||||
// Protocol states -- accumulates pending packets until signaled as ready.
|
||||
protocol_states: HashMap<ProtocolId, ProtocolState>,
|
||||
compression: bool,
|
||||
}
|
||||
|
||||
enum State {
|
||||
Handshake(Handshake),
|
||||
Session(EncryptedConnection),
|
||||
}
|
||||
|
||||
/// Structure used to report various session events.
|
||||
pub enum SessionData {
|
||||
None,
|
||||
/// Session is ready to send/receive packets.
|
||||
Ready,
|
||||
/// A packet has been received
|
||||
Packet {
|
||||
/// Packet data
|
||||
data: Vec<u8>,
|
||||
/// Packet protocol ID
|
||||
protocol: [u8; 3],
|
||||
/// Zero based packet ID
|
||||
packet_id: u8,
|
||||
},
|
||||
/// Session has more data to be read
|
||||
Continue,
|
||||
}
|
||||
|
||||
const PACKET_HELLO: u8 = 0x80;
|
||||
const PACKET_DISCONNECT: u8 = 0x01;
|
||||
const PACKET_PING: u8 = 0x02;
|
||||
const PACKET_PONG: u8 = 0x03;
|
||||
const PACKET_GET_PEERS: u8 = 0x04;
|
||||
const PACKET_PEERS: u8 = 0x05;
|
||||
const PACKET_USER: u8 = 0x10;
|
||||
const PACKET_LAST: u8 = 0x7f;
|
||||
|
||||
impl Session {
|
||||
/// Create a new session out of comepleted handshake. This clones the handshake connection object
|
||||
/// and leaves the handhsake in limbo to be deregistered from the event loop.
|
||||
pub fn new<Message>(io: &IoContext<Message>, socket: TcpStream, token: StreamToken, id: Option<&NodeId>,
|
||||
nonce: &H256, stats: Arc<NetworkStats>, host: &HostInfo) -> Result<Session, Error>
|
||||
where Message: Send + Clone + Sync + 'static {
|
||||
let originated = id.is_some();
|
||||
let mut handshake = Handshake::new(token, id, socket, nonce, stats).expect("Can't create handshake");
|
||||
let local_addr = handshake.connection.local_addr_str();
|
||||
handshake.start(io, host, originated)?;
|
||||
Ok(Session {
|
||||
state: State::Handshake(handshake),
|
||||
had_hello: false,
|
||||
info: SessionInfo {
|
||||
id: id.cloned(),
|
||||
client_version: String::new(),
|
||||
protocol_version: 0,
|
||||
capabilities: Vec::new(),
|
||||
peer_capabilities: Vec::new(),
|
||||
ping_ms: None,
|
||||
originated: originated,
|
||||
remote_address: "Handshake".to_owned(),
|
||||
local_address: local_addr,
|
||||
},
|
||||
ping_time_ns: 0,
|
||||
pong_time_ns: None,
|
||||
expired: false,
|
||||
protocol_states: HashMap::new(),
|
||||
compression: false,
|
||||
})
|
||||
}
|
||||
|
||||
fn complete_handshake<Message>(&mut self, io: &IoContext<Message>, host: &HostInfo) -> Result<(), Error> where Message: Send + Sync + Clone {
|
||||
let connection = if let State::Handshake(ref mut h) = self.state {
|
||||
self.info.id = Some(h.id.clone());
|
||||
self.info.remote_address = h.connection.remote_addr_str();
|
||||
EncryptedConnection::new(h)?
|
||||
} else {
|
||||
panic!("Unexpected state");
|
||||
};
|
||||
self.state = State::Session(connection);
|
||||
self.write_hello(io, host)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn connection(&self) -> &Connection {
|
||||
match self.state {
|
||||
State::Handshake(ref h) => &h.connection,
|
||||
State::Session(ref s) => &s.connection,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get id of the remote peer
|
||||
pub fn id(&self) -> Option<&NodeId> {
|
||||
self.info.id.as_ref()
|
||||
}
|
||||
|
||||
/// Check if session is ready to send/receive data
|
||||
pub fn is_ready(&self) -> bool {
|
||||
self.had_hello
|
||||
}
|
||||
|
||||
/// Mark this session as inactive to be deleted lated.
|
||||
pub fn set_expired(&mut self) {
|
||||
self.expired = true;
|
||||
}
|
||||
|
||||
/// Check if this session is expired.
|
||||
pub fn expired(&self) -> bool {
|
||||
match self.state {
|
||||
State::Handshake(ref h) => self.expired || h.expired(),
|
||||
_ => self.expired,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if this session is over and there is nothing to be sent.
|
||||
pub fn done(&self) -> bool {
|
||||
self.expired() && !self.connection().is_sending()
|
||||
}
|
||||
|
||||
/// Get remote peer address
|
||||
pub fn remote_addr(&self) -> io::Result<SocketAddr> {
|
||||
self.connection().remote_addr()
|
||||
}
|
||||
|
||||
/// Readable IO handler. Returns packet data if available.
|
||||
pub fn readable<Message>(&mut self, io: &IoContext<Message>, host: &HostInfo) -> Result<SessionData, Error> where Message: Send + Sync + Clone {
|
||||
if self.expired() {
|
||||
return Ok(SessionData::None)
|
||||
}
|
||||
let mut create_session = false;
|
||||
let mut packet_data = None;
|
||||
match self.state {
|
||||
State::Handshake(ref mut h) => {
|
||||
h.readable(io, host)?;
|
||||
if h.done() {
|
||||
create_session = true;
|
||||
}
|
||||
}
|
||||
State::Session(ref mut c) => {
|
||||
match c.readable(io)? {
|
||||
data @ Some(_) => packet_data = data,
|
||||
None => return Ok(SessionData::None)
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(data) = packet_data {
|
||||
return Ok(self.read_packet(io, data, host)?);
|
||||
}
|
||||
if create_session {
|
||||
self.complete_handshake(io, host)?;
|
||||
io.update_registration(self.token()).unwrap_or_else(|e| debug!(target: "network", "Token registration error: {:?}", e));
|
||||
}
|
||||
Ok(SessionData::None)
|
||||
}
|
||||
|
||||
/// Writable IO handler. Sends pending packets.
|
||||
pub fn writable<Message>(&mut self, io: &IoContext<Message>, _host: &HostInfo) -> Result<(), Error> where Message: Send + Sync + Clone {
|
||||
match self.state {
|
||||
State::Handshake(ref mut h) => h.writable(io),
|
||||
State::Session(ref mut s) => s.writable(io),
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if peer supports given capability
|
||||
pub fn have_capability(&self, protocol: [u8; 3]) -> bool {
|
||||
self.info.capabilities.iter().any(|c| c.protocol == protocol)
|
||||
}
|
||||
|
||||
/// Checks if peer supports given capability
|
||||
pub fn capability_version(&self, protocol: [u8; 3]) -> Option<u8> {
|
||||
self.info.capabilities.iter().filter_map(|c| if c.protocol == protocol { Some(c.version) } else { None }).max()
|
||||
}
|
||||
|
||||
/// Register the session socket with the event loop
|
||||
pub fn register_socket<Host:Handler<Timeout = Token>>(&self, reg: Token, event_loop: &mut EventLoop<Host>) -> Result<(), Error> {
|
||||
if self.expired() {
|
||||
return Ok(());
|
||||
}
|
||||
self.connection().register_socket(reg, event_loop)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update registration with the event loop. Should be called at the end of the IO handler.
|
||||
pub fn update_socket<Host:Handler>(&self, reg:Token, event_loop: &mut EventLoop<Host>) -> Result<(), Error> {
|
||||
self.connection().update_socket(reg, event_loop)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete registration
|
||||
pub fn deregister_socket<Host:Handler>(&self, event_loop: &mut EventLoop<Host>) -> Result<(), Error> {
|
||||
self.connection().deregister_socket(event_loop)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a protocol packet to peer.
|
||||
pub fn send_packet<Message>(&mut self, io: &IoContext<Message>, protocol: Option<[u8; 3]>, packet_id: u8, data: &[u8]) -> Result<(), Error>
|
||||
where Message: Send + Sync + Clone {
|
||||
if protocol.is_some() && (self.info.capabilities.is_empty() || !self.had_hello) {
|
||||
debug!(target: "network", "Sending to unconfirmed session {}, protocol: {:?}, packet: {}", self.token(), protocol.as_ref().map(|p| str::from_utf8(&p[..]).unwrap_or("??")), packet_id);
|
||||
bail!(ErrorKind::BadProtocol);
|
||||
}
|
||||
if self.expired() {
|
||||
return Err(ErrorKind::Expired.into());
|
||||
}
|
||||
let mut i = 0usize;
|
||||
let pid = match protocol {
|
||||
Some(protocol) => {
|
||||
while protocol != self.info.capabilities[i].protocol {
|
||||
i += 1;
|
||||
if i == self.info.capabilities.len() {
|
||||
debug!(target: "network", "Unknown protocol: {:?}", protocol);
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
self.info.capabilities[i].id_offset + packet_id
|
||||
},
|
||||
None => packet_id
|
||||
};
|
||||
let mut rlp = RlpStream::new();
|
||||
rlp.append(&(pid as u32));
|
||||
let mut compressed = Vec::new();
|
||||
let mut payload = data; // create a reference with local lifetime
|
||||
if self.compression {
|
||||
if payload.len() > MAX_PAYLOAD_SIZE {
|
||||
bail!(ErrorKind::OversizedPacket);
|
||||
}
|
||||
let len = snappy::compress_into(&payload, &mut compressed);
|
||||
trace!(target: "network", "compressed {} to {}", payload.len(), len);
|
||||
payload = &compressed[0..len];
|
||||
}
|
||||
rlp.append_raw(payload, 1);
|
||||
self.send(io, &rlp.drain())
|
||||
}
|
||||
|
||||
/// Keep this session alive. Returns false if ping timeout happened
|
||||
pub fn keep_alive<Message>(&mut self, io: &IoContext<Message>) -> bool where Message: Send + Sync + Clone {
|
||||
if let State::Handshake(_) = self.state {
|
||||
return true;
|
||||
}
|
||||
let timed_out = if let Some(pong) = self.pong_time_ns {
|
||||
pong - self.ping_time_ns > PING_TIMEOUT_SEC * 1000_000_000
|
||||
} else {
|
||||
time::precise_time_ns() - self.ping_time_ns > PING_TIMEOUT_SEC * 1000_000_000
|
||||
};
|
||||
|
||||
if !timed_out && time::precise_time_ns() - self.ping_time_ns > PING_INTERVAL_SEC * 1000_000_000 {
|
||||
if let Err(e) = self.send_ping(io) {
|
||||
debug!("Error sending ping message: {:?}", e);
|
||||
}
|
||||
}
|
||||
!timed_out
|
||||
}
|
||||
|
||||
pub fn token(&self) -> StreamToken {
|
||||
self.connection().token()
|
||||
}
|
||||
|
||||
/// Signal that a subprotocol has handled the connection successfully and
|
||||
/// get all pending packets in order received.
|
||||
pub fn mark_connected(&mut self, protocol: ProtocolId) -> Vec<(ProtocolId, u8, Vec<u8>)> {
|
||||
match self.protocol_states.insert(protocol, ProtocolState::Connected) {
|
||||
None => Vec::new(),
|
||||
Some(ProtocolState::Connected) => {
|
||||
debug!(target: "network", "Protocol {:?} marked as connected more than once", protocol);
|
||||
Vec::new()
|
||||
}
|
||||
Some(ProtocolState::Pending(pending)) =>
|
||||
pending.into_iter().map(|(data, id)| (protocol, id, data)).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_packet<Message>(&mut self, io: &IoContext<Message>, packet: Packet, host: &HostInfo) -> Result<SessionData, Error>
|
||||
where Message: Send + Sync + Clone {
|
||||
if packet.data.len() < 2 {
|
||||
return Err(ErrorKind::BadProtocol.into());
|
||||
}
|
||||
let packet_id = packet.data[0];
|
||||
if packet_id != PACKET_HELLO && packet_id != PACKET_DISCONNECT && !self.had_hello {
|
||||
return Err(ErrorKind::BadProtocol.into());
|
||||
}
|
||||
let data = if self.compression {
|
||||
let compressed = &packet.data[1..];
|
||||
if snappy::decompressed_len(&compressed)? > MAX_PAYLOAD_SIZE {
|
||||
bail!(ErrorKind::OversizedPacket);
|
||||
}
|
||||
snappy::decompress(&compressed)?
|
||||
} else {
|
||||
packet.data[1..].to_owned()
|
||||
};
|
||||
match packet_id {
|
||||
PACKET_HELLO => {
|
||||
let rlp = UntrustedRlp::new(&data); //TODO: validate rlp expected size
|
||||
self.read_hello(io, &rlp, host)?;
|
||||
Ok(SessionData::Ready)
|
||||
},
|
||||
PACKET_DISCONNECT => {
|
||||
let rlp = UntrustedRlp::new(&data);
|
||||
let reason: u8 = rlp.val_at(0)?;
|
||||
if self.had_hello {
|
||||
debug!(target:"network", "Disconnected: {}: {:?}", self.token(), DisconnectReason::from_u8(reason));
|
||||
}
|
||||
Err(ErrorKind::Disconnect(DisconnectReason::from_u8(reason)).into())
|
||||
}
|
||||
PACKET_PING => {
|
||||
self.send_pong(io)?;
|
||||
Ok(SessionData::Continue)
|
||||
},
|
||||
PACKET_PONG => {
|
||||
let time = time::precise_time_ns();
|
||||
self.pong_time_ns = Some(time);
|
||||
self.info.ping_ms = Some((time - self.ping_time_ns) / 1000_000);
|
||||
Ok(SessionData::Continue)
|
||||
},
|
||||
PACKET_GET_PEERS => Ok(SessionData::None), //TODO;
|
||||
PACKET_PEERS => Ok(SessionData::None),
|
||||
PACKET_USER ... PACKET_LAST => {
|
||||
let mut i = 0usize;
|
||||
while packet_id >= self.info.capabilities[i].id_offset + self.info.capabilities[i].packet_count {
|
||||
i += 1;
|
||||
if i == self.info.capabilities.len() {
|
||||
debug!(target: "network", "Unknown packet: {:?}", packet_id);
|
||||
return Ok(SessionData::Continue)
|
||||
}
|
||||
}
|
||||
|
||||
// map to protocol
|
||||
let protocol = self.info.capabilities[i].protocol;
|
||||
let protocol_packet_id = packet_id - self.info.capabilities[i].id_offset;
|
||||
|
||||
match *self.protocol_states.entry(protocol).or_insert_with(|| ProtocolState::Pending(Vec::new())) {
|
||||
ProtocolState::Connected => {
|
||||
trace!(target: "network", "Packet {} mapped to {:?}:{}, i={}, capabilities={:?}", packet_id, protocol, protocol_packet_id, i, self.info.capabilities);
|
||||
Ok(SessionData::Packet { data: data, protocol: protocol, packet_id: protocol_packet_id } )
|
||||
}
|
||||
ProtocolState::Pending(ref mut pending) => {
|
||||
trace!(target: "network", "Packet {} deferred until protocol connection event completion", packet_id);
|
||||
pending.push((data, protocol_packet_id));
|
||||
|
||||
Ok(SessionData::Continue)
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
debug!(target: "network", "Unknown packet: {:?}", packet_id);
|
||||
Ok(SessionData::Continue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_hello<Message>(&mut self, io: &IoContext<Message>, host: &HostInfo) -> Result<(), Error> where Message: Send + Sync + Clone {
|
||||
let mut rlp = RlpStream::new();
|
||||
rlp.append_raw(&[PACKET_HELLO as u8], 0);
|
||||
rlp.begin_list(5)
|
||||
.append(&host.protocol_version)
|
||||
.append(&host.client_version())
|
||||
.append_list(&host.capabilities)
|
||||
.append(&host.local_endpoint.address.port())
|
||||
.append(host.id());
|
||||
self.send(io, &rlp.drain())
|
||||
}
|
||||
|
||||
fn read_hello<Message>(&mut self, io: &IoContext<Message>, rlp: &UntrustedRlp, host: &HostInfo) -> Result<(), Error>
|
||||
where Message: Send + Sync + Clone {
|
||||
let protocol = rlp.val_at::<u32>(0)?;
|
||||
let client_version = rlp.val_at::<String>(1)?;
|
||||
let peer_caps: Vec<PeerCapabilityInfo> = rlp.list_at(2)?;
|
||||
let id = rlp.val_at::<NodeId>(4)?;
|
||||
|
||||
// Intersect with host capabilities
|
||||
// Leave only highset mutually supported capability version
|
||||
let mut caps: Vec<SessionCapabilityInfo> = Vec::new();
|
||||
for hc in &host.capabilities {
|
||||
if peer_caps.iter().any(|c| c.protocol == hc.protocol && c.version == hc.version) {
|
||||
caps.push(SessionCapabilityInfo {
|
||||
protocol: hc.protocol,
|
||||
version: hc.version,
|
||||
id_offset: 0,
|
||||
packet_count: hc.packet_count,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
caps.retain(|c| host.capabilities.iter().any(|hc| hc.protocol == c.protocol && hc.version == c.version));
|
||||
let mut i = 0;
|
||||
while i < caps.len() {
|
||||
if caps.iter().any(|c| c.protocol == caps[i].protocol && c.version > caps[i].version) {
|
||||
caps.remove(i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort capabilities alphabeticaly.
|
||||
caps.sort();
|
||||
|
||||
i = 0;
|
||||
let mut offset: u8 = PACKET_USER;
|
||||
while i < caps.len() {
|
||||
caps[i].id_offset = offset;
|
||||
offset += caps[i].packet_count;
|
||||
i += 1;
|
||||
}
|
||||
debug!(target: "network", "Hello: {} v{} {} {:?}", client_version, protocol, id, caps);
|
||||
let protocol = ::std::cmp::min(protocol, host.protocol_version);
|
||||
self.info.protocol_version = protocol;
|
||||
self.info.client_version = client_version;
|
||||
self.info.capabilities = caps;
|
||||
self.info.peer_capabilities = peer_caps;
|
||||
if self.info.capabilities.is_empty() {
|
||||
trace!(target: "network", "No common capabilities with peer.");
|
||||
return Err(From::from(self.disconnect(io, DisconnectReason::UselessPeer)));
|
||||
}
|
||||
if protocol < MIN_PROTOCOL_VERSION {
|
||||
trace!(target: "network", "Peer protocol version mismatch: {}", protocol);
|
||||
return Err(From::from(self.disconnect(io, DisconnectReason::UselessPeer)));
|
||||
}
|
||||
self.compression = protocol >= MIN_COMPRESSION_PROTOCOL_VERSION;
|
||||
self.send_ping(io)?;
|
||||
self.had_hello = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Senf ping packet
|
||||
pub fn send_ping<Message>(&mut self, io: &IoContext<Message>) -> Result<(), Error> where Message: Send + Sync + Clone {
|
||||
self.send_packet(io, None, PACKET_PING, &EMPTY_LIST_RLP)?;
|
||||
self.ping_time_ns = time::precise_time_ns();
|
||||
self.pong_time_ns = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_pong<Message>(&mut self, io: &IoContext<Message>) -> Result<(), Error> where Message: Send + Sync + Clone {
|
||||
self.send_packet(io, None, PACKET_PONG, &EMPTY_LIST_RLP)
|
||||
}
|
||||
|
||||
/// Disconnect this session
|
||||
pub fn disconnect<Message>(&mut self, io: &IoContext<Message>, reason: DisconnectReason) -> Error where Message: Send + Sync + Clone {
|
||||
if let State::Session(_) = self.state {
|
||||
let mut rlp = RlpStream::new();
|
||||
rlp.begin_list(1);
|
||||
rlp.append(&(reason as u32));
|
||||
self.send_packet(io, None, PACKET_DISCONNECT, &rlp.drain()).ok();
|
||||
}
|
||||
ErrorKind::Disconnect(reason).into()
|
||||
}
|
||||
|
||||
fn send<Message>(&mut self, io: &IoContext<Message>, data: &[u8]) -> Result<(), Error> where Message: Send + Sync + Clone {
|
||||
match self.state {
|
||||
State::Handshake(_) => {
|
||||
warn!(target:"network", "Unexpected send request");
|
||||
},
|
||||
State::Session(ref mut s) => {
|
||||
s.send_packet(io, data)?
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
76
util/network-devp2p/src/stats.rs
Normal file
76
util/network-devp2p/src/stats.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Network Statistics
|
||||
use std::sync::atomic::*;
|
||||
|
||||
/// Network statistics structure
|
||||
#[derive(Default, Debug)]
|
||||
pub struct NetworkStats {
|
||||
/// Bytes received
|
||||
recv: AtomicUsize,
|
||||
/// Bytes sent
|
||||
send: AtomicUsize,
|
||||
/// Total number of sessions created
|
||||
sessions: AtomicUsize,
|
||||
}
|
||||
|
||||
impl NetworkStats {
|
||||
/// Increase bytes received.
|
||||
#[inline]
|
||||
pub fn inc_recv(&self, size: usize) {
|
||||
self.recv.fetch_add(size, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Increase bytes sent.
|
||||
#[inline]
|
||||
pub fn inc_send(&self, size: usize) {
|
||||
self.send.fetch_add(size, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Increase number of sessions.
|
||||
#[inline]
|
||||
pub fn inc_sessions(&self) {
|
||||
self.sessions.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Get bytes sent.
|
||||
#[inline]
|
||||
pub fn send(&self) -> usize {
|
||||
self.send.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Get bytes received.
|
||||
#[inline]
|
||||
pub fn recv(&self) -> usize {
|
||||
self.recv.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Get total number of sessions created.
|
||||
#[inline]
|
||||
pub fn sessions(&self) -> usize {
|
||||
self.sessions.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Create a new empty instance.
|
||||
pub fn new() -> NetworkStats {
|
||||
NetworkStats {
|
||||
recv: AtomicUsize::new(0),
|
||||
send: AtomicUsize::new(0),
|
||||
sessions: AtomicUsize::new(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user