summaryrefslogtreecommitdiffstats
path: root/src/firewall.rs
blob: 917fe5fa92e49d45b0cc997d84a7ca33f365c8eb (plain)
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
105
use std::collections::HashSet;
use std::sync::{Arc, RwLock, RwLockReadGuard};

use anyhow::Result;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;

use crate::transport::EncodedMessage;
use crate::transport::{MessageHandler, ParsedMessage, Transport};

#[derive(Debug, Clone, Default, Serialize, Deserialize, ToSchema)]
pub struct FirewallRules {
    /// Messages from these peers are dropped before the application sees them.
    #[serde(default)]
    pub inbound: HashSet<String>,
    /// Messages to these peers are silently dropped, like a lossy network.
    #[serde(default)]
    pub outbound: HashSet<String>,
}

/// Uses a std lock: checks are tiny and the guard is never held across an await.
#[derive(Default)]
pub struct Firewall {
    rules: RwLock<FirewallRules>,
}

impl Firewall {
    fn read(&self) -> RwLockReadGuard<'_, FirewallRules> {
        self.rules
            .read()
            .unwrap_or_else(|poisoned| poisoned.into_inner())
    }

    pub fn rules(&self) -> FirewallRules {
        self.read().clone()
    }

    pub fn set_rules(&self, rules: FirewallRules) {
        *self
            .rules
            .write()
            .unwrap_or_else(|poisoned| poisoned.into_inner()) = rules;
    }

    pub fn blocks_inbound(&self, peer: &str) -> bool {
        self.read().inbound.contains(peer)
    }

    pub fn blocks_outbound(&self, peer: &str) -> bool {
        self.read().outbound.contains(peer)
    }
}

pub struct FirewallTransport {
    inner: Arc<dyn Transport>,
    firewall: Arc<Firewall>,
}

impl FirewallTransport {
    pub fn new(inner: Arc<dyn Transport>, firewall: Arc<Firewall>) -> Self {
        Self { inner, firewall }
    }
}

#[async_trait]
impl Transport for FirewallTransport {
    fn local_id(&self) -> &str {
        self.inner.local_id()
    }

    async fn resolve_peer(&self, hint: &str) -> Result<String> {
        self.inner.resolve_peer(hint).await
    }

    fn encode(&self, peer: &str, message: &ParsedMessage) -> Result<EncodedMessage> {
        self.inner.encode(peer, message)
    }

    async fn send_encoded(&self, message: &EncodedMessage) -> Result<()> {
        if self.firewall.blocks_outbound(&message.peer) {
            tracing::trace!(peer = %message.peer, "Firewall dropped outbound message");
            return Ok(());
        }
        self.inner.send_encoded(message).await
    }

    async fn start_consumer(&self, handler: MessageHandler) -> Result<()> {
        let firewall = Arc::clone(&self.firewall);

        self.inner
            .start_consumer(Box::new(move |sender, message| {
                if firewall.blocks_inbound(&sender) {
                    tracing::trace!(peer = %sender, "Firewall dropped inbound message");
                    return Box::pin(async { Ok(()) });
                }
                handler(sender, message)
            }))
            .await
    }

    async fn wait_for_exit(&self) -> std::io::Result<std::process::ExitStatus> {
        self.inner.wait_for_exit().await
    }
}