4

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 or Array[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.

Jerry Dodge
  • 26,858
  • 31
  • 155
  • 327
  • Lots and lots of questions. What libraries are you planning to use? Winsock? Indy? Whatever library you use will most likely have all this covered. – David Heffernan Dec 15 '11 at 19:19
  • I don't plan on using any libraries like that. I plan to just manipulate IP strings. But if anything, just raw winsock. – Jerry Dodge Dec 15 '11 at 19:21
  • If you don't want to reinvent these things then you are looking for a library, in my opinion. – David Heffernan Dec 15 '11 at 19:51
  • 1
    Just pick a library. Synapse, ICS, Indy. Etc. – Warren P Dec 15 '11 at 19:55
  • 1
    Indy has some class methods and global functions for this kind of thing. So, even if you're not using their components to connect, take a look at the library, especially the IdGlobal unit. – Marcus Adams Dec 15 '11 at 19:56
  • @DavidHeffernan I didn't say I don't want to reinvent them, that would be a good experience. I'd rather have some good reference to how to do so before I try to make my own. WinSock looks like it has some tools, but it also has many things I don't need, and it also requires some lower-level coding. I'll try the Synapse. – Jerry Dodge Dec 15 '11 at 20:05
  • Libraries are like that. You just use the parts that you need. You won't use all of Synapse either. Delphi comes with Indy by the way. – David Heffernan Dec 15 '11 at 20:23
  • 3
    See also: [Arithmetic with IPv6 addresses](http://stackoverflow.com/questions/697731/arithmetic-with-ipv6-addresses-large-integers). – NGLN Dec 15 '11 at 20:55
  • To tell you the truth, I've personally never worked with IPv6 and in fact always disable it from all our computers (because our DHCP is flaky with IPv6). However, that poses the point that I should implement v6 too because hey, I gotta learn sometime :D – Jerry Dodge Dec 15 '11 at 22:37
  • My [IP library](https://github.com/rmaupin/IPvX/blob/main/IP.pas) for proper IP math using string inputs and outputs with some class functions has been updated. It uses a lot of validations, and it even uses compressed, mixed, and compressed-mixed IPv6 addresses. The library is used in the [IPvX](https://github.com/rmaupin/IPvX) calculator. – Ron Maupin Oct 09 '22 at 19:30

2 Answers2

10

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.

NGLN
  • 43,011
  • 8
  • 105
  • 200
  • Thanks! 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. – Jerry Dodge Dec 15 '11 at 22:41
  • @Jerry These are no methods because they are no member of a class. – NGLN Dec 16 '11 at 06:41
  • 1
    @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. – NGLN Dec 16 '11 at 06:42
  • @NGLN, Thank you, but there are 2 missing functions: IPv4ErrorFmt, IPv6ErrorFmt, maybe to raise an exception, It's right? – antonio Sep 29 '20 at 07:04
  • @antonio They're added (already long time ago ;-)). – NGLN Jan 30 '22 at 08:51
4

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.

norgepaul
  • 6,013
  • 4
  • 43
  • 76