• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lduchosal / ipnetwork / 960

15 May 2026 07:18AM UTC coverage: 0.0% (-94.1%) from 94.053%
960

Pull #411

appveyor

web-flow
Merge ef6b054ce into 4cec191ae
Pull Request #411: Bump MSTest.TestFramework from 4.2.2 to 4.2.3

0 of 2438 relevant lines covered (0.0%)

0.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/System.Net.IPNetwork/UniqueLocalAddress.cs
1
// <copyright file="UniqueLocalAddress.cs" company="IPNetwork">
2
// Copyright (c) IPNetwork. All rights reserved.
3
// </copyright>
4

5
using System.Net.Sockets;
6
using System.Security.Cryptography;
7
using System.Text;
8

9
namespace System.Net;
10

11
/// <summary>
12
/// Utility class for IPv6 Unique Local Address (ULA) generation and validation.
13
/// Implements RFC 4193 for generating ULA prefixes in the fd00::/8 range.
14
/// 
15
/// A locally-assigned ULA always looks like this (128 bits total):
16
/// | 8 bits  | 40 bits        | 16 bits    | 64 bits           |
17
/// +---------+----------------+-------------+-------------------+
18
/// | fd (8)  | Global ID      | Subnet ID   | Interface ID      |
19
///
20
/// •        fd = the fixed 8-bit prefix (fd00::/8 for locally assigned).
21
/// •        Global ID = 40 random bits, chosen once to make your ULA unique.
22
/// •        Subnet ID = 16 bits, chosen by you inside your site.
23
/// •        Interface ID = 64 bits, assigned to each host within the subnet (same rule as all IPv6).
24
/// 
25
/// The /48 prefix is the site prefix (fdXX:XXXX:XXXX::/48).
26
/// </summary>
27
public static class UniqueLocalAddress
28
{
29
    /// <summary>
30
    /// ULA prefix for locally assigned addresses (fd00::/8).
31
    /// </summary>
32
    public static readonly IPNetwork2 UlaLocallyAssigned = IPNetwork2.Parse("fd00::/8");
×
33

34
    /// <summary>
35
    /// ULA prefix for centrally assigned addresses (fc00::/8) - currently undefined.
36
    /// </summary>
37
    public static readonly IPNetwork2 UlaCentrallyAssigned = IPNetwork2.Parse("fc00::/8");
×
38

39
    /// <summary>
40
    /// Full ULA range (fc00::/7).
41
    /// </summary>
42
    public static readonly IPNetwork2 UlaRange = IPNetwork2.Parse("fc00::/7");
×
43

44
    /// <summary>
45
    /// Generates a random ULA /48 prefix using the algorithm from RFC 4193.
46
    /// </summary>
47
    /// <returns>A randomly generated ULA /48 network.</returns>
48
    public static IPNetwork2 GenerateUlaPrefix()
49
    {
×
50
        byte[] globalId = GenerateRandomGlobalId();
×
51
        return CreateUlaPrefix(globalId);
×
52
    }
×
53

54
    /// <summary>
55
    /// Generates a random ULA /48 prefix using a specific MAC address for entropy.
56
    /// </summary>
57
    /// <param name="macAddress">MAC address to use for entropy generation.</param>
58
    /// <returns>A ULA /48 network generated using the provided MAC address.</returns>
59
    public static IPNetwork2 GenerateUlaPrefix(byte[] macAddress)
60
    {
×
61
        if (macAddress == null)
×
62
        {
×
63
            throw new ArgumentNullException(nameof(macAddress));
×
64
        }
65

66
        if (macAddress.Length != 6)
×
67
        {
×
68
            throw new ArgumentException("MAC address must be 6 bytes long", nameof(macAddress));
×
69
        }
70

71
        byte[] globalId = GenerateGlobalIdFromMac(macAddress);
×
72
        return CreateUlaPrefix(globalId);
×
73
    }
×
74

75
    /// <summary>
76
    /// Generates a random ULA /48 prefix using a seed for deterministic generation.
77
    /// </summary>
78
    /// <param name="seed">Seed value for deterministic generation.</param>
79
    /// <returns>A ULA /48 network generated using the provided seed.</returns>
80
    public static IPNetwork2 GenerateUlaPrefix(string seed)
81
    {
×
82
        if (string.IsNullOrEmpty(seed))
×
83
        {
×
84
            throw new ArgumentNullException(nameof(seed), "Seed cannot be null or empty");
×
85
        }
86

87
        byte[] globalId = GenerateGlobalIdFromSeed(seed);
×
88
        return CreateUlaPrefix(globalId);
×
89
    }
×
90

91
    /// <summary>
92
    /// Creates a ULA subnet within a ULA /48 prefix.
93
    /// </summary>
94
    /// <param name="ulaPrefix">The ULA /48 prefix.</param>
95
    /// <param name="subnetId">16-bit subnet identifier.</param>
96
    /// <returns>A ULA /64 subnet.</returns>
97
    public static IPNetwork2 CreateUlaSubnet(IPNetwork2 ulaPrefix, int subnetId)
98
    {
×
99
        if (!IsUlaPrefix(ulaPrefix))
×
100
        {
×
101
            throw new ArgumentException("Network must be a valid ULA prefix", nameof(ulaPrefix));
×
102
        }
103

104
        if (ulaPrefix.Cidr != 48)
×
105
        {
×
106
            throw new ArgumentException("ULA prefix must be /48", nameof(ulaPrefix));
×
107
        }
108
        
109
        if (subnetId < 0 || subnetId > 65535)
×
110
        {
×
111
            throw new ArgumentOutOfRangeException(nameof(subnetId));
×
112
        }
113

114
        byte[] networkBytes = ulaPrefix.Network.GetAddressBytes();
×
115
            
116
        // Set subnet ID in bytes 6-7 (positions after the /48 prefix)
117
        networkBytes[6] = (byte)(subnetId >> 8);
×
118
        networkBytes[7] = (byte)(subnetId & 0xFF);
×
119

120
        var subnetAddress = new IPAddress(networkBytes);
×
121
        return IPNetwork2.Parse($"{subnetAddress}/64");
×
122
    }
×
123

124
    /// <summary>
125
    /// Validates whether an IP address is within the ULA range.
126
    /// </summary>
127
    /// <param name="address">IP address to validate.</param>
128
    /// <returns>True if the address is a ULA, false otherwise.</returns>
129
    public static bool IsUla(IPAddress address)
130
    {
×
131
        return address?.AddressFamily == AddressFamily.InterNetworkV6 && UlaRange.Contains(address);
×
132
    }
×
133

134
    /// <summary>
135
    /// Validates whether a network is within the ULA range.
136
    /// </summary>
137
    /// <param name="network">Network to validate.</param>
138
    /// <returns>True if the network is a ULA, false otherwise.</returns>
139
    public static bool IsUlaPrefix(IPNetwork2 network)
140
    {
×
141
        return network?.AddressFamily == AddressFamily.InterNetworkV6 
×
142
               && UlaRange.Contains(network.Network);
×
143
    }
×
144

145
    /// <summary>
146
    /// Validates whether a network is a locally assigned ULA (fd00::/8).
147
    /// </summary>
148
    /// <param name="network">Network to validate.</param>
149
    /// <returns>True if the network is locally assigned ULA, false otherwise.</returns>
150
    public static bool IsLocallyAssignedUla(IPNetwork2 network)
151
    {
×
152
        return network?.AddressFamily == AddressFamily.InterNetworkV6 
×
153
               && UlaLocallyAssigned.Contains(network.Network);
×
154
    }
×
155

156
    /// <summary>
157
    /// Generates a 40-bit random Global ID according to RFC 4193 algorithm.
158
    /// </summary>
159
    /// <returns>40-bit Global ID as a byte array.</returns>
160
    private static byte[] GenerateRandomGlobalId()
161
    {
×
162
        using var rng = RandomNumberGenerator.Create();
×
163
        byte[] globalId = new byte[5]; // 40 bits = 5 bytes
×
164
        rng.GetBytes(globalId);
×
165
        return globalId;
×
166
    }
×
167

168
    /// <summary>
169
    /// Generates a Global ID using MAC address and timestamp as suggested in RFC 4193.
170
    /// </summary>
171
    /// <param name="macAddress">6-byte MAC address.</param>
172
    /// <returns>40-bit Global ID as a byte array.</returns>
173
    private static byte[] GenerateGlobalIdFromMac(byte[] macAddress)
174
    {
×
175
        byte[] input = new byte[macAddress.Length + 8];
×
176
        Array.Copy(macAddress, 0, input, 0, macAddress.Length);
×
177

178
        // Add current timestamp
179
        byte[] timestamp = BitConverter.GetBytes(DateTimeOffset.UtcNow.ToUnixTimeSeconds());
×
180
        Array.Copy(timestamp, 0, input, macAddress.Length, timestamp.Length);
×
181

182
#if NETSTANDARD2_1
183
        using var sha2 = SHA256.Create();
184
        byte[] hash = sha2.ComputeHash(input);
185
#else
186
        byte[] hash = SHA256.HashData(input);
×
187
#endif
188
        byte[] globalId = new byte[5];
×
189
        Array.Copy(hash, 0, globalId, 0, 5);
×
190
        return globalId;
×
191
    }
×
192

193
    /// <summary>
194
    /// Generates a Global ID from a seed string for deterministic generation.
195
    /// </summary>
196
    /// <param name="seed">Seed string.</param>
197
    /// <returns>40-bit Global ID as a byte array.</returns>
198
    private static byte[] GenerateGlobalIdFromSeed(string seed)
199
    {
×
200
        byte[] seedBytes = Encoding.UTF8.GetBytes(seed);
×
201
#if NETSTANDARD2_1
202
        using var sha2 = SHA256.Create();
203
        byte[] hash = sha2.ComputeHash(seedBytes);
204
#else
205
        byte[] hash = SHA256.HashData(seedBytes);
×
206
#endif
207
        byte[] globalId = new byte[5];
×
208
        Array.Copy(hash, 0, globalId, 0, 5);
×
209
        return globalId;
×
210
    }
×
211

212
    /// <summary>
213
    /// Creates a ULA /48 prefix from a 40-bit Global ID.
214
    /// </summary>
215
    /// <param name="globalId">5-byte Global ID.</param>
216
    /// <returns>ULA /48 network.</returns>
217
    private static IPNetwork2 CreateUlaPrefix(byte[] globalId)
218
    {
×
219
        byte[] addressBytes = new byte[16];
×
220
            
221
        // Set ULA locally assigned prefix (fd)
222
        addressBytes[0] = 0xfd;
×
223
            
224
        // Set the 40-bit Global ID (5 bytes)
225
        Array.Copy(globalId, 0, addressBytes, 1, 5);
×
226
            
227
        // Remaining bytes are zero for /48 prefix
228
            
229
        var address = new IPAddress(addressBytes);
×
230
        return IPNetwork2.Parse($"{address}/48");
×
231
    }
×
232
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc