// // File: hzIpaddr.cpp // // Legal Notice: This file is part of the HadronZoo C++ Class Library. Copyright 2025 HadronZoo Project (http://www.hadronzoo.com) // // The HadronZoo C++ Class Library is free software: You can redistribute it, and/or modify it under the terms of the GNU Lesser General Public License, as published by the Free // Software Foundation, either version 3 of the License, or any later version. // // The HadronZoo C++ Class Library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR // A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License along with the HadronZoo C++ Class Library. If not, see http://www.gnu.org/licenses. //
// // Implimentation of the hzIpaddr (IP address) class. //
#include <fstream> #include <iostream>
using namespace std ;
#include <fcntl.h> #include <sys/ipc.h> #include <sys/sem.h> #include <sys/stat.h> #include <sys/shm.h> #include <sys/mman.h> #include <errno.h>
#include "hzChars.h" #include "hzTextproc.h" #include "hzDirectory.h" #include "hzIpaddr.h"
global const hzIpaddr _hzGlobal_nullIP ; // Null IP address
bool IsIPAddr (uint32_t& nIpValue, const char* cpIpa) { // Category: Text Processing // // Test if supplied string is of the form of a valid IP address (four numbers between 0 and 255 separated by a period) // // Arguments: 1) nIpValue The IP value, set if supplied string is of the form // 2) cpIpa The string to be tested // // Returns: True If the supplied string amounts to a valid IP address // False If it doesn't
const char* i ; // Text IP address iterator uint32_t nVal = 0 ; // 32-bit IP address value uint32_t nTmp ; // IP segment value int32_t nNos = 0 ; // Count of IP numeric segments
nIpValue = IPADDR_BAD ;
if (!cpIpa) return false ; if (!cpIpa[0]) return false ;
for (i = cpIpa ; *i ; i++) { if (*i < '0' || *i > '9') return false ;
for (nTmp = 0 ; *i >= '0' && *i <= '9' ; i++) { nTmp *= 10 ; nTmp += (*i - '0') ; }
if (nTmp > 255) return false ;
nVal *= 256 ; nVal += nTmp ; nNos++ ;
if (*i == 0 || *i == CHAR_COMMA) break ;
if (*i != CHAR_PERIOD) return false ; }
if (nNos != 4) return false ;
nIpValue = nVal ; return true ; }
bool IsIPAddr (const char* cpIpa) { // Category: Text Processing // // Test if supplied string is of the form of a valid IP address (four numbers between 0 and 255 separated by a period) // // Arguments: 1) The string to be tested // // Returns: True If the supplied string amounts to a valid IP address // False If it doesn't
uint32_t nVal ; // 32-bit IP address value
return IsIPAddr(nVal, cpIpa) ; }
hzEcode hzIpaddr::SetValue (const char* cpIpa) { // Sets the IP address to the value provided as a character string. The string must be of the form x.x.x.x where x can be any number between 0 and 255 // // Arguments: 1) cpIpa IP address as a cstr // // Returns: E_NODATA If the input is not supplied // E_FORMAT If the input cstr is no an IP address // E_OK If this IP address instance is set
if (!cpIpa || !cpIpa[0]) return E_NODATA ;
if (IsIPAddr(m_Ipa, cpIpa)) return E_OK ;
return E_FORMAT ; }
hzIpaddr& hzIpaddr::operator= (const char* cpIpa) { // Sets the IP address to the operand provided as a character string. The string must be of the form x.x.x.x where x can be any number between 0 and 255 // // Arguments: 1) cpIpa IP address as a cstr // // Returns: Reference to this instance
if (!cpIpa || !cpIpa[0]) m_Ipa = 0 ; else IsIPAddr(m_Ipa, cpIpa) ;
return *this ; }
/* ** Get value methods */
const char* hzIpaddr::AsBytes (void) const { // Populates the supplied buffer with the IP address as a series of 4 bytes. This is for the benefit of network functions. // // Argument: buf The buffer to be populated // // Returns: Pointer to the populated buffer
_hzfunc("hzIpaddr::AsBytes") ;
char* pBuf ; // Text recepticle
pBuf = _thisfn.ScratchPad(8) ;
pBuf[0] = (m_Ipa & 0xff000000) >> 24 ; pBuf[1] = (m_Ipa & 0xff0000) >> 16 ; pBuf[2] = (m_Ipa & 0xff00) >> 8 ; pBuf[3] = m_Ipa & 0xff ;
return pBuf ; }
const char* hzIpaddr::Full (void) const { // Populates the supplied buffer with the fully expanded text value of the IP address. This is for the benefit of diagnostics. // // Argument: r The 16-byte recepticle to be populated // // Returns: Pointer to the populated buffer
_hzfunc("hzIpaddr::Full") ;
char* pBuf ; // Text recepticle
pBuf = _thisfn.ScratchPad(24) ;
sprintf(pBuf, "%03u.%03u.%03u.%03u", (m_Ipa & 0xff000000) >> 24, (m_Ipa & 0xff0000) >> 16, (m_Ipa & 0xff00) >> 8, m_Ipa & 0xff) ;
return pBuf ; }
// Stream operator std::ostream& operator<< (std::ostream& os, const hzIpaddr& op) { // Category: Data Output // // Streams out the text form of the IP address to the supplied output stream // // Arguments: 1) os Reference to the output stream // 2) op The IP address instance // // Returns: Reference to the supplied output stream
uint32_t val ; // 32-bit IP address value char buf [20] ; // Text formulation buffer
val = (uint32_t) op ; sprintf(buf, "%d.%d.%d.%d", (val & 0xff000000) >> 24, (val & 0xff0000) >> 16, (val & 0xff00) >> 8, (val & 0xff)) ;
os << buf ; return os ; }
/* ** IP Location and Country Codes */
// The s_CC series just deals with country codes and country code lookup. static const char* s_CC_buffer ; // Array of 2-char null terminated country codes plus country name static const uint16_t* s_CC_offsets ; // Array of country code offsets - one per IP range static uint32_t s_CC_max ; // Total country codes static uint32_t s_CC_start ; // Country code binary chop start point static uint32_t s_CC_div ; // Country code initial binary divider
// The s_IpBasic series deals with Basic level IP location static const uchar* s_IpBasic_codes ; // Array of country codes. Each element is an 8-bit unsigned number indicating the country. There is an element for each IP range. static const uint32_t* s_IpBasic_zones ; // Array of IP ranges. These comprise only the starting IP. The range limit is given by the entry above or 255.255.255.255. static uint32_t s_IpBasic_max ; // Total population of IP ranges in the base level data static uint32_t s_IpBasic_start ; // IP range binary chop start point static uint32_t s_IpBasic_div ; // IP range initial binary divider
// The s_IpCity series deals with City level IP location static const char* s_IpCity_text ; // Array of city-level locations (as concatenated Cstr) static const uint32_t* s_IpCity_zones ; // Array of IP ranges. These comprise only the starting IP. The range limit is given by the entry above or 255.255.255.255. static const uint32_t* s_IpCity_osets ; // Array of offsets into the city-level location (as they vary in length) static uint32_t s_IpCity_max ; // Total population of IP ranges in the city level data static uint32_t s_IpCity_start ; // IP range binary chop start point static uint32_t s_IpCity_div ; // IP range initial binary divider
hzEcode InitCountryCodes (void) { // Category: Internet // // Read in the HadronZoo data file global.country.dat which provides a mapping of two letter country codes to country names. This file is expected to be in // the HadronZoo data directory $HADRONZOO/data which is supplied as a part of the HadronZoo Suite download. // // Arguments: None // // Returns: E_NODATA If the Country code source could not be established // E_NOTFOUND If the Country code file does not exist // E_OPENFAIL If the Country code file could not be opened // E_READFAIL If the Country code file could not be read // E_FORMAT If the Country code file does not have the expected format // E_OK If the Country code tables were initialized
_hzfunc(__func__) ;
ifstream is ; // Input stream FSTAT fs ; // File status char* pCC_buf ; // Shared mem segment uint16_t* pShort ; // Offsets in mem segment uint32_t n ; // Buffer iterator int32_t fd ; // Shered mem 'file descriptor' hzEcode rc = E_OK ; // Return code
fd = shm_open("deltaCountryCodes", O_RDONLY, 0) ; if (fd < 0) return hzerr(E_INITFAIL, "Cannot initialize shared memory segment (deltaCountryCodes)\n") ;
fstat(fd, &fs); threadLog("Set fd to %d\n", fd) ;
pCC_buf = (char*) mmap(0, fs.st_size, PROT_READ, MAP_SHARED, fd, 0); if (!pCC_buf) return hzerr(E_INITFAIL, "Could not map Shared memory segment. Errno %d", errno) ; threadLog("Country codes mem at %p, size is %d\n", pCC_buf, fs.st_size) ;
pShort = (uint16_t*) pCC_buf ; s_CC_max = *pShort++ ; threadLog("Total codes %d\n", s_CC_max) ;
s_CC_offsets = pShort ; threadLog("Offsets at %p\n", s_CC_offsets) ;
pShort += s_CC_max ; s_CC_buffer = (char*) pShort ; threadLog("Buffer at %p\n", s_CC_buffer) ;
for (n = 2 ; n <= s_CC_max ; n *= 2) ; s_CC_div = n / 2 ; s_CC_start = n - 1 ;
threadLog("Total codes %d (div %d start %d)\n\n", s_CC_max, s_CC_div, s_CC_start) ;
/* for (n = 0 ; n < s_CC_max ; n++) threadLog("%s -> %s (%d)\n", s_CC_buffer + s_CC_offsets[n], s_CC_buffer + s_CC_offsets[n] + 3, s_CC_offsets[n]) ; */
return rc ; }
uint32_t GetCountryByCode (const char* ccode) { // Category: Internet // // Locate country by country code // // Argument: ccode Two letter country code // // Returns: Number being RID of country or 0 if not found.
_hzfunc(__func__) ;
const char* i ; // In-table country code uint32_t nDiv ; // Binary chop divider uint32_t nPos ; // Starting position int32_t res ; // Comparison result bool bFound ; // Position if redult
// Ensure country code shared memory is in place if (!s_CC_buffer) return 0 ;
// Ensure country code is two upper case letters if (!ccode) return 0 ; if (!ccode[0]) return 0 ; if (ccode[2]) return 0 ;
if (ccode[0] < 'A' || ccode[0] > 'Z') return 0 ; if (ccode[1] < 'A' || ccode[1] > 'Z') return 0 ;
// Perform binary chop if (!s_CC_start) return 0 ;
nPos = s_CC_start ; bFound = false ;
for (nDiv = s_CC_div ;; nDiv /= 2) { if (nPos > s_CC_max) { if (!nDiv) break ; nPos -= nDiv ; continue ; }
// Compare i = s_CC_buffer + s_CC_offsets[nPos] ; if (i[0] > ccode[0]) res = -1 ; else if (i[0] < ccode[0]) res = 1 ; else res = i[1] > ccode[1] ? -1 : i[1] < ccode[1] ? 1 : 0 ;
// Chop if (res > 0) { if (!nDiv) break ; nPos += nDiv ; continue ; }
if (res < 0) { if (!nDiv) break ; nPos -= nDiv ; continue ; }
bFound = true ; break ; }
return bFound ? nPos : 0 ; }
const char* GetCountryCode (uint32_t countryRID) { // Category: Internet // // Return the 2-byte country code
_hzfunc(__func__) ;
if (countryRID > s_CC_max) return s_CC_buffer ;
return s_CC_buffer + s_CC_offsets[countryRID] ; }
const char* GetCountryName (uint32_t countryRID) { // Category: Internet // // Return the country Name
_hzfunc(__func__) ;
if (countryRID > s_CC_max) return s_CC_buffer ;
return s_CC_buffer + s_CC_offsets[countryRID] + 3 ; }
hzEcode InitIpBasic (void) { // Category: Internet // // Initialize basic IP lookup tables as client. The HadronZoo Delta Server is assumed to have previously created the necessary shared memory segments. // // Arguments: None // // Returns: E_NODATA If there are no IP ranges // E_OPENFAIL If the IP range file cannot be opened
_hzfunc(__func__) ;
ifstream is ; // Input stream FSTAT fs ; // Shared memory 'file' stat uint32_t* pRanges ; // Pointer to IP Ranges block uint32_t* pLocations ; // Pointer to IP Locations block uint32_t n ; // Loop control int32_t m_fd ; // Sheared memory file descriptor
// Check we have country codes. This will ensure _hzGlobal_HadronZooBase has a value and from this, _hzGlobal_IpRanges can be derived.
// Check we have deltaIpBaseZone m_fd = shm_open("deltaIpBaseZone", O_RDONLY, 0) ; if (m_fd < 0) return hzerr(E_OPENFAIL, "Could not open deltaIpBaseZone") ; threadLog("Opened deltaIpBaseZone\n") ;
fstat(m_fd, &fs); pRanges = (uint32_t*) mmap(0, fs.st_size, PROT_READ, MAP_SHARED, m_fd, 0); if (!pRanges) return hzerr(E_INITFAIL, "Could not map shared memory segment for deltaIpBaseZone. Errno %d\n", errno) ; threadLog("IP ranges size %u mem at %p\n", fs.st_size, pRanges) ;
// Check we have deltaIpBaseCode m_fd = shm_open("deltaIpBaseCode", O_RDONLY, 0) ; if (m_fd < 0) return hzerr(E_OPENFAIL, "Could not open deltaIpBaseCode") ; threadLog("Opened deltaIpBaseCode\n") ;
fstat(m_fd, &fs); s_IpBasic_max = fs.st_size ; pLocations = (uint32_t*) mmap(0, fs.st_size, PROT_READ, MAP_SHARED, m_fd, 0); if (!pLocations) return hzerr(E_INITFAIL, "Shared memory address at %p errno is %d\n", pLocations, errno) ; threadLog("locations size %u mem at %p\n", fs.st_size, pLocations) ;
s_IpBasic_zones = pRanges ; s_IpBasic_codes = (uchar*) pLocations ;
// Calculate start and divider for (n = 2 ; n < s_IpBasic_max ; n *= 2) ; s_IpBasic_div = n / 2 ; s_IpBasic_start = n - 1 ;
threadLog("Total ranges %d (div %d start %d)\n\n", s_IpBasic_max, s_IpBasic_div, s_IpBasic_start) ; return E_OK ; }
hzEcode InitIpCity (void) { // Category: Internet // // Initialize city-level IP lookup tables as client. The HadronZoo Delta Server is assumed to have previously created the necessary shared memory segments. // // Arguments: None // // Returns: E_NODATA If there are no IP ranges // E_OPENFAIL If the IP range file cannot be opened
_hzfunc(__func__) ;
ifstream is ; // Input stream FSTAT fs ; // Shared memory 'file' stat uint32_t* pIpr ; // Pointer to IP Ranges block uint32_t m_fd ; // Loop control uint32_t n ; // Loop control
// Check we have country codes. This will ensure _hzGlobal_HadronZooBase has a value and from this, _hzGlobal_IpRanges can be derived. m_fd = shm_open("deltaIpCityText", O_RDONLY, 0) ; if (m_fd < 0) return hzerr(E_OPENFAIL, "Could not open deltaIpCityText") ; threadLog("Set fd to %d\n", m_fd) ;
fstat(m_fd, &fs); s_IpCity_text = (char*) mmap(0, fs.st_size, PROT_READ, MAP_SHARED, m_fd, 0); if (!s_IpCity_text) return hzerr(E_INITFAIL, "Shared memory address at %p errno is %d\n", s_IpCity_text, errno) ; threadLog("text mem at %p\n", s_IpCity_text) ;
m_fd = shm_open("deltaIpCityZone", O_RDONLY, 0) ; if (m_fd < 0) return hzerr(E_OPENFAIL, "Could not open deltaIpCityZone") ; threadLog("Set fd to %d\n", m_fd) ;
fstat(m_fd, &fs); pIpr = (uint32_t*) mmap(0, fs.st_size, PROT_READ, MAP_SHARED, m_fd, 0); if (!pIpr) return hzerr(E_INITFAIL, "Shared memory address at %p errno is %d\n", pIpr, errno) ; threadLog("zone mem at %p\n", pIpr) ;
s_IpCity_max = pIpr[0] ; s_IpCity_zones = pIpr + 1 ;
m_fd = shm_open("deltaIpCityLocn", O_RDONLY, 0) ; fstat(m_fd, &fs); threadLog("Set fd to %d\n", m_fd) ;
s_IpCity_osets = (uint32_t*) mmap(0, fs.st_size, PROT_READ, MAP_SHARED, m_fd, 0); if (!s_IpCity_osets) return hzerr(E_INITFAIL, "Shared memory address at %p errno is %d\n", s_IpCity_osets, errno) ; threadLog("locn mem at %p\n", s_IpCity_osets) ;
// Calculate start and divider for (n = 2 ; n < s_IpCity_max ; n *= 2) ; s_IpCity_div = n / 2 ; s_IpCity_start = n - 1 ; threadLog("Have %d zones, div %d start %d\n", s_IpCity_max, s_IpCity_div, s_IpCity_start) ;
return E_OK ; }
/* ** IP Lookup Functions */
const char* GetIpLocation (const hzIpaddr& ipa) { // Category: Internet // // This returns a location description for the supplied IP address if the IP location table has been loaded. Otherwise this function calls LocateCountry(), // which will return the country code for the IP address - if the country codes table has been set up. // // This function never returns NULL. A pointer to a static string is returned in the event of lookup failure. If the supplied IP address is invalid but the // IP location table has been loaded, this will read "-- Invalid IP location". If the IP location table is not loaded and LocateCountry() is used instead, // this will read "-- Invalid Country Code". // // The lookup is by binary chop array of known IP blocks to see if the IP address falls within any of them. // // Arguments: 1) The client IP address // // Returns: Pointer to either the country code or the location code
_hzfunc(__func__) ;
static hzString ip_not_found = "-- IP Location Not-found" ; static hzString ip_error = "-- IP Location Error" ;
const uint32_t* pZones ; // IP ranges uint32_t nDiv ; // Binary chop divider uint32_t nPos ; // Position found within IP range table uint32_t nMax ; // Total IP zones in operation uint32_t ipval ; // IP as 32 bit uint bool bFound ; // Position if redult
if (s_IpCity_max) { // There is a city level IP location table
pZones = s_IpCity_zones ; nDiv = s_IpCity_div ; nPos = s_IpCity_start ; nMax = s_IpCity_max ; } else { if (!s_IpBasic_max) return *ip_not_found ;
pZones = s_IpBasic_zones ; nDiv = s_IpBasic_div ; nPos = s_IpBasic_start ; nMax = s_IpBasic_max ; }
bFound = false ; ipval = (uint32_t) ipa ;
for (;;) { if (nPos >= nMax) { if (!nDiv) break ; nPos -= nDiv ; nDiv /= 2 ; continue ; }
if (ipval >= pZones[nPos+1]) { if (!nDiv) break ; nPos += nDiv ; nDiv /= 2 ; continue ; }
if (ipval < pZones[nPos]) { if (!nDiv) break ; nPos -= nDiv ; nDiv /= 2 ; continue ; }
bFound = true ; break ; }
if (!bFound) { threadLog("Not found\n") ; return *ip_not_found ; }
if (s_IpCity_max) { return s_IpCity_text + s_IpCity_osets[nPos] ; }
return GetCountryCode(s_IpBasic_codes[nPos]) ; }