Skip to content

Commit e0703b0

Browse files
committed
Expand CIDR block list to support IPv6 ranges
This refactors the `WEB_PAGE_OFFSET_CIDR_BLOCKLIST` block list to also support IPv6 based CIDR blocks.
1 parent dfe0c87 commit e0703b0

File tree

3 files changed

+46
-32
lines changed

3 files changed

+46
-32
lines changed

src/config.rs

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use ipnetwork::Ipv4Network;
1+
use ipnetwork::IpNetwork;
22

33
use crate::publish_rate_limit::PublishRateLimit;
44
use crate::{env, env_optional, uploaders::Uploader, Env};
@@ -27,7 +27,7 @@ pub struct Server {
2727
pub blocked_traffic: Vec<(String, Vec<String>)>,
2828
pub max_allowed_page_offset: u32,
2929
pub page_offset_ua_blocklist: Vec<String>,
30-
pub page_offset_cidr_blocklist: Vec<Ipv4Network>,
30+
pub page_offset_cidr_blocklist: Vec<IpNetwork>,
3131
pub excluded_crate_names: Vec<String>,
3232
pub domain_name: String,
3333
pub allowed_origins: Vec<String>,
@@ -158,29 +158,37 @@ pub(crate) fn domain_name() -> String {
158158
dotenv::var("DOMAIN_NAME").unwrap_or_else(|_| "crates.io".into())
159159
}
160160

161-
/// Parses list of CIDR block strings to valid `Ipv4Cidr` structs.
161+
/// Parses list of CIDR block strings to valid `IpNetwork` structs.
162162
///
163-
/// The purpose is to be able to block IP ranges that overload the API that contains pagination.
164-
/// A valid CIDR block has the following restriction:
163+
/// The purpose is to be able to block IP ranges that overload the API that uses pagination.
165164
///
166-
/// * Only IPv4 blocks are currently supported.
167-
/// * The minimum number of host prefix bits must be at least 16.
165+
/// The minimum number of bits for a host prefix must be
168166
///
169-
fn parse_cidr_blocks(blocks: &[String]) -> Vec<Ipv4Network> {
167+
/// * at least 16 for IPv4 based CIDRs.
168+
/// * at least 64 for IPv6 based CIDRs
169+
///
170+
fn parse_cidr_blocks(blocks: &[String]) -> Vec<IpNetwork> {
170171
blocks
171172
.iter()
172-
.map(|block| match block.parse::<Ipv4Network>() {
173-
Ok(cidr) => {
174-
if cidr.prefix() < 16 {
175-
panic!(
176-
"WEB_PAGE_OFFSET_CIDR_BLOCKLIST must only contain CIDR blocks with \
177-
a host prefix of at least 16 bits."
178-
)
179-
} else {
180-
cidr
181-
}
173+
.map(|block| {
174+
let network = block.parse::<IpNetwork>();
175+
match network {
176+
Ok(cidr) => {
177+
let host_prefix = match cidr {
178+
IpNetwork::V4(_) => 16,
179+
IpNetwork::V6(_) => 64,
180+
};
181+
if cidr.prefix() < host_prefix {
182+
panic!(
183+
"WEB_PAGE_OFFSET_CIDR_BLOCKLIST only allows CIDR blocks with a host prefix \
184+
of at least 16 bits (IPv4) or 64 bits (IPv6)."
185+
);
186+
} else {
187+
cidr
188+
}
189+
},
190+
Err(_) => panic!("WEB_PAGE_OFFSET_CIDR_BLOCKLIST must contain IPv4 or IPv6 CIDR blocks."),
182191
}
183-
Err(_) => panic!("WEB_PAGE_OFFSET_CIDR_BLOCKLIST only allows IPv4 CIDR blocks"),
184192
})
185193
.collect::<Vec<_>>()
186194
}
@@ -232,23 +240,30 @@ fn parse_cidr_block_list_successfully() {
232240
let blocks = parse_cidr_blocks(&cidr_blocks);
233241
assert_eq!(
234242
vec![
235-
"127.0.0.1/24".parse::<Ipv4Network>().unwrap(),
236-
"192.168.0.1/31".parse::<Ipv4Network>().unwrap(),
243+
"127.0.0.1/24".parse::<IpNetwork>().unwrap(),
244+
"192.168.0.1/31".parse::<IpNetwork>().unwrap(),
237245
],
238246
blocks,
239247
);
240248
}
241249

242250
#[test]
243251
#[should_panic]
244-
fn parse_cidr_blocks_panics_when_host_prefix_is_too_low() {
245-
let input = vec!["127.0.0.1/8".to_string()];
246-
parse_cidr_blocks(&input);
252+
fn parse_cidr_blocks_panics_when_host_ipv4_prefix_is_too_low() {
253+
parse_cidr_blocks(&["127.0.0.1/8".to_string()]);
247254
}
248255

249256
#[test]
250257
#[should_panic]
251-
fn parse_cidr_blocks_panics_when_ipv6_is_given() {
252-
let input = vec!["2002::1234:abcd:ffff:c0a8:101/64".to_string()];
253-
parse_cidr_blocks(&input);
258+
fn parse_cidr_blocks_panics_when_host_ipv6_prefix_is_too_low() {
259+
parse_cidr_blocks(&["2001:0db8:0123:4567:89ab:cdef:1234:5678/56".to_string()]);
260+
}
261+
262+
#[test]
263+
fn parse_ipv6_based_cidr_blocks() {
264+
let input = vec![
265+
"2002::1234:abcd:ffff:c0a8:101/64".to_string(),
266+
"2001:0db8:0123:4567:89ab:cdef:1234:5678/92".to_string(),
267+
];
268+
assert_eq!(2, parse_cidr_blocks(&input).len());
254269
}

src/controllers/helpers/pagination.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use diesel::query_dsl::LoadQuery;
1212
use diesel::sql_types::BigInt;
1313
use indexmap::IndexMap;
1414
use serde::{Deserialize, Serialize};
15-
use std::net::Ipv4Addr;
15+
use std::net::IpAddr;
1616
use std::str::FromStr;
1717
use std::sync::Arc;
1818

@@ -274,7 +274,7 @@ fn is_useragent_or_ip_blocked(config: &Server, req: &dyn RequestExt) -> bool {
274274
}
275275

276276
// check if client ip is blocked, needs to be an IPv4 address
277-
if let Ok(client_ip) = Ipv4Addr::from_str(client_ip) {
277+
if let Ok(client_ip) = IpAddr::from_str(client_ip) {
278278
if config
279279
.page_offset_cidr_blocklist
280280
.iter()

src/tests/krate/search.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use cargo_registry::models::Category;
55
use cargo_registry::schema::crates;
66
use diesel::{dsl::*, prelude::*, update};
77
use http::StatusCode;
8-
use ipnetwork::Ipv4Network;
8+
use ipnetwork::IpNetwork;
99

1010
#[test]
1111
fn index() {
@@ -829,8 +829,7 @@ fn pagination_blocks_ip_from_cidr_block_list() {
829829
let (app, anon, user) = TestApp::init()
830830
.with_config(|config| {
831831
config.max_allowed_page_offset = 1;
832-
config.page_offset_cidr_blocklist =
833-
vec!["127.0.0.1/24".parse::<Ipv4Network>().unwrap()];
832+
config.page_offset_cidr_blocklist = vec!["127.0.0.1/24".parse::<IpNetwork>().unwrap()];
834833
})
835834
.with_user();
836835
let user = user.as_model();

0 commit comments

Comments
 (0)