1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
use crate::{client::Client, settings::banlist::NormalizedIpAddr, EditableSettings};
use common::{
comp::Player,
event::{ClientDisconnectEvent, EventBus},
};
use common_ecs::{Job, Origin, Phase, System};
use common_net::msg::{DisconnectReason, ServerGeneral};
use network::ParticipantEvent;
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, WriteStorage};
/// This system consumes events from the `Participant::try_fetch_event`. These
/// currently indicate channels being created and destroyed which potentially
/// corresponds to the client using new addresses.
///
/// New addresses are checked against the existing IP bans. If a match is found
/// that client will be kicked. Otherwise, the IP is added to the set of IPs
/// that client has used. When a new IP ban is created, the set of IP addrs used
/// by each client is scanned and any clients with matches are kicked.
///
/// We could retain addresses of removed channels and use them when banning but
/// that would use a potentially unknown amount of memory (so they are removed).
#[derive(Default)]
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
ReadStorage<'a, Player>,
WriteStorage<'a, Client>,
Read<'a, EventBus<ClientDisconnectEvent>>,
ReadExpect<'a, EditableSettings>,
);
const NAME: &'static str = "msg::network_events";
const ORIGIN: Origin = Origin::Server;
const PHASE: Phase = Phase::Create;
fn run(
_job: &mut Job<Self>,
(entities, players, mut clients, client_disconnect_event_bus, editable_settings): Self::SystemData,
) {
let now = chrono::Utc::now();
let mut client_disconnect_emitter = client_disconnect_event_bus.emitter();
for (entity, client) in (&entities, &mut clients).join() {
while let Some(Ok(Some(event))) = client
.participant
.as_mut()
.map(|participant| participant.try_fetch_event())
{
match event {
ParticipantEvent::ChannelCreated(connect_addr) => {
// Ignore mpsc connections
if let Some(addr) = connect_addr.socket_addr() {
client.current_ip_addrs.push(addr);
let banned = editable_settings
.banlist
.ip_bans()
.get(&NormalizedIpAddr::from(addr.ip()))
.and_then(|ban_entry| ban_entry.current.action.ban())
.and_then(|ban| {
// Hardcoded admins can always log in.
let admin = players.get(entity).and_then(|player| {
editable_settings.admins.get(&player.uuid())
});
crate::login_provider::ban_applies(ban, admin, now)
.then(|| ban.info())
});
if let Some(ban_info) = banned {
// Kick client
client_disconnect_emitter.emit(ClientDisconnectEvent(
entity,
common::comp::DisconnectReason::Kicked,
));
let _ = client.send(ServerGeneral::Disconnect(
DisconnectReason::Banned(ban_info),
));
}
}
},
ParticipantEvent::ChannelDeleted(connect_addr) => {
// Ignore mpsc connections
if let Some(addr) = connect_addr.socket_addr() {
if let Some(i) = client
.current_ip_addrs
.iter()
.rev()
.position(|a| *a == addr)
{
client.current_ip_addrs.remove(i);
} else {
tracing::error!(
"Channel deleted but its address isn't present in \
client.current_ip_addrs!"
);
}
}
},
}
}
}
}
}