This stuff was written ages ago. Updated just now to add IPv6 support, and to use a different CSV format (more than doubling the code in the process...). I use it all the time.
Setup:
Put the shell scripts in $HOME/bin
Get the CSV DB from here. Compress it (xz
recommended; change the ip2c.sh
script otherwise) to save space (1.6 MB vs. 20 MB), then copy the .xz
file into $HOME/lib
Compile the C source. (Parsing half a million lines or so in awk
is just too bloody slow.)
Usage:
Run the ns.sh
to see which countries you're connected to. Or, if you run a server or serve torrent, where your connections come from. Won't work on Linux because the netstat
command is different. The help message is pretty self-explanatory. I mostly use just the -l
and -e
flags.
The ip2c.sh
can be run standalone to check specific IP addresses/hostnames.
IPv6 not entirely tested--my ISP/router doesn't do IPv6, and I haven't bothered to find out why.
Enjoy.
ns.sh
#!/bin/sh
#
# Show countries hosting IP addresses in the "Foreign Address"
# field of a `netstat -n -f inet,inet6' output.
#
# Requires the `ip2c' program and its CSV database file.
# A user supplied database can be specified using the `-f file' option.
#
# The "Foreign Address" field will be sorted (IPv4 only) numerically in
# ascending order. The `-n' flag turns off this sorting. The `-r' flag
# reverses the sort order.
set -eu
(set -o pipefail 2>/dev/null) && set -o pipefail
me=${0##*/}
usage()
{
cat <<EoF
Usage: $me [-ehls] [-f dbfile] [-n|-r]
Show countries hosting IP addresses in the "Foreign Address" field
of a \`netstat -n -f inet,inet6' output on *BSD systems.
Requires the \`ip2c' program and its CSV database file.
The "Foreign Address" field will be sorted in ascending order.
-e Show only established connections
-f file Use CSV database \`file' instead of the ip2c default
-h Show this help message
-l Show country name instead of the ISO 3166 2-letter code
-n Do not sort the "Foreign Address" field
-r Reverse the usual sort order (makes it descending)
-s Run netstat(1) as superuser
Note: only IPv4 addresses are sorted.
EoF
}
# Defaults
doestab=false
dosort=true
dorev=false
sucmd=exec
ipopts=""
while getopts ef:hlnrs opt
do case $opt in
e) doestab=true
;;
f) ipopts="$ipopts -f $OPTARG"
;;
h) usage
exit 0
;;
l) ipopts="$ipopts -l"
;;
n) dosort=false
;;
r) dosort=true
dorev=true
;;
s) sucmd=sudo
;;
\?) usage >&2
exit 1
;;
esac
done
shift $((OPTIND - 1))
if $doestab
then S="egrep '^Active|^Proto|ESTAB'"
else S="cat"
fi
if $dosort # only for IPv4 addrs.
then P='([^ ]+ +)'
pre=" sed -Ee 's/$P$P$P$P$P$P(.+)/\\\5\\\1\\\2\\\3\\\4\\\6\\\7/'"
post="sed -Ee 's/$P$P$P$P$P$P(.+)/\\\2\\\3\\\4\\\5\\\1\\\6\\\7/'"
if $dorev
then sort='sort -s -t. -k1,1nr -k2,2nr -k3,3nr -k4,4nr'
else sort='sort -s -t. -k1,1n -k2,2n -k3,3n -k4,4n'
fi
S="$S | $pre | $sort | $post"
fi
OS=$(uname -s)
case $OS in
FreeBSD) nopts="-p tcp"
;;
NetBSD|OpenBSD) nopts="-f inet,inet6"
;;
*) echo >&2 "$me: $OS: OS not supported."
exit 1
;;
esac
$sucmd netstat -n $nopts |
awk -v icmd="ip2c.sh $ipopts" -v scmd="$S" 'BEGIN {
MAX = 0
iplist = ""
IPv4 = "^([0-9]{1,3}\\.){4}[0-9]+$" # IPv4 field: addr. + port
IPv6 = "^[[:xdigit:]:]+\\.[0-9]+$" # IPv6 "
}
$5 !~ IPv4 && $5 !~ IPv6 { # lines w/o IP addresses
L[NR] = $0 # save orig. line
}
$5 ~ IPv4 || $5 ~ IPv6 { # lines with IP addresses
L[NR] = $0 # save orig. line
if (length > MAX)
MAX = length
sub(/\.[0-9]+$/, "", $5) # get rid of port number
C[$5] = "??" # show ?? in case of error
iplist = iplist " " $5 # make args for "ip2c"
IP[NR] = $5 # save IP address for later
}
END {
icmd = icmd iplist " 2>/dev/null"
while (icmd | getline line) {
split(line, a, /: /)
C[a[1]] = a[2] # replace ?? with actual country
}
close(icmd)
for (i = 1; i <= NR; i++)
if (i in IP) { # show country matching IP address
if ($5 ~ IPv6) # IPv6 lines not sorted
printf "%-*s %s\n", MAX, L[i], C[IP[i]]
else
printf "%-*s %s\n", MAX, L[i], C[IP[i]] | scmd
} else { # lines w/o IP addresses
if (L[i] ~ /^Proto/)
printf "%-*s %s\n", MAX, L[i], "Country"
else
print L[i]
}
close(scmd)
}'
ip2c.sh
#!/bin/sh
set -eu
(set -o pipefail 2>/dev/null) && set -o pipefail
exec xz -dc ~/lib/dbip-country-lite-*.csv.xz | ip2c -f - "$@"
ip2c.c
/**
* Show countries hosting IP addresses or hostnames.
*
* Get the CSV database from:
* https://db-ip.com/db/download/ip-to-country-lite
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <limits.h> /* PATH_MAX */
#include <search.h> /* hcreate(), hsearch(), ... */
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h> /* getopt() */
/* Declarations */
struct ctry {
char* iso; /* ISO 3166 2-letter country code */
char* name; /* Country name */
};
struct db {
union {
struct in_addr in;
struct in6_addr in6;
} min; /* IP address range min */
union {
struct in_addr in;
struct in6_addr in6;
} max; /* IP address range max */
struct ctry* ctp; /* entry in `ctrys' array */
};
struct dbhdr {
struct db* dbp; /* (allocated) DB array */
size_t nip4; /* no. of IPv4 addrs. in DB */
size_t nip6; /* no. of IPv6 addrs. in DB */
size_t sip4; /* start idx of IPv4 addrs. in DB */
size_t sip6; /* start idx of IPv6 addrs. in DB */
};
static char* dblook(struct dbhdr* dhp, struct sockaddr* sa);
static char* getdbfile(void);
static char* getnm(int argc, char* argv[]);
static int cmpip4(const void* key, const void* entry);
static int cmpip6(const void* key, const void* entry);
static int do_opts(int argc, char* argv[]);
static int getflds(char* line, int af, void* saddr, void* eaddr, struct ctry** ctp);
static int getip(char* host, struct sockaddr* sa);
static int is_gt(int af, void* addr1, void* addr2);
static int is_lt(int af, void* addr1, void* addr2);
static struct ctry* ctlook(char* iso);
static void* Realloc(void* ptr, size_t size);
static void ctmk(void);
static void dbmk(FILE* fp, struct dbhdr* dhp);
static void E(const char* fmt, ...);
static void W(const char* fmt, ...);
static void prerr(char* host, struct sockaddr* sa);
static void usage(void);
/* Globals */
static char* prog; /* Program name */
static struct options { /* Program options */
char* file; /* IP-to-country CSV database file name */
int longname; /* Show name instead of ISO 3166 code if 1 */
} opts;
/*
* ISO 3166 codes and corresponding country names from:
*
* https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
*/
static struct ctry ctrys[] = {
{ "AD", "Andorra" },
{ "AE", "United Arab Emirates" },
{ "AF", "Afghanistan" },
{ "AG", "Antigua and Barbuda" },
{ "AI", "Anguilla" },
{ "AL", "Albania" },
{ "AM", "Armenia" },
{ "AO", "Angola" },
{ "AQ", "Antarctica" },
{ "AR", "Argentina" },
{ "AS", "American Samoa" },
{ "AT", "Austria" },
{ "AU", "Australia" },
{ "AW", "Aruba" },
{ "AX", "Åland Islands" },
{ "AZ", "Azerbaijan" },
{ "BA", "Bosnia and Herzegovina" },
{ "BB", "Barbados" },
{ "BD", "Bangladesh" },
{ "BE", "Belgium" },
{ "BF", "Burkina Faso" },
{ "BG", "Bulgaria" },
{ "BH", "Bahrain" },
{ "BI", "Burundi" },
{ "BJ", "Benin" },
{ "BL", "Saint Barthélemy" },
{ "BM", "Bermuda" },
{ "BN", "Brunei Darussalam" },
{ "BO", "Bolivia (Plurinational State of)" },
{ "BQ", "Bonaire, Sint Eustatius and Saba" },
{ "BR", "Brazil" },
{ "BS", "Bahamas" },
{ "BT", "Bhutan" },
{ "BV", "Bouvet Island" },
{ "BW", "Botswana" },
{ "BY", "Belarus" },
{ "BZ", "Belize" },
{ "CA", "Canada" },
{ "CC", "Cocos (Keeling) Islands" },
{ "CD", "Congo, Democratic Republic of the" },
{ "CF", "Central African Republic" },
{ "CG", "Congo" },
{ "CH", "Switzerland" },
{ "CI", "Côte d'Ivoire" },
{ "CK", "Cook Islands" },
{ "CL", "Chile" },
{ "CM", "Cameroon" },
{ "CN", "China" },
{ "CO", "Colombia" },
{ "CR", "Costa Rica" },
{ "CU", "Cuba" },
{ "CV", "Cabo Verde" },
{ "CW", "Curaçao" },
{ "CX", "Christmas Island" },
{ "CY", "Cyprus" },
{ "CZ", "Czechia" },
{ "DE", "Germany" },
{ "DJ", "Djibouti" },
{ "DK", "Denmark" },
{ "DM", "Dominica" },
{ "DO", "Dominican Republic" },
{ "DZ", "Algeria" },
{ "EC", "Ecuador" },
{ "EE", "Estonia" },
{ "EG", "Egypt" },
{ "EH", "Western Sahara" },
{ "ER", "Eritrea" },
{ "ES", "Spain" },
{ "ET", "Ethiopia" },
{ "FI", "Finland" },
{ "FJ", "Fiji" },
{ "FK", "Falkland Islands (Malvinas)" },
{ "FM", "Micronesia (Federated States of)" },
{ "FO", "Faroe Islands" },
{ "FR", "France" },
{ "GA", "Gabon" },
{ "GB", "United Kingdom of Great Britain and Northern Ireland" },
{ "GD", "Grenada" },
{ "GE", "Georgia" },
{ "GF", "French Guiana" },
{ "GG", "Guernsey" },
{ "GH", "Ghana" },
{ "GI", "Gibraltar" },
{ "GL", "Greenland" },
{ "GM", "Gambia" },
{ "GN", "Guinea" },
{ "GP", "Guadeloupe" },
{ "GQ", "Equatorial Guinea" },
{ "GR", "Greece" },
{ "GS", "South Georgia and the South Sandwich Islands" },
{ "GT", "Guatemala" },
{ "GU", "Guam" },
{ "GW", "Guinea-Bissau" },
{ "GY", "Guyana" },
{ "HK", "Hong Kong" },
{ "HM", "Heard Island and McDonald Islands" },
{ "HN", "Honduras" },
{ "HR", "Croatia" },
{ "HT", "Haiti" },
{ "HU", "Hungary" },
{ "ID", "Indonesia" },
{ "IE", "Ireland" },
{ "IL", "Israel" },
{ "IM", "Isle of Man" },
{ "IN", "India" },
{ "IO", "British Indian Ocean Territory" },
{ "IQ", "Iraq" },
{ "IR", "Iran (Islamic Republic of)" },
{ "IS", "Iceland" },
{ "IT", "Italy" },
{ "JE", "Jersey" },
{ "JM", "Jamaica" },
{ "JO", "Jordan" },
{ "JP", "Japan" },
{ "KE", "Kenya" },
{ "KG", "Kyrgyzstan" },
{ "KH", "Cambodia" },
{ "KI", "Kiribati" },
{ "KM", "Comoros" },
{ "KN", "Saint Kitts and Nevis" },
{ "KP", "Korea (Democratic People's Republic of)" },
{ "KR", "Korea, Republic of" },
{ "KW", "Kuwait" },
{ "KY", "Cayman Islands" },
{ "KZ", "Kazakhstan" },
{ "LA", "Lao People's Democratic Republic" },
{ "LB", "Lebanon" },
{ "LC", "Saint Lucia" },
{ "LI", "Liechtenstein" },
{ "LK", "Sri Lanka" },
{ "LR", "Liberia" },
{ "LS", "Lesotho" },
{ "LT", "Lithuania" },
{ "LU", "Luxembourg" },
{ "LV", "Latvia" },
{ "LY", "Libya" },
{ "MA", "Morocco" },
{ "MC", "Monaco" },
{ "MD", "Moldova, Republic of" },
{ "ME", "Montenegro" },
{ "MF", "Saint Martin (French part)" },
{ "MG", "Madagascar" },
{ "MH", "Marshall Islands" },
{ "MK", "North Macedonia" },
{ "ML", "Mali" },
{ "MM", "Myanmar" },
{ "MN", "Mongolia" },
{ "MO", "Macao" },
{ "MP", "Northern Mariana Islands" },
{ "MQ", "Martinique" },
{ "MR", "Mauritania" },
{ "MS", "Montserrat" },
{ "MT", "Malta" },
{ "MU", "Mauritius" },
{ "MV", "Maldives" },
{ "MW", "Malawi" },
{ "MX", "Mexico" },
{ "MY", "Malaysia" },
{ "MZ", "Mozambique" },
{ "NA", "Namibia" },
{ "NC", "New Caledonia" },
{ "NE", "Niger" },
{ "NF", "Norfolk Island" },
{ "NG", "Nigeria" },
{ "NI", "Nicaragua" },
{ "NL", "Netherlands" },
{ "NO", "Norway" },
{ "NP", "Nepal" },
{ "NR", "Nauru" },
{ "NU", "Niue" },
{ "NZ", "New Zealand" },
{ "OM", "Oman" },
{ "PA", "Panama" },
{ "PE", "Peru" },
{ "PF", "French Polynesia" },
{ "PG", "Papua New Guinea" },
{ "PH", "Philippines" },
{ "PK", "Pakistan" },
{ "PL", "Poland" },
{ "PM", "Saint Pierre and Miquelon" },
{ "PN", "Pitcairn" },
{ "PR", "Puerto Rico" },
{ "PS", "Palestine, State of" },
{ "PT", "Portugal" },
{ "PW", "Palau" },
{ "PY", "Paraguay" },
{ "QA", "Qatar" },
{ "RE", "Réunion" },
{ "RO", "Romania" },
{ "RS", "Serbia" },
{ "RU", "Russian Federation" },
{ "RW", "Rwanda" },
{ "SA", "Saudi Arabia" },
{ "SB", "Solomon Islands" },
{ "SC", "Seychelles" },
{ "SD", "Sudan" },
{ "SE", "Sweden" },
{ "SG", "Singapore" },
{ "SH", "Saint Helena, Ascension and Tristan da Cunha" },
{ "SI", "Slovenia" },
{ "SJ", "Svalbard and Jan Mayen" },
{ "SK", "Slovakia" },
{ "SL", "Sierra Leone" },
{ "SM", "San Marino" },
{ "SN", "Senegal" },
{ "SO", "Somalia" },
{ "SR", "Suriname" },
{ "SS", "South Sudan" },
{ "ST", "Sao Tome and Principe" },
{ "SV", "El Salvador" },
{ "SX", "Sint Maarten (Dutch part)" },
{ "SY", "Syrian Arab Republic" },
{ "SZ", "Eswatini" },
{ "TC", "Turks and Caicos Islands" },
{ "TD", "Chad" },
{ "TF", "French Southern Territories" },
{ "TG", "Togo" },
{ "TH", "Thailand" },
{ "TJ", "Tajikistan" },
{ "TK", "Tokelau" },
{ "TL", "Timor-Leste" },
{ "TM", "Turkmenistan" },
{ "TN", "Tunisia" },
{ "TO", "Tonga" },
{ "TR", "Türkiye" },
{ "TT", "Trinidad and Tobago" },
{ "TV", "Tuvalu" },
{ "TW", "Taiwan, Province of China" },
{ "TZ", "Tanzania, United Republic of" },
{ "UA", "Ukraine" },
{ "UG", "Uganda" },
{ "UM", "United States Minor Outlying Islands" },
{ "US", "United States of America" },
{ "UY", "Uruguay" },
{ "UZ", "Uzbekistan" },
{ "VA", "Holy See" },
{ "VC", "Saint Vincent and the Grenadines" },
{ "VE", "Venezuela (Bolivarian Republic of)" },
{ "VG", "Virgin Islands (British)" },
{ "VI", "Virgin Islands (U.S.)" },
{ "VN", "Viet Nam" },
{ "VU", "Vanuatu" },
{ "WF", "Wallis and Futuna" },
{ "WS", "Samoa" },
{ "XK", "Kosovo" }, /* XXX: addition */
{ "YE", "Yemen" },
{ "YT", "Mayotte" },
{ "ZA", "South Africa" },
{ "ZM", "Zambia" },
{ "ZW", "Zimbabwe" },
{ "ZZ", "Unknown" }
};
/**
* M A I N
*/
int
main(int argc, char* argv[])
{
struct dbhdr dbhdr;
int i;
FILE* fp;
prog = getnm(argc, argv);
i = do_opts(argc, argv);
argc -= i; argv += i;
if (argc == 0) {
usage();
return EXIT_FAILURE;
}
if (strcmp(opts.file, "-") == 0) {
opts.file = "<stdin>";
fp = stdin;
} else
fp = fopen(opts.file, "r");
if (fp == NULL) {
E("%s: can't open CSV database file.", opts.file);
return EXIT_FAILURE;
}
ctmk();
dbmk(fp, &dbhdr); /* convert CSV data into `db' array */
if (fp != stdin)
fclose(fp); /* no longer needed */
for (i = 0; i < argc; i++) {
uint64_t buf[8]; /* 64 bytes 8-byte aligned blob */
struct sockaddr* sa = (struct sockaddr* )buf;
if (getip(argv[i], sa)) {
char* ctry = dblook(&dbhdr, sa);
if (ctry != NULL)
printf("%s: %s\n", argv[i], ctry);
else
prerr(argv[i], sa);
}
}
free(dbhdr.dbp);
hdestroy();
return EXIT_SUCCESS;
}
static void
dbmk(FILE* fp, struct dbhdr* dhp)
{
size_t dbsz, ndb, nip4, nip6, sip4, sip6, lineno;
enum state { INIT, INV4, INV6 } state;
struct db* db;
char line[128];
state = INIT;
db = NULL;
dbsz = ndb = nip4 = nip6 = sip4 = sip6 = lineno = 0;
while (fgets(line, sizeof line, fp) != NULL) {
struct in_addr sin4 = { 0 }, ein4 = { 0 }; /* placate compiler */
struct in6_addr sin6 = { 0 }, ein6 = { 0 }; /* " */
struct ctry* ctp;
void* saddr = NULL, *eaddr = NULL; /* " */
int af = -1; /* " */
lineno++;
if (lineno == 1 && strncmp(line, "::", 2) && strchr(line, '.')) {
state = INV4;
sip4 = ndb;
} else if (lineno > 1 && !strncmp(line, "::", 2) && state == INV4) {
state = INV6;
sip6 = ndb;
}
switch (state) {
case INV4:
af = AF_INET;
saddr = &sin4;
eaddr = &ein4;
break;
case INV6:
af = AF_INET6;
saddr = &sin6;
eaddr = &ein6;
break;
case INIT:
W("%s: bad CSV file", opts.file);
free(db);
exit(EXIT_FAILURE);
}
if (getflds(line, af, saddr, eaddr, &ctp) == 0) {
W("%s:%zu: bad CSV line: %s", opts.file, lineno, line);
free(db);
exit(EXIT_FAILURE);
}
/* Grow array if needed. */
if (ndb == dbsz) {
dbsz = (dbsz == 0) ? 512*1024 : dbsz * 2;
if (dbsz > 512 * 1024 * 1024) {
W("error: >512MB allocated");
free(db);
exit(EXIT_FAILURE);
}
db = Realloc(db, dbsz * sizeof (struct db));
}
switch (state) {
case INV4:
db[ndb].min.in = sin4;
db[ndb].max.in = ein4;
nip4++;
break;
case INV6:
db[ndb].min.in6 = sin6;
db[ndb].max.in6 = ein6;
nip6++;
break;
case INIT: /* pacify compiler */
W("invalid state");
exit(EXIT_FAILURE);
}
db[ndb].ctp = ctp;
ndb++;
}
if (ndb == 0) {
W("%s: empty DB", opts.file);
exit(EXIT_FAILURE);
}
dhp->dbp = db;
dhp->nip4 = nip4;
dhp->nip6 = nip6;
dhp->sip4 = sip4;
dhp->sip6 = sip6;
}
static int
getflds(char* line, int af, void* saddr, void* eaddr, struct ctry** ctp)
{
char *p, *s, buf[INET6_ADDRSTRLEN];
size_t n;
/* start of IP address range */
s = line;
if ((p = strchr(s, ',')) == NULL || (n = p - s) >= INET6_ADDRSTRLEN)
return 0;
strncpy(buf, s, n); buf[n] = '\0';
if (inet_pton(af, buf, saddr) != 1)
return 0;
/* end of IP address range */
s = p + 1;
if ((p = strchr(s, ',')) == NULL || (n = p - s) >= INET6_ADDRSTRLEN)
return 0;
strncpy(buf, s, n); buf[n] = '\0';
if (inet_pton(af, buf, eaddr) != 1)
return 0;
if (is_lt(af, eaddr, saddr)) /* XXX: check is sorted too? */
return 0;
/* ISO 3166 two-letter country code */
s = p + 1;
if ((p = strchr(s, '\n')) == NULL || (n = p - s) > 2)
return 0;
strncpy(buf, s, n); buf[n] = '\0';
if ((*ctp = ctlook(buf)) == NULL) {
W("%s: country code not in internal list", buf);
return 0;
}
return 1;
}
#define NELEM(x) (sizeof (x) / sizeof (x[0]))
static void
ctmk(void)
{
ENTRY item;
if (hcreate(NELEM(ctrys)) == 0) {
E("can't create hash table.");
exit(EXIT_FAILURE);
}
for (size_t i = 0; i < NELEM(ctrys); i++) {
#ifdef __FreeBSD__
char* s;
if ((s = strdup(ctrys[i].iso)) == NULL) {
E("%s: out of memory.", __func__);
exit(EXIT_FAILURE);
}
item.key = s;
#else
item.key = ctrys[i].iso;
#endif
item.data = &ctrys[i];
if (hsearch(item, ENTER) == NULL) {
E("hash table full.");
exit(EXIT_FAILURE);
}
}
}
static struct ctry*
ctlook(char* iso)
{
ENTRY item, *ent;
item.key = iso;
item.data = NULL; /* appease static-analyzer */
if ((ent = hsearch(item, FIND)) == NULL)
return NULL;
return ent->data;
}
#if 0 /* binary search of (sorted) country list */
#define NELEM(x) (sizeof (x) / sizeof (x[0]))
static struct ctry*
ctlook(char* iso)
{
return bsearch(iso, ctrys, NELEM(ctrys), sizeof ctrys[0], cmpiso);
}
static int
cmpiso(const void* key, const void* entry)
{
return strcmp((const char* )key, ((const struct ctry* )entry)->iso);
}
#endif
static int
getip(char* host, struct sockaddr* sa)
{
struct addrinfo *res0 = NULL;
int rc;
rc = getaddrinfo(host, NULL, NULL, &res0);
if (rc) {
W("%s: DNS lookup failed: %s", host, gai_strerror(rc));
return 0;
}
/* pure paranoia... */
if ((res0->ai_family != AF_INET && res0->ai_family != AF_INET6) ||
res0->ai_addrlen > 64 || res0->ai_addrlen <= 4) {
W("bad DNS server response");
return 0;
}
memcpy(sa, res0->ai_addr, res0->ai_addrlen);
freeaddrinfo(res0);
return 1;
}
static char*
dblook(struct dbhdr* dhp, struct sockaddr* sa)
{
struct db* ent;
switch (sa->sa_family) {
case AF_INET: {
struct db* dbp = dhp->dbp + dhp->sip4;
size_t nip = dhp->nip4;
struct sockaddr_in* sin = (struct sockaddr_in *)sa;
void* ip = &sin->sin_addr;
ent = bsearch(ip, dbp, nip, sizeof dbp[0], cmpip4);
break;
}
case AF_INET6: {
struct db* dbp = dhp->dbp + dhp->sip6;
size_t nip = dhp->nip6;
struct sockaddr_in6* sin6 = (struct sockaddr_in6 *)sa;
void* ip6 = &sin6->sin6_addr;
ent = bsearch(ip6, dbp, nip, sizeof dbp[0], cmpip6);
break;
}
default:
W("unknown address family");
exit(EXIT_FAILURE);
}
if (ent == NULL || ent->ctp == NULL)
return NULL;
if (opts.longname)
return ent->ctp->name;
return ent->ctp->iso;
}
static int
cmpip4(const void* key, const void* entry)
{
struct in_addr* ip4 = (struct in_addr* )key;
struct db* dbp = (struct db* )entry;
uint32_t ip, min, max;
ip = ip4->s_addr;
min = dbp->min.in.s_addr;
max = dbp->max.in.s_addr;
return is_lt(AF_INET, &ip, &min) ? -1 : is_gt(AF_INET, &ip, &max);
}
static int
cmpip6(const void* key, const void* entry)
{
struct in6_addr* ip6 = (struct in6_addr* )key;
struct db* dbp = (struct db* )entry;
void* ip = ip6->s6_addr;
void* min = dbp->min.in6.s6_addr;
void* max = dbp->max.in6.s6_addr;
return is_lt(AF_INET6, ip, min) ? -1 : is_gt(AF_INET6, ip, max);
}
static int
is_lt(int af, void* addr1, void* addr2)
{
uint32_t a1[4], a2[4], i;
switch (af) {
case AF_INET:
return ntohl(*(uint32_t* )addr1) < ntohl(*(uint32_t* )addr2);
case AF_INET6:
for (i = 0; i < 4; i++) {
memcpy(a1 + i, (uint8_t* )addr1 + i * 4, 4);
memcpy(a2 + i, (uint8_t* )addr2 + i * 4, 4);
a1[i] = ntohl(a1[i]);
a2[i] = ntohl(a2[i]);
if (a1[i] < a2[i])
return 1;
if (a1[i] > a2[i])
return 0;
}
return 0;
default:
W("unknown address family");
exit(EXIT_FAILURE);
}
}
static int
is_gt(int af, void* addr1, void* addr2)
{
uint32_t a1[4], a2[4], i;
switch (af) {
case AF_INET:
return ntohl(*(uint32_t* )addr1) > ntohl(*(uint32_t* )addr2);
case AF_INET6:
for (i = 0; i < 4; i++) {
memcpy(a1 + i, (uint8_t* )addr1 + i * 4, 4);
memcpy(a2 + i, (uint8_t* )addr2 + i * 4, 4);
a1[i] = ntohl(a1[i]);
a2[i] = ntohl(a2[i]);
if (a1[i] > a2[i])
return 1;
if (a1[i] < a2[i])
return 0;
}
return 0;
default:
W("unknown address family");
exit(EXIT_FAILURE);
}
}
static void
prerr(char* host, struct sockaddr* sa)
{
char ip[INET6_ADDRSTRLEN];
struct sockaddr_in* sin;
struct sockaddr_in6* sin6;
void* addr;
switch (sa->sa_family) {
case AF_INET:
sin = (struct sockaddr_in* )sa;
addr = &sin->sin_addr.s_addr;
break;
case AF_INET6:
sin6 = (struct sockaddr_in6* )sa;
addr = sin6->sin6_addr.s6_addr;
break;
default:
W("unknown address family");
exit(EXIT_FAILURE);
}
if (inet_ntop(sa->sa_family, addr, ip, sizeof ip) == NULL)
E("%s: inet_ntop error.", host);
else
W("%s[%s]: not found in CSV database", host, ip);
}
static char*
getnm(int argc, char* argv[])
{
if (argc < 1 || argv[0] == NULL || *argv[0] == '\0')
return "<noname>";
return argv[0];
}
static char*
getdbfile(void)
{
static char dbfile[PATH_MAX];
char* home;
if ((home = getenv("HOME")) == NULL || *home == '\0')
strcpy(dbfile, "dbip.csv");
else {
strcpy(dbfile, home);
strcat(dbfile, "/lib/dbip.csv");
}
return dbfile;
}
/**
* Process program options.
*/
static int
do_opts(int argc, char* argv[])
{
int opt;
/* defaults */
opts.file = getdbfile(); /* get default CSV database filename */
opts.longname = 0; /* show ISO 3166 2-letter country code */
while ((opt = getopt(argc, argv, "f:l")) != -1) {
switch (opt) {
case 'f':
opts.file = optarg;
break;
case 'l':
opts.longname = 1;
break;
default:
usage();
exit(EXIT_FAILURE);
}
}
return optind;
}
/**
* Print usage information.
*/
static void
usage(void)
{
fprintf(stderr,
"Usage: %s [-f file] [-l] HOSTNAME...\n"
"%s: Show countries hosting IP addresses or hostnames\n"
"\n"
" -f file Use the specified `file' (`-' for stdin) instead of\n"
" the default CSV DB, `$HOME/lib/dbip.csv'.\n"
" -l Show country name instead of the ISO 3166 2-letter code\n"
"\n"
"Get a CSV DB from: https://db-ip.com/db/download/ip-to-country-lite\n",
prog, prog);
}
/**
* realloc wrapper.
*/
static void*
Realloc(void* ptr, size_t size)
{
void* p;
p = realloc(ptr, size);
if (p == NULL) {
E("realloc() failed.");
exit(EXIT_FAILURE);
}
return p;
}
/**
* Print a warning message.
*/
static void
W(const char* fmt, ...)
{
va_list ap;
fflush(stdout);
if (prog && *prog)
fprintf(stderr, "%s: ", prog);
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fprintf(stderr, "\n");
fflush(stderr);
}
/**
* Print an error message.
*/
static void
E(const char* fmt, ...)
{
va_list ap;
fflush(stdout);
if (prog && *prog)
fprintf(stderr, "%s: ", prog);
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
if (errno != 0) {
fprintf(stderr, " %s", strerror(errno));
errno = 0;
}
fprintf(stderr, "\n");
fflush(stderr);
}