mirror of
https://github.com/shizunge/endlessh-go.git
synced 2026-06-23 04:10:08 +00:00
Compare commits
69 Commits
2025.0914.0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d27ee12f8 | |||
| 637aa0fcc4 | |||
| bb02fb0180 | |||
| 9100b14abf | |||
| b7fdabf81e | |||
| 9b9c174b84 | |||
| bc43eca7de | |||
| a059852463 | |||
| 8b180ee911 | |||
| 2fd6c7a407 | |||
| eddce4b85a | |||
| 591ad1be81 | |||
| 2c421593a3 | |||
| 15e848bcd7 | |||
| d2fc221cb2 | |||
| 816b060a1c | |||
| 9db64900e0 | |||
| 8631efdc1c | |||
| 4d9fe71b39 | |||
| f0045d32fc | |||
| 3da6dd03b3 | |||
| cc3e0539ff | |||
| 5b3b5973c3 | |||
| 4bc342b136 | |||
| 66244e80a7 | |||
| 16a32d06cd | |||
| 1e5f0a29ea | |||
| 4344bfbde4 | |||
| f6e922f36f | |||
| cf6fbb5f41 | |||
| ac07a37754 | |||
| b6b3fe2678 | |||
| 3832d95f14 | |||
| 33129ba155 | |||
| 7d5eed824d | |||
| 43859e73f2 | |||
| 005145242c | |||
| a677b35f05 | |||
| 760b9cfb6b | |||
| 7bfa69cba0 | |||
| 35b2cb887c | |||
| b6200c5030 | |||
| ca1d51b594 | |||
| 9292291cbd | |||
| 62a54396cd | |||
| ebbcd539fc | |||
| ecdfc514d0 | |||
| b088bdbd15 | |||
| 5a88627724 | |||
| 3fd26b15db | |||
| df8978a8f1 | |||
| 1133f6ad28 | |||
| 869e25828b | |||
| eea67a33b1 | |||
| cfb21bf1c5 | |||
| 3d6365caf0 | |||
| 5fa58afc0b | |||
| ac6cda3e73 | |||
| f48f6d748f | |||
| 0d5395b4ee | |||
| 1585df7876 | |||
| 07df006502 | |||
| 829fe080a8 | |||
| 63b07abf45 | |||
| b4077f7002 | |||
| f64550176c | |||
| 3c60cdf2ed | |||
| 5e3b96cc65 | |||
| f6e2189c79 |
@@ -12,9 +12,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v7
|
||||
- name: Update Docker Hub description
|
||||
uses: peter-evans/dockerhub-description@v4
|
||||
uses: peter-evans/dockerhub-description@v5
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
@@ -15,19 +15,37 @@ env:
|
||||
PLATFORMS: "linux/amd64,linux/arm64,linux/arm/v7"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.1'
|
||||
cache: true
|
||||
|
||||
- name: Install dependencies
|
||||
run: go mod download
|
||||
|
||||
- name: Run Tests
|
||||
run: go test -v ./...
|
||||
|
||||
build_container_image:
|
||||
name: Build Docker image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v7
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.11.1
|
||||
uses: docker/setup-buildx-action@v4.1.0
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/${{ github.repository }}-development
|
||||
@@ -37,7 +55,7 @@ jobs:
|
||||
type=ref,event=branch
|
||||
type=edge,branch=main
|
||||
- name: Build
|
||||
uses: docker/build-push-action@v6.18.0
|
||||
uses: docker/build-push-action@v7.2.0
|
||||
with:
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
push: false
|
||||
|
||||
@@ -16,31 +16,48 @@ env:
|
||||
PLATFORMS: "linux/amd64,linux/arm64,linux/arm/v7"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.1'
|
||||
cache: true
|
||||
|
||||
- name: Install dependencies
|
||||
run: go mod download
|
||||
|
||||
- name: Run Tests
|
||||
run: go test -v ./...
|
||||
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
|
||||
uses: actions/checkout@v7
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.11.1
|
||||
uses: docker/setup-buildx-action@v4.1.0
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3.5.0
|
||||
uses: docker/login-action@v4.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3.5.0
|
||||
uses: docker/login-action@v4.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ github.token }}
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: |
|
||||
${{ github.repository }}-development
|
||||
@@ -51,7 +68,7 @@ jobs:
|
||||
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
|
||||
uses: docker/build-push-action@v7.2.0
|
||||
with:
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
push: true
|
||||
|
||||
@@ -13,25 +13,25 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v7
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.11.1
|
||||
uses: docker/setup-buildx-action@v4.1.0
|
||||
- name: Login to docker hub
|
||||
uses: docker/login-action@v3.5.0
|
||||
uses: docker/login-action@v4.2.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3.5.0
|
||||
uses: docker/login-action@v4.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ github.token }}
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: |
|
||||
${{ github.repository }}
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
type=ref,event=branch
|
||||
type=ref,event=tag
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6.18.0
|
||||
uses: docker/build-push-action@v7.2.0
|
||||
with:
|
||||
platforms: ${{ env.PLATFORMS }}
|
||||
push: true
|
||||
|
||||
+22
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2021-2024 Shizun Ge
|
||||
// Copyright (C) 2021-2026 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
|
||||
@@ -54,6 +54,23 @@ func NewClient(conn net.Conn, interval time.Duration, maxClients int64) *Client
|
||||
time.Sleep(interval)
|
||||
}
|
||||
atomic.AddInt64(&numCurrentClients, 1)
|
||||
// Enable TCP keepalive to detect dead peers at the kernel level.
|
||||
// This is a safety net for long intervals where the write deadline
|
||||
// alone would be too slow to detect dead connections. For example,
|
||||
// with interval=10min, without keepalive a ghost connection would
|
||||
// go undetected for 10+ minutes.
|
||||
// Keepalive cannot detect peers that are alive at the TCP level
|
||||
// (kernel still responds to probes) but stalled at the application
|
||||
// level — the write deadline in Send() covers that case.
|
||||
// Detection time: ~idle + 9 probes (e.g. 10s + 9×10s ≈ 100s on Linux).
|
||||
if tcpConn, ok := conn.(*net.TCPConn); ok {
|
||||
tcpConn.SetKeepAlive(true)
|
||||
keepalive := interval
|
||||
if keepalive > 30*time.Second {
|
||||
keepalive = 30 * time.Second
|
||||
}
|
||||
tcpConn.SetKeepAlivePeriod(keepalive)
|
||||
}
|
||||
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{
|
||||
@@ -80,6 +97,10 @@ func (c *Client) Send(bannerMaxLength int64) (int, error) {
|
||||
}
|
||||
c.next = time.Now().Add(c.interval)
|
||||
length := rand.Int63n(bannerMaxLength)
|
||||
// Set a write deadline to detect dead connections where the kernel
|
||||
// buffers data but the remote peer is gone. Without this, Write()
|
||||
// can succeed indefinitely on dead connections, causing goroutine leaks.
|
||||
c.conn.SetWriteDeadline(time.Now().Add(c.interval))
|
||||
bytesSent, err := c.conn.Write(randStringBytes(length))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
||||
+73
-42
@@ -21,7 +21,7 @@
|
||||
"type": "grafana",
|
||||
"id": "grafana",
|
||||
"name": "Grafana",
|
||||
"version": "10.3.0-64399"
|
||||
"version": "12.3.0-18356121373.patch9"
|
||||
},
|
||||
{
|
||||
"type": "panel",
|
||||
@@ -76,10 +76,9 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": "Dashboard for endlessh (Fix current connections)",
|
||||
"description": "Dashboard for endlessh (Add ports selection)",
|
||||
"editable": false,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"gnetId": 15156,
|
||||
"graphTooltip": 0,
|
||||
"id": null,
|
||||
"links": [
|
||||
@@ -108,7 +107,6 @@
|
||||
"url": "https://grafana.com/grafana/dashboards/15156"
|
||||
}
|
||||
],
|
||||
"liveNow": false,
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
@@ -126,7 +124,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -147,6 +145,7 @@
|
||||
"graphMode": "none",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
@@ -154,11 +153,12 @@
|
||||
"fields": "/^Total number connections that endlessh trapped$/",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"text": {},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "10.3.0-64399",
|
||||
"pluginVersion": "12.3.0-18356121373.patch9",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
@@ -166,7 +166,7 @@
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "(endlessh_client_open_count{instance=~\"$host\",job=~\"$job\"} - endlessh_client_open_count{instance=~\"$host\",job=~\"$job\"} offset $__interval) > 0 or (endlessh_client_open_count{instance=~\"$host\",job=~\"$job\"}!=0 unless endlessh_client_open_count{instance=~\"$host\",job=~\"$job\"} offset $__interval)",
|
||||
"expr": "(endlessh_client_open_count{instance=~\"$host\", local_port=~\"$port\",job=~\"$job\"} - endlessh_client_open_count{instance=~\"$host\",local_port=~\"$port\",job=~\"$job\"} offset $__interval) > 0 or (endlessh_client_open_count{instance=~\"$host\",local_port=~\"$port\",job=~\"$job\"}!=0 unless endlessh_client_open_count{instance=~\"$host\",local_port=~\"$port\",job=~\"$job\"} offset $__interval)",
|
||||
"format": "table",
|
||||
"instant": false,
|
||||
"legendFormat": "Seen {{ip}}",
|
||||
@@ -179,7 +179,7 @@
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"expr": "(endlessh_client_trapped_time_seconds{instance=~\"$host\",job=~\"$job\"} - endlessh_client_trapped_time_seconds{instance=~\"$host\",job=~\"$job\"} offset $__interval) > 0 or (endlessh_client_trapped_time_seconds{instance=~\"$host\",job=~\"$job\"}!=0 unless endlessh_client_trapped_time_seconds{instance=~\"$host\",job=~\"$job\"} offset $__interval)",
|
||||
"expr": "(endlessh_client_trapped_time_seconds{instance=~\"$host\",local_port=~\"$port\",job=~\"$job\"} - endlessh_client_trapped_time_seconds{instance=~\"$host\",local_port=~\"$port\",job=~\"$job\"} offset $__interval) > 0 or (endlessh_client_trapped_time_seconds{instance=~\"$host\",local_port=~\"$port\",job=~\"$job\"}!=0 unless endlessh_client_trapped_time_seconds{instance=~\"$host\",local_port=~\"$port\",job=~\"$job\"} offset $__interval)",
|
||||
"format": "table",
|
||||
"hide": false,
|
||||
"instant": false,
|
||||
@@ -271,7 +271,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -291,6 +291,7 @@
|
||||
"graphMode": "none",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
@@ -298,11 +299,12 @@
|
||||
"fields": "/^Time spent on endlessh$/",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"text": {},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "10.3.0-64399",
|
||||
"pluginVersion": "12.3.0-18356121373.patch9",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
@@ -397,7 +399,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -417,6 +419,7 @@
|
||||
"graphMode": "none",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
@@ -424,22 +427,25 @@
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"text": {},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "10.3.0-64399",
|
||||
"pluginVersion": "12.3.0-18356121373.patch9",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": true,
|
||||
"expr": "sum(increase(endlessh_sent_bytes_total{instance=~\"$host\",job=~\"$job\"}[$__range]))",
|
||||
"expr": "sum(increase(endlessh_sent_bytes_total{instance=~\"$host\",local_port=~\"$port\",job=~\"$job\"}[$__range]))",
|
||||
"hide": false,
|
||||
"interval": "",
|
||||
"legendFormat": "Bytes sent by endlessh",
|
||||
"range": true,
|
||||
"refId": "sent_bytes"
|
||||
}
|
||||
],
|
||||
@@ -462,7 +468,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -481,6 +487,7 @@
|
||||
"graphMode": "none",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
@@ -488,11 +495,12 @@
|
||||
"fields": "/^Unique IPs connected$/",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"text": {},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "10.3.0-64399",
|
||||
"pluginVersion": "12.3.0-18356121373.patch9",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
@@ -576,7 +584,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -595,6 +603,7 @@
|
||||
"graphMode": "none",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
@@ -602,11 +611,12 @@
|
||||
"fields": "/^Client IP of the latest connection$/",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"text": {},
|
||||
"textMode": "value",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "10.3.0-64399",
|
||||
"pluginVersion": "12.3.0-18356121373.patch9",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
@@ -712,7 +722,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"color": "#EAB839",
|
||||
@@ -740,6 +750,7 @@
|
||||
"graphMode": "none",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
@@ -747,19 +758,21 @@
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"text": {},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "10.3.0-64399",
|
||||
"pluginVersion": "12.3.0-18356121373.patch9",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": true,
|
||||
"expr": "sum((endlessh_client_open_count_total{instance=~\"$host\",job=~\"$job\"}) - (endlessh_client_closed_count_total{instance=~\"$host\",job=~\"$job\"} or endlessh_client_open_count_total{instance=~\"$host\",job=~\"$job\"} * 0))",
|
||||
"expr": "sum((endlessh_client_open_count_total{instance=~\"$host\",local_port=~\"$port\",job=~\"$job\"}) - (endlessh_client_closed_count_total{instance=~\"$host\",local_port=~\"$port\",job=~\"$job\"} or endlessh_client_open_count_total{instance=~\"$host\",local_port=~\"$port\",job=~\"$job\"} * 0))",
|
||||
"instant": false,
|
||||
"interval": "",
|
||||
"legendFormat": "Open Connections",
|
||||
@@ -786,6 +799,7 @@
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"barWidthFactor": 0.6,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
@@ -802,6 +816,7 @@
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"showValues": false,
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
@@ -812,13 +827,13 @@
|
||||
}
|
||||
},
|
||||
"mappings": [],
|
||||
"min": -0.01,
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -840,10 +855,12 @@
|
||||
"showLegend": false
|
||||
},
|
||||
"tooltip": {
|
||||
"hideZeros": false,
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "12.3.0-18356121373.patch9",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
@@ -904,11 +921,14 @@
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"sort": "desc",
|
||||
"tooltip": {
|
||||
"hideZeros": false,
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "12.3.0-18356121373.patch9",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
@@ -1015,7 +1035,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"color": "#96D98D",
|
||||
"value": null
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1029,7 +1049,6 @@
|
||||
"y": 7
|
||||
},
|
||||
"id": 48,
|
||||
"links": [],
|
||||
"options": {
|
||||
"basemap": {
|
||||
"config": {},
|
||||
@@ -1051,7 +1070,7 @@
|
||||
"field": "Connections",
|
||||
"fixed": "dark-green"
|
||||
},
|
||||
"fillOpacity": 0.4,
|
||||
"fillOpacity": 0.8,
|
||||
"shape": "circle",
|
||||
"showLegend": false,
|
||||
"size": {
|
||||
@@ -1094,10 +1113,11 @@
|
||||
"id": "zero",
|
||||
"lat": 0,
|
||||
"lon": 0,
|
||||
"noRepeat": false,
|
||||
"zoom": 1
|
||||
}
|
||||
},
|
||||
"pluginVersion": "10.3.0-64399",
|
||||
"pluginVersion": "12.3.0-18356121373.patch9",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
@@ -1194,6 +1214,9 @@
|
||||
"type": "auto"
|
||||
},
|
||||
"filterable": true,
|
||||
"footer": {
|
||||
"reducers": []
|
||||
},
|
||||
"inspect": false,
|
||||
"minWidth": 50
|
||||
},
|
||||
@@ -1204,7 +1227,7 @@
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1271,14 +1294,6 @@
|
||||
"id": 49,
|
||||
"options": {
|
||||
"cellHeight": "sm",
|
||||
"footer": {
|
||||
"countRows": false,
|
||||
"fields": "",
|
||||
"reducer": [
|
||||
"sum"
|
||||
],
|
||||
"show": false
|
||||
},
|
||||
"frameIndex": 0,
|
||||
"showHeader": true,
|
||||
"sortBy": [
|
||||
@@ -1288,7 +1303,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"pluginVersion": "10.3.0-64399",
|
||||
"pluginVersion": "12.3.0-18356121373.patch9",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
@@ -1461,7 +1476,7 @@
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 39,
|
||||
"schemaVersion": 42,
|
||||
"tags": [
|
||||
"prometheus"
|
||||
],
|
||||
@@ -1475,7 +1490,6 @@
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"definition": "label_values(endlessh_client_open_count_total, job)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": "Job",
|
||||
"multi": true,
|
||||
@@ -1487,7 +1501,6 @@
|
||||
},
|
||||
"refresh": 2,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 1,
|
||||
"type": "query"
|
||||
},
|
||||
@@ -1499,7 +1512,6 @@
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"definition": "label_values(endlessh_client_open_count_total{job=~\"$job\"}, instance)",
|
||||
"hide": 0,
|
||||
"includeAll": true,
|
||||
"label": "Host",
|
||||
"multi": true,
|
||||
@@ -1511,7 +1523,26 @@
|
||||
},
|
||||
"refresh": 2,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 1,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"allValue": ".*",
|
||||
"current": {},
|
||||
"definition": "label_values(endlessh_client_open_count_total{job=~\"$job\", instance=~\"$host\"},local_port)",
|
||||
"description": "",
|
||||
"includeAll": true,
|
||||
"label": "Port",
|
||||
"multi": true,
|
||||
"name": "port",
|
||||
"options": [],
|
||||
"query": {
|
||||
"qryType": 1,
|
||||
"query": "label_values(endlessh_client_open_count_total{job=~\"$job\", instance=~\"$host\"},local_port)",
|
||||
"refId": "PrometheusVariableQueryEditor-VariableQuery"
|
||||
},
|
||||
"refresh": 2,
|
||||
"regex": "",
|
||||
"sort": 1,
|
||||
"type": "query"
|
||||
}
|
||||
@@ -1525,6 +1556,6 @@
|
||||
"timezone": "",
|
||||
"title": "Endlessh",
|
||||
"uid": "ATIxYkO7k",
|
||||
"version": 12,
|
||||
"version": 13,
|
||||
"weekStart": ""
|
||||
}
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
waitForListenTimeout = 10 * time.Second
|
||||
waitForConnectTimeout = 3 * time.Second
|
||||
pollInterval = 50 * time.Millisecond
|
||||
)
|
||||
|
||||
func waitForLogMatch(stderr *bytes.Buffer, pattern string, timeout time.Duration) bool {
|
||||
re := regexp.MustCompile(pattern)
|
||||
deadline := time.Now().Add(timeout)
|
||||
for {
|
||||
if time.Now().After(deadline) {
|
||||
return false
|
||||
}
|
||||
if re.MatchString(stderr.String()) {
|
||||
return true
|
||||
}
|
||||
time.Sleep(pollInterval)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndlesshIntegration_MultiplePorts(t *testing.T) {
|
||||
const nPorts = 3
|
||||
ports := make([]int, nPorts)
|
||||
for i := 0; i < nPorts; i++ {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get free port: %v", err)
|
||||
}
|
||||
ports[i] = ln.Addr().(*net.TCPAddr).Port
|
||||
ln.Close()
|
||||
}
|
||||
args := []string{"run", "main.go",
|
||||
"-interval_ms=100",
|
||||
"-max_clients=10",
|
||||
"-logtostderr",
|
||||
"-v=1",
|
||||
}
|
||||
for _, p := range ports {
|
||||
args = append(args, fmt.Sprintf("-port=%d", p))
|
||||
}
|
||||
|
||||
cmd := exec.Command("go", args...)
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatalf("Failed to start server: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := cmd.Process.Kill(); err != nil {
|
||||
t.Logf("Failed to kill process: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if !waitForLogMatch(&stderr, "Listening on", waitForListenTimeout) {
|
||||
t.Fatalf("Timeout waiting for server to start, got logs: %s", stderr.String())
|
||||
}
|
||||
for _, port := range ports {
|
||||
addr := fmt.Sprintf("localhost:%d", port)
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to connect to server on port %d: %v", port, err)
|
||||
}
|
||||
if !waitForLogMatch(&stderr, `ACCEPT host=(127\.0\.0\.1|::1)`, waitForConnectTimeout) {
|
||||
t.Errorf("Never saw any ACCEPT log, got logs: %s", stderr.String())
|
||||
}
|
||||
conn.Close()
|
||||
|
||||
if !waitForLogMatch(&stderr, `CLOSE host=(127\.0\.0\.1|::1)`, waitForConnectTimeout) {
|
||||
t.Errorf("Never saw any CLOSE log, got logs: %s", stderr.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndlesshIntegration_TarpitBehavior(t *testing.T) {
|
||||
var stderr bytes.Buffer
|
||||
cmd := exec.Command("go", "run", "main.go", "-port=0", "-interval_ms=5000", "-max_clients=10", "-logtostderr", "-v=1")
|
||||
cmd.Stderr = &stderr
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatalf("Failed to start server: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := cmd.Process.Kill(); err != nil {
|
||||
t.Logf("Failed to kill process: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if !waitForLogMatch(&stderr, "Listening on", waitForListenTimeout) {
|
||||
t.Fatalf("Timeout waiting for server to start, got logs: %s", stderr.String())
|
||||
}
|
||||
|
||||
stderrOutput := stderr.String()
|
||||
re := regexp.MustCompile(`Listening on .*:(\d+)`)
|
||||
m := re.FindStringSubmatch(stderrOutput)
|
||||
if len(m) != 2 {
|
||||
t.Fatalf("Could not parse port from logs: %s", stderrOutput)
|
||||
}
|
||||
port := m[1]
|
||||
addr := "localhost:" + port
|
||||
|
||||
conn, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to connect to server on port %s: %v", port, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Simulate SSH client banner
|
||||
// Connect & send client banner
|
||||
_, err = conn.Write([]byte("SSH-2.0-OpenSSH_8.2p1\r\n"))
|
||||
if err != nil {
|
||||
t.Fatalf("Write failed: %v", err)
|
||||
}
|
||||
|
||||
// Expect FIRST LINE immediately (no delay)
|
||||
buf := make([]byte, 1024)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil || n == 0 {
|
||||
t.Fatalf("Expected first tarpit line immediately, got: %v (%d bytes)", err, n)
|
||||
}
|
||||
t.Logf("Got first tarpit line (%d bytes): %q", n, buf[:n])
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
conn.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||
n2, err := conn.Read(buf)
|
||||
if err == nil && n2 > 0 {
|
||||
t.Errorf("Got %d bytes too soon (within 500ms), tarpit failed", n2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEndlesshIntegration_Concurrency(t *testing.T) {
|
||||
maxClients := 5
|
||||
var stderr bytes.Buffer
|
||||
cmd := exec.Command("go", "run", "main.go", "-port=0", "-interval_ms=1000", fmt.Sprintf("-max_clients=%d", maxClients), "-logtostderr", "-v=1")
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatalf("Failed to start server: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := cmd.Process.Kill(); err != nil {
|
||||
t.Logf("Failed to kill process: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if !waitForLogMatch(&stderr, "Listening on", waitForListenTimeout) {
|
||||
t.Fatalf("Timeout waiting for server to start, got logs: %s", stderr.String())
|
||||
}
|
||||
stderrOutput := stderr.String()
|
||||
re := regexp.MustCompile(`Listening on .*:(\d+)`)
|
||||
m := re.FindStringSubmatch(stderrOutput)
|
||||
if len(m) != 2 {
|
||||
t.Fatalf("Could not parse port from logs: %s", stderrOutput)
|
||||
}
|
||||
port := m[1]
|
||||
addr := fmt.Sprintf("localhost:%s", port)
|
||||
|
||||
// Test multiple connections
|
||||
var wg sync.WaitGroup
|
||||
var mu sync.Mutex
|
||||
activeClients := 0
|
||||
maxActiveClients := 0
|
||||
//sixthClientFailed := false
|
||||
successfulReads := 0
|
||||
|
||||
for i := 0; i < maxClients+1; i++ {
|
||||
wg.Add(1)
|
||||
go func(clientID int) {
|
||||
defer wg.Done()
|
||||
|
||||
conn, dialErr := net.Dial("tcp", addr)
|
||||
if dialErr != nil {
|
||||
if clientID == maxClients {
|
||||
//sixthClientFailed = true
|
||||
t.Logf("Client %d dial failed (expected): %v", clientID, dialErr)
|
||||
} else {
|
||||
t.Errorf("Client %d dial failed (unexpected): %v", clientID, dialErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
_, writeErr := conn.Write([]byte("SSH-2.0-OpenSSH_8.2p1\r\n"))
|
||||
if writeErr != nil {
|
||||
t.Logf("Client %d write failed: %v", clientID, writeErr)
|
||||
return
|
||||
}
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
conn.SetReadDeadline(time.Now().Add(3 * time.Second))
|
||||
n1, readErr1 := conn.Read(buf)
|
||||
if readErr1 != nil || n1 == 0 {
|
||||
if clientID != maxClients { // only log unexpected failures
|
||||
t.Logf("Client %d failed first tarpit line (unexpected): %v (%d bytes)", clientID, readErr1, n1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Client is active only if it successfully received data
|
||||
mu.Lock()
|
||||
activeClients++
|
||||
if activeClients > maxActiveClients {
|
||||
maxActiveClients = activeClients
|
||||
}
|
||||
successfulReads++
|
||||
mu.Unlock()
|
||||
|
||||
t.Logf("Client %d got line 1 (%d bytes): %q", clientID, n1, buf[:n1])
|
||||
|
||||
// Keep the connection open for a while
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
mu.Lock()
|
||||
activeClients--
|
||||
mu.Unlock()
|
||||
}(i)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
mu.Lock()
|
||||
deferredSuccessfulReads := successfulReads
|
||||
mu.Unlock()
|
||||
|
||||
if deferredSuccessfulReads < 1 {
|
||||
t.Errorf("Expected at least one client to receive a tarpit line, got %d", deferredSuccessfulReads)
|
||||
}
|
||||
|
||||
// Check if maxActiveClients exceeded maxClients
|
||||
if maxActiveClients > maxClients {
|
||||
t.Errorf("Expected max %d concurrent clients, got %d", maxClients, maxActiveClients)
|
||||
}
|
||||
|
||||
// Check if the 6th client failed
|
||||
/*mu.Lock()
|
||||
if !sixthClientFailed {
|
||||
t.Errorf("Expected 6th client to fail, but it succeeded")
|
||||
}
|
||||
mu.Unlock()
|
||||
*/
|
||||
// The 6th client does not necessarily fail at Dial because max_clients is
|
||||
// enforced at the protocol/Read stage instead of at Accept.
|
||||
//
|
||||
// TODO: Enforce max_clients at Accept so the N+1 client fails fast at Dial.
|
||||
}
|
||||
|
||||
func TestEndlesshIntegration_PrometheusMetrics(t *testing.T) {
|
||||
var stderr bytes.Buffer
|
||||
|
||||
cmd := exec.Command(
|
||||
"go", "run", "main.go",
|
||||
"-port=0",
|
||||
"-enable_prometheus",
|
||||
"-prometheus_port=0",
|
||||
"-interval_ms=100",
|
||||
"-logtostderr", "-v=1",
|
||||
)
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatalf("Failed to start server: %v", err)
|
||||
}
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
if !waitForLogMatch(&stderr, "Starting Prometheus", waitForListenTimeout) {
|
||||
t.Fatalf("Prometheus did not start: %s", stderr.String())
|
||||
}
|
||||
|
||||
reProm := regexp.MustCompile(`Prometheus on IP port .*:(\d+)`)
|
||||
reMain := regexp.MustCompile(`Listening on .*:(\d+)`)
|
||||
|
||||
promMatch := reProm.FindStringSubmatch(stderr.String())
|
||||
mainMatch := reMain.FindStringSubmatch(stderr.String())
|
||||
|
||||
if len(promMatch) < 2 || len(mainMatch) < 2 {
|
||||
t.Fatalf("Could not parse ports: %s", stderr.String())
|
||||
}
|
||||
|
||||
promPort := promMatch[1]
|
||||
mainPort := mainMatch[1]
|
||||
|
||||
conn, err := net.Dial("tcp", "localhost:"+mainPort)
|
||||
if err != nil {
|
||||
t.Fatalf("Dial failed: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
conn.Write([]byte("SSH-2.0-test\r\n"))
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
// Fetch metrics
|
||||
resp, err := net.Dial("tcp", "localhost:"+promPort)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to connect to metrics endpoint: %v", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(resp, "GET /metrics HTTP/1.1\r\nHost: localhost\r\n\r\n")
|
||||
|
||||
buf := make([]byte, 8192)
|
||||
n, _ := resp.Read(buf)
|
||||
body := string(buf[:n])
|
||||
|
||||
if !strings.Contains(body, "endlessh_client_open_count_total") {
|
||||
t.Errorf("Missing expected metric in output:\n%s", body)
|
||||
}
|
||||
|
||||
if !strings.Contains(body, "endlessh_sent_bytes_total") {
|
||||
t.Errorf("Expected bytes metric not found:\n%s", body)
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,12 @@ An example how to setup endlessh-go, Prometheus, and Grafana using [docker compo
|
||||
|
||||
An example how to setup endlessh-go with the Maxmind GeoIP Database.
|
||||
|
||||
## [kustomize-simple](./kustomize-simple)
|
||||
|
||||
An example how to setup endlessh-go using [kustomize](https://kustomize.io/).
|
||||
|
||||
## FAQ
|
||||
|
||||
### Bind to privileged ports (<1024) in a container
|
||||
|
||||
You need to add capability `NET_BIND_SERVICE` to the program.
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
## kustomize
|
||||
|
||||
This is an example how to setup endlessh-go with existing Prometheus and Grafana using [kustomize](https://kustomize.io/).
|
||||
|
||||
This example assumes the cluster already has a Prometheus Operator based monitoring stack. It deploys:
|
||||
|
||||
- endlessh-go
|
||||
- a Service exposing SSH and Prometheus metrics
|
||||
- a `ServiceMonitor` for scraping endlessh-go metrics
|
||||
- a Grafana dashboard `ConfigMap`
|
||||
|
||||
To deploy the stack, run:
|
||||
|
||||
```bash
|
||||
kubectl apply -k examples/kustomize-simple
|
||||
```
|
||||
|
||||
`dashboard.json` is added to a `ConfigMap` with label `grafana_dashboard=1`, which can be picked up by a Grafana sidecar based dashboard loader.
|
||||
|
||||
The `ServiceMonitor` in `monitor.yaml` scrapes the `metrics` port every `60s`. If your Prometheus stack only selects `ServiceMonitor` objects with specific labels, add the matching label in `kustomization.yaml`.
|
||||
|
||||
The `endlessh` Service exposes the following ports inside the cluster:
|
||||
|
||||
- **22**: SSH for endlessh-go
|
||||
- **2112**: Prometheus metrics exported by endlessh-go
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,31 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: endlessh
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
containers:
|
||||
- name: endlessh
|
||||
image: shizunge/endlessh-go:latest
|
||||
args:
|
||||
- -interval_ms=1000
|
||||
- -logtostderr
|
||||
- -v=1
|
||||
- -enable_prometheus
|
||||
- -geoip_supplier=ip-api
|
||||
- -host=[::]
|
||||
- -prometheus_host=[::]
|
||||
ports:
|
||||
- name: ssh
|
||||
containerPort: 2222
|
||||
- name: metrics
|
||||
containerPort: 2112
|
||||
securityContext:
|
||||
readOnlyRootFilesystem: true
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
@@ -0,0 +1,21 @@
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- monitor.yaml
|
||||
configMapGenerator:
|
||||
- name: endlessh-dashboard
|
||||
files:
|
||||
- dashboard.json
|
||||
options:
|
||||
labels:
|
||||
grafana_dashboard: "1"
|
||||
labels:
|
||||
- pairs:
|
||||
app.kubernetes.io/name: endlessh
|
||||
includeSelectors: true
|
||||
includeTemplates: true
|
||||
fields:
|
||||
- group: monitoring.coreos.com
|
||||
kind: ServiceMonitor
|
||||
path: spec/selector/matchLabels
|
||||
create: true
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: endlessh
|
||||
spec:
|
||||
endpoints:
|
||||
- interval: 60s
|
||||
path: /metrics
|
||||
port: metrics
|
||||
jobLabel: app.kubernetes.io/name
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: endlessh
|
||||
spec:
|
||||
ports:
|
||||
- name: ssh
|
||||
port: 22
|
||||
targetPort: ssh
|
||||
protocol: TCP
|
||||
- name: metrics
|
||||
port: 2112
|
||||
targetPort: metrics
|
||||
protocol: TCP
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2023-2024 Shizun Ge
|
||||
// Copyright (C) 2023-2026 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
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2021-2024 Shizun Ge
|
||||
// Copyright (C) 2021-2026 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
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
module endlessh-go
|
||||
|
||||
go 1.24.2
|
||||
go 1.26.1
|
||||
|
||||
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/pierrre/geohash v1.1.4
|
||||
github.com/pires/go-proxyproto v0.12.0
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
)
|
||||
|
||||
@@ -15,7 +16,6 @@ require (
|
||||
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
|
||||
|
||||
@@ -33,18 +33,18 @@ github.com/oschwald/geoip2-golang v1.13.0 h1:Q44/Ldc703pasJeP5V9+aFSZFmBN7DKHbNs
|
||||
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/pierrre/assert v0.12.1 h1:Hg+r10601guGiOvXoWHx1Xp2TLj3w6FMb+PN7pR+iQc=
|
||||
github.com/pierrre/assert v0.12.1/go.mod h1:9AEGGFuJp339PdEUkhfOq8zqYPdSurPWAua35EEpY2o=
|
||||
github.com/pierrre/compare v1.4.14 h1:snH4S/2nClsCCgYAvseyQYX6RHX8WvCdd23deiL1ovc=
|
||||
github.com/pierrre/compare v1.4.14/go.mod h1:5RkfHZojsCAFZR+mW9SHH55mH1tCWNWMP7xhWgxc6QE=
|
||||
github.com/pierrre/geohash v1.1.4 h1:yzKGZnvToMhSorOzdR7dULzYkzc6BgEYN//yAn/sWsw=
|
||||
github.com/pierrre/geohash v1.1.4/go.mod h1:acEkGYRKiLkhsU88AolKimmPzyBC7vqi9YUs0/LkKNI=
|
||||
github.com/pierrre/go-libs v0.28.1 h1:PCz1fdKdSwCmwO5GMCN/L5qlvqxIBT54iKKzrbI1Nik=
|
||||
github.com/pierrre/go-libs v0.28.1/go.mod h1:KXbqq+niFCQ48LPSjX2lKG1IdCoTBrTpurkVVDH2W8U=
|
||||
github.com/pierrre/pretty v0.23.0 h1:LYCkefa+DV87cu/xqUnPh75x1T0zXgnHvAn7wW+OgN0=
|
||||
github.com/pierrre/pretty v0.23.0/go.mod h1:ILYWjIqXAO9iLC2sDcHkgv2Vp0JIIKGMjRtwHAdOVAM=
|
||||
github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM=
|
||||
github.com/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2021-2024 Shizun Ge
|
||||
// Copyright (C) 2021-2026 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
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -70,7 +71,14 @@ func startSending(maxClients int64, bannerMaxLength int64, records chan<- metric
|
||||
|
||||
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)
|
||||
connPortInt, err := strconv.Atoi(connPort)
|
||||
if err != nil {
|
||||
glog.Errorf("Invalid port: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
addr := connHost + fmt.Sprintf(":%d", connPortInt)
|
||||
l, err := net.Listen(connType, addr)
|
||||
if err != nil {
|
||||
glog.Errorf("Error listening: %v", err)
|
||||
os.Exit(1)
|
||||
@@ -84,7 +92,9 @@ func startAccepting(maxClients int64, connType, connHost, connPort string, inter
|
||||
// Close the listener when the application closes.
|
||||
defer l.Close()
|
||||
|
||||
glog.Infof("Listening on %v:%v", connHost, connPort)
|
||||
realAddr := l.Addr().(*net.TCPAddr)
|
||||
localPortStr := strconv.Itoa(realAddr.Port)
|
||||
glog.Infof("Listening on %v:%v", connHost, realAddr.Port)
|
||||
for {
|
||||
// Listen for an incoming connection.
|
||||
conn, err := l.Accept()
|
||||
@@ -97,7 +107,7 @@ func startAccepting(maxClients int64, connType, connHost, connPort string, inter
|
||||
records <- metrics.RecordEntry{
|
||||
RecordType: metrics.RecordEntryTypeStart,
|
||||
IpAddr: remoteIpAddr,
|
||||
LocalPort: connPort,
|
||||
LocalPort: localPortStr,
|
||||
}
|
||||
clients <- c
|
||||
}
|
||||
@@ -146,6 +156,15 @@ func main() {
|
||||
if *connType == "tcp6" && *prometheusHost == "0.0.0.0" {
|
||||
*prometheusHost = "[::]"
|
||||
}
|
||||
if *prometheusPort == "0" || *prometheusPort == "" {
|
||||
l, err := net.Listen("tcp", *prometheusHost+":0")
|
||||
if err != nil {
|
||||
glog.Fatalf("Failed to pick a free Prometheus port: %v", err)
|
||||
}
|
||||
actualPort := l.Addr().(*net.TCPAddr).Port
|
||||
*prometheusPort = strconv.Itoa(actualPort)
|
||||
l.Close()
|
||||
}
|
||||
metrics.InitPrometheus(*prometheusHost, *prometheusPort, *prometheusEntry)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2024 Shizun Ge
|
||||
// Copyright (C) 2024-2026 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
|
||||
|
||||
Reference in New Issue
Block a user