Skip to content

Commit

Permalink
UI (#2)
Browse files Browse the repository at this point in the history
* fix: slow start for python server

* feat: add proof http server

* WIP: event

* test: add single connection test for easy debugging

* chore: drop debug

* feat: pass worker id

* feat: bind worker lifecycle with event handler

* test: observe events in test

* refactor: move event handler to target struct

* doc: doc string

* chore: send host & accept headers

* fix: debug in proof server

* feat: implement async event handler

* feat: implement terminal dashboard ui

* feat: add helper methods

* feat: switch to bubbletea

* f

* chore: provide AfterApply hook for kong

* feat: refine ui

* doc: document the stale connections behavior

* doc: add screenshot
  • Loading branch information
bcho authored Aug 27, 2023
1 parent e7ae4b3 commit 1b80a1e
Show file tree
Hide file tree
Showing 15 changed files with 762 additions and 30 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.DS_Store
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# turtle

![](/docs/demo/demo.gif)

## Disclaimer

> Usage of this program for attacking targets without
Expand Down
30 changes: 28 additions & 2 deletions cmd/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,32 @@ go 1.20

replace github.com/b4fun/turtle => ../

require github.com/b4fun/turtle v0.0.0-00010101000000-000000000000
require (
github.com/b4fun/turtle v0.0.0-00010101000000-000000000000
github.com/charmbracelet/bubbles v0.16.1
github.com/charmbracelet/bubbletea v0.24.2
github.com/charmbracelet/lipgloss v0.7.1
)

require github.com/alecthomas/kong v0.8.0 // indirect
require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.1 // indirect
golang.org/x/sync v0.1.0 // indirect
)

require (
github.com/alecthomas/kong v0.8.0
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/term v0.6.0 // indirect
golang.org/x/text v0.5.0 // indirect
)
48 changes: 48 additions & 0 deletions cmd/go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,50 @@
github.com/alecthomas/assert/v2 v2.1.0 h1:tbredtNcQnoSd3QBhQWI7QZ3XHOVkw1Moklp2ojoH/0=
github.com/alecthomas/kong v0.8.0 h1:ryDCzutfIqJPnNn0omnrgHLbAggDQM2VWHikE1xqK7s=
github.com/alecthomas/kong v0.8.0/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U=
github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
105 changes: 105 additions & 0 deletions cmd/proof/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package main

import (
"context"
"errors"
"fmt"
"io"
"net"
"net/http"
"os"
"os/signal"
"time"

"github.com/alecthomas/kong"
)

type CLI struct {
ServerAddr string `cmd:"server-addr" help:"the address to listen on" default:"127.0.0.1:8889"`
Scenario string `cmd:"scenario" enum:"none,proof" help:"the scenario to run" default:"none"`

ServerReadHeaderTimeout time.Duration `cmd:"server-read-header-timeout" help:"the amount of time allowed to read request headers. Non-positive value means no timeout"`
ServerReadTimeout time.Duration `cmd:"server-read-timeout" help:"the maximum duration for reading the entire request, including the body. Non-positive value means no timeout"`
ServerWriteTimeout time.Duration `cmd:"server-write-timeout" help:"the maximum duration before timing out writes of the response. Non-positive value means no timeout"`
}

func (c *CLI) defaults() error {
switch c.Scenario {
case "proof":
if c.ServerReadHeaderTimeout <= 0 {
c.ServerReadHeaderTimeout = 3 * time.Second
}
if c.ServerReadTimeout <= 0 {
c.ServerReadTimeout = 60 * time.Second
}
if c.ServerWriteTimeout <= 0 {
c.ServerWriteTimeout = 60 * time.Second
}
default:
if c.ServerReadHeaderTimeout <= 0 {
c.ServerReadHeaderTimeout = 0
}
if c.ServerReadTimeout <= 0 {
c.ServerReadTimeout = 0
}
if c.ServerWriteTimeout <= 0 {
c.ServerWriteTimeout = 0
}
}

return nil
}

func (c *CLI) CreateServer() *http.Server {
return &http.Server{
Addr: c.ServerAddr,
ReadHeaderTimeout: c.ServerReadHeaderTimeout,
ReadTimeout: c.ServerReadTimeout,
WriteTimeout: c.ServerWriteTimeout,

ConnState: func(conn net.Conn, state http.ConnState) {
fmt.Println("ConnState", conn.RemoteAddr(), state)
},

Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Println("here")

if r.Body != nil {
fmt.Println("read body")
if _, err := io.ReadAll(r.Body); err != nil {
fmt.Println("read error", err)
w.WriteHeader(http.StatusInternalServerError)
}
}

w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
}),
}
}

func main() {
cli := &CLI{}
cliCtx := kong.Parse(cli)

if err := cli.defaults(); err != nil {
cliCtx.FatalIfErrorf(err)
}

ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()

server := cli.CreateServer()
go func() {
err := server.ListenAndServe()
if err != nil && errors.Is(err, http.ErrServerClosed) {
cliCtx.FatalIfErrorf(err)
}
}()

<-ctx.Done()

shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
server.Shutdown(shutdownCtx)
}
28 changes: 27 additions & 1 deletion cmd/turtle/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"context"
"os"
"os/signal"
"time"

"github.com/alecthomas/kong"
tea "github.com/charmbracelet/bubbletea"

"github.com/b4fun/turtle"
)
Expand All @@ -23,6 +25,30 @@ func main() {

cliCtx.BindTo(ctx, (*context.Context)(nil))

prog := tea.NewProgram(newUI())
CLI.Slowloris.Target.EventHandler = UIEventHandler(
ctx,
prog,
CLI.Slowloris.Target,
500*time.Millisecond,
)
CLI.SlowBodyRead.Target.EventHandler = UIEventHandler(
ctx,
prog,
CLI.SlowBodyRead.Target,
500*time.Millisecond,
)

go func() {
if _, err := prog.Run(); err != nil {
cliCtx.FatalIfErrorf(err)
}
cancel()
}()

err := cliCtx.Run()
cliCtx.FatalIfErrorf(err)
go prog.Quit()
if err != nil {
cliCtx.FatalIfErrorf(err)
}
}
Loading

0 comments on commit 1b80a1e

Please sign in to comment.