2

I want to check if an IP address is within a range of a minimum and maximum IP address. How can I do that in Delphi?

For example I want to do something like this:

if CheckIp("127.0.0.15","127.0.0.1","127.0.0.255") then ShowMessage('ok');

127.0.0.1 is start value of range, 127.0.0.255 is end value of range and 127.0.0.15 is IP address that will be checked.

OnTheFly
  • 2,059
  • 5
  • 26
  • 61
Someone
  • 728
  • 2
  • 12
  • 23

3 Answers3

7

For IPv4 addresses, you can simply convert them to their integer forms and then perform standard ordinal comparisons on them.

IPv6 address are too big to convert to integers (unless you use a third party BigInt library), so you would have to convert them to their binary form and compare them byte-by-byte instead.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
2

I'm going to assume that your addresses are IPv4 addresses stored in a 32 bit integer in host byte order. And I'm also assuming that you want a lexicographic ordering so that:

 a.b.c.d < p.q.r.s 

is compared by first comparing a and p, and if equal then comparing b and q, and so on.

In which case, the natural unsigned integer ordering (using the < or > operators) will produce the ordering that you desire.

If the addresses are in network byte order, then you need to convert to host byte order before comparing.

In your question, you have addresses as strings. So you'd need to convert them to network byte order 32 bit unsigned integers with inet_addr, and then to host byte order with ntohl. And then you could compare.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
2

I asked a slightly similar question here before, for general string routines for IP addresses. Based off of the answer by NGLN, I have implemented a set of comparison functions, and a demo application. The function IPRange detects whether it's v4 or v6 and compares them accordingly.

uMain.pas

unit uMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
  IPTypes;

type
  TfrmCheckIPRange = class(TForm)
    txtFrom: TEdit;
    txtTo: TEdit;
    txtIP: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    txtResult: TEdit;
    Label4: TLabel;
    procedure DoCheck(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  frmCheckIPRange: TfrmCheckIPRange;

implementation

{$R *.dfm}

function IntRange(const Val, Min, Max: Integer): Boolean;
begin
  Result:= (Val >= Min) and (Val <= Max);
end;

function IPRangeV4(const IP, IPFrom, IPTo: TIPv4): Boolean;
begin
  Result:= IntRange(IP.D, IPFrom.D, IPTo.D);
  if Result then
    Result:= IntRange(IP.C, IPFrom.C, IPTo.C);
    if Result then
      Result:= IntRange(IP.B, IPFrom.B, IPTo.B);
      if Result then
        Result:= IntRange(IP.A, IPFrom.A, IPTo.A);
end;

function IPRangeV6(const IP, IPFrom, IPTo: TIPv6): Boolean;
begin
  Result:= IntRange(IP.H, IPFrom.H, IPTo.H);
  if Result then
    Result:= IntRange(IP.G, IPFrom.G, IPTo.G);
    if Result then
      Result:= IntRange(IP.F, IPFrom.F, IPTo.F);
      if Result then
        Result:= IntRange(IP.E, IPFrom.E, IPTo.E);
        if Result then
          Result:= IntRange(IP.D, IPFrom.D, IPTo.D);
          if Result then
            Result:= IntRange(IP.C, IPFrom.C, IPTo.C);
            if Result then
              Result:= IntRange(IP.B, IPFrom.B, IPTo.B);
              if Result then
                Result:= IntRange(IP.A, IPFrom.A, IPTo.A);
end;

function IPRange(const IP, IPFrom, IPTo: String): Boolean;
var
  IP4, FR4, TO4: TIPv4;
  IP6, FR6, TO6: TIPv6;
  function IsV4(const S: String): Boolean;
  begin
    Result:= Pos('.', S) > 1;
  end;
  function IsV6(const S: String): Boolean;
  begin
    Result:= Pos(':', S) > 0;
  end;
begin
  Result:= False;
  if (IsV6(IP)) and (IsV6(IPFrom)) and (IsV6(IPTo)) then begin
    IP6:= StrToIPv6(IP);
    FR6:= StrToIPv6(IPFrom);
    TO6:= StrToIPv6(IPTo);
    Result:= IPRangeV6(IP6, FR6, TO6);
  end else
  if (IsV4(IP)) and (IsV4(IPFrom)) and (IsV4(IPTo)) then begin
    IP4:= StrToIPv4(IP);
    FR4:= StrToIPv4(IPFrom);
    TO4:= StrToIPv4(IPTo);
    Result:= IPRangeV4(IP4, FR4, TO4);
  end else begin
    raise Exception.Create('Invalid IP Address Input');
  end;
end;

{ TfrmCheckIPRange }

procedure TfrmCheckIPRange.FormCreate(Sender: TObject);
begin
  DoCheck(nil);
end;

procedure TfrmCheckIPRange.DoCheck(Sender: TObject);
begin
  try
    if IPRange(txtIP.Text, txtFrom.Text, txtTo.Text) then begin
      txtResult.Text:= 'IP is in range';
      txtResult.Color:= clGreen;
    end else begin
      txtResult.Text:= 'IP is NOT in range';
      txtResult.Color:= clRed;
    end;
  except
    on e: exception do begin
      txtResult.Text:= e.Message;
      txtResult.Color:= clYellow;
    end;
  end;
end;

end.

uMain.dfm

object frmCheckIPRange: TfrmCheckIPRange
  Left = 350
  Top = 113
  BorderIcons = [biSystemMenu]
  BorderStyle = bsSingle
  Caption = 'Check IP Range'
  ClientHeight = 124
  ClientWidth = 296
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  Position = poScreenCenter
  OnCreate = FormCreate
  DesignSize = (
    296
    124)
  PixelsPerInch = 96
  TextHeight = 13
  object Label1: TLabel
    Left = 11
    Top = 11
    Width = 71
    Height = 13
    Alignment = taRightJustify
    Caption = 'IP To Compare'
  end
  object Label2: TLabel
    Left = 11
    Top = 38
    Width = 71
    Height = 13
    Alignment = taRightJustify
    Caption = 'IP Range From'
  end
  object Label3: TLabel
    Left = 23
    Top = 65
    Width = 59
    Height = 13
    Alignment = taRightJustify
    Caption = 'IP Range To'
  end
  object Label4: TLabel
    Left = 52
    Top = 92
    Width = 30
    Height = 13
    Alignment = taRightJustify
    Caption = 'Result'
  end
  object txtFrom: TEdit
    Left = 88
    Top = 35
    Width = 196
    Height = 21
    Anchors = [akLeft, akTop, akRight]
    TabOrder = 1
    Text = '192.168.3.100'
    OnChange = DoCheck
    ExplicitWidth = 158
  end
  object txtTo: TEdit
    Left = 88
    Top = 62
    Width = 196
    Height = 21
    Anchors = [akLeft, akTop, akRight]
    TabOrder = 2
    Text = '192.168.3.200'
    OnChange = DoCheck
    ExplicitWidth = 158
  end
  object txtIP: TEdit
    Left = 88
    Top = 8
    Width = 196
    Height = 21
    Anchors = [akLeft, akTop, akRight]
    TabOrder = 0
    Text = '192.168.3.105'
    OnChange = DoCheck
    ExplicitWidth = 158
  end
  object txtResult: TEdit
    Left = 88
    Top = 89
    Width = 196
    Height = 21
    Anchors = [akLeft, akTop, akRight]
    Font.Charset = DEFAULT_CHARSET
    Font.Color = clWindowText
    Font.Height = -11
    Font.Name = 'Tahoma'
    Font.Style = [fsBold]
    ParentFont = False
    ReadOnly = True
    TabOrder = 3
    OnChange = DoCheck
    ExplicitWidth = 158
  end
end

I've tested IPv4, but have NOT tested IPv6, although it should work. I'm not familiar enough with IPv6 to even know different test scenarios.

You may also want to add some logic to check if an IP is within the same subnet, because you might not want to include different subnets. That's as easy as making sure the first 3 numbers (v4) are exactly the same. You may wish to raise an exception if there are any differences in subnets, but that's all up to how you need to implement this.


EDIT

I fixed the logic in determining v4 vs v6, because an IPv6 address could also possibly have a . in it, I had to switch the order of checking from v4-v6 to v6-v4.

NGLN
  • 43,011
  • 8
  • 105
  • 200
Jerry Dodge
  • 26,858
  • 31
  • 155
  • 327
  • 2
    `if (IsV4) and (IsV4) and (IsV4) then begin` ... How many times you have to check it to be convinced that it is V4? – Sertac Akyuz Jul 28 '13 at 11:23
  • Using the same `TIPv4` structure along with the `InRange` function from `Math` unit, you could simply write a [`code like this`](http://pastebin.com/ZbhQpzXd). That's the point of using variant records for those structures... – TLama Jul 28 '13 at 13:59
  • @SertacAkyuz Oh snap, that subroutine was supposed to check each of the 3, but I missed that major part of adding a parameter... Fixed and thanks for pointing out. I had those parameters but for whatever reason I decided to remove it, but of course I can't let it magically detect which one I want to check – Jerry Dodge Jul 28 '13 at 15:12
  • @Jeryy - You're welcome :). IMHO you could skip the check altogether, AFAICS 'test.bmp' would qualify as a call to StrToIPv4 which would raise an exception there anyway. If it is to decide if an address is V4 or V6, some notations of V4 mapped V6 addresses have dots too. – Sertac Akyuz Jul 28 '13 at 16:19
  • @SertacAkyuz So I'm assuming that means I need to check for a `:` before a `.` right? – Jerry Dodge Aug 02 '13 at 01:27
  • @Jerry - Yep.  .. I notice your paragraph about subnets, that's not exactly accurate, not all subnets have 8 bits hosts. E.g. with a mask of 255.255.255.248 there would be 8 addresses within a subnet. – Sertac Akyuz Aug 02 '13 at 02:21