// Copyright 2018 The Grin Developers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use std::collections::VecDeque; use std::net::{SocketAddr, TcpStream}; use std::sync::{Arc, RwLock}; use rand::os::OsRng; use rand::Rng; use core::core::hash::Hash; use core::core::target::Difficulty; use msg::{read_message, write_message, Hand, Shake, SockAddr, Type, PROTOCOL_VERSION, USER_AGENT}; use peer::Peer; use types::{Capabilities, Direction, Error, P2PConfig, PeerInfo}; use util::LOGGER; const NONCES_CAP: usize = 100; /// Handles the handshake negotiation when two peers connect and decides on /// protocol. pub struct Handshake { /// Ring buffer of nonces sent to detect self connections without requiring /// a node id. nonces: Arc>>, /// The genesis block header of the chain seen by this node. /// We only want to connect to other nodes seeing the same chain (forks are /// ok). genesis: Hash, config: P2PConfig, } unsafe impl Sync for Handshake {} unsafe impl Send for Handshake {} impl Handshake { /// Creates a new handshake handler pub fn new(genesis: Hash, config: P2PConfig) -> Handshake { Handshake { nonces: Arc::new(RwLock::new(VecDeque::with_capacity(NONCES_CAP))), genesis, config, } } pub fn initiate( &self, capab: Capabilities, total_difficulty: Difficulty, self_addr: SocketAddr, conn: &mut TcpStream, ) -> Result { // prepare the first part of the handshake let nonce = self.next_nonce(); let peer_addr = match conn.peer_addr() { Ok(pa) => pa, Err(e) => return Err(Error::Connection(e)), }; let hand = Hand { version: PROTOCOL_VERSION, capabilities: capab, nonce: nonce, genesis: self.genesis, total_difficulty: total_difficulty, sender_addr: SockAddr(self_addr), receiver_addr: SockAddr(peer_addr), user_agent: USER_AGENT.to_string(), }; // write and read the handshake response write_message(conn, hand, Type::Hand)?; let shake: Shake = read_message(conn, Type::Shake)?; if shake.version != PROTOCOL_VERSION { return Err(Error::ProtocolMismatch { us: PROTOCOL_VERSION, peer: shake.version, }); } else if shake.genesis != self.genesis { return Err(Error::GenesisMismatch { us: self.genesis, peer: shake.genesis, }); } let peer_info = PeerInfo { capabilities: shake.capabilities, user_agent: shake.user_agent, addr: peer_addr, version: shake.version, total_difficulty: shake.total_difficulty, height: 0, direction: Direction::Outbound, }; // If denied then we want to close the connection // (without providing our peer with any details why). if Peer::is_denied(&self.config, &peer_info.addr) { return Err(Error::ConnectionClose); } debug!( LOGGER, "Connected! Cumulative {} offered from {:?} {:?} {:?}", peer_info.total_difficulty.to_num(), peer_info.addr, peer_info.user_agent, peer_info.capabilities ); // when more than one protocol version is supported, choosing should go here Ok(peer_info) } pub fn accept( &self, capab: Capabilities, total_difficulty: Difficulty, conn: &mut TcpStream, ) -> Result { let hand: Hand = read_message(conn, Type::Hand)?; // all the reasons we could refuse this connection for if hand.version != PROTOCOL_VERSION { return Err(Error::ProtocolMismatch { us: PROTOCOL_VERSION, peer: hand.version, }); } else if hand.genesis != self.genesis { return Err(Error::GenesisMismatch { us: self.genesis, peer: hand.genesis, }); } else { // check the nonce to see if we are trying to connect to ourselves let nonces = self.nonces.read().unwrap(); if nonces.contains(&hand.nonce) { return Err(Error::PeerWithSelf); } } // all good, keep peer info let peer_info = PeerInfo { capabilities: hand.capabilities, user_agent: hand.user_agent, addr: extract_ip(&hand.sender_addr.0, &conn), version: hand.version, total_difficulty: hand.total_difficulty, height: 0, direction: Direction::Inbound, }; // At this point we know the published ip and port of the peer // so check if we are configured to explicitly allow or deny it. // If denied then we want to close the connection // (without providing our peer with any details why). if Peer::is_denied(&self.config, &peer_info.addr) { return Err(Error::ConnectionClose); } // send our reply with our info let shake = Shake { version: PROTOCOL_VERSION, capabilities: capab, genesis: self.genesis, total_difficulty: total_difficulty, user_agent: USER_AGENT.to_string(), }; write_message(conn, shake, Type::Shake)?; trace!(LOGGER, "Success handshake with {}.", peer_info.addr); // when more than one protocol version is supported, choosing should go here Ok(peer_info) } /// Generate a new random nonce and store it in our ring buffer fn next_nonce(&self) -> u64 { let mut rng = OsRng::new().unwrap(); let nonce = rng.next_u64(); let mut nonces = self.nonces.write().unwrap(); nonces.push_back(nonce); if nonces.len() >= NONCES_CAP { nonces.pop_front(); } nonce } } // Attempts to make a best guess at the correct remote IP by checking if the // advertised address is the loopback and our TCP connection. Note that the // port reported by the connection is always incorrect for receiving // connections as it's dynamically allocated by the server. fn extract_ip(advertised: &SocketAddr, conn: &TcpStream) -> SocketAddr { match advertised { &SocketAddr::V4(v4sock) => { let ip = v4sock.ip(); if ip.is_loopback() || ip.is_unspecified() { if let Ok(addr) = conn.peer_addr() { return SocketAddr::new(addr.ip(), advertised.port()); } } } &SocketAddr::V6(v6sock) => { let ip = v6sock.ip(); if ip.is_loopback() || ip.is_unspecified() { if let Ok(addr) = conn.peer_addr() { return SocketAddr::new(addr.ip(), advertised.port()); } } } } advertised.clone() }