use std::collections::HashMap;
use std::io::{Read, Write};
use std::net::{IpAddr, TcpStream};
use std::sync::Mutex;
use std::time::Duration;
use net_io::{
AdmitStage, Admitted, CloseReason, ConnectionInfo, Denial, Gate, GateTag, Lane, ListenerSpec,
NetError, Refusal, ServerBuilder, TcpConnection, WriteCheck,
};
const PER_ADDRESS: u32 = 2;
const BYTES_PER_CONNECTION: u64 = 64;
#[derive(Default)]
struct PerAddress {
open: Mutex<HashMap<IpAddr, u32>>,
}
struct Ticket {
ip: IpAddr,
sent: u64,
}
impl Gate for PerAddress {
type Ticket = Ticket;
fn admit(
&self,
info: &ConnectionInfo,
_stage: AdmitStage,
) -> Result<Admitted<Ticket>, Refusal> {
let ip = info.client_addr().ok_or(Refusal::Reset { code: 4 })?.ip();
let mut open = self.open.lock().map_err(|_| Refusal::Reset { code: 1 })?;
let n = open.entry(ip).or_insert(0);
if *n >= PER_ADDRESS {
return Err(Refusal::WithBytes {
code: 2,
bytes: b"too many connections\n",
});
}
*n += 1;
Ok(Admitted {
lane: Lane(0),
ticket: Ticket { ip, sent: 0 },
tag: GateTag(0),
})
}
fn readmit(&self, _ticket: &mut Ticket, _info: &ConnectionInfo) -> Result<(), Refusal> {
Ok(())
}
fn check(&self, ticket: &mut Ticket, write: &WriteCheck) -> Result<(), Denial> {
ticket.sent += u64::from(write.bytes);
if ticket.sent > BYTES_PER_CONNECTION {
return Err(Denial { code: 3 });
}
Ok(())
}
fn closed(&self, ticket: Ticket, info: &ConnectionInfo, reason: CloseReason) {
if let Ok(mut open) = self.open.lock() {
if let Some(n) = open.get_mut(&ticket.ip) {
*n = n.saturating_sub(1);
}
}
println!(" {} closed: {}", info.id, reason.as_str());
}
}
fn echo(conn: &mut TcpConnection) -> Result<(), NetError> {
let mut buf = [0u8; 256];
loop {
let n = conn.recv(&mut buf)?;
if n == 0 {
return Ok(());
}
conn.send_all(&buf[..n])?;
}
}
fn main() -> Result<(), NetError> {
let server = ServerBuilder::new()
.listener(ListenerSpec::tcp("127.0.0.1:0"))
.workers(4)
.handler(echo)
.gate(PerAddress::default())
.build()?;
server.open_door()?;
let addr = server.listeners()[0]
.local
.socket_addr()
.ok_or(NetError::Closed)?;
let mut a = TcpStream::connect(addr)?;
let _b = TcpStream::connect(addr)?;
std::thread::sleep(Duration::from_millis(50));
let mut c = TcpStream::connect(addr)?;
c.set_read_timeout(Some(Duration::from_secs(2)))?;
let mut refusal = String::new();
let _ = c.read_to_string(&mut refusal);
println!("third connection: {:?}", refusal.trim_end());
a.set_read_timeout(Some(Duration::from_secs(2)))?;
let mut echoed = [0u8; 40];
a.write_all(&[b'x'; 40])?;
a.read_exact(&mut echoed)?;
println!("first 40 bytes echoed");
a.write_all(&[b'y'; 40])?;
let mut rest = Vec::new();
let r = a.read_to_end(&mut rest);
println!(
"next 40 bytes: write denied by the gate, connection closed ({} bytes, {:?})",
rest.len(),
r.err().map(|e| e.kind())
);
let until = std::time::Instant::now() + Duration::from_secs(2);
while server.stats().egress_denied == 0 && std::time::Instant::now() < until {
std::thread::sleep(Duration::from_millis(5));
}
let stats = server.stats();
println!(
"refused {}, egress denied {}",
stats.refused, stats.egress_denied
);
Ok(())
}