Skip to content

Commit 7d183a9

Browse files
authored
fix/369-firstusable-lastusable-31-network (#370)
* fix: ipnetwork / first last usable / inverted issue
1 parent 226563e commit 7d183a9

2 files changed

Lines changed: 144 additions & 3 deletions

File tree

src/System.Net.IPNetwork/IPNetwork2Members.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,34 +96,38 @@ public IPAddress Broadcast
9696

9797
/// <summary>
9898
/// Gets first usable IPAddress in Network.
99+
/// Per RFC 3021, /31 networks have no reserved network address.
99100
/// </summary>
100101
public IPAddress FirstUsable
101102
{
102103
get
103104
{
104105
BigInteger first = this.InternalNetwork;
105106
if (this.family == AddressFamily.InterNetwork
106-
&& this.Usable > 1)
107+
&& this.cidr < 31)
107108
{
108-
first+= 1;
109+
first += 1;
109110
}
111+
110112
return ToIPAddress(first, this.family);
111113
}
112114
}
113115

114116
/// <summary>
115117
/// Gets last usable IPAddress in Network.
118+
/// Per RFC 3021, /31 networks have no reserved broadcast address.
116119
/// </summary>
117120
public IPAddress LastUsable
118121
{
119122
get
120123
{
121124
BigInteger last = this.InternalBroadcast;
122125
if (this.family == AddressFamily.InterNetwork
123-
&& this.Usable > 1)
126+
&& this.cidr < 31)
124127
{
125128
last -= 1;
126129
}
130+
127131
return ToIPAddress(last, this.family);
128132
}
129133
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// <copyright file="IPNetworkSlash31Tests.cs" company="IPNetwork">
2+
// Copyright (c) IPNetwork. All rights reserved.
3+
// </copyright>
4+
5+
namespace TestProject.IPNetworkTest;
6+
7+
/// <summary>
8+
/// Tests for /31 networks (point-to-point links) as per RFC 3021.
9+
/// See GitHub issue #369: https://github.com/lduchosal/ipnetwork/issues/369
10+
/// </summary>
11+
[TestClass]
12+
public class IPNetworkSlash31Tests
13+
{
14+
/// <summary>
15+
/// Tests that FirstUsable and LastUsable are correct for /31 networks.
16+
/// According to RFC 3021, /31 networks have 2 usable addresses with no
17+
/// network or broadcast addresses reserved.
18+
///
19+
/// Bug: In version 2.6, FirstUsable and LastUsable are inverted for /31 networks.
20+
/// Example: 167.92.212.82/31
21+
/// Current (Wrong): FirstUsable = 167.92.212.83, LastUsable = 167.92.212.82
22+
/// Expected: FirstUsable = 167.92.212.82, LastUsable = 167.92.212.83
23+
/// </summary>
24+
[TestMethod]
25+
[TestCategory("Parse")]
26+
[TestCategory("RFC3021")]
27+
public void TestSlash31_FirstUsable_LastUsable_Issue369()
28+
{
29+
// Arrange - using the exact example from issue #369
30+
string ipaddress = "167.92.212.82/31";
31+
32+
// Expected values per RFC 3021
33+
string expectedNetwork = "167.92.212.82";
34+
string expectedNetmask = "255.255.255.254";
35+
string expectedBroadcast = "167.92.212.83";
36+
string expectedFirst = "167.92.212.82";
37+
string expectedLast = "167.92.212.83";
38+
string expectedFirstUsable = "167.92.212.82";
39+
string expectedLastUsable = "167.92.212.83";
40+
byte expectedCidr = 31;
41+
uint expectedUsable = 2;
42+
43+
// Act
44+
var ipnetwork = IPNetwork2.Parse(ipaddress);
45+
46+
// Assert
47+
Assert.AreEqual(expectedNetwork, ipnetwork.Network.ToString(), "Network");
48+
Assert.AreEqual(expectedNetmask, ipnetwork.Netmask.ToString(), "Netmask");
49+
Assert.AreEqual(expectedBroadcast, ipnetwork.Broadcast.ToString(), "Broadcast");
50+
Assert.AreEqual(expectedFirst, ipnetwork.First.ToString(), "First");
51+
Assert.AreEqual(expectedLast, ipnetwork.Last.ToString(), "Last");
52+
Assert.AreEqual(expectedCidr, ipnetwork.Cidr, "Cidr");
53+
Assert.AreEqual(expectedUsable, ipnetwork.Usable, "Usable");
54+
Assert.AreEqual(expectedFirstUsable, ipnetwork.FirstUsable.ToString(), "FirstUsable");
55+
Assert.AreEqual(expectedLastUsable, ipnetwork.LastUsable.ToString(), "LastUsable");
56+
57+
// Critical assertion: FirstUsable must be <= LastUsable
58+
Assert.IsTrue(
59+
ipnetwork.FirstUsable.ToString().CompareTo(ipnetwork.LastUsable.ToString()) <= 0 ||
60+
ipnetwork.FirstUsable.Equals(ipnetwork.LastUsable),
61+
"FirstUsable must be less than or equal to LastUsable");
62+
}
63+
64+
/// <summary>
65+
/// Tests another /31 network to ensure the fix works for any /31 subnet.
66+
/// </summary>
67+
[TestMethod]
68+
[TestCategory("Parse")]
69+
[TestCategory("RFC3021")]
70+
public void TestSlash31_AnotherNetwork()
71+
{
72+
// Arrange
73+
string ipaddress = "10.0.0.0/31";
74+
75+
string expectedFirstUsable = "10.0.0.0";
76+
string expectedLastUsable = "10.0.0.1";
77+
uint expectedUsable = 2;
78+
79+
// Act
80+
var ipnetwork = IPNetwork2.Parse(ipaddress);
81+
82+
// Assert
83+
Assert.AreEqual(expectedUsable, ipnetwork.Usable, "Usable");
84+
Assert.AreEqual(expectedFirstUsable, ipnetwork.FirstUsable.ToString(), "FirstUsable");
85+
Assert.AreEqual(expectedLastUsable, ipnetwork.LastUsable.ToString(), "LastUsable");
86+
}
87+
88+
/// <summary>
89+
/// Tests that /32 networks still work correctly (host route - single address).
90+
/// </summary>
91+
[TestMethod]
92+
[TestCategory("Parse")]
93+
public void TestSlash32_StillWorksCorrectly()
94+
{
95+
// Arrange
96+
string ipaddress = "192.168.1.1/32";
97+
98+
string expectedFirstUsable = "192.168.1.1";
99+
string expectedLastUsable = "192.168.1.1";
100+
uint expectedUsable = 1;
101+
102+
// Act
103+
var ipnetwork = IPNetwork2.Parse(ipaddress);
104+
105+
// Assert
106+
Assert.AreEqual(expectedUsable, ipnetwork.Usable, "Usable");
107+
Assert.AreEqual(expectedFirstUsable, ipnetwork.FirstUsable.ToString(), "FirstUsable");
108+
Assert.AreEqual(expectedLastUsable, ipnetwork.LastUsable.ToString(), "LastUsable");
109+
}
110+
111+
/// <summary>
112+
/// Tests that /30 networks still work correctly (traditional smallest subnet with usable hosts).
113+
/// </summary>
114+
[TestMethod]
115+
[TestCategory("Parse")]
116+
public void TestSlash30_StillWorksCorrectly()
117+
{
118+
// Arrange
119+
string ipaddress = "192.168.1.0/30";
120+
121+
string expectedNetwork = "192.168.1.0";
122+
string expectedBroadcast = "192.168.1.3";
123+
string expectedFirstUsable = "192.168.1.1";
124+
string expectedLastUsable = "192.168.1.2";
125+
uint expectedUsable = 2;
126+
127+
// Act
128+
var ipnetwork = IPNetwork2.Parse(ipaddress);
129+
130+
// Assert
131+
Assert.AreEqual(expectedNetwork, ipnetwork.Network.ToString(), "Network");
132+
Assert.AreEqual(expectedBroadcast, ipnetwork.Broadcast.ToString(), "Broadcast");
133+
Assert.AreEqual(expectedUsable, ipnetwork.Usable, "Usable");
134+
Assert.AreEqual(expectedFirstUsable, ipnetwork.FirstUsable.ToString(), "FirstUsable");
135+
Assert.AreEqual(expectedLastUsable, ipnetwork.LastUsable.ToString(), "LastUsable");
136+
}
137+
}

0 commit comments

Comments
 (0)