Last active
August 12, 2024 14:01
-
-
Save a-luna/bd93686ace9f6f22acf0a7032fc41777 to your computer and use it in GitHub Desktop.
Helpful C# Network/IP functions. Parse IPs from any arbitrary block of text, retrieve private/public IP addresses and check if IP exists in any range defined by an address in CIDR notation.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace NetworkUtilitiesTest | |
{ | |
using AaronLuna.Common.Network; | |
using AaronLuna.Common.Extensions; | |
using System.Threading.Tasks; | |
class Program | |
{ | |
// async Main is a C# 7.1 feature, change your project settings to the | |
// new version if this is flagged as an error | |
static async Task Main() | |
{ | |
// Usage examples for classes in this gist: | |
// | |
// NetworkUtilities | |
// bool IpAddressIsInRange(string checkIp, string cidrIp) | |
// bool IpAddressIsInRange(IPAddress checkIp, string cidrIp) | |
// List<IPAddress> ParseIPv4Addresses(string input) | |
// IPAddress GetLocalIPv4Address(string localNetworkCidrIp) | |
// List<IPAddress> GetLocalIPv4AddressList() | |
// Task<IPAddress> GetPublicIPv4AddressAsync() | |
// | |
// IpAddressExtensions | |
// bool IsInRange(this IPAddress ip, string cidrIp) | |
// bool IsPrivateAddress(this IPAddress ip) | |
// bool IsPublicAddress(this IPAddress ip) | |
var ip1 = "192.168.2.4"; | |
var cidrIp1 = "192.168.2.0/24"; | |
var isInRange1 = NetworkUtilities.IpAddressIsInRange(ip1, cidrIp1); // isInRange = true | |
var textToParse = "10.0.2.2 is a private (internal) address and 75.94.26.74 is a public (external) addresss"; | |
var parse2Ips = NetworkUtilities.ParseIPv4Addresses(textToParse); // parse2Ips = {"10.0.2.2", "75.94.26.74"} | |
var isInRange2 = parse2Ips[1].IsPrivateAddress(); // isInRange2 = false | |
var myLocalIp = NetworkUtilities.GetLocalIPv4Address(cidrIp1); // myLocalIp = "192.168.3.5" | |
var myPublicIp = await NetworkUtilities.GetPublicIPv4AddressAsync(); // myPublicIp = "75.94.26.74" | |
var testLocalTrue = myLocalIp.IsPublicAddress(); // testLocalTrue = true | |
var testLocalFalse = myLocalIp.IsPrivateAddress(); // testLocalFalse = false' | |
var testPublic = myPublicIp.IsPublicAddress(); // testPublic = true; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace AaronLuna.Common.Network | |
{ | |
using System; | |
using System.Linq; | |
using System.Net; | |
public static partial class NetworkUtilities | |
{ | |
// This method is used multiple times by the other methods in this class. | |
// The input string must contain ONLY a single IPv4 address in a.b.c.d format. | |
// If the format of the input is unknown or you are attempting to parse | |
// an IP address from an arbitrary block of text, use ParseIPv4Addresses(). | |
public static IPAddress ParseSingleIPv4Address(string input) | |
{ | |
if (string.IsNullOrEmpty(input)) | |
{ | |
throw new ArgumentException("Input string must not be null", input); | |
} | |
var addressBytesSplit = input.Trim().Split('.').ToList(); | |
if (addressBytesSplit.Count != 4) | |
{ | |
throw new ArgumentException("Input string was not in valid IPV4 format \"a.b.c.d\"", input); | |
} | |
var addressBytes = new byte[4]; | |
foreach (var i in Enumerable.Range(0, addressBytesSplit.Count)) | |
{ | |
if (!int.TryParse(addressBytesSplit[i], out var parsedInt)) | |
{ | |
throw new FormatException($"Unable to parse integer from {addressBytesSplit[i]}"); | |
} | |
if (0 > parsedInt || parsedInt > 255) | |
{ | |
throw new ArgumentOutOfRangeException($"{parsedInt} not within required IP address range [0,255]"); | |
} | |
addressBytes[i] = (byte)parsedInt; | |
} | |
return new IPAddress(addressBytes); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace AaronLuna.Common.Network | |
{ | |
using System; | |
using System.Collections.Generic; | |
using System.Net; | |
using System.Text.RegularExpressions; | |
public static partial class NetworkUtilities | |
{ | |
// THis method can be used to parse all IP addresses from an arbitrary block of text, if you | |
// know that your input is a single IPv4 address in a.b.c.d format, use ParseSingleIPv4Address() | |
public static List<IPAddress> ParseIPv4Addresses(string input) | |
{ | |
const string ipV4Pattern = | |
@"(?:(?:1\d\d|2[0-5][0-5]|2[0-4]\d|0?[1-9]\d|0?0?\d)\.){3}(?:1\d\d|2[0-5][0-5]|2[0-4]\d|0?[1-9]\d|0?0?\d)"; | |
if (string.IsNullOrEmpty(input)) | |
{ | |
throw new ArgumentException("Input string must not be null", input); | |
} | |
var ips = new List<IPAddress>(); | |
try | |
{ | |
var regex = new Regex(ipV4Pattern); | |
foreach (Match match in regex.Matches(input)) | |
{ | |
var ip = ParseSingleIPv4Address(match.Value); | |
ips.Add(ip); | |
} | |
} | |
catch (RegexMatchTimeoutException ex) | |
{ | |
// Handle exeption | |
} | |
return ips; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace AaronLuna.Common.Network | |
{ | |
using System; | |
using System.Net; | |
public static partial class NetworkUtilities | |
{ | |
// true if ipAddress falls inside the range defined by cidrIp, example: | |
// bool result = IsInCidrRange("192.168.2.3", "192.168.2.0/24"); // result = true | |
public static bool IpAddressIsInRange(string checkIp, string cidrIp) | |
{ | |
if (string.IsNullOrEmpty(checkIp)) | |
{ | |
throw new ArgumentException("Input string must not be null", checkIp); | |
} | |
var ipAddress = ParseIPv4Addresses(checkIp)[0]; | |
return IpAddressIsInRange(ipAddress, cidrIp); | |
} | |
public static bool IpAddressIsInRange(IPAddress checkIp, string cidrIp) | |
{ | |
if (string.IsNullOrEmpty(cidrIp)) | |
{ | |
throw new ArgumentException("Input string must not be null", cidrIp); | |
} | |
var cidrAddress = ParseIPv4Addresses(cidrIp)[0]; | |
var parts = cidrIp.Split('/'); | |
if (parts.Length != 2) | |
{ | |
throw new FormatException($"cidrMask was not in the correct format:\nExpected: a.b.c.d/n\nActual: {cidrIp}"); | |
} | |
if (!Int32.TryParse(parts[1], out var netmaskBitCount)) | |
{ | |
throw new FormatException($"Unable to parse netmask bit count from {cidrIp}"); | |
} | |
if (0 > netmaskBitCount || netmaskBitCount > 32) | |
{ | |
throw new ArgumentOutOfRangeException($"Netmask bit count value of {netmaskBitCount} is invalid, must be in range 0-32"); | |
} | |
var ipAddressBytes = BitConverter.ToInt32(checkIp.GetAddressBytes(), 0); | |
var cidrAddressBytes = BitConverter.ToInt32(cidrAddress.GetAddressBytes(), 0); | |
var cidrMaskBytes = IPAddress.HostToNetworkOrder(-1 << (32 - netmaskBitCount)); | |
var ipIsInRange = (ipAddressBytes & cidrMaskBytes) == (cidrAddressBytes & cidrMaskBytes); | |
return ipIsInRange; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace AaronLuna.Common.Network | |
{ | |
using System.Net; | |
public static partial class NetworkUtilities | |
{ | |
public const string CidrPrivateAddressBlockA = "10.0.0.0/8"; | |
public const string CidrPrivateAddressBlockB = "172.16.0.0/12"; | |
public const string CidrPrivateAddressBlockC = "192.168.0.0/16"; | |
public static bool IpAddressIsInPrivateAddressSpace(string ipv4string) | |
{ | |
var checkIp = ParseIPv4Addresses(ipv4string)[0]; | |
return IpAddressIsInPrivateAddressSpace(checkIp); | |
} | |
public static bool IpAddressIsInPrivateAddressSpace(IPAddress ipAddress) | |
{ | |
var inPrivateBlockA = IpAddressIsInCidrRange(ipAddress, CidrPrivateAddressBlockA); | |
var inPrivateBlockB = IpAddressIsInCidrRange(ipAddress, CidrPrivateAddressBlockB); | |
var inPrivateBlockC = IpAddressIsInCidrRange(ipAddress, CidrPrivateAddressBlockC); | |
return inPrivateBlockA || inPrivateBlockB || inPrivateBlockC; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace AaronLuna.Common.Network | |
{ | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Net; | |
using System.Net.NetworkInformation; | |
using System.Net.Sockets; | |
public static partial class NetworkUtilities | |
{ | |
// This method first attempts to retrieve the local IP address by connecting | |
// to Google's Public DNS service. If there is no internet connection, the | |
// address range defined by localNetworkCidrIp is used to determine which | |
// IP is the correct local address. The CIDR IP must be in the correct | |
// format: a.b.c.d/n. For example, if your LAN is setup to allow 254 hosts | |
// and the router's address is 192.168.2.1, the correct value for | |
// localNetworkCidrIp would be "192.168.2.0/24" | |
public static IPAddress GetLocalIPv4Address(string localNetworkCidrIp) | |
{ | |
var acquiredIp = true; | |
var localIp = IPAddress.None; | |
try | |
{ | |
localIp = GetLocalIPv4AddressRequiresInternet(); | |
} | |
catch (SocketException ex) | |
{ | |
acquiredIp = false; | |
} | |
if (acquiredIp) | |
{ | |
return localIp; | |
} | |
return GetLocalIPv4AddressWithoutInternet(localNetworkCidrIp); | |
} | |
static IPAddress GetLocalIPv4AddressRequiresInternet() | |
{ | |
var localIp = IPAddress.None; | |
try | |
{ | |
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, 0)) | |
{ | |
// Connect socket to Google's Public DNS service | |
if (!(socket.LocalEndPoint is IPEndPoint endPoint)) | |
{ | |
throw new InvalidOperationException($"Error occurred casting {socket.LocalEndPoint} to IPEndPoint"); | |
} | |
localIp = endPoint.Address; | |
} | |
} | |
catch (SocketException ex) | |
{ | |
// Handle exception | |
} | |
return localIp; | |
} | |
static IPAddress GetLocalIPv4AddressWithoutInternet(string localNetworkCidrIp) | |
{ | |
var localIps = GetLocalIPv4AddressList(); | |
if (localIps.Count == 1) | |
{ | |
return localIps[0]; | |
} | |
foreach (var ip in localIps) | |
{ | |
if (IpAddressIsInRange(ip, localNetworkCidrIp)) | |
{ | |
return ip; | |
} | |
} | |
return IPAddress.None; | |
} | |
public static List<IPAddress> GetLocalIPv4AddressList() | |
{ | |
var localIps = new List<IPAddress>(); | |
foreach (var nic in NetworkInterface.GetAllNetworkInterfaces()) | |
{ | |
var ips = | |
nic.GetIPProperties().UnicastAddresses | |
.Select(uni => uni.Address) | |
.Where(ip => ip.AddressFamily == AddressFamily.InterNetwork).ToList(); | |
localIps.AddRange(ips); | |
} | |
return localIps; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace AaronLuna.Common.Network | |
{ | |
using System; | |
using System.Net; | |
using System.Net.Http; | |
using System.Threading.Tasks; | |
public static partial class NetworkUtilities | |
{ | |
public static async Task<IPAddress> GetPublicIPv4AddressAsync() | |
{ | |
var urlContent = await GetUrlContentAsStringAsync("http://ipv4.icanhazip.com/").ConfigureAwait(false); | |
return ParseSingleIPv4Address(urlContent); | |
} | |
static async Task<string> GetUrlContentAsStringAsync(string url) | |
{ | |
var urlContent = string.Empty; | |
try | |
{ | |
using (var httpClient = new HttpClient()) | |
using (var httpResonse = await httpClient.GetAsync(url).ConfigureAwait(false)) | |
{ | |
urlContent = await httpResonse.Content.ReadAsStringAsync().ConfigureAwait(false); | |
} | |
} | |
catch (HttpRequestException ex) | |
{ | |
// Handle exception | |
} | |
catch(InvalidOperationException ex) | |
{ | |
// Handle exception | |
} | |
return urlContent; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace AaronLuna.Common.Extensions | |
{ | |
using System.Net; | |
using Network; | |
public static class IpAddressExtensions | |
{ | |
public static bool IsInRange(this IPAddress ip, string cidrIp) | |
{ | |
return NetworkUtilities.IpAddressIsInRange(ip, cidrIp); | |
} | |
public static bool IsPrivateAddress(this IPAddress ip) | |
{ | |
return NetworkUtilities.IpAddressIsInPrivateAddressSpace(ip); | |
} | |
public static bool IsPublicAddress(this IPAddress ip) | |
{ | |
return !NetworkUtilities.IpAddressIsInPrivateAddressSpace(ip); | |
} | |
public static string ConvertToBinaryString(this IPAddress ip) | |
{ | |
return NetworkUtilities.ConvertIpAddressToBinary(ip); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey, thanks for the class NetworkUtilities. I noticed that in the files you posted there are two missing methods IpAddressIsInCidrRange and ConvertIpAddressToBinary. Just thought you might want to know...
KV