I’ve been looking for more applications for the ip geolocation. so I wrote this small tool wich show the open remote tcp connections location on a Web Map (Google Maps, Bing Maps, Yahoo Maps and OpenStreetMap).
DISCLAIMER
This application is only for educational purposes. because some maps services does not allow to display content from a desktop application.
Check the screenshots samples
Showing the location of a ip address in Google Maps

Showing the location of a ip address in Yahoo Maps

Showing the location of a ip address in Bing Maps

Showing the location of a ip address in OpenStreet Maps

First we need obtain the current tcp connections, to do this we can use the GetExtendedTcpTable function wich is part of the iphlpapi.dll.
the header declaration goes like this
type
TCP_TABLE_CLASS = Integer;
PMibTcpRowOwnerPid = ^TMibTcpRowOwnerPid;
TMibTcpRowOwnerPid = packed record
dwState : DWORD;
dwLocalAddr : DWORD;
dwLocalPort : DWORD;
dwRemoteAddr: DWORD;
dwRemotePort: DWORD;
dwOwningPid : DWORD;
end;
PMIB_TCPTABLE_OWNER_PID = ^MIB_TCPTABLE_OWNER_PID;
MIB_TCPTABLE_OWNER_PID = packed record
dwNumEntries: DWord;
table: array [0..ANY_SIZE - 1] OF TMibTcpRowOwnerPid;
end;
var
GetExtendedTcpTable:function (pTcpTable: Pointer; dwSize: PDWORD; bOrder: BOOL; lAf: ULONG; TableClass: TCP_TABLE_CLASS; Reserved: ULONG): DWord; stdcall;
To use this function we need to determine size of the TcpTable returned, to allocate the memory. (look the first parameter is set to nil)
TableSize := 0;
Error := GetExtendedTcpTable(nil, @TableSize, False, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0);
if Error <> ERROR_INSUFFICIENT_BUFFER then
Exit;
Now in the TableSize variable we have the size of the TcpTable, so we can retrieve the tcp info passing in the first parameter the buffer to contain the data
var FExtendedTcpTable : PMIB_TCPTABLE_OWNER_PID; GetMem(FExtendedTcpTable, TableSize); GetExtendedTcpTable(FExtendedTcpTable, @TableSize, TRUE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0)
this is the full code to fill the listview with the tcp connections
procedure LoadTCPConnections;
var
Server : Cardinal;
Error : DWORD;
TableSize : DWORD;
Snapshot : THandle;
i : integer;
ListItem : TListItem;
IpAddress : in_addr;
FCurrentPid : Cardinal;
IsLocal : Boolean;
RemoteIp : string;
begin
ListViewIPaddress.Items.BeginUpdate;
try
ListViewIPaddress.Items.Clear;
FCurrentPid:=GetCurrentProcessId();
FExternalIpAddress:=GetExternalIP;
TableSize := 0;
Error := GetExtendedTcpTable(nil, @TableSize, False, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0); //get the size of the TcpTable
if Error <> ERROR_INSUFFICIENT_BUFFER then
Exit;
try
GetMem(FExtendedTcpTable, TableSize);
SnapShot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); //take a Snapshot of the running process to obtain the exe name of the pid associated
if GetExtendedTcpTable(FExtendedTcpTable, @TableSize, TRUE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0) = NO_ERROR then
for i := 0 to FExtendedTcpTable.dwNumEntries - 1 do // for each record in the tcptable
//avoid show connections of the current application and system connections (PID=0)
if (FExtendedTcpTable.Table[i].dwOwningPid<>0) and (FExtendedTcpTable.Table[i].dwOwningPid<>FCurrentPid) and (FExtendedTcpTable.Table[i].dwRemoteAddr<>0) then
begin
IpAddress.s_addr := FExtendedTcpTable.Table[i].dwRemoteAddr;
RemoteIp := string(inet_ntoa(IpAddress));
Server := FExtendedTcpTable.Table[i].dwRemoteAddr;
//determine if the remote ip is local or not
IsLocal := (FLocalIpAddresses.IndexOf(RemoteIp)>=0) or (Server=0) or (Server=16777343);
if CheckBoxRemote.Checked and IsLocal then Continue;
if FExtendedTcpTable.Table[i].dwRemoteAddr = 0 then
FExtendedTcpTable.Table[i].dwRemotePort := 0;
//Fill the Listview
ListItem:=ListViewIPaddress.Items.Add;
ListItem.ImageIndex:=-1;
ListItem.Caption:=IntToStr(FExtendedTcpTable.Table[i].dwOwningPid);
ListItem.SubItems.Add(GetPIDName(SnapShot,FExtendedTcpTable.Table[i].dwOwningPid));
ListItem.SubItems.Add('TCP');
ListItem.SubItems.Add(FLocalComputerName);
IpAddress.s_addr := FExtendedTcpTable.Table[i].dwLocalAddr;
ListItem.SubItems.Add(string(inet_ntoa(IpAddress))); //get the local ip address
ListItem.SubItems.Add(IntToStr(ntohs(FExtendedTcpTable.Table[i].dwLocalPort)));
ListItem.SubItems.AddObject('',Pointer(FExtendedTcpTable.Table[i].dwRemoteAddr));
ListItem.SubItems.Add(RemoteIp);
ListItem.SubItems.Add(IntToStr(ntohs(FExtendedTcpTable.Table[i].dwRemotePort)));
ListItem.SubItems.Add(MIB_TCP_STATE[FExtendedTcpTable.Table[i].dwState]);
ListItem.SubItems.Add('');
ListItem.SubItems.Add('');
ListItem.SubItems.Add('');
ListItem.SubItems.Add('');
end;
finally
FreeMem(FExtendedTcpTable);
end;
finally
ListViewIPaddress.Items.EndUpdate;
end;
//now for resolve the server location and show the flag icon we run a tthread for each row in the listview
for i:= 0 to ListViewIPaddress.Items.Count-1 do
begin
Server:=Cardinal(ListViewIPaddress.Items.Item[i].SubItems.Objects[COLUMN_RemoteServer]);
IsLocal := (FLocalIpAddresses.IndexOf(ListViewIPaddress.Items.Item[i].SubItems[COLUMN_RemoteIP])>=0) or (Server=0) or (Server=16777343);
if not IsLocal then
TResolveServerName.Create(Server,ListViewIPaddress.Items.Item[i].SubItems[COLUMN_RemoteIP],ImageList1,ListViewIPaddress.Items.Item[i]);
end;
end;
The code of the thread for resolve the ip locations and retrieve the flags images.
type
TResolveGeoLocation = class(TThread)
private
FListItem : TListItem;
FGeoInfo : TGeoInfoClass;
FRemoteHostName : string;
FRemoteIP : string;
FServer : Cardinal;
FImageList : TImageList;
procedure SetData;
protected
procedure Execute; override;
constructor Create(Server : Cardinal;const RemoteIP:string;ImageList:TImageList;ListItem:TListItem);
end;
constructor TResolveGeoLocation.Create(Server: Cardinal;const RemoteIP:string;ImageList:TImageList;ListItem:TListItem);
begin
inherited Create(False);
FServer :=Server;
FRemoteIP :=RemoteIP;
FImageList:=ImageList;
FListItem :=ListItem;
FreeOnTerminate := True;
end;
procedure TResolveGeoLocation.Execute;
begin
FreeOnTerminate := True;
FRemoteHostName := GetRemoteHostName(FServer);
FGeoInfo:=TGeoInfoClass.Create(FRemoteIP);
try
Synchronize(SetData);
finally
FGeoInfo.Free;
end;
end;
procedure TResolveGeoLocation.SetData;
var
Bitmap : TBitmap;
begin
FListItem.SubItems[COLUMN_RemoteServer]:=FRemoteHostName;
FListItem.SubItems[COLUMN_Country] :=FGeoInfo.GeoInfo.CountryName;
FListItem.SubItems[COLUMN_City] :=FGeoInfo.GeoInfo.City;
FListItem.SubItems[COLUMN_Latitude] :=FGeoInfo.GeoInfo.LatitudeToString;
FListItem.SubItems[COLUMN_Longitude] :=FGeoInfo.GeoInfo.LongitudeToString;
if Assigned(FGeoInfo.GeoInfo.FlagImage) then
begin
Bitmap := TBitmap.Create;
try
Bitmap.Assign(FGeoInfo.GeoInfo.FlagImage);
if (Bitmap.Width=FImageList.Width) and ((Bitmap.Height=FImageList.Height)) then
FListItem.ImageIndex:=FImageList.Add(Bitmap,nil)
else
Bitmap.Width;
finally
Bitmap.Free;
end;
end;
FListItem.MakeVisible(False);
end;
Now the class to obtain the geolocation info and the flag of the country.
type
PGeoInfo = ^TGeoInfo;
TGeoInfo = record
Status : string;
CountryCode : string;
CountryName : string;
RegionCode : string;
City : string;
ZipPostalCode : string;
Latitude : Double;
Longitude : Double;
TimezoneName : string;
Gmtoffset : string;
Isdst : string;
FlagImage : TPngImage;
function LatitudeToString:string;
function LongitudeToString:string;
end;
TGeoInfoClass = class
private
FIpAddress : string;
FGeoInfo : TGeoInfo;
public
property GeoInfo : TGeoInfo read FGeoInfo;
constructor Create(IpAddress : string); overload;
Destructor Destroy; override;
end;
and the new function to retrieve the geolocation data from ipinfodb.com
procedure GetGeoInfo(const IpAddress : string;var GeoInfo :TGeoInfo);
var
XMLDoc : OleVariant;
ANode : OleVariant;
FormatSettings: TFormatSettings;
d : Double;
Success : HResult;
UrlImage : string;
XmlContent : string;
StreamData : TMemoryStream;
begin
GeoInfo.FlagImage:=nil;
Success := CoInitializeEx(nil, COINIT_MULTITHREADED);
try
XmlContent:=WinInet_HttpGet(Format(UrlGeoLookupInfo,[IpAddress]));
if XmlContent<>'' then
begin
XMLDoc := CreateOleObject('Msxml2.DOMDocument.6.0');
XMLDoc.async := false;
XMLDoc.LoadXML(XmlContent);
XMLDoc.setProperty('SelectionLanguage','XPath');
ANode:=XMLDoc.selectSingleNode('/Response/Status');
if not VarIsNull(ANode) then GeoInfo.Status:=ANode.Text;
ANode:=XMLDoc.selectSingleNode('/Response/CountryCode');
if not VarIsNull(ANode) then GeoInfo.CountryCode:=ANode.Text;
ANode:=XMLDoc.selectSingleNode('/Response/CountryName');
if not VarIsNull(ANode) then GeoInfo.CountryName:=ANode.Text;
ANode:=XMLDoc.selectSingleNode('/Response/RegionCode');
if not VarIsNull(ANode) then GeoInfo.RegionCode:=ANode.Text;
ANode:=XMLDoc.selectSingleNode('/Response/City');
if not VarIsNull(ANode) then GeoInfo.City:=ANode.Text;
ANode:=XMLDoc.selectSingleNode('/Response/ZipPostalCode');
if not VarIsNull(ANode) then GeoInfo.ZipPostalCode:=ANode.Text;
ANode:=XMLDoc.selectSingleNode('/Response/Latitude');
if not VarIsNull(ANode) then
begin
FormatSettings.DecimalSeparator:='.';
d:=StrToFloat(ANode.Text,FormatSettings);
GeoInfo.Latitude:=d;
end;
ANode:=XMLDoc.selectSingleNode('/Response/Longitude');
if not VarIsNull(ANode) then
begin
FormatSettings.DecimalSeparator:='.';
d:=StrToFloat(ANode.Text,FormatSettings);
GeoInfo.Longitude:=d;
end;
ANode:=XMLDoc.selectSingleNode('/Response/TimezoneName');
if not VarIsNull(ANode) then GeoInfo.TimezoneName:=ANode.Text;
ANode:=XMLDoc.selectSingleNode('/Response/Gmtoffset');
if not VarIsNull(ANode) then GeoInfo.Gmtoffset:=ANode.Text;
ANode:=XMLDoc.selectSingleNode('/Response/Isdst');
if not VarIsNull(ANode) then GeoInfo.Isdst:=ANode.Text;
end;
finally
case Success of
S_OK, S_FALSE: CoUninitialize;
end;
end;
if GeoInfo.CountryCode<>'' then //get the image
begin
GeoInfo.FlagImage := TPngImage.Create;
StreamData := TMemoryStream.Create;
try
UrlImage:=Format(UrlFlags,[LowerCase(GeoInfo.CountryCode)]);
WinInet_HttpGet(UrlImage,StreamData);
if StreamData.Size>0 then
begin
StreamData.Seek(0,0);
try
GeoInfo.FlagImage.LoadFromStream(StreamData);//load the image in a Stream
except //the image is not valid
GeoInfo.FlagImage.Free;
GeoInfo.FlagImage:=nil;
end;
end;
finally
StreamData.Free;
end;
end;
end;
The part of the maps is easy, just only need load a html page in a Twebbrowser with the Latitude and longitude to show in the current selected map
procedure GetMapListItem();
var
HTMLWindow2 : IHTMLWindow2;
MemoryStream : TMemoryStream;
Item : TListItem;
Lat : AnsiString;
Lng : AnsiString;
Title : AnsiString;
MapType : string;
MapStr : AnsiString;
//sorry , but the html pages contains a lot of % (porcent) chars
function ReplaceTag(const PageStr,Tag,NewValue:string):AnsiString;
begin
Result:=AnsiString(StringReplace(PageStr,Tag,NewValue,[rfReplaceAll]));
end;
begin
Item:=ListViewIPaddress.Selected;
if not Assigned(Item) then exit;
if Item.SubItems.Count<COLUMN_Latitude then Exit;
if Item.SubItems[COLUMN_Latitude]='' then Exit;
Lat:=AnsiString(Item.SubItems[COLUMN_Latitude]);
Lng:=AnsiString(Item.SubItems[COLUMN_Longitude]);
Title:=AnsiString(Format('(%s,%s) %s - %s',[Lat,Lng,Item.SubItems[COLUMN_RemoteServer],Item.SubItems[COLUMN_RemoteIP]]));
MapType:=ComboBoxTypes.Text;
WebBrowser1.Navigate('about:blank');
while WebBrowser1.ReadyState < READYSTATE_INTERACTIVE do
Application.ProcessMessages;
if Assigned(WebBrowser1.Document) then
begin
MemoryStream := TMemoryStream.Create;
try
case FCurrentMapType of
Google_Maps : MapStr:=GoogleMapsPage;
Yahoo_Map : MapStr:=YahooMapsPage;
Bing_Map : MapStr:=BingsMapsPage;
Open_Streetmap : MapStr:=OpenStreetMapsPage;
end;
MapStr:=ReplaceTag(MapStr,'[Lat]',Lat);
MapStr:=ReplaceTag(MapStr,'[Lng]',Lng);
MapStr:=ReplaceTag(MapStr,'[Title]',Title);
MapStr:=ReplaceTag(MapStr,'[Type]',MapType);
MemoryStream.WriteBuffer(Pointer(MapStr)^, Length(MapStr));
MemoryStream.Seek(0, soFromBeginning);
(WebBrowser1.Document as IPersistStreamInit).Load(TStreamAdapter.Create(MemoryStream));
finally
MemoryStream.Free;
end;
HTMLWindow2 := (WebBrowser1.Document as IHTMLDocument2).parentWindow;
end;
end;
and finally the html code embedded in a delphi const string for each map type
const
GoogleMapsPage: AnsiString =
'<html> '+
'<head> '+
'<meta name="viewport" content="initial-scale=1.0, user-scalable=yes" /> '+
'<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true"></script> '+
'<script type="text/javascript"> '+
' var map;'+
' function initialize() { '+
' geocoder = new google.maps.Geocoder();'+
' var latlng = new google.maps.LatLng([Lat],[Lng]); '+
' var myOptions = { '+
' zoom: 12, '+
' center: latlng, '+
' mapTypeId: google.maps.MapTypeId.[Type] '+
' }; '+
' map = new google.maps.Map(document.getElementById("map_canvas"), myOptions); '+
' var marker = new google.maps.Marker({'+
' position: latlng, '+
' title: "[Title]", '+
' map: map '+
' });'+
' } '+
''+'</script> '+
'</head> '+
'<body onload="initialize()"> '+
' <div id="map_canvas" style="width:100%; height:100%"></div> '+
'</body>'+
'</html>';
YahooMapsPage: AnsiString =
'<html> '+
'<head> '+
'<meta name="viewport" content="initial-scale=1.0, user-scalable=yes" /> '+
'<script type="text/javascript" src="http://api.maps.yahoo.com/ajaxymap?v=3.8&appid=08gJIU7V34H9WlTSGrIyEIb73GLT5TpAaF2HzOSJIuTO2AVn6qzftRPDQtcQyynObIG8"></script> '+
'<script type="text/javascript"> '+
' function initialize() '+
'{'+
' var map = new YMap ( document.getElementById ( "map_canvas" ) );'+
' map.addTypeControl();'+
' map.addZoomLong(); '+
' map.addPanControl();'+
' map.setMapType ( YAHOO_MAP_[Type] );'+
' var geopoint = new YGeoPoint ( [Lat] , [Lng] ); '+
' map.drawZoomAndCenter ( geopoint , 5 );'+
' var newMarker= new YMarker(geopoint); '+
' var markerMarkup = "[Title]";'+
' newMarker.openSmartWindow(markerMarkup);'+
' map.addOverlay(newMarker);'+
'}'+
''+'</script> '+
'</head> '+
'<body onload="initialize()"> '+
' <div id="map_canvas" style="width:100%; height:100%"></div> '+
'</body>'+
'</html>';
BingsMapsPage: AnsiString =
'<html> '+
'<head> '+
'<meta name="viewport" content="initial-scale=1.0, user-scalable=yes" /> '+
'<script type="text/javascript" src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2"></script> '+
'<script type="text/javascript"> '+
'var map = null; '+
' function initialize() '+
'{'+
' map = new VEMap("map_canvas"); '+
' map.LoadMap(new VELatLong([Lat],[Lng]), 10 ,"h" ,false);'+
' map.SetMapStyle(VEMapStyle.[Type]);'+
' map.ShowMiniMap((document.getElementById("map_canvas").offsetWidth - 180), 200, VEMiniMapSize.Small);'+
' map.SetZoomLevel (12);'+
' shape = new VEShape(VEShapeType.Pushpin, map.GetCenter()); '+
' shape.SetTitle("[Title]");'+
' map.AddShape ( shape );'+
'}'+
''+'</script> '+
'</head> '+
'<body onload="initialize()"> '+
' <div id="map_canvas" style="width:100%; height:100%"></div> '+
'</body>'+
'</html>';
OpenStreetMapsPage: AnsiString =
'<html> '+
'<head> '+
'<meta name="viewport" content="initial-scale=1.0, user-scalable=yes" /> '+
'<script src="http://www.openlayers.org/api/OpenLayers.js"></script> '+
'<script type="text/javascript"> '+
' function initialize() '+
'{'+
' map = new OpenLayers.Map("map_canvas");'+
' map.addLayer(new OpenLayers.Layer.OSM()); '+
' var lonLat = new OpenLayers.LonLat( [Lng] , [Lat] ) '+
' .transform( '+
' new OpenLayers.Projection("EPSG:4326"), '+
' map.getProjectionObject() '+
' ); '+
' var zoom=16; '+
' var markers = new OpenLayers.Layer.Markers( "Markers" ); '+
' map.addLayer(markers); '+
' markers.addMarker(new OpenLayers.Marker(lonLat)); '+
' map.setCenter (lonLat, zoom); '+
'}'+
''+'</script> '+
'</head> '+
'<body onload="initialize()"> '+
' <div id="map_canvas" style="width:100%; height:100%"></div> '+
'</body>'+
'</html>';
