IP Address String Routines in Delphi?
Solution 1
I also once wrote a IPv4 and IPv6 conversion unit including a custom variant type for both types of IP addresses. This answer shows a few examples of its capabilities. Originally, it was designed to visualize values of various types in scale on some slider control 1). The requirements then were such that default existing libraries weren't sufficient, but I agree with the comments here that you probably will be helped with just Indy (10!) or alike.
Answering your list of questions with a few code snippets from that unit:
-
Q4: Storage type for IP types:
const IPv4BitSize = SizeOf(Byte) * 4 * 8; IPv6BitSize = SizeOf(Word) * 8 * 8; type T4 = 0..3; T8 = 0..7; TIPv4ByteArray = array[T4] of Byte; TIPv6WordArray = array[T8] of Word; TIPv4 = packed record case Integer of 0: (D, C, B, A: Byte); 1: (Groups: TIPv4ByteArray); 2: (Value: Cardinal); end; TIPv6 = packed record case Integer of 0: (H, G, F, E, D, C, B, A: Word); 1: (Groups: TIPv6WordArray); end;
-
Q5: Conversion of IP address strings to these record or array types:
function StrToIPv4(const S: String): TIPv4; var SIP: String; Start: Integer; I: T4; Index: Integer; Count: Integer; SGroup: String; G: Integer; begin SIP := S + '.'; Start := 1; for I := High(T4) downto Low(T4) do begin Index := PosEx('.', SIP, Start); if Index = 0 then IPv4ErrorFmt(SInvalidIPv4Value, S); Count := Index - Start + 1; SGroup := Copy(SIP, Start, Count - 1); if TryStrToInt(SGroup, G) and (G >= Low(Word)) and (G <= High(Word)) then Result.Groups[I] := G else Result.Groups[I] := 0; Inc(Start, Count); end; end; function StrToIPv6(const S: String): TIPv6; { Valid examples for S: 2001:0db8:85a3:0000:0000:8a2e:0370:7334 2001:db8:85a3:0:0:8a2e:370:7334 2001:db8:85a3::8a2e:370:7334 ::8a2e:370:7334 2001:db8:85a3:: ::1 :: ::ffff:c000:280 ::ffff:192.0.2.128 } var ZeroPos: Integer; DotPos: Integer; SIP: String; Start: Integer; Index: Integer; Count: Integer; SGroup: String; G: Integer; procedure NormalNotation; var I: T8; begin SIP := S + ':'; Start := 1; for I := High(T8) downto Low(T8) do begin Index := PosEx(':', SIP, Start); if Index = 0 then IPv6ErrorFmt(SInvalidIPv6Value, S); Count := Index - Start + 1; SGroup := '$' + Copy(SIP, Start, Count - 1); if not TryStrToInt(SGroup, G) or (G > High(Word)) or (G < 0) then IPv6ErrorFmt(SInvalidIPv6Value, S); Result.Groups[I] := G; Inc(Start, Count); end; end; procedure CompressedNotation; var I: T8; A: array of Word; begin SIP := S + ':'; Start := 1; I := High(T8); while Start < ZeroPos do begin Index := PosEx(':', SIP, Start); if Index = 0 then IPv6ErrorFmt(SInvalidIPv6Value, S); Count := Index - Start + 1; SGroup := '$' + Copy(SIP, Start, Count - 1); if not TryStrToInt(SGroup, G) or (G > High(Word)) or (G < 0) then IPv6ErrorFmt(SInvalidIPv6Value, S); Result.Groups[I] := G; Inc(Start, Count); Dec(I); end; FillChar(Result.H, (I + 1) * SizeOf(Word), 0); if ZeroPos < (Length(S) - 1) then begin SetLength(A, I + 1); Start := ZeroPos + 2; repeat Index := PosEx(':', SIP, Start); if Index > 0 then begin Count := Index - Start + 1; SGroup := '$' + Copy(SIP, Start, Count - 1); if not TryStrToInt(SGroup, G) or (G > High(Word)) or (G < 0) then IPv6ErrorFmt(SInvalidIPv6Value, S); A[I] := G; Inc(Start, Count); Dec(I); end; until Index = 0; Inc(I); Count := Length(A) - I; Move(A[I], Result.H, Count * SizeOf(Word)); end; end; procedure DottedQuadNotation; var I: T4; begin if UpperCase(Copy(S, ZeroPos + 2, 4)) <> 'FFFF' then IPv6ErrorFmt(SInvalidIPv6Value, S); FillChar(Result.E, 5 * SizeOf(Word), 0); Result.F := $FFFF; SIP := S + '.'; Start := ZeroPos + 7; for I := Low(T4) to High(T4) do begin Index := PosEx('.', SIP, Start); if Index = 0 then IPv6ErrorFmt(SInvalidIPv6Value, S); Count := Index - Start + 1; SGroup := Copy(SIP, Start, Count - 1); if not TryStrToInt(SGroup, G) or (G > High(Byte)) or (G < 0) then IPv6ErrorFmt(SInvalidIPv6Value, S); case I of 0: Result.G := G shl 8; 1: Inc(Result.G, G); 2: Result.H := G shl 8; 3: Inc(Result.H, G); end; Inc(Start, Count); end; end; begin ZeroPos := Pos('::', S); if ZeroPos = 0 then NormalNotation else begin DotPos := Pos('.', S); if DotPos = 0 then CompressedNotation else DottedQuadNotation; end; end;
For Q1 to Q3 you have to derive some routines yourself, but that should not be any problem.
1) For those interested, it's this slider control and this topic served as initiation of this unit.
Solution 2
I have already written all the functions you require but I'm afraid I'm not in a position to share the code.
However, the Synapse library contains quite a few functions in the synaip unit. e.g.
function IsIP(const Value: string): Boolean;
function IsIP6(const Value: string): Boolean;
function IPToID(Host: string): Ansistring;
function StrToIp6(value: string): TIp6Bytes;
function Ip6ToStr(value: TIp6Bytes): string;
function StrToIp(value: string): integer;
function IpToStr(value: integer): string;
function ReverseIP(Value: AnsiString): AnsiString;
function ReverseIP6(Value: AnsiString): AnsiString;
When I tried the functions a few years ago, the IPv6 functions were a bit buggy, especially when dealing with compressed IPv6 addresses.
If you want to roll your own, here a few pointers:
- Manipulating IPv4 addresses is fairly simple as they only consist of 32 bits which can be stored in a standard integer type. IPv6 addresses are harder as they need 128 bits and no native type has that many bits.
- Before trying to manipulate an IP address, first convert it into an integer (IPv4 only of course, IPv6 will require a different storage method). To do this, split the IP address using '.' as a delimiter. Check each individual value only contains numbers and falls between 0 and 255 then use these values to generate the final integer.
- Once the IP address has been converted into an integer, you can manipulate it however you like. For example, given an IP address and a subnet mask you can find the subnet that the IP address belongs to e.g. IPtoInt(IPAddress) AND NOT(IPToInt(SubnetMask)) = The integer of the subnet. Now, you can comaare an integer IP address to the integer subnet to see if the IP falls within the subnet.
- Finally, convert the integer IP address back into a string.
If you have any specific questions I can try to answer them too.
Jerry Dodge
I'm a Delphi developer. I work for a software company which does solutions for retail management, including inventory, POS, reporting, BI, Tags, and more. It's been in Delphi since Delphi's been around. I am actively in Stack Overflow monitoring the Delphi tag, and looking for those questions I can answer and also contributing my time to keep Stack Overflow in order. I'm not an expert in anything, a jack of all trades rather. But I love to help people when I'm able to. I've known Delphi since about 2007 now, and before that, I had learned VB6. I havn't gone back to VB since I learned Delphi. I also taught myself QBasic and HTML as a kid. It hasn't been until the past 5 years that I've been diving into programming. Since then I've also become vaguely familiar with ASP.NET with C#, as well as some C# windows apps. But I'm not too fond of the whole .NET idea. .NET is good for web platforms and such, but not for win apps. My latest work has been with Delphi 10 Seattle mobile development. I'm still very raw on the subject, but see a huge potential behind it. My strengths: Understanding the bigger picture of projects Writing Custom Classes, Components, and Controls Code organization (within unit or namespace) Writing purely independent classes (as opposed to cross-referencing units or namespaces) User Friendly UI's Developer Friendly Classes Encapsulating layers of business logic My weaknesses: Lower-level coding (such as Assembly) Platform-specific design (using Firemonkey) Web Design It's always nice to know you're able to do something, even if you never use it.
Updated on June 04, 2022Comments
-
Jerry Dodge almost 2 years
I'm looking for a way in Delphi to validate and manipulate IP Addresses. Some of the things it should be able to do is...
- Verify that a string is a valid IP address
- Verify that a string is a valid subnet mask
- Verify that an IP address is within a given Subnet
- Some type (record or string or whatever) which is meant for storing an IP address
- Basic conversion of an IP address types, such as
String
orArray[0..3] of Byte
- Any other IP address routines that can make IP manipulation easier
The basic reason is that I want to see if these things are already out there before I go ahead and reinvent them.
-
Marcus Adams over 12 yearsMost of those same functions are also in IdGlobal.
-
NGLN over 12 yearsNote: Indy 9 limits that list to only IsValidIP and IsHostname.
-
Jerry Dodge over 12 yearsThanks! I've also noticed that you like to implement embedding methods inside of methods, same as the glass drawing you put together for my other question... I use it sometimes too but otherwise I usually declare other methods directly in the unit or a class.
-
NGLN over 12 years@Jerry These are no methods because they are no member of a class.
-
NGLN over 12 years@Jerry I embed routines within routines when it is a gain in readability, wherelse it would result in one large function or procedure. When I would need comments in code to explain myself what I am doing, e.g. where something ends and starts, then I split. If those embedded routines prove to be useful elsewhere, then they evolve to isolated code or even a separate unit.
-
Mehmet Fide over 3 yearsIPv4ToUInt32() in IdGlobal seems buggy, it accepts "300.1.1.1" as a valid ip.
-
antonio over 3 years@NGLN, Thank you, but there are 2 missing functions: IPv4ErrorFmt, IPv6ErrorFmt, maybe to raise an exception, It's right?
-
NGLN over 2 years@antonio They're added (already long time ago ;-)).