Skip to content

Commit 85c47be

Browse files
committed
Implement KnownNetworks dual list
Fixes #63627
1 parent 1cc1447 commit 85c47be

File tree

5 files changed

+301
-19
lines changed

5 files changed

+301
-19
lines changed

src/DefaultBuilder/src/ForwardedHeadersOptionsSetup.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,6 @@ public void Configure(ForwardedHeadersOptions options)
2727
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
2828
// Only loopback proxies are allowed by default. Clear that restriction because forwarders are
2929
// being enabled by explicit configuration.
30-
#pragma warning disable ASPDEPR005 // KnownNetworks is obsolete
31-
options.KnownNetworks.Clear();
32-
#pragma warning restore ASPDEPR005 // KnownNetworks is obsolete
3330
options.KnownIPNetworks.Clear();
3431
options.KnownProxies.Clear();
3532
}
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#pragma warning disable ASPDEPR005 // Type or member is obsolete
5+
6+
using AspNetIPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
7+
using IPAddress = System.Net.IPAddress;
8+
using IPNetwork = System.Net.IPNetwork;
9+
10+
namespace Microsoft.AspNetCore.Builder;
11+
12+
/// <summary>
13+
/// Internal list implementation that keeps <see cref="System.Net.IPNetwork"/> and the obsolete
14+
/// <see cref="Microsoft.AspNetCore.HttpOverrides.IPNetwork"/> collections in sync. Modifications
15+
/// through either interface are reflected in the other.
16+
/// </summary>
17+
internal sealed class DualIPNetworkList : IList<IPNetwork>, IList<AspNetIPNetwork>
18+
{
19+
private readonly List<(IPNetwork system, AspNetIPNetwork aspnet)> _items = new();
20+
21+
public DualIPNetworkList()
22+
{
23+
Add(new IPNetwork(IPAddress.Loopback, 8));
24+
}
25+
26+
int ICollection<IPNetwork>.Count => _items.Count;
27+
int ICollection<AspNetIPNetwork>.Count => _items.Count;
28+
29+
bool ICollection<IPNetwork>.IsReadOnly => false;
30+
bool ICollection<AspNetIPNetwork>.IsReadOnly => false;
31+
32+
IPNetwork IList<IPNetwork>.this[int index]
33+
{
34+
get => _items[index].system;
35+
set => _items[index] = (value, new AspNetIPNetwork(value.BaseAddress, value.PrefixLength));
36+
}
37+
38+
AspNetIPNetwork IList<AspNetIPNetwork>.this[int index]
39+
{
40+
get => _items[index].aspnet;
41+
set => _items[index] = (new IPNetwork(value.Prefix, value.PrefixLength), value);
42+
}
43+
44+
public void Add(IPNetwork item)
45+
{
46+
_items.Add((item, new AspNetIPNetwork(item.BaseAddress, item.PrefixLength)));
47+
}
48+
49+
public void Add(AspNetIPNetwork item)
50+
{
51+
_items.Add((new IPNetwork(item.Prefix, item.PrefixLength), item));
52+
}
53+
54+
void ICollection<IPNetwork>.Add(IPNetwork item) => Add(item);
55+
void ICollection<AspNetIPNetwork>.Add(AspNetIPNetwork item) => Add(item);
56+
57+
public void Clear() => _items.Clear();
58+
void ICollection<IPNetwork>.Clear() => Clear();
59+
void ICollection<AspNetIPNetwork>.Clear() => Clear();
60+
61+
public bool Contains(IPNetwork item)
62+
{
63+
var text = item.ToString();
64+
for (var i = 0; i < _items.Count; i++)
65+
{
66+
if (string.Equals(_items[i].system.ToString(), text, StringComparison.Ordinal))
67+
{
68+
return true;
69+
}
70+
}
71+
return false;
72+
}
73+
74+
public bool Contains(AspNetIPNetwork item)
75+
{
76+
for (var i = 0; i < _items.Count; i++)
77+
{
78+
if (_items[i].aspnet.Prefix.Equals(item.Prefix) && _items[i].aspnet.PrefixLength == item.PrefixLength)
79+
{
80+
return true;
81+
}
82+
}
83+
return false;
84+
}
85+
86+
bool ICollection<IPNetwork>.Contains(IPNetwork item) => Contains(item);
87+
bool ICollection<AspNetIPNetwork>.Contains(AspNetIPNetwork item) => Contains(item);
88+
89+
public void CopyTo(IPNetwork[] array, int arrayIndex)
90+
{
91+
for (var i = 0; i < _items.Count; i++)
92+
{
93+
array[arrayIndex + i] = _items[i].system;
94+
}
95+
}
96+
97+
public void CopyTo(AspNetIPNetwork[] array, int arrayIndex)
98+
{
99+
for (var i = 0; i < _items.Count; i++)
100+
{
101+
array[arrayIndex + i] = _items[i].aspnet;
102+
}
103+
}
104+
105+
void ICollection<IPNetwork>.CopyTo(IPNetwork[] array, int arrayIndex) => CopyTo(array, arrayIndex);
106+
void ICollection<AspNetIPNetwork>.CopyTo(AspNetIPNetwork[] array, int arrayIndex) => CopyTo(array, arrayIndex);
107+
108+
public IEnumerator<IPNetwork> GetEnumerator()
109+
{
110+
for (var i = 0; i < _items.Count; i++)
111+
{
112+
yield return _items[i].system;
113+
}
114+
}
115+
116+
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
117+
118+
IEnumerator<AspNetIPNetwork> IEnumerable<AspNetIPNetwork>.GetEnumerator()
119+
{
120+
for (var i = 0; i < _items.Count; i++)
121+
{
122+
yield return _items[i].aspnet;
123+
}
124+
}
125+
126+
public int IndexOf(IPNetwork item)
127+
{
128+
var text = item.ToString();
129+
for (var i = 0; i < _items.Count; i++)
130+
{
131+
if (string.Equals(_items[i].system.ToString(), text, StringComparison.Ordinal))
132+
{
133+
return i;
134+
}
135+
}
136+
return -1;
137+
}
138+
139+
public int IndexOf(AspNetIPNetwork item)
140+
{
141+
for (var i = 0; i < _items.Count; i++)
142+
{
143+
var n = _items[i].aspnet;
144+
if (n.Prefix.Equals(item.Prefix) && n.PrefixLength == item.PrefixLength)
145+
{
146+
return i;
147+
}
148+
}
149+
return -1;
150+
}
151+
152+
int IList<IPNetwork>.IndexOf(IPNetwork item) => IndexOf(item);
153+
int IList<AspNetIPNetwork>.IndexOf(AspNetIPNetwork item) => IndexOf(item);
154+
155+
public void Insert(int index, IPNetwork item)
156+
{
157+
_items.Insert(index, (item, new AspNetIPNetwork(item.BaseAddress, item.PrefixLength)));
158+
}
159+
160+
public void Insert(int index, AspNetIPNetwork item)
161+
{
162+
_items.Insert(index, (new IPNetwork(item.Prefix, item.PrefixLength), item));
163+
}
164+
165+
void IList<IPNetwork>.Insert(int index, IPNetwork item) => Insert(index, item);
166+
void IList<AspNetIPNetwork>.Insert(int index, AspNetIPNetwork item) => Insert(index, item);
167+
168+
public bool Remove(IPNetwork item)
169+
{
170+
var idx = IndexOf(item);
171+
if (idx >= 0)
172+
{
173+
_items.RemoveAt(idx);
174+
return true;
175+
}
176+
return false;
177+
}
178+
179+
public bool Remove(AspNetIPNetwork item)
180+
{
181+
var idx = IndexOf(item);
182+
if (idx >= 0)
183+
{
184+
_items.RemoveAt(idx);
185+
return true;
186+
}
187+
return false;
188+
}
189+
190+
bool ICollection<IPNetwork>.Remove(IPNetwork item) => Remove(item);
191+
bool ICollection<AspNetIPNetwork>.Remove(AspNetIPNetwork item) => Remove(item);
192+
193+
public void RemoveAt(int index) => _items.RemoveAt(index);
194+
void IList<IPNetwork>.RemoveAt(int index) => RemoveAt(index);
195+
void IList<AspNetIPNetwork>.RemoveAt(int index) => RemoveAt(index);
196+
}
197+
198+
#pragma warning restore ASPDEPR005 // Type or member is obsolete

src/Middleware/HttpOverrides/src/ForwardedHeadersMiddleware.cs

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -213,11 +213,7 @@ public void ApplyForwarders(HttpContext context)
213213
// Host and Scheme initial values are never inspected, no need to set them here.
214214
};
215215

216-
var checkKnownIps = _options.KnownIPNetworks.Count > 0
217-
#pragma warning disable ASPDEPR005 // KnownNetworks is obsolete
218-
|| _options.KnownNetworks.Count > 0
219-
#pragma warning restore ASPDEPR005 // KnownNetworks is obsolete
220-
|| _options.KnownProxies.Count > 0;
216+
var checkKnownIps = _options.KnownIPNetworks.Count > 0 || _options.KnownProxies.Count > 0;
221217
bool applyChanges = false;
222218
int entriesConsumed = 0;
223219

@@ -410,15 +406,6 @@ private bool CheckKnownAddress(IPAddress address)
410406
return true;
411407
}
412408
}
413-
#pragma warning disable ASPDEPR005 // KnownNetworks is obsolete
414-
foreach (var network in _options.KnownNetworks)
415-
{
416-
if (network.Contains(address))
417-
{
418-
return true;
419-
}
420-
}
421-
#pragma warning restore ASPDEPR005 // KnownNetworks is obsolete
422409
return false;
423410
}
424411

src/Middleware/HttpOverrides/src/ForwardedHeadersOptions.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ namespace Microsoft.AspNetCore.Builder;
1313
/// </summary>
1414
public class ForwardedHeadersOptions
1515
{
16+
// Backing dual list that keeps the obsolete and new types in sync.
17+
private readonly DualIPNetworkList _knownNetworks = new();
18+
1619
/// <summary>
1720
/// Gets or sets the header used to retrieve the originating client IP. Defaults to the value specified by
1821
/// <see cref="ForwardedHeadersDefaults.XForwardedForHeaderName"/>.
@@ -87,12 +90,12 @@ public class ForwardedHeadersOptions
8790
/// Obsolete, please use <see cref="KnownIPNetworks"/> instead
8891
/// </summary>
8992
[Obsolete("Please use KnownIPNetworks instead. For more information, visit https://aka.ms/aspnet/deprecate/005.", DiagnosticId = "ASPDEPR005")]
90-
public IList<AspNetIPNetwork> KnownNetworks { get; } = new List<AspNetIPNetwork>() { new(IPAddress.Loopback, 8) };
93+
public IList<AspNetIPNetwork> KnownNetworks => _knownNetworks;
9194

9295
/// <summary>
9396
/// Address ranges of known proxies to accept forwarded headers from.
9497
/// </summary>
95-
public IList<IPNetwork> KnownIPNetworks { get; } = new List<IPNetwork>() { new(IPAddress.Loopback, 8) };
98+
public IList<IPNetwork> KnownIPNetworks => _knownNetworks;
9699

97100
/// <summary>
98101
/// The allowed values from x-forwarded-host. If the list is empty then all hosts are allowed.
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Net;
5+
using Microsoft.AspNetCore.Builder;
6+
using Xunit;
7+
using AspNetIPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
8+
9+
namespace Microsoft.AspNetCore.HttpOverrides;
10+
11+
public class DualIPNetworkListTests
12+
{
13+
[Fact]
14+
public void DefaultContainsLoopback()
15+
{
16+
var options = new ForwardedHeadersOptions();
17+
Assert.Single(options.KnownIPNetworks);
18+
Assert.Equal("127.0.0.0", options.KnownIPNetworks[0].BaseAddress.ToString());
19+
Assert.Equal(8, options.KnownIPNetworks[0].PrefixLength);
20+
#pragma warning disable ASPDEPR005
21+
Assert.Single(options.KnownNetworks);
22+
Assert.Equal("127.0.0.0", options.KnownNetworks[0].Prefix.ToString());
23+
Assert.Equal(8, options.KnownNetworks[0].PrefixLength);
24+
#pragma warning restore ASPDEPR005
25+
}
26+
27+
[Fact]
28+
public void AddThroughSystemCollectionVisibleViaObsolete()
29+
{
30+
var options = new ForwardedHeadersOptions();
31+
options.KnownIPNetworks.Add(global::System.Net.IPNetwork.Parse("10.0.0.0/8"));
32+
#pragma warning disable ASPDEPR005
33+
var obsoleteList = options.KnownNetworks;
34+
Assert.Equal(2, obsoleteList.Count);
35+
Assert.Equal(IPAddress.Parse("10.0.0.0"), obsoleteList[1].Prefix);
36+
Assert.Equal(8, obsoleteList[1].PrefixLength);
37+
#pragma warning restore ASPDEPR005
38+
}
39+
40+
[Fact]
41+
public void AddThroughObsoleteCollectionVisibleViaSystem()
42+
{
43+
#pragma warning disable ASPDEPR005
44+
var options = new ForwardedHeadersOptions();
45+
options.KnownNetworks.Add(new AspNetIPNetwork(IPAddress.Parse("192.168.0.0"), 16));
46+
Assert.Equal(2, options.KnownIPNetworks.Count);
47+
Assert.Equal("192.168.0.0/16", options.KnownIPNetworks[1].ToString());
48+
#pragma warning restore ASPDEPR005
49+
}
50+
51+
[Fact]
52+
public void ReplaceViaSystemIndexerUpdatesObsolete()
53+
{
54+
var options = new ForwardedHeadersOptions();
55+
((IList<global::System.Net.IPNetwork>)options.KnownIPNetworks)[0] = global::System.Net.IPNetwork.Parse("172.16.0.0/12");
56+
#pragma warning disable ASPDEPR005
57+
Assert.Equal(IPAddress.Parse("172.16.0.0"), options.KnownNetworks[0].Prefix);
58+
Assert.Equal(12, options.KnownNetworks[0].PrefixLength);
59+
#pragma warning restore ASPDEPR005
60+
}
61+
62+
[Fact]
63+
public void ReplaceViaObsoleteIndexerUpdatesSystem()
64+
{
65+
#pragma warning disable ASPDEPR005
66+
var options = new ForwardedHeadersOptions();
67+
options.KnownNetworks[0] = new AspNetIPNetwork(IPAddress.Parse("172.16.0.0"), 12);
68+
Assert.Equal("172.16.0.0/12", options.KnownIPNetworks[0].ToString());
69+
#pragma warning restore ASPDEPR005
70+
}
71+
72+
[Fact]
73+
public void ClearClearsBoth()
74+
{
75+
var options = new ForwardedHeadersOptions();
76+
options.KnownIPNetworks.Clear();
77+
#pragma warning disable ASPDEPR005
78+
Assert.Empty(options.KnownNetworks);
79+
#pragma warning restore ASPDEPR005
80+
Assert.Empty(options.KnownIPNetworks);
81+
}
82+
83+
[Fact]
84+
public void RemoveThroughEitherCollectionRemovesFromBoth()
85+
{
86+
var options = new ForwardedHeadersOptions();
87+
options.KnownIPNetworks.Add(global::System.Net.IPNetwork.Parse("10.0.0.0/8"));
88+
var first = options.KnownIPNetworks[0];
89+
var removed = options.KnownIPNetworks.Remove(first);
90+
Assert.True(removed);
91+
#pragma warning disable ASPDEPR005
92+
var obsoleteList = options.KnownNetworks;
93+
Assert.DoesNotContain(obsoleteList, n => n.Prefix.Equals(IPAddress.Loopback));
94+
#pragma warning restore ASPDEPR005
95+
Assert.Single(options.KnownIPNetworks); // only the 10.0.0.0/8 entry should remain
96+
}
97+
}

0 commit comments

Comments
 (0)