1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
|
/* Go (cgo) interface to libgeoip */
package geoip
/*
#cgo pkg-config: geoip
#include <stdio.h>
#include <errno.h>
#include <GeoIP.h>
#include <GeoIPCity.h>
//typedef GeoIP* GeoIP_pnt
*/
import "C"
import (
"fmt"
"log"
"os"
"runtime"
"sync"
"unsafe"
)
type GeoIP struct {
db *C.GeoIP
// We don't use GeoIP's thread-safe API calls, which means there is a
// single global netmask variable that gets clobbered in the main
// lookup routine. Any calls which have _GeoIP_seek_record_gl need to
// be wrapped in this mutex.
mu sync.Mutex
}
func (gi *GeoIP) free() {
if gi == nil {
return
}
if gi.db == nil {
gi = nil
return
}
C.GeoIP_delete(gi.db)
gi = nil
return
}
// Default convenience wrapper around OpenDb
func Open(files ...string) (*GeoIP, error) {
return OpenDb(files, GEOIP_MEMORY_CACHE)
}
// Opens a GeoIP database by filename with specified GeoIPOptions flag.
// All formats supported by libgeoip are supported though there are only
// functions to access some of the databases in this API.
// If you don't pass a filename, it will try opening the database from
// a list of common paths.
func OpenDb(files []string, flag int) (*GeoIP, error) {
if len(files) == 0 {
files = []string{
"/usr/share/GeoIP/GeoIP.dat", // Linux default
"/usr/share/local/GeoIP/GeoIP.dat", // source install?
"/usr/local/share/GeoIP/GeoIP.dat", // FreeBSD
"/opt/local/share/GeoIP/GeoIP.dat", // MacPorts
"/usr/share/GeoIP/GeoIP.dat", // ArchLinux
}
}
g := &GeoIP{}
runtime.SetFinalizer(g, (*GeoIP).free)
var err error
for _, file := range files {
// libgeoip prints errors if it can't open the file, so check first
if _, err := os.Stat(file); err != nil {
if os.IsExist(err) {
log.Println(err)
}
continue
}
cbase := C.CString(file)
defer C.free(unsafe.Pointer(cbase))
g.db, err = C.GeoIP_open(cbase, C.int(flag))
if g.db != nil && err != nil {
break
}
}
if err != nil {
return nil, fmt.Errorf("Error opening GeoIP database (%s): %s", files, err)
}
if g.db == nil {
return nil, fmt.Errorf("Didn't open GeoIP database (%s)", files)
}
C.GeoIP_set_charset(g.db, C.GEOIP_CHARSET_UTF8)
return g, nil
}
// SetCustomDirectory sets the default location for the GeoIP .dat files used when
// calling OpenType()
func SetCustomDirectory(dir string) {
cdir := C.CString(dir)
// GeoIP doesn't copy the string, so don't free it when we're done here.
// defer C.free(unsafe.Pointer(cdir))
C.GeoIP_setup_custom_directory(cdir)
}
// OpenType opens a specified GeoIP database type in the default location with the
// specified GeoIPOptions flag. Constants are defined for each database type
// (for example GEOIP_COUNTRY_EDITION).
func OpenTypeFlag(dbType int, flag int) (*GeoIP, error) {
g := &GeoIP{}
runtime.SetFinalizer(g, (*GeoIP).free)
var err error
g.db, err = C.GeoIP_open_type(C.int(dbType), C.int(flag))
if err != nil {
return nil, fmt.Errorf("Error opening GeoIP database (%d): %s", dbType, err)
}
if g.db == nil {
return nil, fmt.Errorf("Didn't open GeoIP database (%d)", dbType)
}
C.GeoIP_set_charset(g.db, C.GEOIP_CHARSET_UTF8)
return g, nil
}
// OpenType opens a specified GeoIP database type in the default location
// and the 'memory cache' flag. Use OpenTypeFlag() to specify flag.
func OpenType(dbType int) (*GeoIP, error) {
return OpenTypeFlag(dbType, GEOIP_MEMORY_CACHE)
}
// Takes an IPv4 address string and returns the organization name for that IP.
// Requires the GeoIP organization database.
func (gi *GeoIP) GetOrg(ip string) string {
name, _ := gi.GetName(ip)
return name
}
// Works on the ASN, Netspeed, Organization and probably other
// databases, takes and IP string and returns a "name" and the
// netmask.
func (gi *GeoIP) GetName(ip string) (name string, netmask int) {
if gi.db == nil {
return
}
gi.mu.Lock()
defer gi.mu.Unlock()
cip := C.CString(ip)
defer C.free(unsafe.Pointer(cip))
cname := C.GeoIP_name_by_addr(gi.db, cip)
if cname != nil {
name = C.GoString(cname)
defer C.free(unsafe.Pointer(cname))
netmask = int(C.GeoIP_last_netmask(gi.db))
return
}
return
}
type GeoIPRecord struct {
CountryCode string
CountryCode3 string
CountryName string
Region string
City string
PostalCode string
Latitude float32
Longitude float32
MetroCode int
AreaCode int
CharSet int
ContinentCode string
}
// Returns the "City Record" for an IP address. Requires the GeoCity(Lite)
// database - http://www.maxmind.com/en/city
func (gi *GeoIP) GetRecord(ip string) *GeoIPRecord {
if gi.db == nil {
return nil
}
cip := C.CString(ip)
defer C.free(unsafe.Pointer(cip))
gi.mu.Lock()
record := C.GeoIP_record_by_addr(gi.db, cip)
gi.mu.Unlock()
if record == nil {
return nil
}
// defer C.free(unsafe.Pointer(record))
defer C.GeoIPRecord_delete(record)
rec := new(GeoIPRecord)
rec.CountryCode = C.GoString(record.country_code)
rec.CountryCode3 = C.GoString(record.country_code3)
rec.CountryName = C.GoString(record.country_name)
rec.Region = C.GoString(record.region)
rec.City = C.GoString(record.city)
rec.PostalCode = C.GoString(record.postal_code)
rec.Latitude = float32(record.latitude)
rec.Longitude = float32(record.longitude)
rec.CharSet = int(record.charset)
rec.ContinentCode = C.GoString(record.continent_code)
if gi.db.databaseType != C.GEOIP_CITY_EDITION_REV0 {
/* DIRTY HACK BELOW:
The GeoIPRecord struct in GeoIPCity.h contains an int32 union of metro_code and dma_code.
The union is unnamed, so cgo names it anon0 and assumes it's a 4-byte array.
*/
union_int := (*int32)(unsafe.Pointer(&record.anon0))
rec.MetroCode = int(*union_int)
rec.AreaCode = int(record.area_code)
}
return rec
}
// Returns the country code and region code for an IP address. Requires
// the GeoIP Region database.
func (gi *GeoIP) GetRegion(ip string) (string, string) {
if gi.db == nil {
return "", ""
}
cip := C.CString(ip)
defer C.free(unsafe.Pointer(cip))
gi.mu.Lock()
region := C.GeoIP_region_by_addr(gi.db, cip)
gi.mu.Unlock()
if region == nil {
return "", ""
}
countryCode := C.GoString(®ion.country_code[0])
regionCode := C.GoString(®ion.region[0])
defer C.free(unsafe.Pointer(region))
return countryCode, regionCode
}
// Returns the region name given a country code and region code
func GetRegionName(countryCode, regionCode string) string {
cc := C.CString(countryCode)
defer C.free(unsafe.Pointer(cc))
rc := C.CString(regionCode)
defer C.free(unsafe.Pointer(rc))
region := C.GeoIP_region_name_by_code(cc, rc)
if region == nil {
return ""
}
// it's a static string constant, don't free this
regionName := C.GoString(region)
return regionName
}
// Same as GetName() but for IPv6 addresses.
func (gi *GeoIP) GetNameV6(ip string) (name string, netmask int) {
if gi.db == nil {
return
}
gi.mu.Lock()
defer gi.mu.Unlock()
cip := C.CString(ip)
defer C.free(unsafe.Pointer(cip))
cname := C.GeoIP_name_by_addr_v6(gi.db, cip)
if cname != nil {
name = C.GoString(cname)
defer C.free(unsafe.Pointer(cname))
netmask = int(C.GeoIP_last_netmask(gi.db))
return
}
return
}
// Takes an IPv4 address string and returns the country code for that IP
// and the netmask for that IP range.
func (gi *GeoIP) GetCountry(ip string) (cc string, netmask int) {
if gi.db == nil {
return
}
gi.mu.Lock()
defer gi.mu.Unlock()
cip := C.CString(ip)
defer C.free(unsafe.Pointer(cip))
ccountry := C.GeoIP_country_code_by_addr(gi.db, cip)
if ccountry != nil {
cc = C.GoString(ccountry)
netmask = int(C.GeoIP_last_netmask(gi.db))
return
}
return
}
// GetCountry_v6 works the same as GetCountry except for IPv6 addresses, be sure to
// load a database with IPv6 data to get any results.
func (gi *GeoIP) GetCountry_v6(ip string) (cc string, netmask int) {
if gi.db == nil {
return
}
gi.mu.Lock()
defer gi.mu.Unlock()
cip := C.CString(ip)
defer C.free(unsafe.Pointer(cip))
ccountry := C.GeoIP_country_code_by_addr_v6(gi.db, cip)
if ccountry != nil {
cc = C.GoString(ccountry)
netmask = int(C.GeoIP_last_netmask(gi.db))
return
}
return
}
|