Skip to content

Commit

Permalink
feat: implement interval jitter
Browse files Browse the repository at this point in the history
Adds a new argument called 'interval_jitter', expressed as a percentage
number (range 0-100) that has the following effects:

If 0 => no effect.

If >0 => choose a random integer in the
[-(interval * jitter / 100), +(interval * jitter / 100)] range

and add use it as an offset for the sleep interval.

So for example, if our interval is 1000ms and we pick
interval_jitter=20, then the sleep values will be
in the range [800ms, 1200ms]

This has been a requested feature in the original implementation,
and is useful to twarth SSH scanners that have tarpit-detecting logic.

Link: skeeto/endlessh#71
  • Loading branch information
rarescosma committed May 9, 2024
1 parent 7341b40 commit e550c6e
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 5 deletions.
16 changes: 14 additions & 2 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,11 @@ type Client struct {
start time.Time
last time.Time
interval time.Duration
jitter int64
bytesSent int
}

func NewClient(conn net.Conn, interval time.Duration, maxClients int64) *Client {
func NewClient(conn net.Conn, interval time.Duration, jitter int64, maxClients int64) *Client {
for numCurrentClients >= maxClients {
time.Sleep(interval)
}
Expand All @@ -62,6 +63,7 @@ func NewClient(conn net.Conn, interval time.Duration, maxClients int64) *Client
start: time.Now(),
last: time.Now(),
interval: interval,
jitter: jitter,
bytesSent: 0,
}
}
Expand All @@ -78,7 +80,7 @@ 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)
c.next = time.Now().Add(c.Interval())
length := rand.Int63n(bannerMaxLength)
bytesSent, err := c.conn.Write(randStringBytes(length))
if err != nil {
Expand All @@ -88,6 +90,16 @@ func (c *Client) Send(bannerMaxLength int64) (int, error) {
return bytesSent, nil
}

func (c *Client) Interval() time.Duration {
if c.jitter != 0 {
// generate an integer offset in the interval [-jitter, +jitter]
offset := time.Duration(rand.Int63n(2*c.jitter+1)-c.jitter) * time.Millisecond
glog.V(1).Infof("Jitter debug interval=%v offset=%v\n", c.interval, offset)
return c.interval + offset
}
return c.interval
}

func (c *Client) MillisecondsSinceLast() int64 {
millisecondsSpent := time.Now().Sub(c.last).Milliseconds()
c.last = time.Now()
Expand Down
14 changes: 11 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func startSending(maxClients int64, bannerMaxLength int64, records chan<- metric
return clients
}

func startAccepting(maxClients int64, connType, connHost, connPort string, interval time.Duration, clients chan<- *client.Client, records chan<- metrics.RecordEntry) {
func startAccepting(maxClients int64, connType, connHost, connPort string, interval time.Duration, jitter int64, clients chan<- *client.Client, records chan<- metrics.RecordEntry) {
go func() {
l, err := net.Listen(connType, connHost+":"+connPort)
if err != nil {
Expand All @@ -84,7 +84,7 @@ func startAccepting(maxClients int64, connType, connHost, connPort string, inter
glog.Errorf("Error accepting connection from port %v: %v", connPort, err)
os.Exit(1)
}
c := client.NewClient(conn, interval, maxClients)
c := client.NewClient(conn, interval, jitter, maxClients)
remoteIpAddr := c.RemoteIpAddr()
records <- metrics.RecordEntry{
RecordType: metrics.RecordEntryTypeStart,
Expand Down Expand Up @@ -113,6 +113,7 @@ var connPorts arrayStrings

func main() {
intervalMs := flag.Int("interval_ms", 1000, "Message millisecond delay")
intervalJitter := flag.Int64("interval_jitter", 0, "Interval jitter as a percentage. Set to 0 to disable. (default 0)")
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")
Expand Down Expand Up @@ -154,8 +155,15 @@ func main() {
if len(connPorts) == 0 {
connPorts = append(connPorts, defaultPort)
}

if *intervalJitter < 0 || *intervalJitter > 100 {
glog.Errorf("Error: interval_jitter must be a number between 0 and 100. Got: %v", *intervalJitter)
os.Exit(1)
}
// use float to compute the jitter limit to avoid overflow
jitter := int64(float64(*intervalMs) * (float64(*intervalJitter) / 100))
for _, connPort := range connPorts {
startAccepting(*maxClients, *connType, *connHost, connPort, interval, clients, records)
startAccepting(*maxClients, *connType, *connHost, connPort, interval, jitter, clients, records)
}
for {
if *prometheusCleanUnseenSeconds <= 0 {
Expand Down

0 comments on commit e550c6e

Please sign in to comment.