Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Use FxHasher in places where we don't need DDoS resistance #2342

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
eab99c3
feat: Try FxHasher to see if it makes a difference
larseggert Jan 10, 2025
0bda917
Merge branch 'main' into feat-fxhasher
larseggert Jan 10, 2025
6762a02
More FxHasher
larseggert Jan 10, 2025
93c8ce5
Merge branch 'main' into feat-fxhasher
larseggert Jan 14, 2025
2b230a5
Merge branch 'main' into feat-fxhasher
larseggert Jan 24, 2025
0d353a6
Merge branch 'main' into feat-fxhasher
larseggert Feb 4, 2025
5879e28
Suggestion from @martinthomson
larseggert Feb 5, 2025
6b305ea
One more
larseggert Feb 5, 2025
ee86d48
Merge branch 'main' into feat-fxhasher
larseggert Feb 5, 2025
bbea965
Merge branch 'main' into feat-fxhasher
larseggert Feb 5, 2025
bd06169
Suggestions from @mxinden
larseggert Feb 5, 2025
7d6c181
Merge branch 'main' into feat-fxhasher
larseggert Feb 7, 2025
a78c1c6
IndexMap
larseggert Feb 10, 2025
9d9a799
More
larseggert Feb 10, 2025
13cdada
Again
larseggert Feb 10, 2025
d280b66
Again
larseggert Feb 10, 2025
8d0b6fb
Undo
larseggert Feb 10, 2025
b609b9e
Merge branch 'main' into feat-fxhasher
larseggert Feb 10, 2025
6e8d194
`swap_remove` -> `shift_remove`
larseggert Feb 10, 2025
40c8383
`BTreeSet` -> `HashSet`
larseggert Feb 10, 2025
420c6e2
Minimize
larseggert Feb 10, 2025
97773c6
Minimize more
larseggert Feb 10, 2025
f4531aa
Merge branch 'main' into feat-fxhasher
larseggert Feb 11, 2025
fd081e4
Merge branch 'main' into feat-fxhasher
larseggert Feb 12, 2025
9f9e220
Fix
larseggert Feb 12, 2025
548027d
Merge branch 'main' into feat-fxhasher
larseggert Feb 12, 2025
69a6710
Fix
larseggert Feb 12, 2025
bf294ed
Merge branch 'main' into feat-fxhasher
larseggert Feb 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ rust-version = "1.76.0"
[workspace.dependencies]
# Checked against https://searchfox.org/mozilla-central/source/Cargo.lock 2024-11-11
enum-map = { version = "2.7", default-features = false }
indexmap = { version = "2.2", default-features = false } # See https://github.com/mozilla/neqo/issues/1858
log = { version = "0.4", default-features = false }
qlog = { version = "0.13", default-features = false }
quinn-udp = { version = "0.5.6", default-features = false, features = ["direct-log", "fast-apple-datapath"] }
regex = { version = "1.9", default-features = false, features = ["unicode-perl"] }
rustc-hash = { version = "1.1", default-features = false, features = [ "std" ]}
static_assertions = { version = "1.1", default-features = false }
url = { version = "2.5.3", default-features = false, features = ["std"] }

Expand Down
1 change: 1 addition & 0 deletions neqo-bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ neqo-udp = { path = "./../neqo-udp" }
qlog = { workspace = true }
quinn-udp = { workspace = true }
regex = { workspace = true }
rustc-hash = { workspace = true }
tokio = { version = "1", default-features = false, features = ["net", "time", "macros", "rt", "rt-multi-thread"] }
url = { workspace = true }

Expand Down
5 changes: 3 additions & 2 deletions neqo-bin/src/client/http09.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

use std::{
cell::RefCell,
collections::{HashMap, VecDeque},
collections::VecDeque,
fs::File,
io::{BufWriter, Write as _},
net::SocketAddr,
Expand All @@ -25,6 +25,7 @@ use neqo_transport::{
CloseReason, Connection, ConnectionEvent, ConnectionIdGenerator, EmptyConnectionIdGenerator,
Error, Output, RandomConnectionIdGenerator, State, StreamId, StreamType,
};
use rustc_hash::FxHashMap as HashMap;
use url::Url;

use super::{get_output_file, qlog_new, Args, CloseState, Res};
Expand Down Expand Up @@ -226,7 +227,7 @@ impl super::Client for Connection {
impl<'b> Handler<'b> {
pub fn new(url_queue: VecDeque<Url>, args: &'b Args) -> Self {
Self {
streams: HashMap::new(),
streams: HashMap::default(),
url_queue,
handled_urls: Vec::new(),
all_paths: Vec::new(),
Expand Down
5 changes: 3 additions & 2 deletions neqo-bin/src/client/http3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

use std::{
cell::RefCell,
collections::{HashMap, VecDeque},
collections::VecDeque,
fmt::Display,
fs::File,
io::{BufWriter, Write as _},
Expand All @@ -27,6 +27,7 @@ use neqo_transport::{
AppError, CloseReason, Connection, EmptyConnectionIdGenerator, Error as TransportError, Output,
RandomConnectionIdGenerator, StreamId,
};
use rustc_hash::FxHashMap as HashMap;
use url::Url;

use super::{get_output_file, qlog_new, Args, CloseState, Res};
Expand All @@ -45,7 +46,7 @@ impl<'a> Handler<'a> {
let url_handler = UrlHandler {
url_queue,
handled_urls: Vec::new(),
stream_handlers: HashMap::new(),
stream_handlers: HashMap::default(),
all_paths: Vec::new(),
args,
};
Expand Down
14 changes: 9 additions & 5 deletions neqo-bin/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#![allow(clippy::unwrap_used)] // This is example code.

use std::{
collections::{HashMap, VecDeque},
collections::VecDeque,
fmt::{self, Display},
fs::{create_dir_all, File, OpenOptions},
io::{self, BufWriter},
Expand All @@ -32,6 +32,7 @@ use neqo_crypto::{
use neqo_http3::Output;
use neqo_transport::{AppError, CloseReason, ConnectionId, Version};
use neqo_udp::RecvBuf;
use rustc_hash::FxHashMap as HashMap;
use tokio::time::Sleep;
use url::{Host, Origin, Url};

Expand Down Expand Up @@ -529,10 +530,13 @@ const fn local_addr_for(remote_addr: &SocketAddr, local_port: u16) -> SocketAddr

fn urls_by_origin(urls: &[Url]) -> impl Iterator<Item = ((Host, u16), VecDeque<Url>)> {
urls.iter()
.fold(HashMap::<Origin, VecDeque<Url>>::new(), |mut urls, url| {
urls.entry(url.origin()).or_default().push_back(url.clone());
urls
})
.fold(
HashMap::<Origin, VecDeque<Url>>::default(),
|mut urls, url| {
urls.entry(url.origin()).or_default().push_back(url.clone());
urls
},
)
.into_iter()
.filter_map(|(origin, urls)| match origin {
Origin::Tuple(_scheme, h, p) => Some(((h, p), urls)),
Expand Down
7 changes: 4 additions & 3 deletions neqo-bin/src/server/http09.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

#![allow(clippy::unwrap_used)] // This is example code.

use std::{borrow::Cow, cell::RefCell, collections::HashMap, fmt::Display, rc::Rc, time::Instant};
use std::{borrow::Cow, cell::RefCell, fmt::Display, rc::Rc, time::Instant};

use neqo_common::{event::Provider as _, hex, qdebug, qerror, qinfo, qwarn, Datagram};
use neqo_crypto::{generate_ech_keys, random, AllowZeroRtt, AntiReplay};
Expand All @@ -16,6 +16,7 @@ use neqo_transport::{
ConnectionEvent, ConnectionIdGenerator, Output, State, StreamId,
};
use regex::Regex;
use rustc_hash::FxHashMap as HashMap;

use super::{qns_read_response, Args};
use crate::{send_data::SendData, STREAM_IO_BUFFER_SIZE};
Expand Down Expand Up @@ -68,8 +69,8 @@ impl HttpServer {
let is_qns_test = args.shared.qns_test.is_some();
Ok(Self {
server,
write_state: HashMap::new(),
read_state: HashMap::new(),
write_state: HashMap::default(),
read_state: HashMap::default(),
is_qns_test,
regex: if is_qns_test {
Regex::new(r"GET +/(\S+)(?:\r)?\n").map_err(|_| Error::Internal)?
Expand Down
6 changes: 3 additions & 3 deletions neqo-bin/src/server/http3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

use std::{
cell::RefCell,
collections::HashMap,
fmt::{self, Display},
rc::Rc,
time::Instant,
Expand All @@ -20,6 +19,7 @@ use neqo_http3::{
Http3OrWebTransportStream, Http3Parameters, Http3Server, Http3ServerEvent, StreamId,
};
use neqo_transport::{server::ValidateAddress, ConnectionIdGenerator};
use rustc_hash::FxHashMap as HashMap;

use super::{qns_read_response, Args};
use crate::send_data::SendData;
Expand Down Expand Up @@ -68,8 +68,8 @@ impl HttpServer {
}
Self {
server,
remaining_data: HashMap::new(),
posts: HashMap::new(),
remaining_data: HashMap::default(),
posts: HashMap::default(),
is_qns_test: args.shared.qns_test.is_some(),
}
}
Expand Down
2 changes: 2 additions & 0 deletions neqo-http3/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ workspace = true
[dependencies]
# Checked against https://searchfox.org/mozilla-central/source/Cargo.lock 2024-11-11
enumset = { version = "1.1", default-features = false }
indexmap = { workspace = true }
log = { workspace = true }
neqo-common = { path = "./../neqo-common" }
neqo-crypto = { path = "./../neqo-crypto" }
neqo-qpack = { path = "./../neqo-qpack" }
neqo-transport = { path = "./../neqo-transport" }
qlog = { workspace = true }
rustc-hash = { workspace = true }
sfv = { version = "0.9", default-features = false }
url = { workspace = true }

Expand Down
41 changes: 18 additions & 23 deletions neqo-http3/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,15 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::{
cell::RefCell,
collections::{BTreeSet, HashMap},
fmt::Debug,
mem,
rc::Rc,
};
use std::{cell::RefCell, fmt::Debug, mem, rc::Rc};

use neqo_common::{qdebug, qerror, qinfo, qtrace, qwarn, Decoder, Header, MessageType, Role};
use neqo_qpack::{decoder::QPackDecoder, encoder::QPackEncoder};
use neqo_transport::{
streams::SendOrder, AppError, CloseReason, Connection, DatagramTracking, State, StreamId,
StreamType, ZeroRttState,
streams::SendOrder, AppError, CloseReason, Connection, DatagramTracking, IndexMap, State,
StreamId, StreamType, ZeroRttState,
};
use rustc_hash::FxHashSet as HashSet;

use crate::{
client_events::Http3ClientEvents,
Expand Down Expand Up @@ -303,9 +298,9 @@ pub struct Http3Connection {
pub qpack_encoder: Rc<RefCell<QPackEncoder>>,
pub qpack_decoder: Rc<RefCell<QPackDecoder>>,
settings_state: Http3RemoteSettingsState,
streams_with_pending_data: BTreeSet<StreamId>,
pub send_streams: HashMap<StreamId, Box<dyn SendStream>>,
pub recv_streams: HashMap<StreamId, Box<dyn RecvStream>>,
streams_with_pending_data: HashSet<StreamId>,
pub send_streams: IndexMap<StreamId, Box<dyn SendStream>>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conceptually, why is an IndexMap faster than a HashMap here? Both end up hashing the key, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not faster, it's possibly even slower. I made the change based on this comment: https://github.com/mozilla/neqo/pull/2342/files/bd061693b40e91b846c4d4cd1bc0ecfcd27c4e45#r1945509653

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do I understand correctly that we want to be able to iterate send_streams and the like in order? Given that HashMap does not guarantee order, you are suggesting to use IndexMap like we do in neqo-transport.

If so, the requirement for ordering might be worth documenting. It wasn't obvious to me.

In that case, preference for using BTreeMap, solely because it is in std and only use IndexMap in case it proves to be more performant than BTreeMap in a meaningful way.

Given that IndexMap is a fairly small dependency, the above is not a strong opinion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I'm not completely sure that we need consistently ordered iteration here. We rely on that in the transport crate because we want frame sending to follow a predictable pattern (clear the old stuff out first). However, that's not something we rely on at this layer of the stack, so whatever is fastest might be the right answer.

Maybe the first question we have is whether we need (insertion-)ordered iteration over this collection?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First, I think we should differentiate between RX and TX.

On RX (both here and in transport), I don't see a point in using anything that maintains order or has semantics that go beyond a simple map or set. I think all we do here is lookups based on received data.

On TX, (both here and in transport), do we really care that iteration is consistently ordered? All these data structures should mostly have a stable order while there are no insertions or removals. When there are, do we care that we'll have a different order afterwards? I can't convince myself that we do.

In other words, I think we should use whatever is fastest.

(Also, unrelated, I find the duplication/overlap between the transport and http3 crates really odd.)

pub recv_streams: IndexMap<StreamId, Box<dyn RecvStream>>,
webtransport: ExtendedConnectFeature,
}

Expand Down Expand Up @@ -334,9 +329,9 @@ impl Http3Connection {
),
local_params: conn_params,
settings_state: Http3RemoteSettingsState::NotReceived,
streams_with_pending_data: BTreeSet::new(),
send_streams: HashMap::new(),
recv_streams: HashMap::new(),
streams_with_pending_data: HashSet::default(),
send_streams: IndexMap::default(),
recv_streams: IndexMap::default(),
role,
}
}
Expand Down Expand Up @@ -1182,10 +1177,10 @@ impl Http3Connection {
events,
self.role,
self.recv_streams
.remove(&stream_id)
.shift_remove(&stream_id)
mxinden marked this conversation as resolved.
Show resolved Hide resolved
.ok_or(Error::Internal)?,
self.send_streams
.remove(&stream_id)
.shift_remove(&stream_id)
.ok_or(Error::Internal)?,
)?));
self.add_streams(
Expand Down Expand Up @@ -1541,14 +1536,14 @@ impl Http3Connection {
qtrace!("Remove the extended connect sub receiver stream {id}");
// Use CloseType::ResetRemote so that an event will be sent. CloseType::LocalError would
// have the same effect.
if let Some(mut s) = self.recv_streams.remove(&id) {
if let Some(mut s) = self.recv_streams.shift_remove(&id) {
drop(s.reset(CloseType::ResetRemote(Error::HttpRequestCancelled.code())));
}
drop(conn.stream_stop_sending(id, Error::HttpRequestCancelled.code()));
}
for id in send {
qtrace!("Remove the extended connect sub send stream {id}");
if let Some(mut s) = self.send_streams.remove(&id) {
if let Some(mut s) = self.send_streams.shift_remove(&id) {
s.handle_stop_sending(CloseType::ResetRemote(Error::HttpRequestCancelled.code()));
}
drop(conn.stream_reset_send(id, Error::HttpRequestCancelled.code()));
Expand All @@ -1560,10 +1555,10 @@ impl Http3Connection {
stream_id: StreamId,
conn: &mut Connection,
) -> Option<Box<dyn RecvStream>> {
let stream = self.recv_streams.remove(&stream_id);
let stream = self.recv_streams.shift_remove(&stream_id);
if let Some(s) = &stream {
if s.stream_type() == Http3StreamType::ExtendedConnect {
self.send_streams.remove(&stream_id)?;
self.send_streams.shift_remove(&stream_id)?;
if let Some(wt) = s.webtransport() {
self.remove_extended_connect(&wt, conn);
}
Expand All @@ -1577,10 +1572,10 @@ impl Http3Connection {
stream_id: StreamId,
conn: &mut Connection,
) -> Option<Box<dyn SendStream>> {
let stream = self.send_streams.remove(&stream_id);
let stream = self.send_streams.shift_remove(&stream_id);
if let Some(s) = &stream {
if s.stream_type() == Http3StreamType::ExtendedConnect {
if let Some(wt) = self.recv_streams.remove(&stream_id)?.webtransport() {
if let Some(wt) = self.recv_streams.shift_remove(&stream_id)?.webtransport() {
self.remove_extended_connect(&wt, conn);
}
}
Expand Down
8 changes: 4 additions & 4 deletions neqo-http3/src/control_stream_local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::collections::{HashMap, VecDeque};
use std::collections::VecDeque;

use neqo_common::{qtrace, Encoder};
use neqo_transport::{Connection, StreamId, StreamType};
use neqo_transport::{Connection, IndexMap, StreamId, StreamType};

use crate::{frames::HFrame, BufferedStream, Error, Http3StreamType, RecvStream, Res};

Expand Down Expand Up @@ -50,7 +50,7 @@ impl ControlStreamLocal {
pub fn send(
&mut self,
conn: &mut Connection,
recv_conn: &mut HashMap<StreamId, Box<dyn RecvStream>>,
recv_conn: &mut IndexMap<StreamId, Box<dyn RecvStream>>,
) -> Res<()> {
self.stream.send_buffer(conn)?;
self.send_priority_update(conn, recv_conn)
Expand All @@ -59,7 +59,7 @@ impl ControlStreamLocal {
fn send_priority_update(
&mut self,
conn: &mut Connection,
recv_conn: &mut HashMap<StreamId, Box<dyn RecvStream>>,
recv_conn: &mut IndexMap<StreamId, Box<dyn RecvStream>>,
) -> Res<()> {
// send all necessary priority updates
while let Some(update_id) = self.outstanding_priority_update.pop_front() {
Expand Down
Loading
Loading