Search code examples
stringdelphidelphi-7ip-address

IP Address String Routines in Delphi?


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.


Solution

  • 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.