Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9afe79b651 |
19
.github/dependabot.yml
vendored
19
.github/dependabot.yml
vendored
@@ -1,19 +0,0 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
31
.github/workflows/docker-hub-build-push.yml
vendored
Normal file
31
.github/workflows/docker-hub-build-push.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: build docker image and push to docker hub
|
||||
|
||||
on:
|
||||
release:
|
||||
types: # This configuration does not affect the page_build event above
|
||||
- created
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: install buildx
|
||||
id: buildx
|
||||
uses: crazy-max/ghaction-docker-buildx@v1
|
||||
with:
|
||||
version: latest
|
||||
- name: login to docker hub
|
||||
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
||||
- name: build and push the latest image
|
||||
run: |
|
||||
docker buildx build --push \
|
||||
--tag shizunge/endlessh-go:latest \
|
||||
--platform linux/amd64,linux/arm/v7,linux/arm64 .
|
||||
- name: extract tag string
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
- name: tag image
|
||||
run: docker tag shizunge/endlessh-go:latest shizunge/endlessh-go:$RELEASE_VERSION
|
||||
- name: push tagged image
|
||||
run: docker push shizunge/endlessh-go:$RELEASE_VERSION
|
||||
22
.github/workflows/docker-hub-description.yml
vendored
22
.github/workflows/docker-hub-description.yml
vendored
@@ -1,22 +0,0 @@
|
||||
name: Update Docker Hub description
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- created
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Update Docker Hub description
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v5
|
||||
- name: Update Docker Hub description
|
||||
uses: peter-evans/dockerhub-description@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
repository: ${{ github.repository }}
|
||||
short-description: ${{ github.event.repository.description }}
|
||||
48
.github/workflows/on-pull-request.yml
vendored
48
.github/workflows/on-pull-request.yml
vendored
@@ -1,48 +0,0 @@
|
||||
name: On pull request
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- 'main'
|
||||
paths-ignore:
|
||||
- 'dashboard/*'
|
||||
- 'examples/*'
|
||||
- 'README.md'
|
||||
- 'LICENSE'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
PLATFORMS: "linux/amd64,linux/arm64,linux/arm/v7"
|
||||
|
||||
jobs:
|
||||
build_container_image:
|
||||
name: Build Docker image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.11.1
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/${{ github.repository }}-development
|
||||
tags: |
|
||||
type=raw,value=dev-{{date 'X'}}
|
||||
type=raw,value=latest
|
||||
type=ref,event=branch
|
||||
type=edge,branch=main
|
||||
- name: Build
|
||||
uses: docker/build-push-action@v6.18.0
|
||||
with:
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
push: false
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
provenance: false
|
||||
|
||||
|
||||
62
.github/workflows/on-push.yml
vendored
62
.github/workflows/on-push.yml
vendored
@@ -1,62 +0,0 @@
|
||||
name: On push
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'release'
|
||||
- 'dependabot/**'
|
||||
paths-ignore:
|
||||
- 'dashboard/*'
|
||||
- 'examples/*'
|
||||
- 'README.md'
|
||||
- 'LICENSE'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
PLATFORMS: "linux/amd64,linux/arm64,linux/arm/v7"
|
||||
|
||||
jobs:
|
||||
build_and_push:
|
||||
name: Build and push Docker image
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.actor != 'dependabot[bot]' }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.11.1
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3.5.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3.5.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ github.token }}
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ github.repository }}-development
|
||||
ghcr.io/${{ github.repository }}-development
|
||||
tags: |
|
||||
type=raw,value=dev-{{date 'X'}}
|
||||
type=raw,value=latest
|
||||
type=ref,event=branch
|
||||
type=edge,branch=main
|
||||
- name: Build and push ${{ github.repository }}:${{ steps.git.outputs.image_tag }}
|
||||
uses: docker/build-push-action@v6.18.0
|
||||
with:
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
provenance: false
|
||||
|
||||
|
||||
49
.github/workflows/on-release.yml
vendored
49
.github/workflows/on-release.yml
vendored
@@ -1,49 +0,0 @@
|
||||
name: On release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: # This configuration does not affect the page_build event above
|
||||
- created
|
||||
env:
|
||||
PLATFORMS: "linux/amd64,linux/arm64,linux/arm/v7"
|
||||
|
||||
jobs:
|
||||
build_and_push:
|
||||
name: Build and push Docker image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.11.1
|
||||
- name: Login to docker hub
|
||||
uses: docker/login-action@v3.5.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3.5.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ github.token }}
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ github.repository }}
|
||||
ghcr.io/${{ github.repository }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6.18.0
|
||||
with:
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
provenance: false
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +0,0 @@
|
||||
endlessh-go
|
||||
15
Dockerfile
15
Dockerfile
@@ -1,20 +1,19 @@
|
||||
FROM golang AS build
|
||||
FROM golang:alpine AS build
|
||||
|
||||
RUN mkdir /endlessh
|
||||
ADD . /endlessh
|
||||
WORKDIR /endlessh
|
||||
ADD . /go/src/app
|
||||
WORKDIR /go/src/app
|
||||
RUN go mod tidy
|
||||
RUN CGO_ENABLED=0 go build -o endlessh .
|
||||
RUN go build -o endlessh .
|
||||
|
||||
FROM gcr.io/distroless/base
|
||||
FROM alpine:latest
|
||||
|
||||
LABEL org.opencontainers.image.title=endlessh-go
|
||||
LABEL org.opencontainers.image.description="Endlessh: an SSH tarpit"
|
||||
LABEL org.opencontainers.image.vendor="Shizun Ge"
|
||||
LABEL org.opencontainers.image.licenses=GPLv3
|
||||
|
||||
COPY --from=build /endlessh/endlessh /endlessh
|
||||
COPY --from=build /go/src/app/endlessh /usr/bin/endlessh
|
||||
EXPOSE 2222 2112
|
||||
USER nobody
|
||||
ENTRYPOINT ["/endlessh"]
|
||||
ENTRYPOINT ["/usr/bin/endlessh"]
|
||||
CMD ["-logtostderr", "-v=1"]
|
||||
|
||||
122
README.md
122
README.md
@@ -1,19 +1,10 @@
|
||||
# endlessh-go
|
||||
|
||||
[](https://github.com/shizunge/endlessh-go/releases/latest)
|
||||
[](https://github.com/shizunge/endlessh-go/blob/main/LICENSE)
|
||||
[](https://hub.docker.com/r/shizunge/endlessh-go)
|
||||
[](https://hub.docker.com/r/shizunge/endlessh-go)
|
||||
[](https://github.com/shizunge/endlessh-go/actions/workflows/on-push.yml)
|
||||
[](https://www.codefactor.io/repository/github/shizunge/endlessh-go)
|
||||
|
||||
A golang implementation of [endlessh](https://nullprogram.com/blog/2019/03/22/) exporting Prometheus metrics, visualized by a Grafana dashboard.
|
||||
|
||||

|
||||
|
||||
## Introduction
|
||||
|
||||
[Endlessh](https://nullprogram.com/blog/2019/03/22/) is a great idea that not only blocks the brute force SSH attacks, but also wastes attackers time as a kind of counter-attack. Besides trapping the attackers, I also want to visualize the Geolocations and other statistics of the sources of attacks. Unfortunately the wonderful original [C implementation of endlessh](https://github.com/skeeto/endlessh) only provides text based log, but I do not like the solution that writes extra scripts to parse the log outputs, then exports the results to a dashboard, because it would introduce extra layers in my current setup and it would depend on the format of the text log file rather than some structured data. Thus I create this golang implementation of endlessh to export [Prometheus](https://prometheus.io/) metrics and a [Grafana](https://grafana.com/) dashboard to visualize them.
|
||||
Endlessh is a great idea that not only blocks the brute force SSH attacks, but also wastes attackers time as a kind of counter-attack. Besides trapping the attackers, I also want to visualize the Geolocations and other statistics of the sources of attacks. Unfortunately the wonderful original [C implementation of endlessh](https://github.com/skeeto/endlessh) only provides text based log, but I do not like the solution that writing extra scripts to parse the log outputs, then exporting the results to a dashboard, because it would introduce extra layers in my current setup and it would depend on the format of the text log file rather than some structured data. Thus I create this golang implementation of endlessh to export [Prometheus](https://prometheus.io/) metrics and a [Grafana](https://grafana.com/) dashboard to visualize them.
|
||||
|
||||
If you want a dashboard of sources of attacks and do not mind the endlessh server, besides trapping the attackers, does extra things including: translating IP to Geohash, exporting Prometheus metrics, and using more memory (about 10MB), this is the solution for you.
|
||||
|
||||
@@ -29,7 +20,7 @@ go build .
|
||||
Alternatively, you can use the [docker image](https://hub.docker.com/r/shizunge/endlessh-go):
|
||||
|
||||
```
|
||||
docker run -d -p 2222:2222 shizunge/endlessh-go -logtostderr -v=1
|
||||
sudo docker run -d -p 2222:2222 shizunge/endlessh-go
|
||||
```
|
||||
|
||||
It listens to port `2222` by default.
|
||||
@@ -40,69 +31,50 @@ Then you can try to connect to the endlessh server. Your SSH client should hang
|
||||
ssh -p 2222 localhost
|
||||
```
|
||||
|
||||
If you want log like the [C implementation](https://github.com/skeeto/endlessh), you need to set both CLI arguments `-logtostderr` and `-v=1`, then the log will go to stderr. You can set different log destinations via CLI arguments.
|
||||
|
||||
Also check out [examples](./examples/README.md) for the setup of the full stack.
|
||||
If you want log like the [C implementation](https://github.com/skeeto/endlessh), you need to set both CLI arguments `-logtostderr` and `-v=1`, then the log will go to the stderr. You can set different log destinations via CLI arguments.
|
||||
|
||||
## Usage
|
||||
|
||||
`./endlessh-go --help`
|
||||
Usage of `./endlessh-go`
|
||||
|
||||
```
|
||||
Usage of ./endlessh-go
|
||||
-alsologtostderr
|
||||
log to standard error as well as files
|
||||
-conn_type string
|
||||
Connection type. Possible values are tcp, tcp4, tcp6 (default "tcp")
|
||||
-enable_prometheus
|
||||
Enable prometheus
|
||||
-geoip_supplier string
|
||||
Supplier to obtain Geohash of IPs. Possible values are "off", "ip-api", "max-mind-db" (default "off")
|
||||
-host string
|
||||
SSH listening address (default "0.0.0.0")
|
||||
-interval_ms int
|
||||
Message millisecond delay (default 1000)
|
||||
-line_length int
|
||||
Maximum banner line length (default 32)
|
||||
-log_backtrace_at value
|
||||
when logging hits line file:N, emit a stack trace
|
||||
-log_dir string
|
||||
If non-empty, write log files in this directory
|
||||
-log_link string
|
||||
If non-empty, add symbolic links in this directory to the log files
|
||||
-logbuflevel int
|
||||
Buffer log messages logged at this level or lower (-1 means don't buffer; 0 means buffer INFO only; ...). Has limited applicability on non-prod platforms.
|
||||
-logtostderr
|
||||
log to standard error instead of files
|
||||
-max_clients int
|
||||
Maximum number of clients (default 4096)
|
||||
-max_mind_db string
|
||||
Path to the MaxMind DB file.
|
||||
-port value
|
||||
SSH listening port. You may provide multiple -port flags to listen to multiple ports. (default "2222")
|
||||
-prometheus_clean_unseen_seconds int
|
||||
Remove series if the IP is not seen for the given time. Set to 0 to disable. (default 0)
|
||||
-prometheus_entry string
|
||||
Entry point for prometheus (default "metrics")
|
||||
-prometheus_host string
|
||||
The address for prometheus (default "0.0.0.0")
|
||||
-prometheus_port string
|
||||
The port for prometheus (default "2112")
|
||||
-proxy_protocol_enabled
|
||||
Enable PROXY protocol support. This causes the server to expect PROXY protocol headers on incoming connections.
|
||||
-proxy_protocol_read_header_timeout_ms int
|
||||
Timeout for reading the PROXY protocol header in milliseconds. If the connection does not send a valid PROXY protocol header in this time, the header is ignored. (default 200)
|
||||
-stderrthreshold value
|
||||
logs at or above this threshold go to stderr (default 2)
|
||||
-v value
|
||||
log level for V logs
|
||||
-vmodule value
|
||||
comma-separated list of pattern=N settings for file-filtered logging
|
||||
```
|
||||
* -alsologtostderr
|
||||
* log to standard error as well as files
|
||||
* -conn_type string
|
||||
* Connection type. Possible values are tcp, tcp4, tcp6 (default "tcp")
|
||||
* -enable_prometheus
|
||||
* Enable prometheus
|
||||
* -geoip_supplier string
|
||||
* Supplier to obtain Geohash of IPs. Possible values are "off", "ip-api", "freegeoip" (default "off")
|
||||
* -host string
|
||||
* Listening address (default "0.0.0.0")
|
||||
* -interval_ms int
|
||||
* Message millisecond delay (default 1000)
|
||||
* -line_length int
|
||||
* Maximum banner line length (default 32)
|
||||
* -log_backtrace_at value
|
||||
* when logging hits line file:N, emit a stack trace
|
||||
* -log_dir string
|
||||
* If non-empty, write log files in this directory
|
||||
* -logtostderr
|
||||
* log to standard error instead of files
|
||||
* -max_clients int
|
||||
* Maximum number of clients (default 4096)
|
||||
* -port string
|
||||
* Listening port (default "2222")
|
||||
* -prometheus_entry string
|
||||
* Entry point for prometheus (default "metrics")
|
||||
* -prometheus_port string
|
||||
* The port for prometheus (default "2112")
|
||||
* -stderrthreshold value
|
||||
* logs at or above this threshold go to stderr
|
||||
* -v value
|
||||
* log level for V logs
|
||||
* -vmodule value
|
||||
* comma-separated list of pattern=N settings for file-filtered logging
|
||||
|
||||
## Metrics
|
||||
|
||||
Endlessh-go exports the following Prometheus metrics.
|
||||
This golang implementation exports the following Prometheus metrics.
|
||||
|
||||
| Metric | Type | Description |
|
||||
|--------------------------------------|-------|--------------|
|
||||
@@ -110,27 +82,21 @@ Endlessh-go exports the following Prometheus metrics.
|
||||
| endlessh_client_closed_count_total | count | Total number of clients that stopped connecting to this host. |
|
||||
| endlessh_sent_bytes_total | count | Total bytes sent to clients that tried to connect to this host. |
|
||||
| endlessh_trapped_time_seconds_total | count | Total seconds clients spent on endlessh. |
|
||||
| endlessh_client_open_count | count | Number of connections of clients. <br> Labels: <br> <ul><li> `ip`: Remote IP of the client </li> <li> `local_port`: Local port the program listens to </li> <li> `country`: Country of the IP </li> <li> `location`: Country, Region, and City </li> <li> `geohash`: Geohash of the location </li></ul> |
|
||||
| endlessh_client_trapped_time_seconds | count | Seconds a client spends on endlessh. <br> Labels: <br> <ul><li> `ip`: Remote IP of the client </li> <li> `local_port`: Local port the program listens to </li></ul> |
|
||||
| endlessh_client_open_count | count | Number of connections of clients. <br> Labels: <br> <ul><li> `ip`: IP of the client </li> <li> `country`: Country of the IP </li> <li> `location`: Country, Region, and City </li> <li> `geohash`: Geohash of the location </li></ul> |
|
||||
| endlessh_client_trapped_time_seconds | count | Seconds a client spends on endlessh. <br> Labels: <br> <ul><li> `ip`: IP of the client </li></ul> |
|
||||
|
||||
The metrics is off by default, you can turn it via the CLI argument `-enable_prometheus`.
|
||||
|
||||
It listens to port `2112` and entry point is `/metrics` by default. The port and entry point can be changed via CLI arguments.
|
||||
|
||||
The endlessh-go server stores the geohash of attackers as a label on `endlessh_client_open_count`, which is also off by default. You can turn it on via the CLI argument `-geoip_supplier`. The endlessh-go uses service from [ip-api](https://ip-api.com/), which may enforce a query rate and limit commercial use. Visit their website for their terms and policies.
|
||||
|
||||
You could also use an offline GeoIP database from [MaxMind](https://www.maxmind.com) by setting `-geoip_supplier` to _max-mind-db_ and `-max_mind_db` to the path of the database file.
|
||||
The endlessh-go server stores the geohash of attackers as a label on `endlessh_client_open_count`, which is also off by default. You can turn it on via the CLI argument `-geoip_supplier`. The endlessh-go uses service from either [ip-api](https://ip-api.com/) or [freegeoip](https://freegeoip.live/), which may enforce a query rate and limit commercial use. Visit their website for their terms and policies.
|
||||
|
||||
## Dashboard
|
||||
|
||||

|
||||
|
||||
The dashboard requires Grafana 8.2.
|
||||
|
||||
You can import the dashboard from Grafana.com using ID [15156](https://grafana.com/grafana/dashboards/15156)
|
||||
|
||||
The dashboard visualizes data for the selected time range.
|
||||
|
||||
The IP addresses are clickable and link you to the [ARIN](https://www.arin.net/) database.
|
||||
|
||||
## Contacts
|
||||
|
||||
If you have any problems or questions, please contact me through a [GitHub issue](https://github.com/shizunge/endlessh-go/issues)
|
||||
|
||||
111
client.go
Normal file
111
client.go
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright (C) 2021 Shizun Ge
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var letterBytes = []byte(" abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890!@#$%^&*()-=_+[]{}|;:',./<>?")
|
||||
|
||||
func randStringBytes(n int64) []byte {
|
||||
b := make([]byte, n+1)
|
||||
for i := range b {
|
||||
b[i] = letterBytes[rand.Intn(len(letterBytes))]
|
||||
}
|
||||
b[n] = '\n'
|
||||
return b
|
||||
}
|
||||
|
||||
type client struct {
|
||||
conn net.Conn
|
||||
last time.Time
|
||||
next time.Time
|
||||
start time.Time
|
||||
interval time.Duration
|
||||
geoipSupplier string
|
||||
geohash string
|
||||
country string
|
||||
location string
|
||||
bytesSent int
|
||||
prometheusEnabled bool
|
||||
}
|
||||
|
||||
func NewClient(conn net.Conn, interval time.Duration, maxClient int64, geoipSupplier string, prometheusEnabled bool) *client {
|
||||
addr := conn.RemoteAddr().(*net.TCPAddr)
|
||||
atomic.AddInt64(&numCurrentClients, 1)
|
||||
atomic.AddInt64(&numTotalClients, 1)
|
||||
geohash, country, location, err := geohashAndLocation(addr.IP.String(), geoipSupplier)
|
||||
if err != nil {
|
||||
glog.Warningf("Failed to obatin the geohash of %v: %v.", addr.IP, err)
|
||||
}
|
||||
if prometheusEnabled {
|
||||
clientIP.With(prometheus.Labels{
|
||||
"ip": addr.IP.String(),
|
||||
"geohash": geohash,
|
||||
"country": country,
|
||||
"location": location}).Inc()
|
||||
}
|
||||
glog.V(1).Infof("ACCEPT host=%v port=%v n=%v/%v\n", addr.IP, addr.Port, numCurrentClients, maxClient)
|
||||
return &client{
|
||||
conn: conn,
|
||||
last: time.Now(),
|
||||
next: time.Now().Add(interval),
|
||||
start: time.Now(),
|
||||
interval: interval,
|
||||
geohash: geohash,
|
||||
country: country,
|
||||
location: location,
|
||||
bytesSent: 0,
|
||||
prometheusEnabled: prometheusEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) Send(bannerMaxLength int64) error {
|
||||
defer func(c *client) {
|
||||
addr := c.conn.RemoteAddr().(*net.TCPAddr)
|
||||
millisecondsSpent := time.Now().Sub(c.last).Milliseconds()
|
||||
c.last = time.Now()
|
||||
c.next = time.Now().Add(c.interval)
|
||||
atomic.AddInt64(&numTotalMilliseconds, millisecondsSpent)
|
||||
if c.prometheusEnabled {
|
||||
clientSeconds.With(prometheus.Labels{"ip": addr.IP.String()}).Add(float64(millisecondsSpent) / 1000)
|
||||
}
|
||||
}(c)
|
||||
length := rand.Int63n(bannerMaxLength)
|
||||
bytesSent, err := c.conn.Write(randStringBytes(length))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.bytesSent += bytesSent
|
||||
atomic.AddInt64(&numTotalBytes, int64(bytesSent))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *client) Close() {
|
||||
addr := c.conn.RemoteAddr().(*net.TCPAddr)
|
||||
atomic.AddInt64(&numCurrentClients, -1)
|
||||
atomic.AddInt64(&numTotalClientsClosed, 1)
|
||||
glog.V(1).Infof("CLOSE host=%v port=%v time=%v bytes=%v\n", addr.IP, addr.Port, time.Now().Sub(c.start).Seconds(), c.bytesSent)
|
||||
c.conn.Close()
|
||||
}
|
||||
102
client/client.go
102
client/client.go
@@ -1,102 +0,0 @@
|
||||
// Copyright (C) 2021-2024 Shizun Ge
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
var (
|
||||
numCurrentClients int64
|
||||
letterBytes = []byte(" abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890!@#$%^&*()-=_+[]{}|;:',./<>?")
|
||||
)
|
||||
|
||||
func randStringBytes(n int64) []byte {
|
||||
b := make([]byte, n+1)
|
||||
for i := range b {
|
||||
b[i] = letterBytes[rand.Intn(len(letterBytes))]
|
||||
}
|
||||
b[n] = '\n'
|
||||
return b
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
conn net.Conn
|
||||
next time.Time
|
||||
start time.Time
|
||||
last time.Time
|
||||
interval time.Duration
|
||||
bytesSent int
|
||||
}
|
||||
|
||||
func NewClient(conn net.Conn, interval time.Duration, maxClients int64) *Client {
|
||||
for numCurrentClients >= maxClients {
|
||||
time.Sleep(interval)
|
||||
}
|
||||
atomic.AddInt64(&numCurrentClients, 1)
|
||||
addr := conn.RemoteAddr().(*net.TCPAddr)
|
||||
glog.V(1).Infof("ACCEPT host=%v port=%v n=%v/%v\n", addr.IP, addr.Port, numCurrentClients, maxClients)
|
||||
return &Client{
|
||||
conn: conn,
|
||||
next: time.Now().Add(interval),
|
||||
start: time.Now(),
|
||||
last: time.Now(),
|
||||
interval: interval,
|
||||
bytesSent: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) RemoteIpAddr() string {
|
||||
return c.conn.RemoteAddr().(*net.TCPAddr).IP.String()
|
||||
}
|
||||
|
||||
func (c *Client) LocalPort() string {
|
||||
return strconv.Itoa(c.conn.LocalAddr().(*net.TCPAddr).Port)
|
||||
}
|
||||
|
||||
func (c *Client) Send(bannerMaxLength int64) (int, error) {
|
||||
if time.Now().Before(c.next) {
|
||||
time.Sleep(c.next.Sub(time.Now()))
|
||||
}
|
||||
c.next = time.Now().Add(c.interval)
|
||||
length := rand.Int63n(bannerMaxLength)
|
||||
bytesSent, err := c.conn.Write(randStringBytes(length))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
c.bytesSent += bytesSent
|
||||
return bytesSent, nil
|
||||
}
|
||||
|
||||
func (c *Client) MillisecondsSinceLast() int64 {
|
||||
millisecondsSpent := time.Now().Sub(c.last).Milliseconds()
|
||||
c.last = time.Now()
|
||||
return millisecondsSpent
|
||||
}
|
||||
|
||||
func (c *Client) Close() {
|
||||
addr := c.conn.RemoteAddr().(*net.TCPAddr)
|
||||
glog.V(1).Infof("CLOSE host=%v port=%v time=%v bytes=%v\n", addr.IP, addr.Port, time.Now().Sub(c.start).Seconds(), c.bytesSent)
|
||||
c.conn.Close()
|
||||
atomic.AddInt64(&numCurrentClients, -1)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
BIN
endlessh-go
Executable file
BIN
endlessh-go
Executable file
Binary file not shown.
@@ -1,18 +0,0 @@
|
||||
# Examples
|
||||
|
||||
> The default container user has uid 65534.
|
||||
|
||||
## [docker-simple](./docker-simple)
|
||||
|
||||
An example how to setup endlessh-go, Prometheus, and Grafana using [docker compose](https://docs.docker.com/compose/).
|
||||
|
||||
## [docker-maxmind](./docker-maxmind)
|
||||
|
||||
An example how to setup endlessh-go with the Maxmind GeoIP Database.
|
||||
|
||||
## FAQ
|
||||
### Bind to privileged ports (<1024) in a container
|
||||
|
||||
You need to add capability `NET_BIND_SERVICE` to the program.
|
||||
|
||||
If you are using docker, this can be done via cli argument [`--cap-add`](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities) or [`cap_add`](https://docs.docker.com/compose/compose-file/compose-file-v3/#cap_add-cap_drop) in the docker compose file.
|
||||
@@ -1,16 +0,0 @@
|
||||
## docker compose
|
||||
|
||||
This is an example how to setup endlessh-go with the Maxmind GeoIP Database using [docker compose](https://docs.docker.com/compose/). The reference of the compose file can be found [here](https://docs.docker.com/compose/compose-file/).
|
||||
|
||||
To start the stack, in the _examples_ folder, run:
|
||||
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
The GeoIP Database will be saved in a mounted volume in: `./geo-data`. And the endlessh-go container will use this database to do the location lookups.
|
||||
|
||||
This example exposes the following ports. Except the SSH port, you should not expose other ports to public without protections (not included in this example) in production.
|
||||
|
||||
- **2222**: The SSH port. You may test endlessh-go by running `ssh -p 2222 localhost`. Your SSH client should hang. View the log of endlessh-go by running `docker logs endlessh`.
|
||||
- **2112**: The Prometheus metrics exported by endlessh-go. Go to [http://localhost:2112/metrics](http://localhost:2112/metrics) in your web browser to view the metrics.
|
||||
@@ -1,35 +0,0 @@
|
||||
services:
|
||||
|
||||
endlessh:
|
||||
container_name: endlessh
|
||||
image: shizunge/endlessh-go:latest
|
||||
restart: unless-stopped
|
||||
#user: root
|
||||
command:
|
||||
- "-logtostderr"
|
||||
- "-v=1"
|
||||
- "-geoip_supplier=max-mind-db"
|
||||
- "-max_mind_db=/geo-data/GeoLite2-City.mmdb"
|
||||
networks:
|
||||
- example_network
|
||||
ports:
|
||||
- 2222:2222 # SSH port
|
||||
- 127.0.0.1:2112:2112 # Prometheus metrics port
|
||||
volumes:
|
||||
- ./geo-data/:/geo-data/:ro # geoip data
|
||||
|
||||
geoipupdate:
|
||||
image: ghcr.io/maxmind/geoipupdate:v5
|
||||
container_name: geoipupdate
|
||||
restart: unless-stopped
|
||||
security_opt: [ "no-new-privileges:true" ]
|
||||
volumes:
|
||||
- ./geo-data/:/usr/share/GeoIP/
|
||||
environment:
|
||||
- GEOIPUPDATE_EDITION_IDS=GeoLite2-City
|
||||
- GEOIPUPDATE_FREQUENCY=72
|
||||
- GEOIPUPDATE_ACCOUNT_ID=xxxxxx
|
||||
- GEOIPUPDATE_LICENSE_KEY=xxxxxx
|
||||
|
||||
networks:
|
||||
example_network:
|
||||
@@ -1,22 +0,0 @@
|
||||
## docker compose
|
||||
|
||||
This is an example how to setup endlessh-go, Prometheus, and Grafana using [docker compose](https://docs.docker.com/compose/). The reference of the compose file can be found [here](https://docs.docker.com/compose/compose-file/).
|
||||
|
||||
*prometheus.yml* is used as a [Prometheus configuration](https://prometheus.io/docs/prometheus/latest/configuration/configuration/).
|
||||
|
||||
*grafana-datasource.yml* is used to provision a data source for Grafana to ease the setup, though Grafana data source can also be setup manually.
|
||||
|
||||
To start the stack, in the *examples* folder, run:
|
||||
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
This example exposes the following ports. Except the SSH port, you should not expose other ports to public without protections (not included in this example) in production.
|
||||
|
||||
* **2222**: The SSH port. You may test endlessh-go by running `ssh -p 2222 localhost`. Your SSH client should hang. View the log of endlessh-go by running `docker logs endlessh`.
|
||||
* **2112**: The Prometheus metrics exported by endlessh-go. Go to [http://localhost:2112/metrics](http://localhost:2112/metrics) in your web browser to view the metrics.
|
||||
* **9090**: Prometheus web interface. Go to [http://localhost:9090](http://localhost:9090) in your web browser for Prometheus. You can check whether the target of endlessh-go is up (Click Status, then Targets).
|
||||
* **3000**: Grafana. Go to [http://localhost:3000](http://localhost:3000) in your web browser for Grafana. Use username *examples* and password *examples* to login.
|
||||
|
||||
In this example, we do not provision a dashboard for Grafana. You need to manually load the endlessh-go dashboard, by either importing it from the Grafana.com using ID [15156](https://grafana.com/grafana/dashboards/15156), or pasting the dashboard JSON text to the text area. See the [Grafana documentation](https://grafana.com/docs/grafana/latest/dashboards/export-import/) about import. Then select *Prometheus* as the data source.
|
||||
@@ -1,59 +0,0 @@
|
||||
services:
|
||||
|
||||
endlessh:
|
||||
container_name: endlessh
|
||||
image: shizunge/endlessh-go:latest
|
||||
restart: always
|
||||
command:
|
||||
- -interval_ms=1000
|
||||
- -logtostderr
|
||||
- -v=1
|
||||
- -enable_prometheus
|
||||
- -geoip_supplier=ip-api
|
||||
networks:
|
||||
- example_network
|
||||
ports:
|
||||
- 2222:2222 # SSH port
|
||||
- 127.0.0.1:2112:2112 # Prometheus metrics port
|
||||
|
||||
prometheus:
|
||||
image: prom/prometheus:latest
|
||||
container_name: prometheus
|
||||
restart: always
|
||||
command:
|
||||
- --config.file=/etc/prometheus/prometheus.yml
|
||||
- --storage.tsdb.path=/prometheus
|
||||
- --storage.tsdb.retention.time=45d
|
||||
- --web.console.libraries=/usr/share/prometheus/console_libraries
|
||||
- --web.console.templates=/usr/share/prometheus/consoles
|
||||
- --web.enable-admin-api
|
||||
networks:
|
||||
- example_network
|
||||
ports:
|
||||
- 127.0.0.1:9090:9090
|
||||
volumes:
|
||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
- prometheus:/prometheus
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
container_name: grafana
|
||||
restart: always
|
||||
networks:
|
||||
- example_network
|
||||
ports:
|
||||
- 127.0.0.1:3000:3000
|
||||
environment:
|
||||
- GF_SECURITY_ADMIN_USER=examples
|
||||
- GF_SECURITY_ADMIN_PASSWORD=examples
|
||||
volumes:
|
||||
- grafana_var:/var/lib/grafana/
|
||||
- ./grafana-datasource.yml:/etc/grafana/provisioning/datasources/prometheus.yml
|
||||
|
||||
networks:
|
||||
example_network:
|
||||
|
||||
|
||||
volumes:
|
||||
prometheus:
|
||||
grafana_var:
|
||||
@@ -1,6 +0,0 @@
|
||||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: Prometheus
|
||||
type: prometheus
|
||||
url: http://prometheus:9090
|
||||
@@ -1,5 +0,0 @@
|
||||
scrape_configs:
|
||||
- job_name: 'endlessh'
|
||||
scrape_interval: 60s
|
||||
static_configs:
|
||||
- targets: ['endlessh:2112']
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2021-2024 Shizun Ge
|
||||
// Copyright (C) 2021 Shizun Ge
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
@@ -14,44 +14,67 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package geoip
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
"github.com/pierrre/geohash"
|
||||
)
|
||||
|
||||
type GeoOption struct {
|
||||
GeoipSupplier string
|
||||
MaxMindDbFileName string
|
||||
type freegeoip struct {
|
||||
Ip string `json:"ip"`
|
||||
CountryCode string `json:"country_code"`
|
||||
CountryName string `json:"country_name"`
|
||||
RegionCode string `json:"region_code"`
|
||||
RegionName string `json:"region_name"`
|
||||
City string `json:"city"`
|
||||
Zipcode string `json:"zipcode"`
|
||||
Latitude float64 `json:"latitude"`
|
||||
Longitude float64 `json:"longitude"`
|
||||
MetroCode int `json:"metro_code"`
|
||||
AreaCode int `json:"area_code"`
|
||||
}
|
||||
|
||||
func composeLocation(country string, region string, city string) string {
|
||||
func geohashAndLocationFromFreegeoip(address string) (string, string, string, error) {
|
||||
var geo freegeoip
|
||||
response, err := http.Get("https://freegeoip.live/json/" + address)
|
||||
if err != nil {
|
||||
return "s000", "Unknown", "Unknown", err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return "s000", "Unknown", "Unknown", err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &geo)
|
||||
if err != nil {
|
||||
return "s000", "Unknown", "Unknown", err
|
||||
}
|
||||
|
||||
var locations []string
|
||||
for _, s := range []string{country, region, city} {
|
||||
for _, s := range []string{geo.CountryName, geo.RegionName, geo.City} {
|
||||
if strings.TrimSpace(s) != "" {
|
||||
locations = append(locations, s)
|
||||
}
|
||||
}
|
||||
location := strings.Join(locations, ", ")
|
||||
if location == "" {
|
||||
return "Unknown"
|
||||
location = "Unknown"
|
||||
}
|
||||
return location
|
||||
}
|
||||
|
||||
func composeCountry(country string) string {
|
||||
country := geo.CountryName
|
||||
if country == "" {
|
||||
return "Unknown"
|
||||
country = "Unknown"
|
||||
}
|
||||
return country
|
||||
gh := geohash.EncodeAuto(geo.Latitude, geo.Longitude)
|
||||
|
||||
return gh, country, location, nil
|
||||
}
|
||||
|
||||
type ipapi struct {
|
||||
@@ -68,9 +91,9 @@ type ipapi struct {
|
||||
Longitude float64 `json:"lon"`
|
||||
}
|
||||
|
||||
func geohashAndLocationFromIpapi(ipAddr string) (string, string, string, error) {
|
||||
func geohashAndLocationFromIpapi(address string) (string, string, string, error) {
|
||||
var geo ipapi
|
||||
response, err := http.Get("http://ip-api.com/json/" + ipAddr)
|
||||
response, err := http.Get("http://ip-api.com/json/" + address)
|
||||
if err != nil {
|
||||
return "s000", "Unknown", "Unknown", err
|
||||
}
|
||||
@@ -87,62 +110,37 @@ func geohashAndLocationFromIpapi(ipAddr string) (string, string, string, error)
|
||||
}
|
||||
|
||||
if geo.Status != "success" {
|
||||
return "s000", "Unknown", "Unknown", fmt.Errorf("failed to query %v via ip-api: status: %v, message: %v", ipAddr, geo.Status, geo.Message)
|
||||
return "s000", "Unknown", "Unknown", fmt.Errorf("failed to query %v via ip-api: status: %v, message: %v", address, geo.Status, geo.Message)
|
||||
}
|
||||
|
||||
gh := geohash.EncodeAuto(geo.Latitude, geo.Longitude)
|
||||
country := composeCountry(geo.CountryName)
|
||||
location := composeLocation(geo.CountryName, geo.RegionName, geo.City)
|
||||
|
||||
return gh, country, location, nil
|
||||
}
|
||||
|
||||
func geohashAndLocationFromMaxMindDb(ipAddr, maxMindDbFileName string) (string, string, string, error) {
|
||||
db, err := geoip2.Open(maxMindDbFileName)
|
||||
if err != nil {
|
||||
return "s000", "Unknown", "Unknown", err
|
||||
}
|
||||
defer db.Close()
|
||||
// If you are using strings that may be invalid, check that ip is not nil
|
||||
ip := net.ParseIP(ipAddr)
|
||||
cityRecord, err := db.City(ip)
|
||||
if err != nil {
|
||||
return "s000", "Unknown", "Unknown", err
|
||||
}
|
||||
countryName := cityRecord.Country.Names["en"]
|
||||
cityName := cityRecord.City.Names["en"]
|
||||
latitude := cityRecord.Location.Latitude
|
||||
longitude := cityRecord.Location.Longitude
|
||||
iso := cityRecord.Country.IsoCode
|
||||
if latitude == 0 && longitude == 0 {
|
||||
// In case of using Country DB, city is not available.
|
||||
loc, ok := countryToLocation[iso]
|
||||
if ok {
|
||||
latitude = loc.Latitude
|
||||
longitude = loc.Longitude
|
||||
} else {
|
||||
if iso != "" {
|
||||
// For debugging, adding the iso to the country name.
|
||||
countryName = countryName + " (" + iso + ")"
|
||||
}
|
||||
var locations []string
|
||||
for _, s := range []string{geo.CountryName, geo.RegionName, geo.City} {
|
||||
if strings.TrimSpace(s) != "" {
|
||||
locations = append(locations, s)
|
||||
}
|
||||
}
|
||||
gh := geohash.EncodeAuto(latitude, longitude)
|
||||
country := composeCountry(countryName)
|
||||
location := composeLocation(countryName, "", cityName)
|
||||
location := strings.Join(locations, ", ")
|
||||
if location == "" {
|
||||
location = "Unknown"
|
||||
}
|
||||
country := geo.CountryName
|
||||
if country == "" {
|
||||
country = "Unknown"
|
||||
}
|
||||
gh := geohash.EncodeAuto(geo.Latitude, geo.Longitude)
|
||||
|
||||
return gh, country, location, nil
|
||||
}
|
||||
|
||||
func GeohashAndLocation(ipAddr string, option GeoOption) (string, string, string, error) {
|
||||
switch option.GeoipSupplier {
|
||||
func geohashAndLocation(address string, geoipSupplier string) (string, string, string, error) {
|
||||
switch geoipSupplier {
|
||||
case "off":
|
||||
return "s000", "Geohash off", "Geohash off", nil
|
||||
case "ip-api":
|
||||
return geohashAndLocationFromIpapi(ipAddr)
|
||||
case "max-mind-db":
|
||||
return geohashAndLocationFromMaxMindDb(ipAddr, option.MaxMindDbFileName)
|
||||
return geohashAndLocationFromIpapi(address)
|
||||
case "freegeoip":
|
||||
return geohashAndLocationFromFreegeoip(address)
|
||||
default:
|
||||
return "s000", "Unknown", "Unknown", fmt.Errorf("unknown geoipSupplier %v.", option.GeoipSupplier)
|
||||
return "s000", "Unknown", "Unknown", fmt.Errorf("unknown geoipSupplier %v.", geoipSupplier)
|
||||
}
|
||||
}
|
||||
264
geoip/country.go
264
geoip/country.go
@@ -1,264 +0,0 @@
|
||||
// Copyright (C) 2023-2024 Shizun Ge
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package geoip
|
||||
|
||||
// Map country's ISO to their capital's latitude and longitude.
|
||||
// Country's ISO see https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
|
||||
type location struct {
|
||||
Latitude float64
|
||||
Longitude float64
|
||||
}
|
||||
|
||||
var countryToLocation = map[string]location{
|
||||
"AD": {42.5, 1.5},
|
||||
"AE": {24.4511, 54.3969},
|
||||
"AF": {34.5328, 69.1658},
|
||||
"AG": {17.1211, -61.8447},
|
||||
"AI": {18.2167, -63.05},
|
||||
"AL": {41.33, 19.82},
|
||||
"AM": {40.1814, 44.5144},
|
||||
"AO": {-8.8383, 13.2344},
|
||||
"AR": {-34.5997, -58.3819},
|
||||
"AS": {-14.274, -170.7046},
|
||||
"AT": {48.2083, 16.3725},
|
||||
"AU": {-35.2931, 149.1269},
|
||||
"AW": {12.5186, -70.0358},
|
||||
"AZ": {40.3667, 49.8352},
|
||||
"BA": {43.8563, 18.4132},
|
||||
"BB": {13.0975, -59.6167},
|
||||
"BD": {23.7289, 90.3944},
|
||||
"BE": {50.8353, 4.3314},
|
||||
"BF": {12.3686, -1.5275},
|
||||
"BG": {42.6979, 23.3217},
|
||||
"BH": {26.225, 50.5775},
|
||||
"BI": {-3.3825, 29.3611},
|
||||
"BJ": {6.402, 2.518},
|
||||
"BL": {17.8958, -62.8508},
|
||||
"BM": {32.2942, -64.7839},
|
||||
"BN": {4.9167, 114.9167},
|
||||
"BO": {-16.4942, -68.1475},
|
||||
"BR": {-15.7939, -47.8828},
|
||||
"BS": {25.0667, -77.3333},
|
||||
"BT": {27.4833, 89.6333},
|
||||
"BW": {-24.6569, 25.9086},
|
||||
"BY": {53.9022, 27.5618},
|
||||
"BZ": {17.25, -88.7675},
|
||||
"CA": {45.4247, -75.695},
|
||||
"CD": {-4.3317, 15.3139},
|
||||
"CF": {4.3732, 18.5628},
|
||||
"CG": {-4.2667, 15.2833},
|
||||
"CH": {46.948, 7.4474},
|
||||
"CI": {5.3364, -4.0267},
|
||||
"CK": {-21.207, -159.771},
|
||||
"CL": {-33.45, -70.6667},
|
||||
"CM": {3.8578, 11.5181},
|
||||
"CN": {39.904, 116.4075},
|
||||
"CO": {4.6126, -74.0705},
|
||||
"CR": {9.9333, -84.0833},
|
||||
"CU": {23.1367, -82.3589},
|
||||
"CV": {14.9177, -23.5092},
|
||||
"CW": {12.108, -68.935},
|
||||
"CX": {-10.4167, 105.7167},
|
||||
"CY": {35.1725, 33.365},
|
||||
"CZ": {50.0833, 14.4167},
|
||||
"DE": {52.5167, 13.3833},
|
||||
"DJ": {11.595, 43.1481},
|
||||
"DK": {55.6805, 12.5615},
|
||||
"DM": {15.3, -61.3833},
|
||||
"DO": {18.4764, -69.8933},
|
||||
"DZ": {36.7764, 3.0586},
|
||||
"EC": {-0.22, -78.5125},
|
||||
"EE": {59.4372, 24.745},
|
||||
"EG": {30.0444, 31.2358},
|
||||
"ER": {15.3333, 38.9167},
|
||||
"ES": {40.4167, -3.7167},
|
||||
"ET": {9.0272, 38.7369},
|
||||
"FI": {60.1756, 24.9342},
|
||||
"FJ": {-18.1333, 178.4333},
|
||||
"FK": {-51.7, -57.85},
|
||||
"FM": {6.9178, 158.185},
|
||||
"FO": {62, -6.7833},
|
||||
"FR": {48.8566, 2.3522},
|
||||
"GA": {0.3901, 9.4544},
|
||||
"GB": {51.5072, -0.1275},
|
||||
"GD": {12.0444, -61.7417},
|
||||
"GE": {41.7225, 44.7925},
|
||||
"GF": {4.933, -52.33},
|
||||
"GH": {5.6037, -0.187},
|
||||
"GI": {36.1324, -5.3781},
|
||||
"GL": {64.175, -51.7333},
|
||||
"GM": {13.4531, -16.5775},
|
||||
"GN": {9.538, -13.6773},
|
||||
"GP": {16.0104, -61.7055},
|
||||
"GQ": {3.7521, 8.7737},
|
||||
"GR": {37.9842, 23.7281},
|
||||
"GS": {-54.2833, -36.5},
|
||||
"GT": {14.6099, -90.5252},
|
||||
"GU": {13.4745, 144.7504},
|
||||
"GW": {11.8592, -15.5956},
|
||||
"GY": {6.7833, -58.1667},
|
||||
"HK": {22.3069, 114.1831},
|
||||
"HN": {14.0942, -87.2067},
|
||||
"HR": {45.8131, 15.9772},
|
||||
"HT": {18.5425, -72.3386},
|
||||
"HU": {47.4983, 19.0408},
|
||||
"ID": {-6.2146, 106.8451},
|
||||
"IE": {53.3497, -6.2603},
|
||||
"IL": {31.7833, 35.2167},
|
||||
"IM": {54.15, -4.4819},
|
||||
"IN": {28.6139, 77.209},
|
||||
"IQ": {33.35, 44.4167},
|
||||
"IR": {35.7, 51.4167},
|
||||
"IS": {64.1475, -21.935},
|
||||
"IT": {41.8931, 12.4828},
|
||||
"JE": {49.1858, -2.11},
|
||||
"JM": {17.9714, -76.7931},
|
||||
"JO": {31.95, 35.9333},
|
||||
"JP": {35.6839, 139.7744},
|
||||
"KE": {-1.2864, 36.8172},
|
||||
"KG": {42.8667, 74.5667},
|
||||
"KH": {11.5696, 104.921},
|
||||
"KI": {1.3382, 173.0176},
|
||||
"KM": {-11.7036, 43.2536},
|
||||
"KN": {17.2983, -62.7342},
|
||||
"KP": {39.03, 125.73},
|
||||
"KR": {37.56, 126.99},
|
||||
"KW": {29.375, 47.98},
|
||||
"KY": {19.2866, -81.3744},
|
||||
"KZ": {51.1333, 71.4333},
|
||||
"LA": {17.9667, 102.6},
|
||||
"LB": {33.8869, 35.5131},
|
||||
"LC": {14.0167, -60.9833},
|
||||
"LI": {47.1397, 9.5219},
|
||||
"LK": {6.9, 79.9164},
|
||||
"LR": {6.3106, -10.8047},
|
||||
"LS": {-29.31, 27.48},
|
||||
"LT": {54.6833, 25.2833},
|
||||
"LU": {49.6106, 6.1328},
|
||||
"LV": {56.9475, 24.1069},
|
||||
"LY": {32.8752, 13.1875},
|
||||
"MA": {26.0928, -10.6089},
|
||||
"MC": {43.7396, 7.4069},
|
||||
"MD": {47.0228, 28.8353},
|
||||
"ME": {42.4397, 19.2661},
|
||||
"MF": {18.0706, -63.0847},
|
||||
"MG": {-18.9386, 47.5214},
|
||||
"MH": {7.0918, 171.3802},
|
||||
"MK": {41.9833, 21.4333},
|
||||
"ML": {12.6458, -7.9922},
|
||||
"MM": {16.795, 96.16},
|
||||
"MN": {47.9214, 106.9055},
|
||||
"MP": {15.2137, 145.7546},
|
||||
"MQ": {14.6104, -61.08},
|
||||
"MR": {18.0858, -15.9785},
|
||||
"MS": {16.7928, -62.2106},
|
||||
"MT": {35.8978, 14.5125},
|
||||
"MU": {-20.1667, 57.5},
|
||||
"MV": {4.175, 73.5083},
|
||||
"MW": {-13.9833, 33.7833},
|
||||
"MX": {19.4333, -99.1333},
|
||||
"MY": {3.1478, 101.6953},
|
||||
"MZ": {-25.9153, 32.5764},
|
||||
"NA": {-22.57, 17.0836},
|
||||
"NC": {-22.2625, 166.4443},
|
||||
"NE": {13.5086, 2.1111},
|
||||
"NF": {-29.0569, 167.9617},
|
||||
"NG": {9.0556, 7.4914},
|
||||
"NI": {12.15, -86.2667},
|
||||
"NL": {52.08, 4.31},
|
||||
"NO": {59.9111, 10.7528},
|
||||
"NP": {27.7167, 85.3667},
|
||||
"NR": {-0.5477, 166.9209},
|
||||
"NU": {-19.056, -169.921},
|
||||
"NZ": {-41.2889, 174.7772},
|
||||
"OM": {23.6139, 58.5922},
|
||||
"PA": {9, -79.5},
|
||||
"PE": {-12.06, -77.0375},
|
||||
"PF": {-17.5334, -149.5667},
|
||||
"PG": {-9.4789, 147.1494},
|
||||
"PH": {14.6, 120.9833},
|
||||
"PK": {33.6989, 73.0369},
|
||||
"PL": {52.23, 21.0111},
|
||||
"PM": {46.7811, -56.1764},
|
||||
"PN": {-25.0667, -130.0833},
|
||||
"PR": {18.4037, -66.0636},
|
||||
"PT": {38.708, -9.139},
|
||||
"PW": {7.5006, 134.6242},
|
||||
"PY": {-25.3, -57.6333},
|
||||
"QA": {25.3, 51.5333},
|
||||
"RE": {-20.8789, 55.4481},
|
||||
"RO": {44.4, 26.0833},
|
||||
"RS": {44.8167, 20.4667},
|
||||
"RU": {55.7558, 37.6178},
|
||||
"RW": {-1.9536, 30.0606},
|
||||
"SA": {24.65, 46.71},
|
||||
"SB": {-9.4333, 159.95},
|
||||
"SC": {-4.6236, 55.4544},
|
||||
"SD": {15.6031, 32.5265},
|
||||
"SE": {59.3294, 18.0686},
|
||||
"SG": {1.3, 103.8},
|
||||
"SH": {-15.9251, -5.7179},
|
||||
"SI": {46.05, 14.5167},
|
||||
"SK": {48.1447, 17.1128},
|
||||
"SL": {8.4833, -13.2331},
|
||||
"SM": {43.932, 12.4484},
|
||||
"SN": {14.7319, -17.4572},
|
||||
"SO": {2.0408, 45.3425},
|
||||
"SR": {5.8667, -55.1667},
|
||||
"SS": {4.85, 31.6},
|
||||
"ST": {0.3333, 6.7333},
|
||||
"SV": {13.6989, -89.1914},
|
||||
"SX": {18.0256, -63.0492},
|
||||
"SY": {33.5131, 36.2919},
|
||||
"SZ": {-26.3208, 31.1617},
|
||||
"TC": {21.4664, -71.136},
|
||||
"TD": {12.11, 15.05},
|
||||
"TG": {6.1319, 1.2228},
|
||||
"TH": {13.75, 100.5167},
|
||||
"TJ": {38.5731, 68.7864},
|
||||
"TL": {-8.5536, 125.5783},
|
||||
"TM": {37.95, 58.3833},
|
||||
"TN": {36.8008, 10.18},
|
||||
"TO": {-21.1347, -175.2083},
|
||||
"TR": {39.93, 32.85},
|
||||
"TT": {10.6667, -61.5167},
|
||||
"TV": {-8.5243, 179.1942},
|
||||
"TZ": {-6.8, 39.2833},
|
||||
"UA": {50.45, 30.5236},
|
||||
"UG": {0.3136, 32.5811},
|
||||
"US": {38.9047, -77.0163},
|
||||
"UY": {-34.8667, -56.1667},
|
||||
"UZ": {41.3, 69.2667},
|
||||
"VA": {41.9033, 12.4534},
|
||||
"VC": {13.1667, -61.2333},
|
||||
"VE": {10.5, -66.9333},
|
||||
"VG": {18.4167, -64.6167},
|
||||
"VI": {18.3419, -64.9332},
|
||||
"VN": {21.0245, 105.8412},
|
||||
"VU": {-17.7333, 168.3167},
|
||||
"WF": {-13.2825, -176.1736},
|
||||
"WS": {-13.8333, -171.8333},
|
||||
"XG": {31.5069, 34.456},
|
||||
"XK": {42.6633, 21.1622},
|
||||
"XR": {78.2167, 15.6333},
|
||||
"XW": {31.7764, 35.2269},
|
||||
"YE": {15.35, 44.2},
|
||||
"YT": {-12.7871, 45.275},
|
||||
"ZA": {-25.7464, 28.1881},
|
||||
"ZM": {-15.4167, 28.2833},
|
||||
"ZW": {-17.8292, 31.0522},
|
||||
}
|
||||
28
go.mod
28
go.mod
@@ -1,25 +1,21 @@
|
||||
module endlessh-go
|
||||
|
||||
go 1.24.2
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/golang/glog v1.2.5
|
||||
github.com/oschwald/geoip2-golang v1.13.0
|
||||
github.com/pierrre/geohash v1.1.3
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/golang/glog v1.0.0
|
||||
github.com/pierrre/geohash v1.0.0
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.13.0 // indirect
|
||||
github.com/pires/go-proxyproto v0.8.1 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
google.golang.org/protobuf v1.36.8 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.1 // indirect
|
||||
github.com/golang/protobuf v1.4.3 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.26.0 // indirect
|
||||
github.com/prometheus/procfs v0.6.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 // indirect
|
||||
google.golang.org/protobuf v1.26.0-rc.1 // indirect
|
||||
)
|
||||
|
||||
206
go.sum
206
go.sum
@@ -1,76 +1,156 @@
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/Codefor/geohash v0.0.0-20140723084247-1b41c28e3a9d h1:iG9B49Q218F/XxXNRM7k/vWf7MKmLIS8AcJV9cGN4nA=
|
||||
github.com/Codefor/geohash v0.0.0-20140723084247-1b41c28e3a9d/go.mod h1:RVnhzAX71far8Kc3TQeA0k/dcaEKUnTDSOyet/JCmGI=
|
||||
github.com/TomiHiltunen/geohash-golang v0.0.0-20150112065804-b3e4e625abfb h1:wumPkzt4zaxO4rHPBrjDK8iZMR41C1qs7njNqlacwQg=
|
||||
github.com/TomiHiltunen/geohash-golang v0.0.0-20150112065804-b3e4e625abfb/go.mod h1:QiYsIBRQEO+Z4Rz7GoI+dsHVneZNONvhczuA+llOZNM=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/broady/gogeohash v0.0.0-20120525094510-7b2c40d64042 h1:iEdmkrNMLXbM7ecffOAtZJQOQUTE4iMonxrb5opUgE4=
|
||||
github.com/broady/gogeohash v0.0.0-20120525094510-7b2c40d64042/go.mod h1:f1L9YvXvlt9JTa+A17trQjSMM6bV40f+tHjB+Pi+Fqk=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fanixk/geohash v0.0.0-20150324002647-c1f9b5fa157a h1:Fyfh/dsHFrC6nkX7H7+nFdTd1wROlX/FxEIWVpKYf1U=
|
||||
github.com/fanixk/geohash v0.0.0-20150324002647-c1f9b5fa157a/go.mod h1:UgNw+PTmmGN8rV7RvjvnBMsoTU8ZXXnaT3hYsDTBlgQ=
|
||||
github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I=
|
||||
github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/mmcloughlin/geohash v0.10.0 h1:9w1HchfDfdeLc+jFEf/04D27KP7E2QmpDu52wPbJWRE=
|
||||
github.com/mmcloughlin/geohash v0.10.0/go.mod h1:oNZxQo5yWJh0eMQEP/8hwQuVx9Z9tjwFUqcTB1SmG0c=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/oschwald/geoip2-golang v1.13.0 h1:Q44/Ldc703pasJeP5V9+aFSZFmBN7DKHbNsSFzQATJI=
|
||||
github.com/oschwald/geoip2-golang v1.13.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo=
|
||||
github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU=
|
||||
github.com/oschwald/maxminddb-golang v1.13.0/go.mod h1:BU0z8BfFVhi1LQaonTwwGQlsHUEu9pWNdMfmq4ztm0o=
|
||||
github.com/pierrre/assert v0.9.0 h1:eIKXsqcLSeLAOXYGHreen2D5CTZ2/N0/cJBNdxuVLdM=
|
||||
github.com/pierrre/assert v0.9.0/go.mod h1:3tthe4L3xYU4biRPVTFo9t2YRO4Dg3+zrLyMS4YanCE=
|
||||
github.com/pierrre/compare v1.4.13 h1:b6gi3OgN1emmD1Ly37m+B/Pbq6tac+w3lNGT5xu4I10=
|
||||
github.com/pierrre/compare v1.4.13/go.mod h1:+ie0ecM2nS32oLck0FWDstwIUSZ0YF4KBIaACOvKhJM=
|
||||
github.com/pierrre/geohash v1.1.3 h1:3u+EbHm2FZQnZCu3E2SaeryIQYtA/eH1YYzDpFm/42c=
|
||||
github.com/pierrre/geohash v1.1.3/go.mod h1:K5UlVmtRxicTXgp6eShrlAOk2Neu9zOe76C/ug7RIZ8=
|
||||
github.com/pierrre/go-libs v0.17.0 h1:bjxd9unioV/YDkUW7obETp2IFct0kO9HePURn81UL8s=
|
||||
github.com/pierrre/go-libs v0.17.0/go.mod h1:920odOqc5mZREW9GFWg056mjQ2prNVRGUZO7HRS2Jlc=
|
||||
github.com/pierrre/pretty v0.14.3 h1:I100hHs1C/MCd3M0D/hIV7J2OXl7amLD0uP2jnB7mRw=
|
||||
github.com/pierrre/pretty v0.14.3/go.mod h1:HTaFDNtT9ELVK5pODLfXRLiEiyIx3MmQUL5UadrR3/0=
|
||||
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
||||
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
|
||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mmcloughlin/geohash v0.9.0 h1:FihR004p/aE1Sju6gcVq5OLDqGcMnpBY+8moBqIsVOs=
|
||||
github.com/mmcloughlin/geohash v0.9.0/go.mod h1:oNZxQo5yWJh0eMQEP/8hwQuVx9Z9tjwFUqcTB1SmG0c=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/pierrre/compare v1.0.2 h1:k4IUsHgh+dbcAOIWCfxVa/7G6STjADH2qmhomv+1quc=
|
||||
github.com/pierrre/compare v1.0.2/go.mod h1:8UvyRHH+9HS8Pczdd2z5x/wvv67krDwVxoOndaIIDVU=
|
||||
github.com/pierrre/geohash v1.0.0 h1:f/zfjdV4rVofTCz1FhP07T+EMQAvcMM2ioGZVt+zqjI=
|
||||
github.com/pierrre/geohash v1.0.0/go.mod h1:atytaeVa21hj5F6kMebHYPf8JbIrGxK2FSzN2ajKXms=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/the42/cartconvert v1.0.0 h1:g8kt6ic2GEhdcZ61ZP9GsWwhosVo5nCnH1n2/oAQXUU=
|
||||
github.com/the42/cartconvert v1.0.0/go.mod h1:fWO/msnJVhHqN1yX6OBoxSyfj7TEj1hHiL8bJSQsK30=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/the42/cartconvert v0.0.0-20131203171324-aae784c392b8 h1:I4DY8wLxJXCrMYzDM6lKCGc3IQwJX0PlTLsd3nQqI3c=
|
||||
github.com/the42/cartconvert v0.0.0-20131203171324-aae784c392b8/go.mod h1:fWO/msnJVhHqN1yX6OBoxSyfj7TEj1hHiL8bJSQsK30=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1 h1:7QnIQpGRHE5RnLKnESfDoxm2dTapTZua5a0kS0A+VXQ=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
240
main.go
240
main.go
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2021-2024 Shizun Ge
|
||||
// Copyright (C) 2021 Shizun Ge
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
@@ -17,124 +17,104 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"endlessh-go/client"
|
||||
"endlessh-go/geoip"
|
||||
"endlessh-go/metrics"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
proxyproto "github.com/pires/go-proxyproto"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
func startSending(maxClients int64, bannerMaxLength int64, records chan<- metrics.RecordEntry) chan *client.Client {
|
||||
clients := make(chan *client.Client, maxClients)
|
||||
var (
|
||||
numCurrentClients int64
|
||||
numTotalClients int64
|
||||
numTotalClientsClosed int64
|
||||
numTotalBytes int64
|
||||
numTotalMilliseconds int64
|
||||
totalClients prometheus.CounterFunc
|
||||
totalClientsClosed prometheus.CounterFunc
|
||||
totalBytes prometheus.CounterFunc
|
||||
totalSeconds prometheus.CounterFunc
|
||||
clientIP *prometheus.CounterVec
|
||||
clientSeconds *prometheus.CounterVec
|
||||
)
|
||||
|
||||
func initPrometheus(connHost, prometheusPort, prometheusEntry string) {
|
||||
totalClients = prometheus.NewCounterFunc(
|
||||
prometheus.CounterOpts{
|
||||
Name: "endlessh_client_open_count_total",
|
||||
Help: "Total number of clients that tried to connect to this host.",
|
||||
}, func() float64 {
|
||||
return float64(numTotalClients)
|
||||
},
|
||||
)
|
||||
totalClientsClosed = prometheus.NewCounterFunc(
|
||||
prometheus.CounterOpts{
|
||||
Name: "endlessh_client_closed_count_total",
|
||||
Help: "Total number of clients that stopped connecting to this host.",
|
||||
}, func() float64 {
|
||||
return float64(numTotalClientsClosed)
|
||||
},
|
||||
)
|
||||
totalBytes = prometheus.NewCounterFunc(
|
||||
prometheus.CounterOpts{
|
||||
Name: "endlessh_sent_bytes_total",
|
||||
Help: "Total bytes sent to clients that tried to connect to this host.",
|
||||
}, func() float64 {
|
||||
return float64(numTotalBytes)
|
||||
},
|
||||
)
|
||||
totalSeconds = prometheus.NewCounterFunc(
|
||||
prometheus.CounterOpts{
|
||||
Name: "endlessh_trapped_time_seconds_total",
|
||||
Help: "Total seconds clients spent on endlessh.",
|
||||
}, func() float64 {
|
||||
return float64(numTotalMilliseconds) / 1000
|
||||
},
|
||||
)
|
||||
clientIP = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "endlessh_client_open_count",
|
||||
Help: "Number of connections of clients.",
|
||||
},
|
||||
[]string{"ip", "geohash", "country", "location"},
|
||||
)
|
||||
clientSeconds = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "endlessh_client_trapped_time_seconds",
|
||||
Help: "Seconds a client spends on endlessh.",
|
||||
},
|
||||
[]string{"ip"},
|
||||
)
|
||||
prometheus.MustRegister(totalClients)
|
||||
prometheus.MustRegister(totalClientsClosed)
|
||||
prometheus.MustRegister(totalBytes)
|
||||
prometheus.MustRegister(totalSeconds)
|
||||
prometheus.MustRegister(clientIP)
|
||||
prometheus.MustRegister(clientSeconds)
|
||||
http.Handle("/"+prometheusEntry, promhttp.Handler())
|
||||
go func() {
|
||||
for {
|
||||
c, more := <-clients
|
||||
if !more {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
bytesSent, err := c.Send(bannerMaxLength)
|
||||
remoteIpAddr := c.RemoteIpAddr()
|
||||
localPort := c.LocalPort()
|
||||
millisecondsSpent := c.MillisecondsSinceLast()
|
||||
if err != nil {
|
||||
c.Close()
|
||||
records <- metrics.RecordEntry{
|
||||
RecordType: metrics.RecordEntryTypeStop,
|
||||
IpAddr: remoteIpAddr,
|
||||
LocalPort: localPort,
|
||||
MillisecondsSpent: millisecondsSpent,
|
||||
}
|
||||
return
|
||||
}
|
||||
clients <- c
|
||||
records <- metrics.RecordEntry{
|
||||
RecordType: metrics.RecordEntryTypeSend,
|
||||
IpAddr: remoteIpAddr,
|
||||
LocalPort: localPort,
|
||||
MillisecondsSpent: millisecondsSpent,
|
||||
BytesSent: bytesSent,
|
||||
}
|
||||
}()
|
||||
}
|
||||
}()
|
||||
return clients
|
||||
}
|
||||
|
||||
func startAccepting(maxClients int64, connType, connHost, connPort string, interval time.Duration, clients chan<- *client.Client, records chan<- metrics.RecordEntry, proxyProtocolEnabled bool, proxyProtocolReadHeaderTimeout int) {
|
||||
go func() {
|
||||
l, err := net.Listen(connType, connHost+":"+connPort)
|
||||
if err != nil {
|
||||
glog.Errorf("Error listening: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Wrap the listener in a proxy protocol listener
|
||||
if proxyProtocolEnabled {
|
||||
l = &proxyproto.Listener{Listener: l, ReadHeaderTimeout: time.Duration(proxyProtocolReadHeaderTimeout) * time.Millisecond}
|
||||
}
|
||||
|
||||
// Close the listener when the application closes.
|
||||
defer l.Close()
|
||||
|
||||
glog.Infof("Listening on %v:%v", connHost, connPort)
|
||||
for {
|
||||
// Listen for an incoming connection.
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
glog.Errorf("Error accepting connection from port %v: %v", connPort, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
c := client.NewClient(conn, interval, maxClients)
|
||||
remoteIpAddr := c.RemoteIpAddr()
|
||||
records <- metrics.RecordEntry{
|
||||
RecordType: metrics.RecordEntryTypeStart,
|
||||
IpAddr: remoteIpAddr,
|
||||
LocalPort: connPort,
|
||||
}
|
||||
clients <- c
|
||||
}
|
||||
glog.Infof("Starting Prometheus on %v:%v, entry point is /%v", connHost, prometheusPort, prometheusEntry)
|
||||
http.ListenAndServe(connHost+":"+prometheusPort, nil)
|
||||
}()
|
||||
}
|
||||
|
||||
type arrayStrings []string
|
||||
|
||||
func (a *arrayStrings) String() string {
|
||||
return strings.Join(*a, ", ")
|
||||
}
|
||||
|
||||
func (a *arrayStrings) Set(value string) error {
|
||||
*a = append(*a, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
const defaultPort = "2222"
|
||||
|
||||
var connPorts arrayStrings
|
||||
|
||||
func main() {
|
||||
intervalMs := flag.Int("interval_ms", 1000, "Message millisecond delay")
|
||||
bannerMaxLength := flag.Int64("line_length", 32, "Maximum banner line length")
|
||||
maxClients := flag.Int64("max_clients", 4096, "Maximum number of clients")
|
||||
connType := flag.String("conn_type", "tcp", "Connection type. Possible values are tcp, tcp4, tcp6")
|
||||
connHost := flag.String("host", "0.0.0.0", "SSH listening address")
|
||||
flag.Var(&connPorts, "port", fmt.Sprintf("SSH listening port. You may provide multiple -port flags to listen to multiple ports. (default %q)", defaultPort))
|
||||
connHost := flag.String("host", "0.0.0.0", "Listening address")
|
||||
connPort := flag.String("port", "2222", "Listening port")
|
||||
prometheusEnabled := flag.Bool("enable_prometheus", false, "Enable prometheus")
|
||||
prometheusHost := flag.String("prometheus_host", "0.0.0.0", "The address for prometheus")
|
||||
prometheusPort := flag.String("prometheus_port", "2112", "The port for prometheus")
|
||||
prometheusEntry := flag.String("prometheus_entry", "metrics", "Entry point for prometheus")
|
||||
prometheusCleanUnseenSeconds := flag.Int("prometheus_clean_unseen_seconds", 0, "Remove series if the IP is not seen for the given time. Set to 0 to disable. (default 0)")
|
||||
geoipSupplier := flag.String("geoip_supplier", "off", "Supplier to obtain Geohash of IPs. Possible values are \"off\", \"ip-api\", \"max-mind-db\"")
|
||||
maxMindDbFileName := flag.String("max_mind_db", "", "Path to the MaxMind DB file.")
|
||||
proxyProtocolEnabled := flag.Bool("proxy_protocol_enabled", false, "Enable PROXY protocol support. This causes the server to expect PROXY protocol headers on incoming connections.")
|
||||
proxyProtocolReadHeaderTimeout := flag.Int("proxy_protocol_read_header_timeout_ms", 200, "Timeout for reading the PROXY protocol header in milliseconds. If the connection does not send a valid PROXY protocol header in this time, the header is ignored.")
|
||||
geoipSupplier := flag.String("geoip_supplier", "off", "Supplier to obtain Geohash of IPs. Possible values are \"off\", \"ip-api\", \"freegeoip\"")
|
||||
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(flag.CommandLine.Output(), "Usage of %v \n", os.Args[0])
|
||||
@@ -143,38 +123,56 @@ func main() {
|
||||
flag.Parse()
|
||||
|
||||
if *prometheusEnabled {
|
||||
if *connType == "tcp6" && *prometheusHost == "0.0.0.0" {
|
||||
*prometheusHost = "[::]"
|
||||
}
|
||||
metrics.InitPrometheus(*prometheusHost, *prometheusPort, *prometheusEntry)
|
||||
initPrometheus(*connHost, *prometheusPort, *prometheusEntry)
|
||||
}
|
||||
|
||||
records := metrics.StartRecording(*maxClients, *prometheusEnabled, *prometheusCleanUnseenSeconds,
|
||||
geoip.GeoOption{
|
||||
GeoipSupplier: *geoipSupplier,
|
||||
MaxMindDbFileName: *maxMindDbFileName,
|
||||
})
|
||||
clients := startSending(*maxClients, *bannerMaxLength, records)
|
||||
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
interval := time.Duration(*intervalMs) * time.Millisecond
|
||||
// Listen for incoming connections.
|
||||
if *connType == "tcp6" && *connHost == "0.0.0.0" {
|
||||
*connHost = "[::]"
|
||||
}
|
||||
if len(connPorts) == 0 {
|
||||
connPorts = append(connPorts, defaultPort)
|
||||
l, err := net.Listen(*connType, *connHost+":"+*connPort)
|
||||
if err != nil {
|
||||
glog.Errorf("Error listening: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
for _, connPort := range connPorts {
|
||||
startAccepting(*maxClients, *connType, *connHost, connPort, interval, clients, records, *proxyProtocolEnabled, *proxyProtocolReadHeaderTimeout)
|
||||
}
|
||||
for {
|
||||
if *prometheusCleanUnseenSeconds <= 0 {
|
||||
time.Sleep(time.Duration(1<<63 - 1))
|
||||
} else {
|
||||
time.Sleep(time.Second * time.Duration(60))
|
||||
records <- metrics.RecordEntry{
|
||||
RecordType: metrics.RecordEntryTypeClean,
|
||||
// Close the listener when the application closes.
|
||||
defer l.Close()
|
||||
glog.Infof("Listening on %v:%v", *connHost, *connPort)
|
||||
|
||||
clients := make(chan *client, *maxClients)
|
||||
go func() {
|
||||
for {
|
||||
c, more := <-clients
|
||||
if !more {
|
||||
return
|
||||
}
|
||||
if time.Now().Before(c.next) {
|
||||
time.Sleep(c.next.Sub(time.Now()))
|
||||
}
|
||||
err := c.Send(*bannerMaxLength)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
continue
|
||||
}
|
||||
go func() { clients <- c }()
|
||||
}
|
||||
}()
|
||||
listener := func() {
|
||||
for {
|
||||
// Listen for an incoming connection.
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
glog.Errorf("Error accepting: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// Handle connections in a new goroutine.
|
||||
for numCurrentClients >= *maxClients {
|
||||
time.Sleep(interval)
|
||||
}
|
||||
clients <- NewClient(conn, interval, *maxClients, *geoipSupplier, *prometheusEnabled)
|
||||
}
|
||||
}
|
||||
listener()
|
||||
}
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
// Copyright (C) 2024 Shizun Ge
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"endlessh-go/geoip"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
"strings"
|
||||
"net"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
var (
|
||||
pq *UpdatablePriorityQueue
|
||||
totalClients *prometheus.CounterVec
|
||||
totalClientsClosed *prometheus.CounterVec
|
||||
totalBytes *prometheus.CounterVec
|
||||
totalSeconds *prometheus.CounterVec
|
||||
clientIP *prometheus.CounterVec
|
||||
clientSeconds *prometheus.CounterVec
|
||||
)
|
||||
|
||||
func InitPrometheus(prometheusHost, prometheusPort, prometheusEntry string) {
|
||||
pq = NewUpdatablePriorityQueue()
|
||||
totalClients = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "endlessh_client_open_count_total",
|
||||
Help: "Total number of clients that tried to connect to this host.",
|
||||
}, []string{"local_port"},
|
||||
)
|
||||
totalClientsClosed = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "endlessh_client_closed_count_total",
|
||||
Help: "Total number of clients that stopped connecting to this host.",
|
||||
}, []string{"local_port"},
|
||||
)
|
||||
totalBytes = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "endlessh_sent_bytes_total",
|
||||
Help: "Total bytes sent to clients that tried to connect to this host.",
|
||||
}, []string{"local_port"},
|
||||
)
|
||||
totalSeconds = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "endlessh_trapped_time_seconds_total",
|
||||
Help: "Total seconds clients spent on endlessh.",
|
||||
}, []string{"local_port"},
|
||||
)
|
||||
clientIP = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "endlessh_client_open_count",
|
||||
Help: "Number of connections of clients.",
|
||||
},
|
||||
[]string{"ip", "local_port", "geohash", "country", "location"},
|
||||
)
|
||||
clientSeconds = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "endlessh_client_trapped_time_seconds",
|
||||
Help: "Seconds a client spends on endlessh.",
|
||||
},
|
||||
[]string{"ip", "local_port"},
|
||||
)
|
||||
promReg := prometheus.NewRegistry()
|
||||
promReg.MustRegister(totalClients)
|
||||
promReg.MustRegister(totalClientsClosed)
|
||||
promReg.MustRegister(totalBytes)
|
||||
promReg.MustRegister(totalSeconds)
|
||||
promReg.MustRegister(clientIP)
|
||||
promReg.MustRegister(clientSeconds)
|
||||
handler := promhttp.HandlerFor(promReg, promhttp.HandlerOpts{EnableOpenMetrics: true})
|
||||
http.Handle("/"+prometheusEntry, handler)
|
||||
go func() {
|
||||
|
||||
if strings.HasPrefix(prometheusHost, "unix:") {
|
||||
socketPath := prometheusHost[5:] // trim the "unix:" prefix
|
||||
glog.Infof("Starting Prometheus on Unix socket %v, entry point is /%v", socketPath, prometheusEntry)
|
||||
serveOnUnixSocket(socketPath)
|
||||
} else {
|
||||
ipPort := prometheusHost+":"+prometheusPort
|
||||
glog.Infof("Starting Prometheus on IP port %v, entry point is /%v", ipPort, prometheusEntry)
|
||||
serveOnIpPort(ipPort)
|
||||
}
|
||||
|
||||
}()
|
||||
}
|
||||
|
||||
func serveOnUnixSocket(socketPath string) {
|
||||
_ = os.Remove(socketPath) // allow failure
|
||||
unixListener, err := net.Listen("unix", socketPath)
|
||||
if err != nil {
|
||||
glog.Errorf("Error starting Prometheus on socket %v: %v", socketPath, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := http.Serve(unixListener, nil); err != nil {
|
||||
glog.Errorf("Error starting Prometheus at socket %v: %v", socketPath, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func serveOnIpPort(ipPort string) {
|
||||
if err := http.ListenAndServe(ipPort, nil); err != nil {
|
||||
glog.Errorf("Error starting Prometheus at IP port %v: %v", ipPort, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
RecordEntryTypeStart = iota
|
||||
RecordEntryTypeSend = iota
|
||||
RecordEntryTypeStop = iota
|
||||
RecordEntryTypeClean = iota
|
||||
)
|
||||
|
||||
type RecordEntry struct {
|
||||
RecordType int
|
||||
IpAddr string
|
||||
LocalPort string
|
||||
MillisecondsSpent int64
|
||||
BytesSent int
|
||||
}
|
||||
|
||||
func StartRecording(maxClients int64, prometheusEnabled bool, prometheusCleanUnseenSeconds int, geoOption geoip.GeoOption) chan RecordEntry {
|
||||
records := make(chan RecordEntry, maxClients)
|
||||
go func() {
|
||||
for {
|
||||
r, more := <-records
|
||||
if !more {
|
||||
return
|
||||
}
|
||||
if !prometheusEnabled {
|
||||
continue
|
||||
}
|
||||
switch r.RecordType {
|
||||
case RecordEntryTypeStart:
|
||||
geohash, country, location, err := geoip.GeohashAndLocation(r.IpAddr, geoOption)
|
||||
if err != nil {
|
||||
glog.Warningf("Failed to obtain the geohash of %v: %v.", r.IpAddr, err)
|
||||
}
|
||||
clientIP.With(prometheus.Labels{
|
||||
"ip": r.IpAddr,
|
||||
"local_port": r.LocalPort,
|
||||
"geohash": geohash,
|
||||
"country": country,
|
||||
"location": location}).Inc()
|
||||
totalClients.With(prometheus.Labels{"local_port": r.LocalPort}).Inc()
|
||||
pq.Update(r.IpAddr, time.Now())
|
||||
case RecordEntryTypeSend:
|
||||
secondsSpent := float64(r.MillisecondsSpent) / 1000
|
||||
clientSeconds.With(prometheus.Labels{
|
||||
"ip": r.IpAddr,
|
||||
"local_port": r.LocalPort}).Add(secondsSpent)
|
||||
totalSeconds.With(prometheus.Labels{"local_port": r.LocalPort}).Add(secondsSpent)
|
||||
totalBytes.With(prometheus.Labels{"local_port": r.LocalPort}).Add(float64(r.BytesSent))
|
||||
pq.Update(r.IpAddr, time.Now())
|
||||
case RecordEntryTypeStop:
|
||||
secondsSpent := float64(r.MillisecondsSpent) / 1000
|
||||
clientSeconds.With(prometheus.Labels{
|
||||
"ip": r.IpAddr,
|
||||
"local_port": r.LocalPort}).Add(secondsSpent)
|
||||
totalSeconds.With(prometheus.Labels{"local_port": r.LocalPort}).Add(secondsSpent)
|
||||
totalClientsClosed.With(prometheus.Labels{"local_port": r.LocalPort}).Inc()
|
||||
pq.Update(r.IpAddr, time.Now())
|
||||
case RecordEntryTypeClean:
|
||||
top := pq.Peek()
|
||||
deadline := time.Now().Add(-time.Second * time.Duration(prometheusCleanUnseenSeconds))
|
||||
for top != nil && top.Value.Before(deadline) {
|
||||
clientIP.DeletePartialMatch(prometheus.Labels{"ip": top.Key})
|
||||
clientSeconds.DeletePartialMatch(prometheus.Labels{"ip": top.Key})
|
||||
pq.Pop()
|
||||
top = pq.Peek()
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return records
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Pair represents a key-value pair with a timestamp
|
||||
type Pair struct {
|
||||
Key string
|
||||
Value time.Time
|
||||
HeapIdx int // Index in the heap for efficient updates
|
||||
}
|
||||
|
||||
// PriorityQueue is a min-heap implementation for Pairs
|
||||
type PriorityQueue []*Pair
|
||||
|
||||
// Len returns the length of the priority queue
|
||||
func (pq PriorityQueue) Len() int { return len(pq) }
|
||||
|
||||
// Less compares two pairs based on their values (timestamps)
|
||||
func (pq PriorityQueue) Less(i, j int) bool {
|
||||
return pq[i].Value.Before(pq[j].Value)
|
||||
}
|
||||
|
||||
// Swap swaps two pairs in the priority queue
|
||||
func (pq PriorityQueue) Swap(i, j int) {
|
||||
pq[i], pq[j] = pq[j], pq[i]
|
||||
pq[i].HeapIdx = i
|
||||
pq[j].HeapIdx = j
|
||||
}
|
||||
|
||||
// Push adds a pair to the priority queue
|
||||
func (pq *PriorityQueue) Push(x interface{}) {
|
||||
pair := x.(*Pair)
|
||||
pair.HeapIdx = len(*pq)
|
||||
*pq = append(*pq, pair)
|
||||
}
|
||||
|
||||
// Pop removes the pair with the minimum value (timestamp) from the priority queue
|
||||
func (pq *PriorityQueue) Pop() interface{} {
|
||||
old := *pq
|
||||
n := len(old)
|
||||
pair := old[n-1]
|
||||
pair.HeapIdx = -1 // for safety
|
||||
*pq = old[0 : n-1]
|
||||
return pair
|
||||
}
|
||||
|
||||
// UpdatablePriorityQueue represents the data structure with the priority queue
|
||||
type UpdatablePriorityQueue struct {
|
||||
pq PriorityQueue
|
||||
keyMap map[string]*Pair
|
||||
}
|
||||
|
||||
// NewUpdatablePriorityQueue initializes a new UpdatablePriorityQueue
|
||||
func NewUpdatablePriorityQueue() *UpdatablePriorityQueue {
|
||||
return &UpdatablePriorityQueue{
|
||||
pq: make(PriorityQueue, 0),
|
||||
keyMap: make(map[string]*Pair),
|
||||
}
|
||||
}
|
||||
|
||||
// Update adds or updates a key-value pair in the data structure
|
||||
func (ds *UpdatablePriorityQueue) Update(key string, value time.Time) {
|
||||
if pair, ok := ds.keyMap[key]; ok {
|
||||
// Key exists, update the time
|
||||
pair.Value = value
|
||||
heap.Fix(&ds.pq, pair.HeapIdx)
|
||||
} else {
|
||||
// Key does not exist, create a new entry
|
||||
pair := &Pair{Key: key, Value: value}
|
||||
heap.Push(&ds.pq, pair)
|
||||
ds.keyMap[key] = pair
|
||||
}
|
||||
}
|
||||
|
||||
// Peek returns the entry with the minimal time
|
||||
func (ds *UpdatablePriorityQueue) Peek() *Pair {
|
||||
if ds.pq.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
return ds.pq[0]
|
||||
}
|
||||
|
||||
// Pop removes the entry with the minimal time
|
||||
func (ds *UpdatablePriorityQueue) Pop() *Pair {
|
||||
if ds.pq.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
pair := heap.Pop(&ds.pq).(*Pair)
|
||||
delete(ds.keyMap, pair.Key)
|
||||
return pair
|
||||
}
|
||||
Reference in New Issue
Block a user