Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions plugin-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,14 @@ Example JSON input:
}
```

**Note on `static_ips`**: When multiple subnets are configured in the network, you can specify multiple static IP addresses. Each IP will be assigned to the subnet it belongs to. For example, with subnets `10.88.0.0/16` and `10.89.0.0/16`, you could specify:

```
"static_ips": ["10.88.0.50", "10.88.0.51", "10.89.0.100"]

Choose a reason for hiding this comment

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

Need ability to specify static IP address in CIDR format.

Copy link
Member

Choose a reason for hiding this comment

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

Are you saying that you need multiple static IPs that are not in the same subnet?

Copy link

@vikas-goel vikas-goel Dec 16, 2025

Choose a reason for hiding this comment

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

Multiple static IP addresses that part of multiple smaller subnets of a large configured subnet?

Copy link
Member

Choose a reason for hiding this comment

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

Can you explain what the exact intention is here? I can't really understand the benefits of setting a subnet different from that of the actual network the interface is connected to, even if that subnet is part of the larger subnet the network uses.

Copy link
Member

Choose a reason for hiding this comment

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

I agree different subnets on a static ip address allocation is not something I like to support.
The way to do that would be multiple --subnet options on network create.

```

All IPs matching a subnet's range will be assigned to that subnet. Networks with duplicate or overlapping subnet definitions will result in an error.

Example JSON output:
```
{
Expand Down
65 changes: 49 additions & 16 deletions src/network/core_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,31 @@ where
Ok(Some(val))
}

fn validate_subnets(network: &types::Network) -> NetavarkResult<()> {
Copy link
Member

Choose a reason for hiding this comment

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

Can you write some rust unit tests for this please. That allows us to cover some more subnet combinations and is faster than dealing with the more expensive integration tests

let subnets: Vec<&types::Subnet> = network.subnets.iter().flatten().collect();
for (i, subnet1) in subnets.iter().enumerate() {
for subnet2 in subnets.iter().skip(i + 1) {
// Check for exact duplicate
if subnet1.subnet == subnet2.subnet {
return Err(NetavarkError::msg(format!(
"duplicate subnet defined: {}",
subnet1.subnet
)));
}
// Check for overlap (one contains the other)
if subnet1.subnet.contains(&subnet2.subnet.network())
|| subnet2.subnet.contains(&subnet1.subnet.network())
{
return Err(NetavarkError::msg(format!(
"overlapping subnets: {} and {}",
subnet1.subnet, subnet2.subnet
)));
}
}
}
Ok(())
}

pub fn get_ipam_addresses<'a>(
per_network_opts: &'a types::PerNetworkOptions,
network: &'a types::Network,
Expand Down Expand Up @@ -91,8 +116,11 @@ pub fn get_ipam_addresses<'a>(
Some(i) => i,
};

// prepare a vector of static aps with appropriate cidr
for (idx, subnet) in network.subnets.iter().flatten().enumerate() {
// check for duplicates and overlaps subnets
validate_subnets(network)?;

// prepare a vector of static ips with appropriate cidr
for subnet in network.subnets.iter().flatten() {
let subnet_mask_cidr = subnet.subnet.prefix_len();
if let Some(gw) = subnet.gateway {
let gw_net = match ipnet::IpNet::new(gw, subnet_mask_cidr) {
Expand All @@ -112,20 +140,25 @@ pub fn get_ipam_addresses<'a>(
ipv6_enabled = true;
}

// Build up response information
let container_address: ipnet::IpNet =
match format!("{}/{}", static_ips[idx], subnet_mask_cidr).parse() {
Ok(i) => i,
Err(e) => {
return Err(NetavarkError::SubnetParse(e));
}
};
// Add the IP to the address_vector
container_addresses.push(container_address);
net_addresses.push(types::NetAddress {
gateway: subnet.gateway,
ipnet: container_address,
});
// build up response information - add all static IPs that match this subnet
for static_ip in static_ips {
// check if the static IP belongs to this subnet
if subnet.subnet.contains(static_ip) {
let container_address: ipnet::IpNet =
match format!("{}/{}", static_ip, subnet_mask_cidr).parse() {
Copy link
Member

Choose a reason for hiding this comment

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

pre existing but it makes no sense to combine this into a string to parse it again, we can create the subnet directly ipnet::IpNet::new(ip, prefix_len)

Ok(i) => i,
Err(e) => {
return Err(NetavarkError::SubnetParse(e));
}
};
// add the IP to the address_vector
container_addresses.push(container_address);
net_addresses.push(types::NetAddress {
gateway: subnet.gateway,
ipnet: container_address,
});
}
}
}

let routes: Vec<Route> = match create_route_list(&network.routes) {
Expand Down
73 changes: 73 additions & 0 deletions test/300-macvlan.bats
Original file line number Diff line number Diff line change
Expand Up @@ -389,3 +389,76 @@ EOF
assert_json "$default_route_v6" '.[0].dst' == "default" "Default route was selected"
assert_json "$default_route_v6" '.[0].metric' == "200" "v6 route metric matches v4"
}



@test "macvlan multiple static ips single subnet" {
Copy link
Member

Choose a reason for hiding this comment

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

There is a ton of duplication between the testfiles here, I would really prefer if we can start deduplicating some of this.

i.e. compare test_port_fw function to generate a config dynamically for tests.

Right now it duplicates all test files for each driver which is not great.

# Config file defines a single subnet:
# - 10.88.0.0/16
# Static IPs: 10.88.0.5, 10.88.0.10, 10.88.0.15

run_netavark --file ${TESTSDIR}/testfiles/macvlan-multiple-ips.json setup $(get_container_netns_path)
result="$output"

assert_json "$result" ".podman.interfaces.eth0.subnets | length" "==" "3" "three ips assigned"
assert_json "$result" ".podman.interfaces.eth0.subnets[0].ipnet" "==" "10.88.0.5/16"
assert_json "$result" ".podman.interfaces.eth0.subnets[1].ipnet" "==" "10.88.0.10/16"
assert_json "$result" ".podman.interfaces.eth0.subnets[2].ipnet" "==" "10.88.0.15/16"

run_in_container_netns ip addr show eth0
assert "$output" "=~" "10.88.0.5/16"
assert "$output" "=~" "10.88.0.10/16"
assert "$output" "=~" "10.88.0.15/16"

run_netavark --file ${TESTSDIR}/testfiles/macvlan-multiple-ips.json teardown $(get_container_netns_path)
}

@test "macvlan multiple static ips multiple subnets" {
# Config file defines two subnets:
# - 10.88.0.0/16 (gateway: 10.88.0.1)
# - 10.89.0.0/16 (gateway: 10.89.0.1)
# Static IPs: 10.88.0.5, 10.89.0.10, 10.88.0.15

run_netavark --file ${TESTSDIR}/testfiles/macvlan-multiple-subnets.json setup $(get_container_netns_path)
result="$output"

assert_json "$result" ".podman.interfaces.eth0.subnets | length" "==" "3" "three ips assigned"

assert_json "$result" ".podman.interfaces.eth0.subnets[0].ipnet" "==" "10.88.0.5/16"
assert_json "$result" ".podman.interfaces.eth0.subnets[0].gateway" "==" "10.88.0.1"
assert_json "$result" ".podman.interfaces.eth0.subnets[1].ipnet" "==" "10.88.0.15/16"
assert_json "$result" ".podman.interfaces.eth0.subnets[1].gateway" "==" "10.88.0.1"

assert_json "$result" ".podman.interfaces.eth0.subnets[2].ipnet" "==" "10.89.0.10/16"
assert_json "$result" ".podman.interfaces.eth0.subnets[2].gateway" "==" "10.89.0.1"


run_in_container_netns ip addr show eth0
assert "$output" "=~" "10.88.0.5/16"
assert "$output" "=~" "10.89.0.10/16"
assert "$output" "=~" "10.88.0.15/16"

run_netavark --file ${TESTSDIR}/testfiles/macvlan-multiple-subnets.json teardown $(get_container_netns_path)
}

@test "macvlan overlapping subnets error" {
# Config file defines overlapping subnets:
# - 10.1.2.0/23 covers 10.1.2.0-10.1.3.255
# - 10.1.3.248/30 covers 10.1.3.248-10.1.3.251 (subset of the /23)
# Static IPs: 10.1.2.5, 10.1.3.249, 10.1.2.100

expected_rc=1 run_netavark --file ${TESTSDIR}/testfiles/macvlan-overlapping-subnets.json setup $(get_container_netns_path)

assert "$output" "=~" "overlapping subnets" "error message should mention overlapping subnets"
}

@test "macvlan duplicate subnet definition error" {
# Config file defines the same subnet twice:
# - 10.88.0.0/16 (first definition)
# - 10.88.0.0/16 (duplicate)
# Static IPs: 10.88.0.5, 10.88.0.10, 10.88.0.15

expected_rc=1 run_netavark --file ${TESTSDIR}/testfiles/macvlan-duplicate-subnet.json setup $(get_container_netns_path)

assert "$output" "=~" "duplicate subnet" "error message should mention duplicate subnet"
}
71 changes: 71 additions & 0 deletions test/400-ipvlan.bats
Original file line number Diff line number Diff line change
Expand Up @@ -323,3 +323,74 @@ EOF
run_in_container_netns ip -o link show
assert "${#lines[@]}" == 2 "only two interfaces (lo, eth0) in the netns, the tmp ipvlan interface should be gone"
}

@test "ipvlan multiple static ips single subnet" {

Choose a reason for hiding this comment

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

Will macvlan, too, support it?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, it should. Should I add tests for that?

Choose a reason for hiding this comment

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

Yes, please.

Copy link
Member Author

Choose a reason for hiding this comment

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

I have added a macvlan test cases.

# Config file defines a single subnet:
# - 10.88.0.0/16
# Static IPs: 10.88.0.5, 10.88.0.10, 10.88.0.15

run_netavark --file ${TESTSDIR}/testfiles/ipvlan-multiple-ips.json setup $(get_container_netns_path)
result="$output"

assert_json "$result" ".podman.interfaces.eth0.subnets | length" "==" "3" "three ips assigned"
assert_json "$result" ".podman.interfaces.eth0.subnets[0].ipnet" "==" "10.88.0.5/16"
assert_json "$result" ".podman.interfaces.eth0.subnets[1].ipnet" "==" "10.88.0.10/16"
assert_json "$result" ".podman.interfaces.eth0.subnets[2].ipnet" "==" "10.88.0.15/16"

run_in_container_netns ip addr show eth0
assert "$output" "=~" "10.88.0.5/16"
assert "$output" "=~" "10.88.0.10/16"
assert "$output" "=~" "10.88.0.15/16"

run_netavark --file ${TESTSDIR}/testfiles/ipvlan-multiple-ips.json teardown $(get_container_netns_path)
}

@test "ipvlan multiple static ips multiple subnets" {
# Config file defines two subnets:
# - 10.88.0.0/16 (gateway: 10.88.0.1)
# - 10.89.0.0/16 (gateway: 10.89.0.1)
# Static IPs: 10.88.0.5, 10.89.0.10, 10.88.0.15

run_netavark --file ${TESTSDIR}/testfiles/ipvlan-multiple-subnets.json setup $(get_container_netns_path)
result="$output"

assert_json "$result" ".podman.interfaces.eth0.subnets | length" "==" "3" "three ips assigned"

assert_json "$result" ".podman.interfaces.eth0.subnets[0].ipnet" "==" "10.88.0.5/16"
assert_json "$result" ".podman.interfaces.eth0.subnets[0].gateway" "==" "10.88.0.1"
assert_json "$result" ".podman.interfaces.eth0.subnets[1].ipnet" "==" "10.88.0.15/16"
assert_json "$result" ".podman.interfaces.eth0.subnets[1].gateway" "==" "10.88.0.1"

assert_json "$result" ".podman.interfaces.eth0.subnets[2].ipnet" "==" "10.89.0.10/16"
assert_json "$result" ".podman.interfaces.eth0.subnets[2].gateway" "==" "10.89.0.1"


run_in_container_netns ip addr show eth0
assert "$output" "=~" "10.88.0.5/16"
assert "$output" "=~" "10.89.0.10/16"
assert "$output" "=~" "10.88.0.15/16"

run_netavark --file ${TESTSDIR}/testfiles/ipvlan-multiple-subnets.json teardown $(get_container_netns_path)
}

@test "ipvlan overlapping subnets error" {
# Config file defines overlapping subnets:
# - 10.1.2.0/23 covers 10.1.2.0-10.1.3.255
# - 10.1.3.248/30 covers 10.1.3.248-10.1.3.251 (subset of the /23)
# Static IPs: 10.1.2.5, 10.1.3.249, 10.1.2.100

expected_rc=1 run_netavark --file ${TESTSDIR}/testfiles/ipvlan-overlapping-subnets.json setup $(get_container_netns_path)

assert "$output" "=~" "overlapping subnets" "error message should mention overlapping subnets"
}

@test "ipvlan duplicate subnet definition error" {
# Config file defines the same subnet twice:
# - 10.88.0.0/16 (first definition)
# - 10.88.0.0/16 (duplicate)
# Static IPs: 10.88.0.5, 10.88.0.10, 10.88.0.15

expected_rc=1 run_netavark --file ${TESTSDIR}/testfiles/ipvlan-duplicate-subnet.json setup $(get_container_netns_path)

assert "$output" "=~" "duplicate subnet" "error message should mention duplicate subnet"
}
38 changes: 38 additions & 0 deletions test/testfiles/ipvlan-duplicate-subnet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"container_id": "someID",
"container_name": "someName",
"networks": {
"podman": {
"static_ips": [
"10.88.0.5",
"10.88.0.10",
"10.88.0.15"
],
"interface_name": "eth0"
}
},
"network_info": {
"podman": {
"name": "podman",
"id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9",
"driver": "ipvlan",
"network_interface": "dummy0",
"subnets": [
{
"subnet": "10.88.0.0/16",
"gateway": "10.88.0.1"
},
{
"subnet": "10.88.0.0/16",
"gateway": "10.88.0.1"
}
],
"ipv6_enabled": false,
"internal": false,
"dns_enabled": false,
"ipam_options": {
"driver": "host-local"
}
}
}
}
34 changes: 34 additions & 0 deletions test/testfiles/ipvlan-multiple-ips.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"container_id": "someID",
"container_name": "someName",
"networks": {
"podman": {
"static_ips": [
"10.88.0.5",
"10.88.0.10",
"10.88.0.15"
],
"interface_name": "eth0"
}
},
"network_info": {
"podman": {
"name": "podman",
"id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9",
"driver": "ipvlan",
"network_interface": "dummy0",
"subnets": [
{
"subnet": "10.88.0.0/16",
"gateway": "10.88.0.1"
}
],
"ipv6_enabled": false,
"internal": false,
"dns_enabled": false,
"ipam_options": {
"driver": "host-local"
}
}
}
}
38 changes: 38 additions & 0 deletions test/testfiles/ipvlan-multiple-subnets.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"container_id": "someID",
"container_name": "someName",
"networks": {
"podman": {
"static_ips": [
"10.88.0.5",
"10.89.0.10",
"10.88.0.15"
],
"interface_name": "eth0"
}
},
"network_info": {
"podman": {
"name": "podman",
"id": "2f259bab93aaaaa2542ba43ef33eb990d0999ee1b9924b557b7be53c0b7a1bb9",
"driver": "ipvlan",
"network_interface": "dummy0",
"subnets": [
{
"subnet": "10.88.0.0/16",
"gateway": "10.88.0.1"
},
{
"subnet": "10.89.0.0/16",
"gateway": "10.89.0.1"
}
],
"ipv6_enabled": false,
"internal": false,
"dns_enabled": false,
"ipam_options": {
"driver": "host-local"
}
}
}
}
Loading