├── .gitignore ├── Dockerfile ├── Dockerfile.build ├── LICENSE ├── Makefile ├── README.rst ├── application.go ├── cli └── cli.go ├── cmd └── main.go ├── config.go ├── constants.go ├── errors.go ├── examples └── client │ └── main.go ├── geoip.go ├── go.mod ├── go.sum ├── handlers_http.go ├── handlers_rpc.go ├── middleware.go ├── modd.conf ├── options.go ├── proto ├── geoipfix.pb.go └── geoipfix.proto ├── scripts ├── deploy.sh ├── kubernetes │ ├── basic-k8s.yml │ ├── geoipfix-config.yml │ ├── geoipfix-deployment.yml │ ├── geoipfix-ingress.yml │ └── geoipfix-service.yml ├── publish.sh ├── release.sh └── version.sh ├── serializer.go ├── server.go ├── server_http.go ├── server_rpc.go └── service.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | *.gz 26 | *.json 27 | bin/* 28 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:stretch-slim 2 | 3 | RUN apt-get update \ 4 | && apt-get install -y wget 5 | 6 | RUN mkdir -p /usr/share/geoip \ 7 | && wget -O /tmp/GeoLite2-City.tar.gz http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz \ 8 | && tar xf /tmp/GeoLite2-City.tar.gz -C /usr/share/geoip --strip 1 \ 9 | && gzip /usr/share/geoip/GeoLite2-City.mmdb \ 10 | && ls -al /usr/share/geoip/ 11 | 12 | ADD bin/geoipfix /geoipfix 13 | 14 | CMD ["/geoipfix"] 15 | -------------------------------------------------------------------------------- /Dockerfile.build: -------------------------------------------------------------------------------- 1 | FROM golang:1.21 2 | 3 | ADD . /go/src/github.com/ulule/geoipfix 4 | 5 | WORKDIR /go/src/github.com/ulule/geoipfix 6 | 7 | CMD make build-static 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2018 Ulule 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ROOT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) 2 | VERSION=$(awk '/Version/ { gsub("\"", ""); print $NF }' ${ROOT_DIR}/application/constants.go) 3 | 4 | BIN_DIR = $(ROOT_DIR)/bin 5 | SHARE_DIR = $(ROOT_DIR)/share 6 | APP_DIR = /go/src/github.com/ulule/geoipfix 7 | 8 | branch = $(shell git rev-parse --abbrev-ref HEAD) 9 | commit = $(shell git log --pretty=format:'%h' -n 1) 10 | now = $(shell date "+%Y-%m-%d %T UTC%z") 11 | compiler = $(shell go version) 12 | 13 | test: unit 14 | 15 | generate: 16 | protoc --gofast_out=plugins=grpc:. proto/geoipfix.proto 17 | 18 | dependencies: 19 | dep ensure -v 20 | 21 | run: 22 | GEOIPFIX_CONF=`pwd`/config.json ./bin/geoipfix 23 | 24 | live: 25 | @modd 26 | 27 | unit: 28 | @(go list ./... | xargs -n1 go test -v -cover) 29 | 30 | all: geoipfix 31 | @(mkdir -p $(BIN_DIR)) 32 | 33 | build: 34 | @(echo "-> Compiling geoipfix binary") 35 | @(mkdir -p $(BIN_DIR)) 36 | @(go build -ldflags "\ 37 | -X 'github.com/ulule/geoipfix.Branch=$(branch)' \ 38 | -X 'github.com/ulule/geoipfix.Revision=$(commit)' \ 39 | -X 'github.com/ulule/geoipfix.BuildTime=$(now)' \ 40 | -X 'github.com/ulule/geoipfix.Compiler=$(compiler)'" -a -installsuffix cgo -o $(BIN_DIR)/geoipfix ./cmd/main.go) 41 | @(echo "-> geoipfix binary created") 42 | 43 | build-static: 44 | @(echo "-> Creating statically linked binary...") 45 | @(mkdir -p $(BIN_DIR)) 46 | @(CGO_ENABLED=0 go build -ldflags "\ 47 | -X 'github.com/ulule/geoipfix.Branch=$(branch)' \ 48 | -X 'github.com/ulule/geoipfix.Revision=$(commit)' \ 49 | -X 'github.com/ulule/geoipfix.BuildTime=$(now)' \ 50 | -X 'github.com/ulule/geoipfix.Compiler=$(compiler)'" -a -installsuffix cgo -o $(BIN_DIR)/geoipfix ./cmd/main.go) 51 | 52 | 53 | docker-build: 54 | @(echo "-> Preparing builder...") 55 | @(docker build -t geoipfix-builder -f Dockerfile.build .) 56 | @(mkdir -p $(BIN_DIR)) 57 | @(echo "-> Running geoipfix builder...") 58 | @(docker run --rm -v $(BIN_DIR):$(APP_DIR)/bin geoipfix-builder) 59 | 60 | format: 61 | @(go fmt ./...) 62 | @(go vet ./...) 63 | 64 | .PNONY: all test format 65 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | geoipfix 2 | ===== 3 | 4 | geoipfix is a Go service (HTTP+RPC) to retrieve geolocation information 5 | about an ip address with freegeoip_ helpers. 6 | 7 | We are using bindings from the maxmind_ database. 8 | 9 | Installation 10 | ============ 11 | 12 | Build it locally 13 | ---------------- 14 | 15 | 1. Make sure you have a Go language compiler >= 1.3 (required) and git installed. 16 | 2. Make sure you have the following go system dependencies in your $PATH: bzr, svn, hg, git 17 | 3. Ensure your GOPATH_ is properly set. 18 | 4. Download it: 19 | 20 | :: 21 | 22 | git clone https://github.com/ulule/geoipfix.git 23 | 24 | 4. Run ``make build`` 25 | 26 | You have now a binary version of geoipfix in the ``bin`` directory which 27 | fits perfectly with your architecture. 28 | 29 | Build it using Docker 30 | --------------------- 31 | 32 | If you don't want to install Go and Docker_ is installed on your computer 33 | 34 | :: 35 | 36 | make docker-build 37 | 38 | You will have a binary version of geoipfix compiled for linux in the ``bin`` directory. 39 | 40 | Configuration 41 | ============= 42 | 43 | Configuration should be stored in a readable file and in JSON format. 44 | 45 | A complete example of the configuration file with RPC+HTTP would be: 46 | 47 | ``config.json`` 48 | 49 | .. code-block:: json 50 | 51 | { 52 | "server": { 53 | "rpc": { 54 | "port": 33001 55 | }, 56 | "http": { 57 | "port": 3001, 58 | "cors": { 59 | "allowed_origins": ["*.ulule.com"], 60 | "allowed_methods": ["GET", "HEAD", "POST"], 61 | "allowed_headers": ["Origin", "Accept", "Content-Type", "X-Requested-With"] 62 | } 63 | } 64 | }, 65 | "database_path": "./GeoLite2-City.mmdb.gz" 66 | } 67 | 68 | Be careful, you should download first locally the GeoLite_ database because the service 69 | will be unavailable until it will download the database. 70 | 71 | HTTP server 72 | =========== 73 | 74 | The HTTP server is based on chi_. 75 | 76 | It's disabled by default, you can activate it by adding the `http` section to `server`. 77 | 78 | ``config.json`` 79 | 80 | .. code-block:: json 81 | 82 | { 83 | "server": { 84 | "http": { 85 | "port": 3001, 86 | } 87 | } 88 | } 89 | 90 | CORS 91 | ---- 92 | 93 | geoipfix supports CORS headers customization in your config file. 94 | 95 | To enable this feature, set ``allowed_origins`` and ``allowed_methods``, 96 | for example: 97 | 98 | ``config.json`` 99 | 100 | .. code-block:: json 101 | 102 | { 103 | "allowed_origins": ["*.ulule.com"], 104 | "allowed_methods": ["GET", "HEAD"] 105 | } 106 | 107 | RPC server 108 | =========== 109 | 110 | The RPC server is based on grpc_. 111 | 112 | It's disabled by default, you can activate it by adding the `rpc` section to `server`. 113 | 114 | ``config.json`` 115 | 116 | .. code-block:: json 117 | 118 | { 119 | "server": { 120 | "http": { 121 | "port": 33001, 122 | } 123 | } 124 | } 125 | 126 | You can found a client example in the `repository `_ and execute it: 127 | 128 | :: 129 | 130 | go run examples/client/main.go -ip {YOUR_IP_ADDRESS} -server-addr {RPC_ADDRESS} 131 | 132 | Usage 133 | ===== 134 | 135 | When your configuration is done, you can start the service as follow: 136 | 137 | :: 138 | 139 | geoipfix -c config.json 140 | 141 | or using an environment variable: 142 | 143 | :: 144 | 145 | GEOIPFIX_CONF=/path/to/config.json geoipfix 146 | 147 | By default, this will run the application on port 3001 and can be accessed by visiting: 148 | 149 | :: 150 | 151 | http://localhost:3001 152 | 153 | The port number can be configured with ``port`` option in your config file. 154 | 155 | To see a list of all available options, run: 156 | 157 | :: 158 | 159 | geoipfix --help 160 | 161 | Development 162 | =========== 163 | 164 | I recommend to install the live reload utility modd_ to make your life easier. 165 | 166 | Install it: 167 | 168 | :: 169 | 170 | go get github.com/cortesi/modd/cmd/modd 171 | 172 | Then launch it in the geoipfix directory: 173 | 174 | :: 175 | 176 | GEOIPFIX_CONF=config.json make live 177 | 178 | 179 | .. _GOPATH: http://golang.org/doc/code.html#GOPATH 180 | .. _GeoLite: http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz 181 | .. _freegeoip: https://github.com/fiorix/freegeoip 182 | .. _maxmind: https://www.maxmind.com/fr/home 183 | .. _modd: https://github.com/cortesi/modd 184 | .. _chi: https://github.com/go-chi/chi 185 | .. _grpc: https://grpc.io/ 186 | .. _Docker: https://docker.com 187 | 188 | Dang, what's this name? 189 | ======================= 190 | 191 | It was an initial proposal from `kyojin `_ based on `Idéfix `_. 192 | 193 | .. image:: https://media.giphy.com/media/Ob7p7lDT99cd2/giphy.gif 194 | -------------------------------------------------------------------------------- /application.go: -------------------------------------------------------------------------------- 1 | package geoipfix 2 | 3 | import ( 4 | "github.com/fiorix/freegeoip" 5 | "go.uber.org/zap" 6 | ) 7 | 8 | // application is the geoipfix application. 9 | type application struct { 10 | DB *freegeoip.DB 11 | Logger *zap.Logger 12 | Config *config 13 | } 14 | 15 | // newApplication initializes a new Application instance. 16 | func newApplication(config string) (*application, error) { 17 | cfg, err := loadConfig(config) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | db, err := openDB(cfg.DatabasePath, UpdateInterval, RetryInterval) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | logger, _ := zap.NewProduction() 28 | 29 | return &application{ 30 | Config: cfg, 31 | Logger: logger, 32 | DB: db, 33 | }, nil 34 | } 35 | -------------------------------------------------------------------------------- /cli/cli.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/urfave/cli" 8 | 9 | "github.com/ulule/geoipfix" 10 | ) 11 | 12 | // Run runs the application. 13 | func Run() { 14 | app := cli.NewApp() 15 | app.Name = "geoipfix" 16 | app.Author = "thoas" 17 | app.Email = "florent@ulule.com" 18 | app.Usage = "A webservice to retrieve geolocation information from an ip address" 19 | app.Version = fmt.Sprintf("%s [git:%s:%s]\ncompiled using %s at %s)", geoipfix.Version, geoipfix.Branch, geoipfix.Revision, geoipfix.Compiler, geoipfix.BuildTime) 20 | app.Flags = []cli.Flag{ 21 | cli.StringFlag{ 22 | Name: "config, c", 23 | Usage: "Config file path", 24 | EnvVar: "GEOIPFIX_CONF", 25 | }, 26 | } 27 | app.Commands = []cli.Command{ 28 | { 29 | Name: "version", 30 | ShortName: "v", 31 | Usage: "Retrieve the version number", 32 | Action: func(c *cli.Context) { 33 | fmt.Printf("geoipfix %s\n", geoipfix.Version) 34 | }, 35 | }, 36 | } 37 | app.Action = func(c *cli.Context) { 38 | config := c.String("config") 39 | 40 | if config != "" { 41 | if _, err := os.Stat(config); err != nil { 42 | fmt.Fprintf(os.Stderr, "Can't find config file `%s`\n", config) 43 | os.Exit(1) 44 | } 45 | } else { 46 | fmt.Fprintf(os.Stderr, "Can't find config file\n") 47 | os.Exit(1) 48 | } 49 | 50 | err := geoipfix.Run(config) 51 | 52 | if err != nil { 53 | fmt.Fprint(os.Stderr, err) 54 | os.Exit(1) 55 | } 56 | } 57 | 58 | app.Run(os.Args) 59 | } 60 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/ulule/geoipfix/cli" 5 | ) 6 | 7 | func main() { 8 | cli.Run() 9 | } 10 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package geoipfix 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "strings" 7 | 8 | "github.com/pkg/errors" 9 | ) 10 | 11 | type corsConfig struct { 12 | AllowedOrigins []string `json:"allowed_origins"` 13 | AllowedMethods []string `json:"allowed_methods"` 14 | AllowedHeaders []string `json:"allowed_headers"` 15 | AllowCredentials bool `json:"allow_credentials"` 16 | ExposedHeaders []string `json:"exposed_headers"` 17 | MaxAge int `json:"max_age"` 18 | } 19 | 20 | type serverRPCConfig struct { 21 | Port int `json:"port"` 22 | } 23 | 24 | type serverHTTPConfig struct { 25 | Port int `json:"port"` 26 | Cors *corsConfig `json:"cors"` 27 | } 28 | 29 | type serverConfig struct { 30 | HTTP *serverHTTPConfig `json:"http"` 31 | RPC *serverRPCConfig `json:"rpc"` 32 | } 33 | 34 | // config is geoipfix config 35 | type config struct { 36 | Debug bool `json:"debug"` 37 | DatabasePath string `json:"database_path"` 38 | Server serverConfig `json:"server"` 39 | } 40 | 41 | // loadConfig return a jsonq instance from a config path 42 | func loadConfig(path string) (*config, error) { 43 | content, err := ioutil.ReadFile(path) 44 | 45 | if err != nil { 46 | return nil, errors.Wrapf(err, "Config file %s cannot be loaded", path) 47 | } 48 | 49 | return loadConfigFromContent(string(content)) 50 | } 51 | 52 | // loadConfigFromContent returns a jsonq instance from a config content 53 | func loadConfigFromContent(content string) (*config, error) { 54 | cfg := &config{} 55 | dec := json.NewDecoder(strings.NewReader(content)) 56 | err := dec.Decode(cfg) 57 | 58 | if err != nil { 59 | return nil, errors.Wrapf(err, "Config file %s cannot be parsed", content) 60 | } 61 | 62 | if cfg.DatabasePath == "" { 63 | cfg.DatabasePath = DatabaseURL 64 | } 65 | 66 | if cfg.Server.HTTP.Port == 0 { 67 | cfg.Server.HTTP.Port = DefaultPort 68 | } 69 | 70 | return cfg, nil 71 | } 72 | -------------------------------------------------------------------------------- /constants.go: -------------------------------------------------------------------------------- 1 | package geoipfix 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Version is the current application version 8 | const Version = "0.1.0" 9 | 10 | // DefaultPort is the default server port 11 | const DefaultPort = 3001 12 | 13 | // DatabaseURL is the full url to download the maxmind database 14 | const DatabaseURL = "http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz" 15 | 16 | // UpdateInterval is the default time to update the database 17 | const UpdateInterval = 24 * time.Hour 18 | 19 | // RetryInterval is the default retry time to retry the update 20 | const RetryInterval = time.Hour 21 | 22 | // compilation variables. 23 | var ( 24 | Branch string 25 | Revision string 26 | BuildTime string 27 | Compiler string 28 | ) 29 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package geoipfix 2 | -------------------------------------------------------------------------------- /examples/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "log" 7 | "time" 8 | 9 | grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry" 10 | "github.com/ulule/geoipfix/proto" 11 | "golang.org/x/net/trace" 12 | "google.golang.org/grpc" 13 | "google.golang.org/grpc/codes" 14 | ) 15 | 16 | func main() { 17 | var serverAddr string 18 | var ipAddress string 19 | var maxRetry uint 20 | 21 | flag.StringVar(&serverAddr, "server-addr", "127.0.0.1:33001", "rpc server addr") 22 | flag.StringVar(&ipAddress, "ip", "127.0.0.1", "ip address") 23 | flag.UintVar(&maxRetry, "retries", 3, "max retries") 24 | flag.Parse() 25 | 26 | tr := trace.New("geoipfix.Client", "GetLocation") 27 | defer tr.Finish() 28 | 29 | conn, err := grpc.Dial(serverAddr, 30 | grpc.WithInsecure(), 31 | grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor()), 32 | ) 33 | if err != nil { 34 | log.Fatalf("did not connect: %v", err) 35 | } 36 | defer conn.Close() 37 | c := proto.NewGeoipfixClient(conn) 38 | 39 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 40 | defer cancel() 41 | ctx = trace.NewContext(ctx, tr) 42 | loc, err := c.GetLocation(ctx, &proto.GetLocationRequest{IpAddress: ipAddress}, 43 | grpc_retry.WithMax(maxRetry), 44 | grpc_retry.WithPerRetryTimeout(1*time.Second), 45 | grpc_retry.WithCodes(codes.Unavailable)) 46 | 47 | if err != nil { 48 | log.Fatalf("could not retrieve result: %v", err) 49 | } 50 | 51 | log.Printf("Retrieved location: %+v", loc) 52 | } 53 | -------------------------------------------------------------------------------- /geoip.go: -------------------------------------------------------------------------------- 1 | // This code has been taken from freegeoip repository 2 | // it can be found here: https://github.com/fiorix/freegeoip/blob/master/apiserver/api.go 3 | 4 | package geoipfix 5 | 6 | import ( 7 | "encoding/xml" 8 | "math" 9 | "net" 10 | "net/url" 11 | "time" 12 | 13 | "golang.org/x/text/language" 14 | 15 | "github.com/fiorix/freegeoip" 16 | ) 17 | 18 | type geoipQuery struct { 19 | freegeoip.DefaultQuery 20 | } 21 | 22 | func parseAcceptLanguage(header string, dbLangs map[string]string) string { 23 | // supported languages -- i.e. languages available in the DB 24 | matchLangs := []language.Tag{ 25 | language.English, 26 | } 27 | 28 | // parse available DB languages and add to supported 29 | for name := range dbLangs { 30 | matchLangs = append(matchLangs, language.Raw.Make(name)) 31 | 32 | } 33 | 34 | var matcher = language.NewMatcher(matchLangs) 35 | 36 | // parse header 37 | t, _, _ := language.ParseAcceptLanguage(header) 38 | // match most acceptable language 39 | tag, _, _ := matcher.Match(t...) 40 | // extract base language 41 | base, _ := tag.Base() 42 | 43 | return base.String() 44 | 45 | } 46 | 47 | func roundFloat(val float64, roundOn float64, places int) (newVal float64) { 48 | var round float64 49 | pow := math.Pow(10, float64(places)) 50 | digit := pow * val 51 | _, div := math.Modf(digit) 52 | if div >= roundOn { 53 | round = math.Ceil(digit) 54 | 55 | } else { 56 | round = math.Floor(digit) 57 | 58 | } 59 | return round / pow 60 | } 61 | 62 | type record struct { 63 | XMLName xml.Name `xml:"Response" json:"-"` 64 | IP string `json:"ip"` 65 | CountryCode string `json:"country_code"` 66 | CountryName string `json:"country_name"` 67 | RegionCode string `json:"region_code"` 68 | RegionName string `json:"region_name"` 69 | City string `json:"city"` 70 | ZipCode string `json:"zip_code"` 71 | TimeZone string `json:"time_zone"` 72 | Latitude float64 `json:"latitude"` 73 | Longitude float64 `json:"longitude"` 74 | MetroCode uint `json:"metro_code"` 75 | } 76 | 77 | func (q *geoipQuery) Record(ip net.IP, lang string) *record { 78 | lang = parseAcceptLanguage(lang, q.Country.Names) 79 | 80 | r := &record{ 81 | IP: ip.String(), 82 | CountryCode: q.Country.ISOCode, 83 | CountryName: q.Country.Names[lang], 84 | City: q.City.Names[lang], 85 | ZipCode: q.Postal.Code, 86 | TimeZone: q.Location.TimeZone, 87 | Latitude: roundFloat(q.Location.Latitude, .5, 4), 88 | Longitude: roundFloat(q.Location.Longitude, .5, 4), 89 | MetroCode: q.Location.MetroCode, 90 | } 91 | if len(q.Region) > 0 { 92 | r.RegionCode = q.Region[0].ISOCode 93 | r.RegionName = q.Region[0].Names[lang] 94 | 95 | } 96 | return r 97 | } 98 | 99 | func openDB(dsn string, updateIntvl time.Duration, maxRetryIntvl time.Duration) (db *freegeoip.DB, err error) { 100 | u, err := url.Parse(dsn) 101 | if err != nil || len(u.Scheme) == 0 { 102 | db, err = freegeoip.Open(dsn) 103 | } else { 104 | db, err = freegeoip.OpenURL(dsn, updateIntvl, maxRetryIntvl) 105 | } 106 | return 107 | } 108 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ulule/geoipfix 2 | 3 | go 1.21.2 4 | 5 | require ( 6 | github.com/fiorix/freegeoip v3.4.1+incompatible 7 | github.com/go-chi/chi v3.3.2+incompatible 8 | github.com/go-chi/cors v1.0.0 9 | github.com/go-chi/render v1.0.0 10 | github.com/go-chi/valve v0.0.0-20170920024740-9e45288364f4 11 | github.com/golang/protobuf v1.0.1-0.20180202184318-bbd03ef6da3a 12 | github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20180301145804-eb23b08d08bb 13 | github.com/pkg/errors v0.8.0 14 | github.com/urfave/cli v1.20.0 15 | go.uber.org/zap v1.7.1 16 | golang.org/x/net v0.0.0-20201021035429-f5854403a974 17 | golang.org/x/text v0.3.3 18 | google.golang.org/grpc v1.10.0 19 | ) 20 | 21 | require ( 22 | github.com/gogo/protobuf v1.3.2 // indirect 23 | github.com/golang/glog v1.1.2 // indirect 24 | github.com/howeyc/fsnotify v0.9.1-0.20140711012604-6b1ef893dc11 // indirect 25 | github.com/oschwald/maxminddb-golang v1.0.0 // indirect 26 | github.com/sirupsen/logrus v1.9.3 // indirect 27 | github.com/stretchr/testify v1.8.4 // indirect 28 | go.uber.org/atomic v1.3.1 // indirect 29 | go.uber.org/multierr v1.1.0 // indirect 30 | golang.org/x/sync v0.4.0 // indirect 31 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect 32 | google.golang.org/genproto v0.0.0-20180301225018-2c5e7ac708aa // indirect 33 | ) 34 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/fiorix/freegeoip v3.4.1+incompatible h1:4hPajn/XW66UUqaRRYiBvCjGLlxJjTZcVFaY4cS8V4k= 5 | github.com/fiorix/freegeoip v3.4.1+incompatible/go.mod h1:Aj4wl0Tp2uPBmbt4yiXd089+Ks7/yWCwyjT9jriM8ng= 6 | github.com/go-chi/chi v3.3.2+incompatible h1:uQNcQN3NsV1j4ANsPh42P4ew4t6rnRbJb8frvpp31qQ= 7 | github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= 8 | github.com/go-chi/cors v1.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0= 9 | github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= 10 | github.com/go-chi/render v1.0.0 h1:cLJlkaTB4xfx5rWhtoB0BSXsXVJKWFqv08Y3cR1bZKA= 11 | github.com/go-chi/render v1.0.0/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= 12 | github.com/go-chi/valve v0.0.0-20170920024740-9e45288364f4 h1:JYZmrkBDj6LwUbsRysF9tpLnz59npoZSI3KG2XHqvHw= 13 | github.com/go-chi/valve v0.0.0-20170920024740-9e45288364f4/go.mod h1:F4ZINQr5T71wO1JOmdQsGTBew+njUAXn65LLGjuagwY= 14 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 15 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 16 | github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= 17 | github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= 18 | github.com/golang/protobuf v1.0.1-0.20180202184318-bbd03ef6da3a h1:MKcZdiXa8z1dyr2AH37WW66sN1UZiLdy/Krj+s9kz8Y= 19 | github.com/golang/protobuf v1.0.1-0.20180202184318-bbd03ef6da3a/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 20 | github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20180301145804-eb23b08d08bb h1:BHl8EzGnQvhq67gXUdUtVKi5L9CTw4LwihxQgUL2VdE= 21 | github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20180301145804-eb23b08d08bb/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 22 | github.com/howeyc/fsnotify v0.9.1-0.20140711012604-6b1ef893dc11 h1:raFbLq/h8x4Mc+XINYR3HynGcBMThwBBrW0MPMGKvTg= 23 | github.com/howeyc/fsnotify v0.9.1-0.20140711012604-6b1ef893dc11/go.mod h1:41HzSPxBGeFRQKEEwgh49TRw/nKBsYZ2cF1OzPjSJsA= 24 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 25 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 26 | github.com/oschwald/maxminddb-golang v1.0.0 h1:J2B9uC7QU/ySxtQi8UkPyry1JzNGfPyY6YjIHmgTLno= 27 | github.com/oschwald/maxminddb-golang v1.0.0/go.mod h1:3jhIUymTJ5VREKyIhWm66LJiQt04F0UCDdodShpjWsY= 28 | github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= 29 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 30 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 31 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 32 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 33 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 34 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 35 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 36 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 37 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 38 | github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= 39 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= 40 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 41 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 42 | go.uber.org/atomic v1.3.1 h1:U8WaWEmp56LGz7PReduqHRVF6zzs9GbMC2NEZ42dxSQ= 43 | go.uber.org/atomic v1.3.1/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 44 | go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= 45 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 46 | go.uber.org/zap v1.7.1 h1:wKPciimwkIgV4Aag/wpSDzvtO5JrfwdHKHO7blTHx7Q= 47 | go.uber.org/zap v1.7.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 48 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 49 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 50 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 51 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 52 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 53 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 54 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 55 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 56 | golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= 57 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 58 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 59 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 60 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 61 | golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= 62 | golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 63 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 64 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 65 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 66 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= 67 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 68 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 69 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 70 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 71 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 72 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 73 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 74 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 75 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 76 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 77 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 78 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 79 | google.golang.org/genproto v0.0.0-20180301225018-2c5e7ac708aa h1:Fjs4cvEc4p5iwq6VXIgHpyciiA7LDttZUkYNI8T2DXA= 80 | google.golang.org/genproto v0.0.0-20180301225018-2c5e7ac708aa/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 81 | google.golang.org/grpc v1.10.0 h1:C9kRCw/lM10n+54FLMKvGRJbhvJ0+7ipOUuWpLS9xIA= 82 | google.golang.org/grpc v1.10.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 83 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 84 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 85 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 86 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 87 | -------------------------------------------------------------------------------- /handlers_http.go: -------------------------------------------------------------------------------- 1 | package geoipfix 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | 7 | "go.uber.org/zap" 8 | 9 | "github.com/go-chi/chi" 10 | "github.com/go-chi/render" 11 | "github.com/pkg/errors" 12 | ) 13 | 14 | type httpHandler struct { 15 | options 16 | } 17 | 18 | // GetLocation retrieves the IP from request. 19 | func (h *httpHandler) GetLocation(w http.ResponseWriter, r *http.Request) error { 20 | rawIP := chi.URLParam(r, "ipAddress") 21 | if rawIP == "" { 22 | rawIP = r.RemoteAddr 23 | } 24 | 25 | log := h.Logger.With(zap.String("ip_address", rawIP)) 26 | log.Info("Retrieve IP Address from request", zap.String("ip_address", rawIP)) 27 | 28 | ip := net.ParseIP(rawIP) 29 | if ip == nil { 30 | log.Error("IP Address cannot be parsed", zap.String("ip_address", rawIP)) 31 | 32 | http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) 33 | 34 | return nil 35 | } 36 | 37 | q := geoipQuery{} 38 | err := h.DB.Lookup(ip, &q) 39 | if err != nil { 40 | return errors.Wrapf(err, "Cannot retrieve geoip information for %s", rawIP) 41 | } 42 | 43 | resp := q.Record(ip, r.Header.Get("Accept-Language")) 44 | 45 | render.JSON(w, r, resp) 46 | 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /handlers_rpc.go: -------------------------------------------------------------------------------- 1 | package geoipfix 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "github.com/pkg/errors" 8 | "github.com/ulule/geoipfix/proto" 9 | "go.uber.org/zap" 10 | ) 11 | 12 | type rpcHandler struct { 13 | options 14 | } 15 | 16 | // GetLocation retrieves location from protobuf 17 | func (h *rpcHandler) GetLocation(ctx context.Context, req *proto.GetLocationRequest) (*proto.Location, error) { 18 | rawIP := req.IpAddress 19 | 20 | log := h.Logger.With(zap.String("ip_address", rawIP)) 21 | log.Info("Retrieve IP Address from request", zap.String("ip_address", rawIP)) 22 | 23 | ip := net.ParseIP(rawIP) 24 | if ip == nil { 25 | log.Error("IP Address cannot be parsed", zap.String("ip_address", rawIP)) 26 | 27 | return nil, errors.Errorf("IP Address %s cannot be parsed", rawIP) 28 | } 29 | 30 | q := geoipQuery{} 31 | err := h.DB.Lookup(ip, &q) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | return recordToProto(q.Record(ip, req.Language)), nil 37 | } 38 | -------------------------------------------------------------------------------- /middleware.go: -------------------------------------------------------------------------------- 1 | package geoipfix 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | "runtime/debug" 8 | "time" 9 | 10 | "github.com/go-chi/chi/middleware" 11 | "go.uber.org/zap" 12 | "go.uber.org/zap/zapcore" 13 | ) 14 | 15 | type middlewareHandler = func(next http.Handler) http.Handler 16 | 17 | type recoverMiddleware struct { 18 | debug bool 19 | logger *zap.Logger 20 | } 21 | 22 | func newRecoverMiddleware(debug bool, logger *zap.Logger) *recoverMiddleware { 23 | return &recoverMiddleware{ 24 | debug: debug, 25 | logger: logger, 26 | } 27 | } 28 | 29 | func (m *recoverMiddleware) Handle(it interface{}) { 30 | if m.debug { 31 | fmt.Fprintf(os.Stderr, "Panic: %+v\n", it) 32 | debug.PrintStack() 33 | } else { 34 | m.logger.Error("Error handled") 35 | } 36 | } 37 | 38 | func (m *recoverMiddleware) Handler(next http.Handler) http.Handler { 39 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 40 | defer func() { 41 | if rvr := recover(); rvr != nil { 42 | m.Handle(rvr) 43 | 44 | http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) 45 | } 46 | }() 47 | 48 | next.ServeHTTP(w, r) 49 | }) 50 | } 51 | 52 | func newLoggerMiddleware(logger *zap.Logger) func(next http.Handler) http.Handler { 53 | return middleware.RequestLogger(&structuredLogger{logger}) 54 | } 55 | 56 | type structuredLogger struct { 57 | Logger *zap.Logger 58 | } 59 | 60 | func (l *structuredLogger) NewLogEntry(r *http.Request) middleware.LogEntry { 61 | entry := &structuredLoggerEntry{Logger: l.Logger} 62 | 63 | fields := []zapcore.Field{zap.String("ts", time.Now().UTC().Format(time.RFC1123))} 64 | 65 | if reqID := middleware.GetReqID(r.Context()); reqID != "" { 66 | fields = append(fields, zap.String("req.id", reqID)) 67 | } 68 | 69 | scheme := "http" 70 | if r.TLS != nil { 71 | scheme = "https" 72 | } 73 | 74 | fields = append(fields, []zapcore.Field{ 75 | zap.String("http.scheme", scheme), 76 | zap.String("http.proto", r.Proto), 77 | zap.String("http.method", r.Method), 78 | zap.String("remote_addr", r.RemoteAddr), 79 | zap.String("user_agent", r.UserAgent()), 80 | zap.String("uri", fmt.Sprintf("%s://%s%s", scheme, r.Host, r.RequestURI)), 81 | }...) 82 | 83 | entry.Logger = l.Logger.With(fields...) 84 | 85 | entry.Logger.Info("request started") 86 | 87 | return entry 88 | } 89 | 90 | type structuredLoggerEntry struct { 91 | Logger *zap.Logger 92 | } 93 | 94 | func (l *structuredLoggerEntry) Write(status, bytes int, elapsed time.Duration) { 95 | l.Logger = l.Logger.With( 96 | zap.Int("res.status", status), 97 | zap.Int("res.bytes_length", bytes), 98 | zap.Float64("res.elapsed_ms", float64(elapsed.Nanoseconds())/1000000.0)) 99 | 100 | l.Logger.Info("request complete") 101 | } 102 | 103 | func (l *structuredLoggerEntry) Panic(v interface{}, stack []byte) { 104 | l.Logger = l.Logger.With( 105 | zap.String("stack", string(stack)), 106 | zap.String("panic", fmt.Sprintf("%+v", v)), 107 | ) 108 | } 109 | -------------------------------------------------------------------------------- /modd.conf: -------------------------------------------------------------------------------- 1 | **/*.go !vendor !plugins !examples !bin { 2 | prep: make build 3 | daemon +sigterm: make run 4 | } 5 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package geoipfix 2 | 3 | import ( 4 | "github.com/fiorix/freegeoip" 5 | "go.uber.org/zap" 6 | ) 7 | 8 | // option is a functional option. 9 | type option func(*options) 10 | 11 | // NewOptions initializes geoipfix options. 12 | func newOptions(opts ...option) options { 13 | opt := options{} 14 | for _, o := range opts { 15 | o(&opt) 16 | } 17 | return opt 18 | } 19 | 20 | // options are geoipfix options. 21 | type options struct { 22 | DB *freegeoip.DB 23 | Logger *zap.Logger 24 | Debug bool 25 | } 26 | 27 | // withDB sets the database. 28 | func withDB(db *freegeoip.DB) option { 29 | return func(o *options) { 30 | o.DB = db 31 | } 32 | } 33 | 34 | // withDebug sets the debug flag. 35 | func withDebug(debug bool) option { 36 | return func(o *options) { 37 | o.Debug = debug 38 | } 39 | } 40 | 41 | // withLogger sets the logger. 42 | func withLogger(logger *zap.Logger) option { 43 | return func(o *options) { 44 | o.Logger = logger 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /proto/geoipfix.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-gogo. DO NOT EDIT. 2 | // source: proto/geoipfix.proto 3 | 4 | /* 5 | Package proto is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | proto/geoipfix.proto 9 | 10 | It has these top-level messages: 11 | Place 12 | GetLocationRequest 13 | Location 14 | */ 15 | package proto 16 | 17 | import proto1 "github.com/golang/protobuf/proto" 18 | import fmt "fmt" 19 | import math "math" 20 | 21 | import context "golang.org/x/net/context" 22 | import grpc "google.golang.org/grpc" 23 | 24 | import binary "encoding/binary" 25 | 26 | import io "io" 27 | 28 | // Reference imports to suppress errors if they are not otherwise used. 29 | var _ = proto1.Marshal 30 | var _ = fmt.Errorf 31 | var _ = math.Inf 32 | 33 | // This is a compile-time assertion to ensure that this generated file 34 | // is compatible with the proto package it is being compiled against. 35 | // A compilation error at this line likely means your copy of the 36 | // proto package needs to be updated. 37 | const _ = proto1.ProtoPackageIsVersion2 // please upgrade the proto package 38 | 39 | type Place struct { 40 | Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"` 41 | Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` 42 | } 43 | 44 | func (m *Place) Reset() { *m = Place{} } 45 | func (m *Place) String() string { return proto1.CompactTextString(m) } 46 | func (*Place) ProtoMessage() {} 47 | func (*Place) Descriptor() ([]byte, []int) { return fileDescriptorGeoipfix, []int{0} } 48 | 49 | func (m *Place) GetCode() string { 50 | if m != nil { 51 | return m.Code 52 | } 53 | return "" 54 | } 55 | 56 | func (m *Place) GetName() string { 57 | if m != nil { 58 | return m.Name 59 | } 60 | return "" 61 | } 62 | 63 | type GetLocationRequest struct { 64 | IpAddress string `protobuf:"bytes,1,opt,name=ip_address,json=ipAddress,proto3" json:"ip_address,omitempty"` 65 | Language string `protobuf:"bytes,2,opt,name=language,proto3" json:"language,omitempty"` 66 | } 67 | 68 | func (m *GetLocationRequest) Reset() { *m = GetLocationRequest{} } 69 | func (m *GetLocationRequest) String() string { return proto1.CompactTextString(m) } 70 | func (*GetLocationRequest) ProtoMessage() {} 71 | func (*GetLocationRequest) Descriptor() ([]byte, []int) { return fileDescriptorGeoipfix, []int{1} } 72 | 73 | func (m *GetLocationRequest) GetIpAddress() string { 74 | if m != nil { 75 | return m.IpAddress 76 | } 77 | return "" 78 | } 79 | 80 | func (m *GetLocationRequest) GetLanguage() string { 81 | if m != nil { 82 | return m.Language 83 | } 84 | return "" 85 | } 86 | 87 | type Location struct { 88 | IpAddress string `protobuf:"bytes,1,opt,name=ip_address,json=ipAddress,proto3" json:"ip_address,omitempty"` 89 | Country *Place `protobuf:"bytes,2,opt,name=country" json:"country,omitempty"` 90 | Region *Place `protobuf:"bytes,3,opt,name=region" json:"region,omitempty"` 91 | City string `protobuf:"bytes,4,opt,name=city,proto3" json:"city,omitempty"` 92 | ZipCode string `protobuf:"bytes,5,opt,name=zip_code,json=zipCode,proto3" json:"zip_code,omitempty"` 93 | TimeZone string `protobuf:"bytes,6,opt,name=time_zone,json=timeZone,proto3" json:"time_zone,omitempty"` 94 | Latitude float32 `protobuf:"fixed32,7,opt,name=latitude,proto3" json:"latitude,omitempty"` 95 | Longitude float32 `protobuf:"fixed32,8,opt,name=longitude,proto3" json:"longitude,omitempty"` 96 | MetroCode int64 `protobuf:"varint,9,opt,name=metro_code,json=metroCode,proto3" json:"metro_code,omitempty"` 97 | } 98 | 99 | func (m *Location) Reset() { *m = Location{} } 100 | func (m *Location) String() string { return proto1.CompactTextString(m) } 101 | func (*Location) ProtoMessage() {} 102 | func (*Location) Descriptor() ([]byte, []int) { return fileDescriptorGeoipfix, []int{2} } 103 | 104 | func (m *Location) GetIpAddress() string { 105 | if m != nil { 106 | return m.IpAddress 107 | } 108 | return "" 109 | } 110 | 111 | func (m *Location) GetCountry() *Place { 112 | if m != nil { 113 | return m.Country 114 | } 115 | return nil 116 | } 117 | 118 | func (m *Location) GetRegion() *Place { 119 | if m != nil { 120 | return m.Region 121 | } 122 | return nil 123 | } 124 | 125 | func (m *Location) GetCity() string { 126 | if m != nil { 127 | return m.City 128 | } 129 | return "" 130 | } 131 | 132 | func (m *Location) GetZipCode() string { 133 | if m != nil { 134 | return m.ZipCode 135 | } 136 | return "" 137 | } 138 | 139 | func (m *Location) GetTimeZone() string { 140 | if m != nil { 141 | return m.TimeZone 142 | } 143 | return "" 144 | } 145 | 146 | func (m *Location) GetLatitude() float32 { 147 | if m != nil { 148 | return m.Latitude 149 | } 150 | return 0 151 | } 152 | 153 | func (m *Location) GetLongitude() float32 { 154 | if m != nil { 155 | return m.Longitude 156 | } 157 | return 0 158 | } 159 | 160 | func (m *Location) GetMetroCode() int64 { 161 | if m != nil { 162 | return m.MetroCode 163 | } 164 | return 0 165 | } 166 | 167 | func init() { 168 | proto1.RegisterType((*Place)(nil), "proto.Place") 169 | proto1.RegisterType((*GetLocationRequest)(nil), "proto.GetLocationRequest") 170 | proto1.RegisterType((*Location)(nil), "proto.Location") 171 | } 172 | 173 | // Reference imports to suppress errors if they are not otherwise used. 174 | var _ context.Context 175 | var _ grpc.ClientConn 176 | 177 | // This is a compile-time assertion to ensure that this generated file 178 | // is compatible with the grpc package it is being compiled against. 179 | const _ = grpc.SupportPackageIsVersion4 180 | 181 | // Client API for Geoipfix service 182 | 183 | type GeoipfixClient interface { 184 | GetLocation(ctx context.Context, in *GetLocationRequest, opts ...grpc.CallOption) (*Location, error) 185 | } 186 | 187 | type geoipfixClient struct { 188 | cc *grpc.ClientConn 189 | } 190 | 191 | func NewGeoipfixClient(cc *grpc.ClientConn) GeoipfixClient { 192 | return &geoipfixClient{cc} 193 | } 194 | 195 | func (c *geoipfixClient) GetLocation(ctx context.Context, in *GetLocationRequest, opts ...grpc.CallOption) (*Location, error) { 196 | out := new(Location) 197 | err := grpc.Invoke(ctx, "/proto.Geoipfix/GetLocation", in, out, c.cc, opts...) 198 | if err != nil { 199 | return nil, err 200 | } 201 | return out, nil 202 | } 203 | 204 | // Server API for Geoipfix service 205 | 206 | type GeoipfixServer interface { 207 | GetLocation(context.Context, *GetLocationRequest) (*Location, error) 208 | } 209 | 210 | func RegisterGeoipfixServer(s *grpc.Server, srv GeoipfixServer) { 211 | s.RegisterService(&_Geoipfix_serviceDesc, srv) 212 | } 213 | 214 | func _Geoipfix_GetLocation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 215 | in := new(GetLocationRequest) 216 | if err := dec(in); err != nil { 217 | return nil, err 218 | } 219 | if interceptor == nil { 220 | return srv.(GeoipfixServer).GetLocation(ctx, in) 221 | } 222 | info := &grpc.UnaryServerInfo{ 223 | Server: srv, 224 | FullMethod: "/proto.Geoipfix/GetLocation", 225 | } 226 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 227 | return srv.(GeoipfixServer).GetLocation(ctx, req.(*GetLocationRequest)) 228 | } 229 | return interceptor(ctx, in, info, handler) 230 | } 231 | 232 | var _Geoipfix_serviceDesc = grpc.ServiceDesc{ 233 | ServiceName: "proto.Geoipfix", 234 | HandlerType: (*GeoipfixServer)(nil), 235 | Methods: []grpc.MethodDesc{ 236 | { 237 | MethodName: "GetLocation", 238 | Handler: _Geoipfix_GetLocation_Handler, 239 | }, 240 | }, 241 | Streams: []grpc.StreamDesc{}, 242 | Metadata: "proto/geoipfix.proto", 243 | } 244 | 245 | func (m *Place) Marshal() (dAtA []byte, err error) { 246 | size := m.Size() 247 | dAtA = make([]byte, size) 248 | n, err := m.MarshalTo(dAtA) 249 | if err != nil { 250 | return nil, err 251 | } 252 | return dAtA[:n], nil 253 | } 254 | 255 | func (m *Place) MarshalTo(dAtA []byte) (int, error) { 256 | var i int 257 | _ = i 258 | var l int 259 | _ = l 260 | if len(m.Code) > 0 { 261 | dAtA[i] = 0xa 262 | i++ 263 | i = encodeVarintGeoipfix(dAtA, i, uint64(len(m.Code))) 264 | i += copy(dAtA[i:], m.Code) 265 | } 266 | if len(m.Name) > 0 { 267 | dAtA[i] = 0x12 268 | i++ 269 | i = encodeVarintGeoipfix(dAtA, i, uint64(len(m.Name))) 270 | i += copy(dAtA[i:], m.Name) 271 | } 272 | return i, nil 273 | } 274 | 275 | func (m *GetLocationRequest) Marshal() (dAtA []byte, err error) { 276 | size := m.Size() 277 | dAtA = make([]byte, size) 278 | n, err := m.MarshalTo(dAtA) 279 | if err != nil { 280 | return nil, err 281 | } 282 | return dAtA[:n], nil 283 | } 284 | 285 | func (m *GetLocationRequest) MarshalTo(dAtA []byte) (int, error) { 286 | var i int 287 | _ = i 288 | var l int 289 | _ = l 290 | if len(m.IpAddress) > 0 { 291 | dAtA[i] = 0xa 292 | i++ 293 | i = encodeVarintGeoipfix(dAtA, i, uint64(len(m.IpAddress))) 294 | i += copy(dAtA[i:], m.IpAddress) 295 | } 296 | if len(m.Language) > 0 { 297 | dAtA[i] = 0x12 298 | i++ 299 | i = encodeVarintGeoipfix(dAtA, i, uint64(len(m.Language))) 300 | i += copy(dAtA[i:], m.Language) 301 | } 302 | return i, nil 303 | } 304 | 305 | func (m *Location) Marshal() (dAtA []byte, err error) { 306 | size := m.Size() 307 | dAtA = make([]byte, size) 308 | n, err := m.MarshalTo(dAtA) 309 | if err != nil { 310 | return nil, err 311 | } 312 | return dAtA[:n], nil 313 | } 314 | 315 | func (m *Location) MarshalTo(dAtA []byte) (int, error) { 316 | var i int 317 | _ = i 318 | var l int 319 | _ = l 320 | if len(m.IpAddress) > 0 { 321 | dAtA[i] = 0xa 322 | i++ 323 | i = encodeVarintGeoipfix(dAtA, i, uint64(len(m.IpAddress))) 324 | i += copy(dAtA[i:], m.IpAddress) 325 | } 326 | if m.Country != nil { 327 | dAtA[i] = 0x12 328 | i++ 329 | i = encodeVarintGeoipfix(dAtA, i, uint64(m.Country.Size())) 330 | n1, err := m.Country.MarshalTo(dAtA[i:]) 331 | if err != nil { 332 | return 0, err 333 | } 334 | i += n1 335 | } 336 | if m.Region != nil { 337 | dAtA[i] = 0x1a 338 | i++ 339 | i = encodeVarintGeoipfix(dAtA, i, uint64(m.Region.Size())) 340 | n2, err := m.Region.MarshalTo(dAtA[i:]) 341 | if err != nil { 342 | return 0, err 343 | } 344 | i += n2 345 | } 346 | if len(m.City) > 0 { 347 | dAtA[i] = 0x22 348 | i++ 349 | i = encodeVarintGeoipfix(dAtA, i, uint64(len(m.City))) 350 | i += copy(dAtA[i:], m.City) 351 | } 352 | if len(m.ZipCode) > 0 { 353 | dAtA[i] = 0x2a 354 | i++ 355 | i = encodeVarintGeoipfix(dAtA, i, uint64(len(m.ZipCode))) 356 | i += copy(dAtA[i:], m.ZipCode) 357 | } 358 | if len(m.TimeZone) > 0 { 359 | dAtA[i] = 0x32 360 | i++ 361 | i = encodeVarintGeoipfix(dAtA, i, uint64(len(m.TimeZone))) 362 | i += copy(dAtA[i:], m.TimeZone) 363 | } 364 | if m.Latitude != 0 { 365 | dAtA[i] = 0x3d 366 | i++ 367 | binary.LittleEndian.PutUint32(dAtA[i:], uint32(math.Float32bits(float32(m.Latitude)))) 368 | i += 4 369 | } 370 | if m.Longitude != 0 { 371 | dAtA[i] = 0x45 372 | i++ 373 | binary.LittleEndian.PutUint32(dAtA[i:], uint32(math.Float32bits(float32(m.Longitude)))) 374 | i += 4 375 | } 376 | if m.MetroCode != 0 { 377 | dAtA[i] = 0x48 378 | i++ 379 | i = encodeVarintGeoipfix(dAtA, i, uint64(m.MetroCode)) 380 | } 381 | return i, nil 382 | } 383 | 384 | func encodeVarintGeoipfix(dAtA []byte, offset int, v uint64) int { 385 | for v >= 1<<7 { 386 | dAtA[offset] = uint8(v&0x7f | 0x80) 387 | v >>= 7 388 | offset++ 389 | } 390 | dAtA[offset] = uint8(v) 391 | return offset + 1 392 | } 393 | func (m *Place) Size() (n int) { 394 | var l int 395 | _ = l 396 | l = len(m.Code) 397 | if l > 0 { 398 | n += 1 + l + sovGeoipfix(uint64(l)) 399 | } 400 | l = len(m.Name) 401 | if l > 0 { 402 | n += 1 + l + sovGeoipfix(uint64(l)) 403 | } 404 | return n 405 | } 406 | 407 | func (m *GetLocationRequest) Size() (n int) { 408 | var l int 409 | _ = l 410 | l = len(m.IpAddress) 411 | if l > 0 { 412 | n += 1 + l + sovGeoipfix(uint64(l)) 413 | } 414 | l = len(m.Language) 415 | if l > 0 { 416 | n += 1 + l + sovGeoipfix(uint64(l)) 417 | } 418 | return n 419 | } 420 | 421 | func (m *Location) Size() (n int) { 422 | var l int 423 | _ = l 424 | l = len(m.IpAddress) 425 | if l > 0 { 426 | n += 1 + l + sovGeoipfix(uint64(l)) 427 | } 428 | if m.Country != nil { 429 | l = m.Country.Size() 430 | n += 1 + l + sovGeoipfix(uint64(l)) 431 | } 432 | if m.Region != nil { 433 | l = m.Region.Size() 434 | n += 1 + l + sovGeoipfix(uint64(l)) 435 | } 436 | l = len(m.City) 437 | if l > 0 { 438 | n += 1 + l + sovGeoipfix(uint64(l)) 439 | } 440 | l = len(m.ZipCode) 441 | if l > 0 { 442 | n += 1 + l + sovGeoipfix(uint64(l)) 443 | } 444 | l = len(m.TimeZone) 445 | if l > 0 { 446 | n += 1 + l + sovGeoipfix(uint64(l)) 447 | } 448 | if m.Latitude != 0 { 449 | n += 5 450 | } 451 | if m.Longitude != 0 { 452 | n += 5 453 | } 454 | if m.MetroCode != 0 { 455 | n += 1 + sovGeoipfix(uint64(m.MetroCode)) 456 | } 457 | return n 458 | } 459 | 460 | func sovGeoipfix(x uint64) (n int) { 461 | for { 462 | n++ 463 | x >>= 7 464 | if x == 0 { 465 | break 466 | } 467 | } 468 | return n 469 | } 470 | func sozGeoipfix(x uint64) (n int) { 471 | return sovGeoipfix(uint64((x << 1) ^ uint64((int64(x) >> 63)))) 472 | } 473 | func (m *Place) Unmarshal(dAtA []byte) error { 474 | l := len(dAtA) 475 | iNdEx := 0 476 | for iNdEx < l { 477 | preIndex := iNdEx 478 | var wire uint64 479 | for shift := uint(0); ; shift += 7 { 480 | if shift >= 64 { 481 | return ErrIntOverflowGeoipfix 482 | } 483 | if iNdEx >= l { 484 | return io.ErrUnexpectedEOF 485 | } 486 | b := dAtA[iNdEx] 487 | iNdEx++ 488 | wire |= (uint64(b) & 0x7F) << shift 489 | if b < 0x80 { 490 | break 491 | } 492 | } 493 | fieldNum := int32(wire >> 3) 494 | wireType := int(wire & 0x7) 495 | if wireType == 4 { 496 | return fmt.Errorf("proto: Place: wiretype end group for non-group") 497 | } 498 | if fieldNum <= 0 { 499 | return fmt.Errorf("proto: Place: illegal tag %d (wire type %d)", fieldNum, wire) 500 | } 501 | switch fieldNum { 502 | case 1: 503 | if wireType != 2 { 504 | return fmt.Errorf("proto: wrong wireType = %d for field Code", wireType) 505 | } 506 | var stringLen uint64 507 | for shift := uint(0); ; shift += 7 { 508 | if shift >= 64 { 509 | return ErrIntOverflowGeoipfix 510 | } 511 | if iNdEx >= l { 512 | return io.ErrUnexpectedEOF 513 | } 514 | b := dAtA[iNdEx] 515 | iNdEx++ 516 | stringLen |= (uint64(b) & 0x7F) << shift 517 | if b < 0x80 { 518 | break 519 | } 520 | } 521 | intStringLen := int(stringLen) 522 | if intStringLen < 0 { 523 | return ErrInvalidLengthGeoipfix 524 | } 525 | postIndex := iNdEx + intStringLen 526 | if postIndex > l { 527 | return io.ErrUnexpectedEOF 528 | } 529 | m.Code = string(dAtA[iNdEx:postIndex]) 530 | iNdEx = postIndex 531 | case 2: 532 | if wireType != 2 { 533 | return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) 534 | } 535 | var stringLen uint64 536 | for shift := uint(0); ; shift += 7 { 537 | if shift >= 64 { 538 | return ErrIntOverflowGeoipfix 539 | } 540 | if iNdEx >= l { 541 | return io.ErrUnexpectedEOF 542 | } 543 | b := dAtA[iNdEx] 544 | iNdEx++ 545 | stringLen |= (uint64(b) & 0x7F) << shift 546 | if b < 0x80 { 547 | break 548 | } 549 | } 550 | intStringLen := int(stringLen) 551 | if intStringLen < 0 { 552 | return ErrInvalidLengthGeoipfix 553 | } 554 | postIndex := iNdEx + intStringLen 555 | if postIndex > l { 556 | return io.ErrUnexpectedEOF 557 | } 558 | m.Name = string(dAtA[iNdEx:postIndex]) 559 | iNdEx = postIndex 560 | default: 561 | iNdEx = preIndex 562 | skippy, err := skipGeoipfix(dAtA[iNdEx:]) 563 | if err != nil { 564 | return err 565 | } 566 | if skippy < 0 { 567 | return ErrInvalidLengthGeoipfix 568 | } 569 | if (iNdEx + skippy) > l { 570 | return io.ErrUnexpectedEOF 571 | } 572 | iNdEx += skippy 573 | } 574 | } 575 | 576 | if iNdEx > l { 577 | return io.ErrUnexpectedEOF 578 | } 579 | return nil 580 | } 581 | func (m *GetLocationRequest) Unmarshal(dAtA []byte) error { 582 | l := len(dAtA) 583 | iNdEx := 0 584 | for iNdEx < l { 585 | preIndex := iNdEx 586 | var wire uint64 587 | for shift := uint(0); ; shift += 7 { 588 | if shift >= 64 { 589 | return ErrIntOverflowGeoipfix 590 | } 591 | if iNdEx >= l { 592 | return io.ErrUnexpectedEOF 593 | } 594 | b := dAtA[iNdEx] 595 | iNdEx++ 596 | wire |= (uint64(b) & 0x7F) << shift 597 | if b < 0x80 { 598 | break 599 | } 600 | } 601 | fieldNum := int32(wire >> 3) 602 | wireType := int(wire & 0x7) 603 | if wireType == 4 { 604 | return fmt.Errorf("proto: GetLocationRequest: wiretype end group for non-group") 605 | } 606 | if fieldNum <= 0 { 607 | return fmt.Errorf("proto: GetLocationRequest: illegal tag %d (wire type %d)", fieldNum, wire) 608 | } 609 | switch fieldNum { 610 | case 1: 611 | if wireType != 2 { 612 | return fmt.Errorf("proto: wrong wireType = %d for field IpAddress", wireType) 613 | } 614 | var stringLen uint64 615 | for shift := uint(0); ; shift += 7 { 616 | if shift >= 64 { 617 | return ErrIntOverflowGeoipfix 618 | } 619 | if iNdEx >= l { 620 | return io.ErrUnexpectedEOF 621 | } 622 | b := dAtA[iNdEx] 623 | iNdEx++ 624 | stringLen |= (uint64(b) & 0x7F) << shift 625 | if b < 0x80 { 626 | break 627 | } 628 | } 629 | intStringLen := int(stringLen) 630 | if intStringLen < 0 { 631 | return ErrInvalidLengthGeoipfix 632 | } 633 | postIndex := iNdEx + intStringLen 634 | if postIndex > l { 635 | return io.ErrUnexpectedEOF 636 | } 637 | m.IpAddress = string(dAtA[iNdEx:postIndex]) 638 | iNdEx = postIndex 639 | case 2: 640 | if wireType != 2 { 641 | return fmt.Errorf("proto: wrong wireType = %d for field Language", wireType) 642 | } 643 | var stringLen uint64 644 | for shift := uint(0); ; shift += 7 { 645 | if shift >= 64 { 646 | return ErrIntOverflowGeoipfix 647 | } 648 | if iNdEx >= l { 649 | return io.ErrUnexpectedEOF 650 | } 651 | b := dAtA[iNdEx] 652 | iNdEx++ 653 | stringLen |= (uint64(b) & 0x7F) << shift 654 | if b < 0x80 { 655 | break 656 | } 657 | } 658 | intStringLen := int(stringLen) 659 | if intStringLen < 0 { 660 | return ErrInvalidLengthGeoipfix 661 | } 662 | postIndex := iNdEx + intStringLen 663 | if postIndex > l { 664 | return io.ErrUnexpectedEOF 665 | } 666 | m.Language = string(dAtA[iNdEx:postIndex]) 667 | iNdEx = postIndex 668 | default: 669 | iNdEx = preIndex 670 | skippy, err := skipGeoipfix(dAtA[iNdEx:]) 671 | if err != nil { 672 | return err 673 | } 674 | if skippy < 0 { 675 | return ErrInvalidLengthGeoipfix 676 | } 677 | if (iNdEx + skippy) > l { 678 | return io.ErrUnexpectedEOF 679 | } 680 | iNdEx += skippy 681 | } 682 | } 683 | 684 | if iNdEx > l { 685 | return io.ErrUnexpectedEOF 686 | } 687 | return nil 688 | } 689 | func (m *Location) Unmarshal(dAtA []byte) error { 690 | l := len(dAtA) 691 | iNdEx := 0 692 | for iNdEx < l { 693 | preIndex := iNdEx 694 | var wire uint64 695 | for shift := uint(0); ; shift += 7 { 696 | if shift >= 64 { 697 | return ErrIntOverflowGeoipfix 698 | } 699 | if iNdEx >= l { 700 | return io.ErrUnexpectedEOF 701 | } 702 | b := dAtA[iNdEx] 703 | iNdEx++ 704 | wire |= (uint64(b) & 0x7F) << shift 705 | if b < 0x80 { 706 | break 707 | } 708 | } 709 | fieldNum := int32(wire >> 3) 710 | wireType := int(wire & 0x7) 711 | if wireType == 4 { 712 | return fmt.Errorf("proto: Location: wiretype end group for non-group") 713 | } 714 | if fieldNum <= 0 { 715 | return fmt.Errorf("proto: Location: illegal tag %d (wire type %d)", fieldNum, wire) 716 | } 717 | switch fieldNum { 718 | case 1: 719 | if wireType != 2 { 720 | return fmt.Errorf("proto: wrong wireType = %d for field IpAddress", wireType) 721 | } 722 | var stringLen uint64 723 | for shift := uint(0); ; shift += 7 { 724 | if shift >= 64 { 725 | return ErrIntOverflowGeoipfix 726 | } 727 | if iNdEx >= l { 728 | return io.ErrUnexpectedEOF 729 | } 730 | b := dAtA[iNdEx] 731 | iNdEx++ 732 | stringLen |= (uint64(b) & 0x7F) << shift 733 | if b < 0x80 { 734 | break 735 | } 736 | } 737 | intStringLen := int(stringLen) 738 | if intStringLen < 0 { 739 | return ErrInvalidLengthGeoipfix 740 | } 741 | postIndex := iNdEx + intStringLen 742 | if postIndex > l { 743 | return io.ErrUnexpectedEOF 744 | } 745 | m.IpAddress = string(dAtA[iNdEx:postIndex]) 746 | iNdEx = postIndex 747 | case 2: 748 | if wireType != 2 { 749 | return fmt.Errorf("proto: wrong wireType = %d for field Country", wireType) 750 | } 751 | var msglen int 752 | for shift := uint(0); ; shift += 7 { 753 | if shift >= 64 { 754 | return ErrIntOverflowGeoipfix 755 | } 756 | if iNdEx >= l { 757 | return io.ErrUnexpectedEOF 758 | } 759 | b := dAtA[iNdEx] 760 | iNdEx++ 761 | msglen |= (int(b) & 0x7F) << shift 762 | if b < 0x80 { 763 | break 764 | } 765 | } 766 | if msglen < 0 { 767 | return ErrInvalidLengthGeoipfix 768 | } 769 | postIndex := iNdEx + msglen 770 | if postIndex > l { 771 | return io.ErrUnexpectedEOF 772 | } 773 | if m.Country == nil { 774 | m.Country = &Place{} 775 | } 776 | if err := m.Country.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { 777 | return err 778 | } 779 | iNdEx = postIndex 780 | case 3: 781 | if wireType != 2 { 782 | return fmt.Errorf("proto: wrong wireType = %d for field Region", wireType) 783 | } 784 | var msglen int 785 | for shift := uint(0); ; shift += 7 { 786 | if shift >= 64 { 787 | return ErrIntOverflowGeoipfix 788 | } 789 | if iNdEx >= l { 790 | return io.ErrUnexpectedEOF 791 | } 792 | b := dAtA[iNdEx] 793 | iNdEx++ 794 | msglen |= (int(b) & 0x7F) << shift 795 | if b < 0x80 { 796 | break 797 | } 798 | } 799 | if msglen < 0 { 800 | return ErrInvalidLengthGeoipfix 801 | } 802 | postIndex := iNdEx + msglen 803 | if postIndex > l { 804 | return io.ErrUnexpectedEOF 805 | } 806 | if m.Region == nil { 807 | m.Region = &Place{} 808 | } 809 | if err := m.Region.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { 810 | return err 811 | } 812 | iNdEx = postIndex 813 | case 4: 814 | if wireType != 2 { 815 | return fmt.Errorf("proto: wrong wireType = %d for field City", wireType) 816 | } 817 | var stringLen uint64 818 | for shift := uint(0); ; shift += 7 { 819 | if shift >= 64 { 820 | return ErrIntOverflowGeoipfix 821 | } 822 | if iNdEx >= l { 823 | return io.ErrUnexpectedEOF 824 | } 825 | b := dAtA[iNdEx] 826 | iNdEx++ 827 | stringLen |= (uint64(b) & 0x7F) << shift 828 | if b < 0x80 { 829 | break 830 | } 831 | } 832 | intStringLen := int(stringLen) 833 | if intStringLen < 0 { 834 | return ErrInvalidLengthGeoipfix 835 | } 836 | postIndex := iNdEx + intStringLen 837 | if postIndex > l { 838 | return io.ErrUnexpectedEOF 839 | } 840 | m.City = string(dAtA[iNdEx:postIndex]) 841 | iNdEx = postIndex 842 | case 5: 843 | if wireType != 2 { 844 | return fmt.Errorf("proto: wrong wireType = %d for field ZipCode", wireType) 845 | } 846 | var stringLen uint64 847 | for shift := uint(0); ; shift += 7 { 848 | if shift >= 64 { 849 | return ErrIntOverflowGeoipfix 850 | } 851 | if iNdEx >= l { 852 | return io.ErrUnexpectedEOF 853 | } 854 | b := dAtA[iNdEx] 855 | iNdEx++ 856 | stringLen |= (uint64(b) & 0x7F) << shift 857 | if b < 0x80 { 858 | break 859 | } 860 | } 861 | intStringLen := int(stringLen) 862 | if intStringLen < 0 { 863 | return ErrInvalidLengthGeoipfix 864 | } 865 | postIndex := iNdEx + intStringLen 866 | if postIndex > l { 867 | return io.ErrUnexpectedEOF 868 | } 869 | m.ZipCode = string(dAtA[iNdEx:postIndex]) 870 | iNdEx = postIndex 871 | case 6: 872 | if wireType != 2 { 873 | return fmt.Errorf("proto: wrong wireType = %d for field TimeZone", wireType) 874 | } 875 | var stringLen uint64 876 | for shift := uint(0); ; shift += 7 { 877 | if shift >= 64 { 878 | return ErrIntOverflowGeoipfix 879 | } 880 | if iNdEx >= l { 881 | return io.ErrUnexpectedEOF 882 | } 883 | b := dAtA[iNdEx] 884 | iNdEx++ 885 | stringLen |= (uint64(b) & 0x7F) << shift 886 | if b < 0x80 { 887 | break 888 | } 889 | } 890 | intStringLen := int(stringLen) 891 | if intStringLen < 0 { 892 | return ErrInvalidLengthGeoipfix 893 | } 894 | postIndex := iNdEx + intStringLen 895 | if postIndex > l { 896 | return io.ErrUnexpectedEOF 897 | } 898 | m.TimeZone = string(dAtA[iNdEx:postIndex]) 899 | iNdEx = postIndex 900 | case 7: 901 | if wireType != 5 { 902 | return fmt.Errorf("proto: wrong wireType = %d for field Latitude", wireType) 903 | } 904 | var v uint32 905 | if (iNdEx + 4) > l { 906 | return io.ErrUnexpectedEOF 907 | } 908 | v = uint32(binary.LittleEndian.Uint32(dAtA[iNdEx:])) 909 | iNdEx += 4 910 | m.Latitude = float32(math.Float32frombits(v)) 911 | case 8: 912 | if wireType != 5 { 913 | return fmt.Errorf("proto: wrong wireType = %d for field Longitude", wireType) 914 | } 915 | var v uint32 916 | if (iNdEx + 4) > l { 917 | return io.ErrUnexpectedEOF 918 | } 919 | v = uint32(binary.LittleEndian.Uint32(dAtA[iNdEx:])) 920 | iNdEx += 4 921 | m.Longitude = float32(math.Float32frombits(v)) 922 | case 9: 923 | if wireType != 0 { 924 | return fmt.Errorf("proto: wrong wireType = %d for field MetroCode", wireType) 925 | } 926 | m.MetroCode = 0 927 | for shift := uint(0); ; shift += 7 { 928 | if shift >= 64 { 929 | return ErrIntOverflowGeoipfix 930 | } 931 | if iNdEx >= l { 932 | return io.ErrUnexpectedEOF 933 | } 934 | b := dAtA[iNdEx] 935 | iNdEx++ 936 | m.MetroCode |= (int64(b) & 0x7F) << shift 937 | if b < 0x80 { 938 | break 939 | } 940 | } 941 | default: 942 | iNdEx = preIndex 943 | skippy, err := skipGeoipfix(dAtA[iNdEx:]) 944 | if err != nil { 945 | return err 946 | } 947 | if skippy < 0 { 948 | return ErrInvalidLengthGeoipfix 949 | } 950 | if (iNdEx + skippy) > l { 951 | return io.ErrUnexpectedEOF 952 | } 953 | iNdEx += skippy 954 | } 955 | } 956 | 957 | if iNdEx > l { 958 | return io.ErrUnexpectedEOF 959 | } 960 | return nil 961 | } 962 | func skipGeoipfix(dAtA []byte) (n int, err error) { 963 | l := len(dAtA) 964 | iNdEx := 0 965 | for iNdEx < l { 966 | var wire uint64 967 | for shift := uint(0); ; shift += 7 { 968 | if shift >= 64 { 969 | return 0, ErrIntOverflowGeoipfix 970 | } 971 | if iNdEx >= l { 972 | return 0, io.ErrUnexpectedEOF 973 | } 974 | b := dAtA[iNdEx] 975 | iNdEx++ 976 | wire |= (uint64(b) & 0x7F) << shift 977 | if b < 0x80 { 978 | break 979 | } 980 | } 981 | wireType := int(wire & 0x7) 982 | switch wireType { 983 | case 0: 984 | for shift := uint(0); ; shift += 7 { 985 | if shift >= 64 { 986 | return 0, ErrIntOverflowGeoipfix 987 | } 988 | if iNdEx >= l { 989 | return 0, io.ErrUnexpectedEOF 990 | } 991 | iNdEx++ 992 | if dAtA[iNdEx-1] < 0x80 { 993 | break 994 | } 995 | } 996 | return iNdEx, nil 997 | case 1: 998 | iNdEx += 8 999 | return iNdEx, nil 1000 | case 2: 1001 | var length int 1002 | for shift := uint(0); ; shift += 7 { 1003 | if shift >= 64 { 1004 | return 0, ErrIntOverflowGeoipfix 1005 | } 1006 | if iNdEx >= l { 1007 | return 0, io.ErrUnexpectedEOF 1008 | } 1009 | b := dAtA[iNdEx] 1010 | iNdEx++ 1011 | length |= (int(b) & 0x7F) << shift 1012 | if b < 0x80 { 1013 | break 1014 | } 1015 | } 1016 | iNdEx += length 1017 | if length < 0 { 1018 | return 0, ErrInvalidLengthGeoipfix 1019 | } 1020 | return iNdEx, nil 1021 | case 3: 1022 | for { 1023 | var innerWire uint64 1024 | var start int = iNdEx 1025 | for shift := uint(0); ; shift += 7 { 1026 | if shift >= 64 { 1027 | return 0, ErrIntOverflowGeoipfix 1028 | } 1029 | if iNdEx >= l { 1030 | return 0, io.ErrUnexpectedEOF 1031 | } 1032 | b := dAtA[iNdEx] 1033 | iNdEx++ 1034 | innerWire |= (uint64(b) & 0x7F) << shift 1035 | if b < 0x80 { 1036 | break 1037 | } 1038 | } 1039 | innerWireType := int(innerWire & 0x7) 1040 | if innerWireType == 4 { 1041 | break 1042 | } 1043 | next, err := skipGeoipfix(dAtA[start:]) 1044 | if err != nil { 1045 | return 0, err 1046 | } 1047 | iNdEx = start + next 1048 | } 1049 | return iNdEx, nil 1050 | case 4: 1051 | return iNdEx, nil 1052 | case 5: 1053 | iNdEx += 4 1054 | return iNdEx, nil 1055 | default: 1056 | return 0, fmt.Errorf("proto: illegal wireType %d", wireType) 1057 | } 1058 | } 1059 | panic("unreachable") 1060 | } 1061 | 1062 | var ( 1063 | ErrInvalidLengthGeoipfix = fmt.Errorf("proto: negative length found during unmarshaling") 1064 | ErrIntOverflowGeoipfix = fmt.Errorf("proto: integer overflow") 1065 | ) 1066 | 1067 | func init() { proto1.RegisterFile("proto/geoipfix.proto", fileDescriptorGeoipfix) } 1068 | 1069 | var fileDescriptorGeoipfix = []byte{ 1070 | // 356 bytes of a gzipped FileDescriptorProto 1071 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x92, 0xcf, 0x4a, 0xc3, 0x40, 1072 | 0x10, 0xc6, 0xbb, 0xfd, 0x9b, 0x4c, 0x15, 0x65, 0xf1, 0x90, 0x56, 0x0d, 0x25, 0x88, 0xf4, 0x94, 1073 | 0x42, 0x7b, 0xf4, 0x64, 0x3d, 0xf4, 0x22, 0x58, 0x72, 0xf4, 0x52, 0x62, 0x32, 0x86, 0x85, 0x76, 1074 | 0x77, 0x4d, 0x37, 0x60, 0xfb, 0x24, 0x1e, 0x7c, 0x20, 0x8f, 0x3e, 0x82, 0xd4, 0x17, 0x91, 0xdd, 1075 | 0x4d, 0xaa, 0xa0, 0xe0, 0x29, 0x33, 0xdf, 0x97, 0x7c, 0xc3, 0x6f, 0x26, 0x70, 0x22, 0x73, 0xa1, 1076 | 0xc4, 0x28, 0x43, 0xc1, 0xe4, 0x23, 0x7b, 0x0e, 0x4d, 0x4b, 0x5b, 0xe6, 0x11, 0x8c, 0xa0, 0x35, 1077 | 0x5f, 0xc6, 0x09, 0x52, 0x0a, 0xcd, 0x44, 0xa4, 0xe8, 0x91, 0x01, 0x19, 0xba, 0x91, 0xa9, 0xb5, 1078 | 0xc6, 0xe3, 0x15, 0x7a, 0x75, 0xab, 0xe9, 0x3a, 0xb8, 0x03, 0x3a, 0x43, 0x75, 0x2b, 0x92, 0x58, 1079 | 0x31, 0xc1, 0x23, 0x7c, 0x2a, 0x70, 0xad, 0xe8, 0x39, 0x00, 0x93, 0x8b, 0x38, 0x4d, 0x73, 0x5c, 1080 | 0xaf, 0xcb, 0x0c, 0x97, 0xc9, 0x6b, 0x2b, 0xd0, 0x3e, 0x38, 0xcb, 0x98, 0x67, 0x45, 0x9c, 0x55, 1081 | 0x61, 0xfb, 0x3e, 0x78, 0xad, 0x83, 0x53, 0xc5, 0xfd, 0x97, 0x73, 0x09, 0x9d, 0x44, 0x14, 0x5c, 1082 | 0xe5, 0x1b, 0x13, 0xd3, 0x1d, 0x1f, 0x58, 0x9a, 0xd0, 0x30, 0x44, 0x95, 0x49, 0x2f, 0xa0, 0x9d, 1083 | 0x63, 0xc6, 0x04, 0xf7, 0x1a, 0x7f, 0xbc, 0x56, 0x7a, 0x06, 0x99, 0xa9, 0x8d, 0xd7, 0x2c, 0x91, 1084 | 0x99, 0xda, 0xd0, 0x1e, 0x38, 0x5b, 0x26, 0x17, 0x66, 0x15, 0x2d, 0xa3, 0x77, 0xb6, 0x4c, 0xde, 1085 | 0xe8, 0x6d, 0x9c, 0x82, 0xab, 0xd8, 0x0a, 0x17, 0x5b, 0xc1, 0xd1, 0x6b, 0x5b, 0x0a, 0x2d, 0xdc, 1086 | 0x0b, 0x8e, 0x96, 0x50, 0x31, 0x55, 0xa4, 0xe8, 0x75, 0x06, 0x64, 0x58, 0x8f, 0xf6, 0x3d, 0x3d, 1087 | 0x03, 0x77, 0x29, 0x78, 0x66, 0x4d, 0xc7, 0x98, 0xdf, 0x82, 0x46, 0x5e, 0xa1, 0xca, 0x85, 0x9d, 1088 | 0xe9, 0x0e, 0xc8, 0xb0, 0x11, 0xb9, 0x46, 0xd1, 0x53, 0xc7, 0x33, 0x70, 0x66, 0xe5, 0xe5, 0xe8, 1089 | 0x15, 0x74, 0x7f, 0xec, 0x9e, 0xf6, 0x4a, 0xaa, 0xdf, 0xf7, 0xe8, 0x1f, 0x95, 0x56, 0xa5, 0x07, 1090 | 0xb5, 0xe9, 0xe4, 0x6d, 0xe7, 0x93, 0xf7, 0x9d, 0x4f, 0x3e, 0x76, 0x3e, 0x79, 0xf9, 0xf4, 0x6b, 1091 | 0x70, 0xcc, 0x44, 0x98, 0xe5, 0x32, 0x09, 0xab, 0x5f, 0x63, 0x7a, 0x58, 0x8d, 0x9a, 0xeb, 0xaf, 1092 | 0xe7, 0xe4, 0xa1, 0x6d, 0x62, 0x26, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x44, 0xfc, 0xe3, 0x99, 1093 | 0x44, 0x02, 0x00, 0x00, 1094 | } 1095 | -------------------------------------------------------------------------------- /proto/geoipfix.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | option java_multiple_files = true; 4 | option java_package = "io.grpc.geoipfix"; 5 | option java_outer_classname = "GeoipfixProto"; 6 | 7 | package proto; 8 | 9 | message Place { 10 | string code = 1; 11 | string name = 2; 12 | } 13 | 14 | message GetLocationRequest { 15 | string ip_address = 1; 16 | string language = 2; 17 | } 18 | 19 | message Location { 20 | string ip_address = 1; 21 | Place country = 2; 22 | Place region = 3; 23 | string city = 4; 24 | string zip_code = 5; 25 | string time_zone = 6; 26 | float latitude = 7; 27 | float longitude = 8; 28 | int64 metro_code = 9; 29 | } 30 | 31 | service Geoipfix { 32 | rpc GetLocation(GetLocationRequest) returns (Location) {} 33 | } 34 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | source scripts/version.sh 2 | 3 | kubectl apply -f scripts/kubernetes/geoipfix-config.yml 4 | 5 | DEPLOYMENT=$(envsubst < scripts/kubernetes/geoipfix-deployment.yml) 6 | echo "Applying deployment manifest..." 7 | echo ${DEPLOYMENT} 8 | cat <