2024-01-16 21:06:03 -08:00
// Copyright (C) 2021-2024 Shizun Ge
2021-10-27 00:34:18 -07:00
//
// 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/>.
//
2021-10-12 00:55:13 -07:00
package main
import (
2024-01-16 21:06:03 -08:00
"endlessh-go/client"
"endlessh-go/geoip"
"endlessh-go/metrics"
2021-10-12 00:55:13 -07:00
"flag"
"fmt"
"net"
"os"
2024-01-16 21:06:03 -08:00
"strings"
2021-10-12 00:55:13 -07:00
"time"
2021-10-26 21:33:55 -07:00
"github.com/golang/glog"
2025-09-10 19:52:59 +02:00
proxyproto "github.com/pires/go-proxyproto"
2021-10-12 00:55:13 -07:00
)
2024-01-16 21:06:03 -08:00
func startSending ( maxClients int64 , bannerMaxLength int64 , records chan <- metrics . RecordEntry ) chan * client . Client {
clients := make ( chan * client . Client , maxClients )
2024-01-16 20:16:27 -08:00
go func ( ) {
for {
c , more := <- clients
if ! more {
return
}
go func ( ) {
bytesSent , err := c . Send ( bannerMaxLength )
2024-01-16 21:06:03 -08:00
remoteIpAddr := c . RemoteIpAddr ( )
localPort := c . LocalPort ( )
2024-01-27 23:26:39 -08:00
millisecondsSpent := c . MillisecondsSinceLast ( )
2024-01-16 20:16:27 -08:00
if err != nil {
c . Close ( )
2024-01-16 21:06:03 -08:00
records <- metrics . RecordEntry {
2024-01-27 23:26:39 -08:00
RecordType : metrics . RecordEntryTypeStop ,
IpAddr : remoteIpAddr ,
LocalPort : localPort ,
MillisecondsSpent : millisecondsSpent ,
2024-01-16 20:16:27 -08:00
}
return
}
clients <- c
2024-01-16 21:06:03 -08:00
records <- metrics . RecordEntry {
RecordType : metrics . RecordEntryTypeSend ,
IpAddr : remoteIpAddr ,
LocalPort : localPort ,
2024-01-16 20:16:27 -08:00
MillisecondsSpent : millisecondsSpent ,
2024-01-27 23:26:39 -08:00
BytesSent : bytesSent ,
2024-01-16 20:16:27 -08:00
}
} ( )
}
} ( )
return clients
}
2025-09-10 19:52:59 +02:00
func startAccepting ( maxClients int64 , connType , connHost , connPort string , interval time . Duration , clients chan <- * client . Client , records chan <- metrics . RecordEntry , proxyProtocolEnabled bool , proxyProtocolReadHeaderTimeout int ) {
2024-01-16 21:06:03 -08:00
go func ( ) {
l , err := net . Listen ( connType , connHost + ":" + connPort )
2024-01-16 20:16:27 -08:00
if err != nil {
2024-01-16 21:06:03 -08:00
glog . Errorf ( "Error listening: %v" , err )
2024-01-16 20:16:27 -08:00
os . Exit ( 1 )
}
2025-09-10 19:52:59 +02:00
// Wrap the listener in a proxy protocol listener
if proxyProtocolEnabled {
l = & proxyproto . Listener { Listener : l , ReadHeaderTimeout : time . Duration ( proxyProtocolReadHeaderTimeout ) * time . Millisecond }
}
2024-01-16 21:06:03 -08:00
// Close the listener when the application closes.
defer l . Close ( )
2025-09-10 19:52:59 +02:00
2024-01-16 21:06:03 -08:00
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
2024-01-16 20:16:27 -08:00
}
2024-01-16 21:06:03 -08:00
} ( )
}
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
2024-01-16 20:16:27 -08:00
}
2024-01-16 21:06:03 -08:00
const defaultPort = "2222"
var connPorts arrayStrings
2021-10-12 00:55:13 -07:00
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" )
2022-07-11 18:41:57 -07:00
connHost := flag . String ( "host" , "0.0.0.0" , "SSH listening address" )
2024-01-16 21:53:00 -08:00
flag . Var ( & connPorts , "port" , fmt . Sprintf ( "SSH listening port. You may provide multiple -port flags to listen to multiple ports. (default %q)" , defaultPort ) )
2021-10-27 00:34:18 -07:00
prometheusEnabled := flag . Bool ( "enable_prometheus" , false , "Enable prometheus" )
2022-07-11 18:41:57 -07:00
prometheusHost := flag . String ( "prometheus_host" , "0.0.0.0" , "The address for prometheus" )
2021-10-26 21:33:55 -07:00
prometheusPort := flag . String ( "prometheus_port" , "2112" , "The port for prometheus" )
prometheusEntry := flag . String ( "prometheus_entry" , "metrics" , "Entry point for prometheus" )
2024-01-18 22:13:20 -08:00
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)" )
2023-02-10 22:22:32 -08:00
geoipSupplier := flag . String ( "geoip_supplier" , "off" , "Supplier to obtain Geohash of IPs. Possible values are \"off\", \"ip-api\", \"max-mind-db\"" )
2024-01-16 20:16:27 -08:00
maxMindDbFileName := flag . String ( "max_mind_db" , "" , "Path to the MaxMind DB file." )
2025-09-10 19:52:59 +02:00
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." )
2021-10-26 21:33:55 -07:00
2021-10-12 00:55:13 -07:00
flag . Usage = func ( ) {
fmt . Fprintf ( flag . CommandLine . Output ( ) , "Usage of %v \n" , os . Args [ 0 ] )
flag . PrintDefaults ( )
}
flag . Parse ( )
2021-10-27 00:34:18 -07:00
if * prometheusEnabled {
2024-01-16 20:16:27 -08:00
if * connType == "tcp6" && * prometheusHost == "0.0.0.0" {
* prometheusHost = "[::]"
}
2024-01-16 21:06:03 -08:00
metrics . InitPrometheus ( * prometheusHost , * prometheusPort , * prometheusEntry )
2021-10-26 21:33:55 -07:00
}
2024-01-18 22:13:20 -08:00
records := metrics . StartRecording ( * maxClients , * prometheusEnabled , * prometheusCleanUnseenSeconds ,
geoip . GeoOption {
GeoipSupplier : * geoipSupplier ,
MaxMindDbFileName : * maxMindDbFileName ,
} )
2024-01-16 20:16:27 -08:00
clients := startSending ( * maxClients , * bannerMaxLength , records )
2021-10-12 00:55:13 -07:00
interval := time . Duration ( * intervalMs ) * time . Millisecond
// Listen for incoming connections.
2021-10-26 21:33:55 -07:00
if * connType == "tcp6" && * connHost == "0.0.0.0" {
* connHost = "[::]"
}
2024-01-16 21:06:03 -08:00
if len ( connPorts ) == 0 {
connPorts = append ( connPorts , defaultPort )
}
for _ , connPort := range connPorts {
2025-09-10 19:52:59 +02:00
startAccepting ( * maxClients , * connType , * connHost , connPort , interval , clients , records , * proxyProtocolEnabled , * proxyProtocolReadHeaderTimeout )
2024-01-16 21:06:03 -08:00
}
2024-01-16 20:16:27 -08:00
for {
2024-01-18 22:13:20 -08:00
if * prometheusCleanUnseenSeconds <= 0 {
time . Sleep ( time . Duration ( 1 << 63 - 1 ) )
} else {
time . Sleep ( time . Second * time . Duration ( 60 ) )
records <- metrics . RecordEntry {
RecordType : metrics . RecordEntryTypeClean ,
}
}
2021-10-12 00:55:13 -07:00
}
}