From e2b1d39d8325c86ffd25c2a9eb321930d25684cc Mon Sep 17 00:00:00 2001 From: nakabonne Date: Mon, 14 Sep 2020 22:29:23 +0900 Subject: [PATCH] Add method and body widgets --- attacker/attacker.go | 2 +- gui/gui.go | 125 +++++++++++++++++++++++++------------------ gui/widgets.go | 33 ++++++------ 3 files changed, 90 insertions(+), 70 deletions(-) diff --git a/attacker/attacker.go b/attacker/attacker.go index 40e327c..5fe26a0 100644 --- a/attacker/attacker.go +++ b/attacker/attacker.go @@ -10,7 +10,7 @@ import ( const ( defaultRate = 50 - defaultDuration = 30 * time.Second + defaultDuration = 10 * time.Second defaultMethod = http.MethodGet ) diff --git a/gui/gui.go b/gui/gui.go index dccdb79..d72f737 100644 --- a/gui/gui.go +++ b/gui/gui.go @@ -3,6 +3,7 @@ package gui import ( "context" "fmt" + "net/http" "net/url" "strconv" "time" @@ -59,66 +60,23 @@ func Run() error { return termdash.Run(ctx, t, c, termdash.KeyboardSubscriber(k), termdash.RedrawInterval(redrawInterval)) } -func keybinds(ctx context.Context, cancel context.CancelFunc, dr *drawer) func(*terminalapi.Keyboard) { - return func(k *terminalapi.Keyboard) { - switch k.Key { - case keyboard.KeyCtrlC: // Quit - cancel() - case keyboard.KeyEnter: // Attack - if dr.chartDrawing { - return - } - var ( - target string - rate int - duration time.Duration - err error - ) - target = dr.widgets.urlInput.Read() - if _, err := url.ParseRequestURI(target); err != nil { - dr.reportCh <- fmt.Sprintf("Bad URL: %v", err) - return - } - if s := dr.widgets.rateLimitInput.Read(); s != "" { - rate, err = strconv.Atoi(s) - if err != nil { - dr.reportCh <- fmt.Sprintf("Given rate limit %q isn't integer: %v", s, err) - return - } - } - if s := dr.widgets.durationInput.Read(); s != "" { - duration, err = time.ParseDuration(s) - if err != nil { - dr.reportCh <- fmt.Sprintf("Unparseable duration %q: %v", s, err) - return - } - } - requestNum := rate * int(duration/time.Second) - // To pre-allocate, run redrawChart on a per-attack basis. - go dr.redrawChart(ctx, requestNum) - go func(ctx context.Context, d *drawer, t string, r int, du time.Duration) { - metrics := attacker.Attack(ctx, t, d.chartsCh, attacker.Options{Rate: r, Duration: du}) - d.reportCh <- metrics.String() - d.chartsCh <- &attacker.Result{End: true} - }(ctx, dr, target, rate, duration) - } - } -} - func gridLayout(w *widgets) ([]container.Option, error) { raw1 := grid.RowHeightPerc(60, grid.ColWidthPerc(99, grid.Widget(w.latencyChart, container.Border(linestyle.Light), container.BorderTitle("Latency (ms)"))), ) raw2 := grid.RowHeightPerc(36, - //grid.ColWidthPerc(50, grid.Widget(w.urlInput, container.Border(linestyle.Light), container.BorderTitle("Input"))), - grid.ColWidthPerc(50, - grid.RowHeightPerc(20, grid.Widget(w.urlInput, container.Border(linestyle.None))), - grid.RowHeightPerc(20, + grid.ColWidthPerc(30, + grid.RowHeightPerc(33, grid.Widget(w.urlInput, container.Border(linestyle.None))), + grid.RowHeightPerc(33, grid.ColWidthPerc(50, grid.Widget(w.rateLimitInput, container.Border(linestyle.None))), grid.ColWidthPerc(49, grid.Widget(w.durationInput, container.Border(linestyle.None))), ), + grid.RowHeightPerc(32, + grid.ColWidthPerc(50, grid.Widget(w.methodInput, container.Border(linestyle.None))), + grid.ColWidthPerc(49, grid.Widget(w.bodyInput, container.Border(linestyle.None))), + ), ), - grid.ColWidthPerc(49, grid.Widget(w.reportText, container.Border(linestyle.Light), container.BorderTitle("Report"))), + grid.ColWidthPerc(69, grid.Widget(w.reportText, container.Border(linestyle.Light), container.BorderTitle("Report"))), ) raw3 := grid.RowHeightFixed(1, grid.ColWidthFixed(100, grid.Widget(w.navi, container.Border(linestyle.Light))), @@ -133,3 +91,68 @@ func gridLayout(w *widgets) ([]container.Option, error) { return builder.Build() } + +func keybinds(ctx context.Context, cancel context.CancelFunc, dr *drawer) func(*terminalapi.Keyboard) { + return func(k *terminalapi.Keyboard) { + switch k.Key { + case keyboard.KeyCtrlC: // Quit + cancel() + case keyboard.KeyEnter: // Attack + attack(ctx, dr) + } + } +} + +func attack(ctx context.Context, dr *drawer) { + if dr.chartDrawing { + return + } + var ( + target string + rate int + duration time.Duration + method string + err error + ) + target = dr.widgets.urlInput.Read() + if _, err := url.ParseRequestURI(target); err != nil { + dr.reportCh <- fmt.Sprintf("Bad URL: %v", err) + return + } + if s := dr.widgets.rateLimitInput.Read(); s != "" { + rate, err = strconv.Atoi(s) + if err != nil { + dr.reportCh <- fmt.Sprintf("Given rate limit %q isn't integer: %v", s, err) + return + } + } + if s := dr.widgets.durationInput.Read(); s != "" { + duration, err = time.ParseDuration(s) + if err != nil { + dr.reportCh <- fmt.Sprintf("Unparseable duration %q: %v", s, err) + return + } + } + if method = dr.widgets.methodInput.Read(); method != "" { + if !validateMethod(method) { + dr.reportCh <- fmt.Sprintf("Given method %q isn't an HTTP request method", method) + return + } + } + requestNum := rate * int(duration/time.Second) + // To pre-allocate, run redrawChart on a per-attack basis. + go dr.redrawChart(ctx, requestNum) + go func(ctx context.Context, d *drawer, t string, r int, du time.Duration) { + metrics := attacker.Attack(ctx, t, d.chartsCh, attacker.Options{Rate: r, Duration: du, Method: method}) + d.reportCh <- metrics.String() + d.chartsCh <- &attacker.Result{End: true} + }(ctx, dr, target, rate, duration) +} + +func validateMethod(method string) bool { + switch method { + case http.MethodGet, http.MethodHead, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodConnect, http.MethodOptions, http.MethodTrace: + return true + } + return false +} diff --git a/gui/widgets.go b/gui/widgets.go index 28b45fa..674a743 100644 --- a/gui/widgets.go +++ b/gui/widgets.go @@ -4,8 +4,6 @@ import ( "time" "github.com/mum4k/termdash/cell" - "github.com/mum4k/termdash/keyboard" - "github.com/mum4k/termdash/widgets/button" "github.com/mum4k/termdash/widgets/linechart" "github.com/mum4k/termdash/widgets/text" "github.com/mum4k/termdash/widgets/textinput" @@ -20,6 +18,8 @@ type widgets struct { urlInput *textinput.TextInput rateLimitInput *textinput.TextInput durationInput *textinput.TextInput + methodInput *textinput.TextInput + bodyInput *textinput.TextInput latencyChart *linechart.LineChart reportText *text.Text navi *text.Text @@ -38,28 +38,32 @@ func newWidgets() (*widgets, error) { if err != nil { return nil, err } - urlInput, err := newTextInput("Target URL: ", "", 100) + urlInput, err := newTextInput("Target URL: ", "", 60) if err != nil { return nil, err } - rateLimitInput, err := newTextInput("Rate limit: ", "50", 50) + rateLimitInput, err := newTextInput("Rate limit: ", "50", 30) if err != nil { return nil, err } - durationInput, err := newTextInput("Duration: ", "30s", 50) + durationInput, err := newTextInput("Duration: ", "10s", 30) + if err != nil { + return nil, err + } + methodInput, err := newTextInput("Method: ", "GET", 30) + if err != nil { + return nil, err + } + bodyInput, err := newTextInput("Body: ", "", 30) if err != nil { return nil, err } - /*attackButton, err := newButton("Attack", func() error { - target := urlInput.Read() - pp.Println(target) - // TODO: Call Attack. - return nil - })*/ return &widgets{ urlInput: urlInput, rateLimitInput: rateLimitInput, durationInput: durationInput, + methodInput: methodInput, + bodyInput: bodyInput, latencyChart: latencyChart, reportText: reportText, navi: navi, @@ -85,13 +89,6 @@ func newRollText(s string) (*text.Text, error) { return t, nil } -func newButton(text string, onSubmit func() error) (*button.Button, error) { - return button.New(text, onSubmit, - button.GlobalKey(keyboard.KeyEnter), - button.FillColor(cell.ColorNumber(196)), - ) -} - func newTextInput(label, placeHolder string, cells int) (*textinput.TextInput, error) { return textinput.New( textinput.Label(label, cell.FgColor(cell.ColorWhite)),