Skip to content

Commit f0fa7f3

Browse files
Validate public IP doesn't clash with dz_prefix
Resolves: #1836
1 parent faed386 commit f0fa7f3

File tree

4 files changed

+156
-9
lines changed

4 files changed

+156
-9
lines changed

smartcontract/cli/src/device/create.rs

Lines changed: 132 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,33 @@ impl CreateDeviceCliCommand {
5757

5858
let devices = client.list_device(ListDeviceCommand)?;
5959
if devices.iter().any(|(_, d)| d.code == self.code) {
60-
return Err(eyre::eyre!(
61-
"Device with code '{}' already exists",
62-
self.code
63-
));
60+
eyre::bail!("Device with code '{}' already exists", self.code);
6461
}
6562
if devices.iter().any(|(_, d)| d.public_ip == self.public_ip) {
66-
return Err(eyre::eyre!(
67-
"Device with public ip '{}' already exists",
68-
&self.public_ip
69-
));
63+
eyre::bail!("Device with public ip '{}' already exists", &self.public_ip);
64+
}
65+
66+
for device in devices.values() {
67+
for dz_prefix in device.dz_prefixes.iter() {
68+
if dz_prefix.contains(self.public_ip) {
69+
eyre::bail!(
70+
"Public IP '{}' conflicts with existing device '{}' dz_prefix '{}'",
71+
self.public_ip,
72+
device.code,
73+
dz_prefix
74+
);
75+
}
76+
}
77+
}
78+
79+
for dz_prefix in self.dz_prefixes.iter() {
80+
if dz_prefix.contains(self.public_ip) {
81+
eyre::bail!(
82+
"Public IP '{}' conflicts with device's own dz_prefix '{}'",
83+
self.public_ip,
84+
dz_prefix
85+
);
86+
}
7087
}
7188

7289
let contributor_pk = match parse_pubkey(&self.contributor) {
@@ -111,7 +128,7 @@ impl CreateDeviceCliCommand {
111128
} else {
112129
match Pubkey::from_str(metrics_publisher) {
113130
Ok(pk) => pk,
114-
Err(_) => return Err(eyre::eyre!("Invalid metrics publisher Pubkey")),
131+
Err(_) => eyre::bail!("Invalid metrics publisher Pubkey"),
115132
}
116133
}
117134
} else {
@@ -278,4 +295,110 @@ mod tests {
278295
output_str,"Signature: 3QnHBSdd4doEF6FgpLCejqEw42UQjfvNhQJwoYDSpoBszpCCqVft4cGoneDCnZ6Ez3ujzavzUu85u6F79WtLhcsv\n"
279296
);
280297
}
298+
299+
#[test]
300+
fn test_cli_device_create_fails_when_public_ip_conflicts_with_existing_device_dz_prefix() {
301+
use doublezero_sdk::{Device, DeviceStatus};
302+
303+
let mut client = create_test_client();
304+
305+
let location_pk = Pubkey::from_str_const("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx");
306+
let exchange_pk = Pubkey::from_str_const("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcc");
307+
let contributor_pk = Pubkey::from_str_const("HQ3UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx");
308+
309+
// Create an existing device with dz_prefix that will conflict
310+
let existing_device_pk =
311+
Pubkey::from_str_const("HQ4UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx");
312+
let existing_device = Device {
313+
account_type: AccountType::Device,
314+
index: 1,
315+
bump_seed: 255,
316+
reference_count: 0,
317+
code: "existing-device".to_string(),
318+
contributor_pk,
319+
location_pk,
320+
exchange_pk,
321+
device_type: DeviceType::Switch,
322+
public_ip: [100, 0, 0, 1].into(),
323+
// This dz_prefix includes 10.1.5.10
324+
dz_prefixes: "10.1.0.0/16".parse().unwrap(),
325+
metrics_publisher_pk: Pubkey::default(),
326+
status: DeviceStatus::Activated,
327+
mgmt_vrf: String::default(),
328+
interfaces: vec![],
329+
users_count: 0,
330+
max_users: 100,
331+
owner: Pubkey::default(),
332+
};
333+
334+
let mut devices = HashMap::new();
335+
devices.insert(existing_device_pk, existing_device);
336+
337+
client
338+
.expect_check_requirements()
339+
.with(predicate::eq(CHECK_ID_JSON | CHECK_BALANCE))
340+
.returning(|_| Ok(()));
341+
client
342+
.expect_list_device()
343+
.with(predicate::eq(ListDeviceCommand))
344+
.returning(move |_| Ok(devices.clone()));
345+
346+
let mut output = Vec::new();
347+
// Create a device with public_ip 10.1.5.10, which is within existing device's dz_prefix
348+
let res = CreateDeviceCliCommand {
349+
code: "new-device".to_string(),
350+
contributor: contributor_pk.to_string(),
351+
location: location_pk.to_string(),
352+
exchange: exchange_pk.to_string(),
353+
public_ip: [10, 1, 5, 10].into(), // This is within 10.1.0.0/16
354+
dz_prefixes: "192.168.0.0/16".parse().unwrap(),
355+
metrics_publisher: Some(Pubkey::default().to_string()),
356+
mgmt_vrf: String::default(),
357+
wait: false,
358+
}
359+
.execute(&client, &mut output);
360+
361+
assert!(res.is_err());
362+
let err = res.unwrap_err();
363+
assert!(err.to_string().contains("Public IP '10.1.5.10' conflicts with existing device 'existing-device' dz_prefix '10.1.0.0/16'"));
364+
}
365+
366+
#[test]
367+
fn test_cli_device_create_fails_when_public_ip_conflicts_with_own_dz_prefix() {
368+
let mut client = create_test_client();
369+
370+
let location_pk = Pubkey::from_str_const("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx");
371+
let exchange_pk = Pubkey::from_str_const("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcc");
372+
let contributor_pk = Pubkey::from_str_const("HQ3UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx");
373+
374+
client
375+
.expect_check_requirements()
376+
.with(predicate::eq(CHECK_ID_JSON | CHECK_BALANCE))
377+
.returning(|_| Ok(()));
378+
client
379+
.expect_list_device()
380+
.with(predicate::eq(ListDeviceCommand))
381+
.returning(move |_| Ok(HashMap::new()));
382+
383+
let mut output = Vec::new();
384+
// Create a device where public_ip is within its own dz_prefix
385+
let res = CreateDeviceCliCommand {
386+
code: "test-device".to_string(),
387+
contributor: contributor_pk.to_string(),
388+
location: location_pk.to_string(),
389+
exchange: exchange_pk.to_string(),
390+
public_ip: [10, 1, 5, 10].into(), // This is within 10.1.0.0/16
391+
dz_prefixes: "10.1.0.0/16".parse().unwrap(), // Own prefix contains public_ip
392+
metrics_publisher: Some(Pubkey::default().to_string()),
393+
mgmt_vrf: String::default(),
394+
wait: false,
395+
}
396+
.execute(&client, &mut output);
397+
398+
assert!(res.is_err());
399+
let err = res.unwrap_err();
400+
assert!(err
401+
.to_string()
402+
.contains("Public IP '10.1.5.10' conflicts with device's own dz_prefix '10.1.0.0/16'"));
403+
}
281404
}

smartcontract/programs/common/src/types/network_v4.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ impl NetworkV4 {
2626
pub fn nth(&self, n: u32) -> Option<Ipv4Addr> {
2727
self.0.nth(n)
2828
}
29+
30+
pub fn contains(&self, ip: Ipv4Addr) -> bool {
31+
self.0.contains(ip)
32+
}
2933
}
3034

3135
impl Default for NetworkV4 {

smartcontract/programs/doublezero-serviceability/src/error.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ pub enum DoubleZeroError {
113113
AccessPassNotFound, // variant 53
114114
#[error("User account not found")]
115115
UserAccountNotFound, // variant 54
116+
#[error("Invalid Public IP: IP conflicts with DZ prefix")]
117+
InvalidPublicIp, // variant 55
116118
}
117119

118120
impl From<DoubleZeroError> for ProgramError {
@@ -173,6 +175,7 @@ impl From<DoubleZeroError> for ProgramError {
173175
DoubleZeroError::InvalidAccountOwner => ProgramError::Custom(52),
174176
DoubleZeroError::AccessPassNotFound => ProgramError::Custom(53),
175177
DoubleZeroError::UserAccountNotFound => ProgramError::Custom(54),
178+
DoubleZeroError::InvalidPublicIp => ProgramError::Custom(55),
176179
}
177180
}
178181
}
@@ -234,6 +237,7 @@ impl From<u32> for DoubleZeroError {
234237
52 => DoubleZeroError::InvalidAccountOwner,
235238
53 => DoubleZeroError::AccessPassNotFound,
236239
54 => DoubleZeroError::UserAccountNotFound,
240+
55 => DoubleZeroError::InvalidPublicIp,
237241

238242
_ => DoubleZeroError::Custom(e),
239243
}
@@ -313,6 +317,10 @@ mod tests {
313317
InvalidVlanId,
314318
InvalidMaxBandwidth,
315319
InvalidMulticastIp,
320+
InvalidAccountOwner,
321+
AccessPassNotFound,
322+
UserAccountNotFound,
323+
InvalidPublicIp,
316324
];
317325
for err in variants {
318326
let pe: ProgramError = err.clone().into();

smartcontract/programs/doublezero-serviceability/src/processors/device/create.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,18 @@ pub fn process_create_device(
112112
location.reference_count += 1;
113113
exchange.reference_count += 1;
114114

115+
for prefix in value.dz_prefixes.iter() {
116+
if prefix.contains(value.public_ip) {
117+
#[cfg(test)]
118+
msg!(
119+
"Public IP {} conflicts with dz_prefix {}",
120+
value.public_ip,
121+
prefix
122+
);
123+
return Err(DoubleZeroError::InvalidPublicIp.into());
124+
}
125+
}
126+
115127
let device: Device = Device {
116128
account_type: AccountType::Device,
117129
owner: *payer_account.key,

0 commit comments

Comments
 (0)