Skip to content

Commit

Permalink
Merge branch 'BOUN-1078-add-rate-connetion-limits-bn' into 'master'
Browse files Browse the repository at this point in the history
(BOUN-1079) rate and connection limits in nftables for API BN

Introduce loose rate- and connection limits to the networking layer of the API Boundary Node. 

See merge request dfinity-lab/public/ic!19200
  • Loading branch information
nikolay-komarevskiy committed May 16, 2024
2 parents b32f4a4 + 4e221b5 commit ee6addc
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 2 deletions.
39 changes: 37 additions & 2 deletions ic-os/rootfs/guestos/opt/ic/share/ic.json5.template
Original file line number Diff line number Diff line change
Expand Up @@ -367,9 +367,26 @@ table ip6 filter {\n\
boundary_node_firewall: {
config_file: "/run/ic-node/nftables-ruleset/nftables.conf",
file_template: "table filter {\n\
set rate_limit {\n\
type ipv4_addr\n\
size 65535\n\
flags dynamic\n\
}\n\
\n\
set connection_limit {\n\
type ipv4_addr\n\
size 65535\n\
flags dynamic\n\
}\n\
\n\
chain INPUT {\n\
type filter hook input priority 0; policy drop;\n\
iif lo accept\n\
ct state new add @rate_limit { ip saddr limit rate over 2000/minute burst 1000 packets } log prefix \"Drop - rate limit: \" drop\n\
# Notes about the rule below:\n\
# - The rule allows a maximum of <<MAX_SIMULTANEOUS_CONNECTIONS_PER_IP_ADDRESS>> persistent connections to any ip address.\n\
# - The rule drops all new connections that goes over the configured limit.\n\
ct state new add @connection_limit { ip saddr ct count over <<MAX_SIMULTANEOUS_CONNECTIONS_PER_IP_ADDRESS>> } log prefix \"Drop - connection limit: \" drop\n\
icmp type { echo-reply, destination-unreachable, source-quench, echo-request, time-exceeded, parameter-problem } accept\n\
ct state invalid drop\n\
ct state { established, related } accept\n\
Expand All @@ -395,9 +412,26 @@ table ip6 filter {\n\
}\n\
\n\
table ip6 filter {\n\
set rate_limit {\n\
type ipv6_addr\n\
size 65535\n\
flags dynamic\n\
}\n\
\n\
set connection_limit {\n\
type ipv6_addr\n\
size 65535\n\
flags dynamic\n\
}\n\
\n\
chain INPUT {\n\
type filter hook input priority 0; policy drop;\n\
iif lo accept\n\
ct state new add @rate_limit { ip6 saddr limit rate over 2000/minute burst 1000 packets } log prefix \"Drop - rate limit: \" drop\n\
# Notes about the rule below:\n\
# - The rule allows a maximum of <<MAX_SIMULTANEOUS_CONNECTIONS_PER_IP_ADDRESS>> persistent connections to any ip6 address.\n\
# - The rule drops all new connections that goes over the configured limit.\n\
ct state new add @connection_limit { ip6 saddr ct count over <<MAX_SIMULTANEOUS_CONNECTIONS_PER_IP_ADDRESS>> } log prefix \"Drop - connection limit: \" drop\n\
icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-request, echo-reply, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept\n\
ct state { invalid } drop\n\
ct state { established, related } accept\n\
Expand Down Expand Up @@ -436,11 +470,12 @@ table ip6 filter {\n\
"2602:fb2b:100::/48",
"2607:fb58:9005::/48",
],
ports: [7070, 9091, 9100, 9324, 19531],
ports: [22, 7070, 9091, 9100, 9324, 19531],
action: 1,
comment: "Default rule from template",
direction: null,
}]
}],
max_simultaneous_connections_per_ip_address: 400,
},

registration: {
Expand Down
1 change: 1 addition & 0 deletions rs/config/src/config_sample.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ pub const SAMPLE_CONFIG: &str = r#"
ipv6_tcp_rule_template: "",
ipv6_udp_rule_template: "",
default_rules: [],
max_simultaneous_connections_per_ip_address: 0,
},
// =================================
Expand Down
4 changes: 4 additions & 0 deletions rs/config/src/firewall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ pub struct BoundaryNodeConfig {
pub ipv6_udp_rule_template: String,
#[cfg_attr(test, proptest(strategy = "any::<String>().prop_map(|_x| vec![])"))]
pub default_rules: Vec<FirewallRule>,
/// We allow a maximum of `max_simultaneous_connections_per_ip_address` persistent connections to any ip address.
/// Any ip address with `max_simultaneous_connections_per_ip_address` connections will be dropped if a new connection is attempted.
pub max_simultaneous_connections_per_ip_address: u32,
}

impl Default for BoundaryNodeConfig {
Expand All @@ -81,6 +84,7 @@ impl Default for BoundaryNodeConfig {
ipv4_udp_rule_template: String::default(),
ipv6_udp_rule_template: String::default(),
default_rules: Vec::default(),
max_simultaneous_connections_per_ip_address: 0,
}
}
}
4 changes: 4 additions & 0 deletions rs/orchestrator/src/firewall.rs
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,10 @@ impl FirewallConfigTemplate for BoundaryNodeFirewallConfig {
],
),
)
.replace(
"<<MAX_SIMULTANEOUS_CONNECTIONS_PER_IP_ADDRESS>>",
&self.max_simultaneous_connections_per_ip_address.to_string(),
)
}
}

Expand Down
34 changes: 34 additions & 0 deletions rs/orchestrator/testdata/nftables_boundary_node.conf.golden
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
table filter {
set rate_limit {
type ipv4_addr
size 65535
flags dynamic
}

set connection_limit {
type ipv4_addr
size 65535
flags dynamic
}

chain INPUT {
type filter hook input priority 0; policy drop;
iif lo accept
ct state new add @rate_limit { ip saddr limit rate over 2000/minute burst 1000 packets } log prefix "Drop - rate limit: " drop
# Notes about the rule below:
# - The rule allows a maximum of 400 persistent connections to any ip address.
# - The rule drops all new connections that goes over the configured limit.
ct state new add @connection_limit { ip saddr ct count over 400 } log prefix "Drop - connection limit: " drop
icmp type { echo-reply, destination-unreachable, source-quench, echo-request, time-exceeded, parameter-problem } accept
ct state invalid drop
ct state { established, related } accept
Expand Down Expand Up @@ -29,9 +46,26 @@ ip saddr {6.6.6.6} ct state { new } tcp dport {1006} accept # global
}

table ip6 filter {
set rate_limit {
type ipv6_addr
size 65535
flags dynamic
}

set connection_limit {
type ipv6_addr
size 65535
flags dynamic
}

chain INPUT {
type filter hook input priority 0; policy drop;
iif lo accept
ct state new add @rate_limit { ip6 saddr limit rate over 2000/minute burst 1000 packets } log prefix "Drop - rate limit: " drop
# Notes about the rule below:
# - The rule allows a maximum of 400 persistent connections to any ip6 address.
# - The rule drops all new connections that goes over the configured limit.
ct state new add @connection_limit { ip6 saddr ct count over 400 } log prefix "Drop - connection limit: " drop
icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-request, echo-reply, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept
ct state { invalid } drop
ct state { established, related } accept
Expand Down
12 changes: 12 additions & 0 deletions rs/tests/src/boundary_nodes/api_boundary_nodes_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,18 @@ pub fn decentralization_test(env: TestEnv) {
.expect("API BNs didn't report healthy");
}

info!(
log,
"Checking nftables with firewall settings enabled on API BNs ..."
);
for node in unassigned_nodes.iter() {
let rules = node
.block_on_bash_script("sudo nft list ruleset")
.expect("unable to read nft ruleset");
assert!(rules.contains("ct state new add @rate_limit"));
assert!(rules.contains("ct state new add @connection_limit"));
}

info!(log, "Installing counter canisters ...");
let canister_values: Vec<u32> = vec![1, 3];
let canister_ids: Vec<Principal> = block_on(install_canisters(
Expand Down

0 comments on commit ee6addc

Please sign in to comment.