Skip to content

Commit 8314870

Browse files
authored
Merge pull request #1074 from maxslarsson/set-ipv4-src-addr
Improve IPv4 source address selection for multi-subnet interfaces
2 parents 8ff7cb9 + 4f739af commit 8314870

File tree

4 files changed

+109
-10
lines changed

4 files changed

+109
-10
lines changed

src/iface/interface/ipv4.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,27 @@ impl InterfaceInner {
3838

3939
/// Get an IPv4 source address based on a destination address.
4040
///
41-
/// **NOTE**: unlike for IPv6, no specific selection algorithm is implemented. The first IPv4
42-
/// address from the interface is returned.
41+
/// This function tries to find the first IPv4 address from the interface
42+
/// that is in the same subnet as the destination address. If no such
43+
/// address is found, the first IPv4 address from the interface is returned.
4344
#[allow(unused)]
44-
pub(crate) fn get_source_address_ipv4(&self, _dst_addr: &Ipv4Address) -> Option<Ipv4Address> {
45+
pub(crate) fn get_source_address_ipv4(&self, dst_addr: &Ipv4Address) -> Option<Ipv4Address> {
46+
let mut first_ipv4 = None;
4547
for cidr in self.ip_addrs.iter() {
4648
#[allow(irrefutable_let_patterns)] // if only ipv4 is enabled
4749
if let IpCidr::Ipv4(cidr) = cidr {
48-
return Some(cidr.address());
50+
// Return immediately if we find an address in the same subnet
51+
if cidr.contains_addr(dst_addr) {
52+
return Some(cidr.address());
53+
}
54+
55+
// Remember the first IPv4 address as fallback
56+
if first_ipv4.is_none() {
57+
first_ipv4 = Some(cidr.address());
58+
}
4959
}
5060
}
51-
None
61+
first_ipv4
5262
}
5363

5464
/// Checks if an address is broadcast, taking into account ipv4 subnet-local

src/iface/interface/mod.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -333,15 +333,18 @@ impl Interface {
333333
self.inner.ipv6_addr()
334334
}
335335

336-
/// Get an address from the interface that could be used as source address. For IPv4, this is
337-
/// the first IPv4 address from the list of addresses. For IPv6, the address is based on the
338-
/// destination address and uses RFC6724 for selecting the source address.
336+
/// Get an address from the interface that could be used as source address.
337+
/// For IPv4, this function tries to find a registered IPv4 address in the same
338+
/// subnet as the destination, falling back to the first IPv4 address if none is
339+
/// found. For IPv6, the selection is based on RFC6724.
339340
pub fn get_source_address(&self, dst_addr: &IpAddress) -> Option<IpAddress> {
340341
self.inner.get_source_address(dst_addr)
341342
}
342343

343-
/// Get an address from the interface that could be used as source address. This is the first
344-
/// IPv4 address from the list of addresses in the interface.
344+
/// Get an IPv4 source address based on a destination address. This function tries
345+
/// to find the first IPv4 address from the interface that is in the same subnet as
346+
/// the destination address. If no such address is found, the first IPv4 address
347+
/// from the interface is returned.
345348
#[cfg(feature = "proto-ipv4")]
346349
pub fn get_source_address_ipv4(&self, dst_addr: &Ipv4Address) -> Option<Ipv4Address> {
347350
self.inner.get_source_address_ipv4(dst_addr)

src/iface/interface/tests/ipv4.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,3 +1208,83 @@ fn test_icmp_reply_size(#[case] medium: Medium) {
12081208
))
12091209
);
12101210
}
1211+
1212+
#[rstest]
1213+
#[case(Medium::Ip)]
1214+
#[cfg(feature = "medium-ip")]
1215+
#[case(Medium::Ethernet)]
1216+
#[cfg(feature = "medium-ethernet")]
1217+
fn get_source_address(#[case] medium: Medium) {
1218+
let (mut iface, _, _) = setup(medium);
1219+
1220+
const OWN_UNIQUE_LOCAL_ADDR1: Ipv4Address = Ipv4Address::new(172, 18, 1, 2);
1221+
const OWN_UNIQUE_LOCAL_ADDR2: Ipv4Address = Ipv4Address::new(172, 24, 24, 14);
1222+
1223+
// List of addresses of the interface:
1224+
// 172.18.1.2/24
1225+
// 172.24.24.14/24
1226+
iface.update_ip_addrs(|addrs| {
1227+
addrs.clear();
1228+
1229+
addrs
1230+
.push(IpCidr::Ipv4(Ipv4Cidr::new(OWN_UNIQUE_LOCAL_ADDR1, 24)))
1231+
.unwrap();
1232+
addrs
1233+
.push(IpCidr::Ipv4(Ipv4Cidr::new(OWN_UNIQUE_LOCAL_ADDR2, 24)))
1234+
.unwrap();
1235+
});
1236+
1237+
// List of addresses we test:
1238+
// 172.18.1.254 -> 172.18.1.2
1239+
// 172.24.24.12 -> 172.24.24.14
1240+
// 172.24.23.254 -> 172.18.1.2
1241+
const UNIQUE_LOCAL_ADDR1: Ipv4Address = Ipv4Address::new(172, 18, 1, 254);
1242+
const UNIQUE_LOCAL_ADDR2: Ipv4Address = Ipv4Address::new(172, 24, 24, 12);
1243+
const UNIQUE_LOCAL_ADDR3: Ipv4Address = Ipv4Address::new(172, 24, 23, 254);
1244+
1245+
assert_eq!(
1246+
iface.inner.get_source_address_ipv4(&UNIQUE_LOCAL_ADDR1),
1247+
Some(OWN_UNIQUE_LOCAL_ADDR1)
1248+
);
1249+
1250+
assert_eq!(
1251+
iface.inner.get_source_address_ipv4(&UNIQUE_LOCAL_ADDR2),
1252+
Some(OWN_UNIQUE_LOCAL_ADDR2)
1253+
);
1254+
assert_eq!(
1255+
iface.inner.get_source_address_ipv4(&UNIQUE_LOCAL_ADDR3),
1256+
Some(OWN_UNIQUE_LOCAL_ADDR1)
1257+
);
1258+
}
1259+
1260+
#[rstest]
1261+
#[case(Medium::Ip)]
1262+
#[cfg(feature = "medium-ip")]
1263+
#[case(Medium::Ethernet)]
1264+
#[cfg(feature = "medium-ethernet")]
1265+
fn get_source_address_empty_interface(#[case] medium: Medium) {
1266+
let (mut iface, _, _) = setup(medium);
1267+
1268+
iface.update_ip_addrs(|ips| ips.clear());
1269+
1270+
// List of addresses we test:
1271+
// 172.18.1.254 -> None
1272+
// 172.24.24.12 -> None
1273+
// 172.24.23.254 -> None
1274+
const UNIQUE_LOCAL_ADDR1: Ipv4Address = Ipv4Address::new(172, 18, 1, 254);
1275+
const UNIQUE_LOCAL_ADDR2: Ipv4Address = Ipv4Address::new(172, 24, 24, 12);
1276+
const UNIQUE_LOCAL_ADDR3: Ipv4Address = Ipv4Address::new(172, 24, 23, 254);
1277+
1278+
assert_eq!(
1279+
iface.inner.get_source_address_ipv4(&UNIQUE_LOCAL_ADDR1),
1280+
None
1281+
);
1282+
assert_eq!(
1283+
iface.inner.get_source_address_ipv4(&UNIQUE_LOCAL_ADDR2),
1284+
None
1285+
);
1286+
assert_eq!(
1287+
iface.inner.get_source_address_ipv4(&UNIQUE_LOCAL_ADDR3),
1288+
None
1289+
);
1290+
}

src/iface/interface/tests/ipv6.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -997,13 +997,15 @@ fn get_source_address() {
997997
// fd00::201:1:1:1:1 -> fd00::201:1:1:1:2
998998
// fd01::201:1:1:1:1 -> fd01::201:1:1:1:2
999999
// fd02::201:1:1:1:1 -> fd00::201:1:1:1:2 (because first added in the list)
1000+
// fd01::201:1:1:1:3 -> fd01::201:1:1:1:2 (because in same subnet)
10001001
// ff02::1 -> fe80::1 (same scope)
10011002
// 2001:db8:3::2 -> 2001:db8:3::1
10021003
// 2001:db9:3::2 -> 2001:db8:3::1
10031004
const LINK_LOCAL_ADDR: Ipv6Address = Ipv6Address::new(0xfe80, 0, 0, 0, 0, 0, 0, 42);
10041005
const UNIQUE_LOCAL_ADDR1: Ipv6Address = Ipv6Address::new(0xfd00, 0, 0, 201, 1, 1, 1, 1);
10051006
const UNIQUE_LOCAL_ADDR2: Ipv6Address = Ipv6Address::new(0xfd01, 0, 0, 201, 1, 1, 1, 1);
10061007
const UNIQUE_LOCAL_ADDR3: Ipv6Address = Ipv6Address::new(0xfd02, 0, 0, 201, 1, 1, 1, 1);
1008+
const UNIQUE_LOCAL_ADDR4: Ipv6Address = Ipv6Address::new(0xfd01, 0, 0, 201, 1, 1, 1, 3);
10071009
const GLOBAL_UNICAST_ADDR1: Ipv6Address =
10081010
Ipv6Address::new(0x2001, 0x0db8, 0x0003, 0, 0, 0, 0, 2);
10091011
const GLOBAL_UNICAST_ADDR2: Ipv6Address =
@@ -1030,6 +1032,10 @@ fn get_source_address() {
10301032
iface.inner.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR3),
10311033
OWN_UNIQUE_LOCAL_ADDR1
10321034
);
1035+
assert_eq!(
1036+
iface.inner.get_source_address_ipv6(&UNIQUE_LOCAL_ADDR4),
1037+
OWN_UNIQUE_LOCAL_ADDR2
1038+
);
10331039
assert_eq!(
10341040
iface
10351041
.inner

0 commit comments

Comments
 (0)