Skip to content

Commit d6e460b

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

File tree

4 files changed

+158
-5
lines changed

4 files changed

+158
-5
lines changed

smartcontract/cli/src/device/create.rs

Lines changed: 134 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,39 @@ 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!(
60+
eyre::bail!(
6161
"Device with code '{}' already exists",
6262
self.code
63-
));
63+
);
6464
}
6565
if devices.iter().any(|(_, d)| d.public_ip == self.public_ip) {
66-
return Err(eyre::eyre!(
66+
eyre::bail!(
6767
"Device with public ip '{}' already exists",
6868
&self.public_ip
69-
));
69+
);
70+
}
71+
72+
for device in devices.values() {
73+
for dz_prefix in device.dz_prefixes.iter() {
74+
if dz_prefix.contains(self.public_ip) {
75+
eyre::bail!(
76+
"Public IP '{}' conflicts with existing device '{}' dz_prefix '{}'",
77+
self.public_ip,
78+
device.code,
79+
dz_prefix
80+
);
81+
}
82+
}
83+
}
84+
85+
for dz_prefix in self.dz_prefixes.iter() {
86+
if dz_prefix.contains(self.public_ip) {
87+
eyre::bail!(
88+
"Public IP '{}' conflicts with device's own dz_prefix '{}'",
89+
self.public_ip,
90+
dz_prefix
91+
);
92+
}
7093
}
7194

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

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)