nostr-rs-relay

My dev fork of nostr-rs-relay
git clone git://jb55.com/nostr-rs-relay
Log | Files | Refs | README | LICENSE

commit 4171a8870e409032a5aecb455557faaf7951fbd4
parent 8f3891c78175ba293f0e20def57a38945fcf230d
Author: Greg Heartsfield <scsibug@imap.cc>
Date:   Fri, 31 Dec 2021 15:19:35 -0600

feat: reject events that are too large

A new configuration setting controls the maximum size of event
messages, and sends a notice to the client if they exceed it.

Fixes https://todo.sr.ht/~gheartsfield/nostr-rs-relay/14

Diffstat:
Mconfig.toml | 12++++++++----
Msrc/config.rs | 2+-
Msrc/db.rs | 2+-
Msrc/error.rs | 2++
Msrc/main.rs | 12+++++++-----
Msrc/protostream.rs | 18++++++++++++++----
6 files changed, 33 insertions(+), 15 deletions(-)

diff --git a/config.toml b/config.toml @@ -3,7 +3,7 @@ # Directory for SQLite files. Defaults to the current directory. Can # also be specified (and overriden) with the "--db dirname" command # line option. -data_directory = "data" +data_directory = "." [network] # Bind to this network address @@ -15,17 +15,21 @@ port = 8080 # Reject events that have timestamps greater than this many seconds in # the future. Defaults to rejecting anything greater than 30 minutes # from the current time. -#reject_future_seconds = 1800 +reject_future_seconds = 1800 [limits] # Limit events created per second, averaged over one minute. Must be # an integer. If not set (or set to 0), defaults to unlimited. messages_per_sec = 0 -# Maximum WebSocket message in bytes. Defaults to 128k. +# Limit the maximum size of an EVENT message. Defaults to 128 KB. +# Set to 0 for unlimited. +max_event_bytes = 131072 + +# Maximum WebSocket message in bytes. Defaults to 128 KB. max_ws_message_bytes = 131072 -# Maximum WebSocket frame size in bytes. Defaults to 128k. +# Maximum WebSocket frame size in bytes. Defaults to 128 KB. max_ws_frame_bytes = 131072 # Broadcast buffer size, in number of events. This prevents slow diff --git a/src/config.rs b/src/config.rs @@ -42,7 +42,7 @@ pub struct Retention { #[allow(unused)] pub struct Limits { pub messages_per_sec: Option<u32>, // Artificially slow down event writing to limit disk consumption (averaged over 1 minute) - pub max_event_bytes: Option<usize>, + pub max_event_bytes: Option<usize>, // Maximum size of an EVENT message pub max_ws_message_bytes: Option<usize>, pub max_ws_frame_bytes: Option<usize>, pub broadcast_buffer: usize, // events to buffer for subscribers (prevents slow readers from consuming memory) diff --git a/src/db.rs b/src/db.rs @@ -145,7 +145,7 @@ pub async fn db_writer( } } loop { - if let Ok(_) = shutdown.try_recv() { + if shutdown.try_recv().is_ok() { info!("shutting down database writer"); break; } diff --git a/src/error.rs b/src/error.rs @@ -21,6 +21,8 @@ pub enum Error { CloseParseFailed, #[error("Event validation failed")] EventInvalid, + #[error("Event too large")] + EventMaxLengthError(usize), #[error("Subscription identifier max length exceeded")] SubIdMaxLengthError, #[error("Maximum concurrent subscription count reached")] diff --git a/src/main.rs b/src/main.rs @@ -23,10 +23,8 @@ use tokio::sync::oneshot; use tungstenite::protocol::WebSocketConfig; fn db_from_args(args: Vec<String>) -> Option<String> { - if args.len() == 3 { - if args.get(1) == Some(&"--db".to_owned()) { - return args.get(2).map(|x| x.to_owned()); - } + if args.len() == 3 && args.get(1) == Some(&"--db".to_owned()) { + return args.get(2).map(|x| x.to_owned()); } None } @@ -44,7 +42,7 @@ fn main() -> Result<(), Error> { let mut c = config::Settings::new(); // update with database location if let Some(db) = db_dir { - c.database.data_directory = db.to_owned(); + c.database.data_directory = db; } *settings = c; } @@ -253,6 +251,10 @@ async fn nostr_server( debug!("got connection close/error, disconnecting client: {}",cid); break; } + Some(Err(Error::EventMaxLengthError(s))) => { + info!("client {} sent event larger ({} bytes) than max size", cid, s); + nostr_stream.send(NoticeRes("event exceeded max size".to_owned())).await.ok(); + }, Some(Err(e)) => { info!("got non-fatal error from client: {}, error: {:?}", cid, e); }, diff --git a/src/protostream.rs b/src/protostream.rs @@ -1,5 +1,6 @@ //! Nostr protocol layered over WebSocket use crate::close::CloseCmd; +use crate::config; use crate::error::{Error, Result}; use crate::event::EventCmd; use crate::subscription::Subscription; @@ -51,14 +52,23 @@ pub fn wrap_ws_in_nostr(ws: WebSocketStream<TcpStream>) -> NostrStream { impl Stream for NostrStream { type Item = Result<NostrMessage>; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { + // get the configuration /// Convert Message to NostrMessage fn convert(msg: String) -> Result<NostrMessage> { - debug!("raw msg: {}", msg); - let event_size = msg.len(); - debug!("event size is {} bytes", event_size); + let config = config::SETTINGS.read().unwrap(); let parsed_res: Result<NostrMessage> = serde_json::from_str(&msg).map_err(|e| e.into()); match parsed_res { - Ok(m) => Ok(m), + Ok(m) => { + if let NostrMessage::EventMsg(_) = m { + if let Some(max_size) = config.limits.max_event_bytes { + // check length, ensure that some max size is set. + if msg.len() > max_size && max_size > 0 { + return Err(Error::EventMaxLengthError(msg.len())); + } + } + } + Ok(m) + } Err(e) => { debug!("proto parse error: {:?}", e); Err(Error::ProtoParseError)