diff --git a/modules/net_recon.go b/modules/net_recon.go index 11bd21ab..e9a5d95c 100644 --- a/modules/net_recon.go +++ b/modules/net_recon.go @@ -10,6 +10,7 @@ import ( type Discovery struct { session.SessionModule + selector *ViewSelector } func NewDiscovery(s *session.Session) *Discovery { @@ -36,33 +37,17 @@ func NewDiscovery(s *session.Session) *Discovery { d.AddHandler(session.NewModuleHandler("net.show", "", "Show cache hosts list (default sorting by ip).", func(args []string) error { - return d.Show("address", "") - })) - - d.AddHandler(session.NewModuleHandler("net.show by seen", "", - "Show cache hosts list (sort by last seen).", - func(args []string) error { - return d.Show("seen", "") - })) - - d.AddHandler(session.NewModuleHandler("net.show by sent", "", - "Show cache hosts list (sort by sent packets).", - func(args []string) error { - return d.Show("sent", "") - })) - - d.AddHandler(session.NewModuleHandler("net.show by rcvd", "", - "Show cache hosts list (sort by received packets).", - func(args []string) error { - return d.Show("rcvd", "") + return d.Show("") })) d.AddHandler(session.NewModuleHandler("net.show ADDRESS1, ADDRESS2", `net.show (.+)`, "Show information about a specific list of addresses (by IP or MAC).", func(args []string) error { - return d.Show("address", args[0]) + return d.Show(args[0]) })) + d.selector = ViewSelectorFor(&d.SessionModule, "net.show", []string{"ip", "mac", "seen", "sent", "rcvd"}, "") + return d } diff --git a/modules/net_recon_show.go b/modules/net_recon_show.go index e37ebdc3..085e1f73 100644 --- a/modules/net_recon_show.go +++ b/modules/net_recon_show.go @@ -110,16 +110,44 @@ func (d *Discovery) getRow(e *network.Endpoint, withMeta bool) [][]string { return rows } -func (d *Discovery) Show(by string, expr string) (err error) { - var targets []*network.Endpoint - if expr != "" { - if targets, err = network.ParseEndpoints(expr, d.Session.Lan); err != nil { - return err +func (d *Discovery) doFilter(target *network.Endpoint) bool { + if d.selector.Expression == nil { + return true + } + return d.selector.Expression.MatchString(target.IpAddress) || + d.selector.Expression.MatchString(target.Ip6Address) || + d.selector.Expression.MatchString(target.HwAddress) || + d.selector.Expression.MatchString(target.Hostname) || + d.selector.Expression.MatchString(target.Alias) || + d.selector.Expression.MatchString(target.Vendor) +} + +func (d *Discovery) doSelection(arg string) (err error, targets []*network.Endpoint) { + if err = d.selector.Update(); err != nil { + return + } + + if arg != "" { + if targets, err = network.ParseEndpoints(arg, d.Session.Lan); err != nil { + return } } else { targets = d.Session.Lan.List() } - switch by { + + filtered := []*network.Endpoint{} + for _, target := range targets { + if d.doFilter(target) { + filtered = append(filtered, target) + } + } + targets = filtered + + switch d.selector.SortBy { + case "ip": + sort.Sort(ByIpSorter(targets)) + case "mac": + sort.Sort(ByMacSorter(targets)) case "seen": sort.Sort(BySeenSorter(targets)) case "sent": @@ -130,6 +158,33 @@ func (d *Discovery) Show(by string, expr string) (err error) { sort.Sort(ByAddressSorter(targets)) } + // default is asc + if d.selector.Sort == "desc" { + // from https://github.com/golang/go/wiki/SliceTricks + for i := len(targets)/2 - 1; i >= 0; i-- { + opp := len(targets) - 1 - i + targets[i], targets[opp] = targets[opp], targets[i] + } + } + + if d.selector.Limit > 0 { + limit := d.selector.Limit + max := len(targets) + if limit > max { + limit = max + } + targets = targets[0:limit] + } + + return +} + +func (d *Discovery) Show(arg string) (err error) { + var targets []*network.Endpoint + if err, targets = d.doSelection(arg); err != nil { + return + } + pad := 1 if d.Session.Interface.HwAddress == d.Session.Gateway.HwAddress { pad = 0 diff --git a/modules/net_recon_sort.go b/modules/net_recon_sort.go index 8111ed9f..7958f6fc 100644 --- a/modules/net_recon_sort.go +++ b/modules/net_recon_sort.go @@ -17,11 +17,27 @@ func (a ByAddressSorter) Less(i, j int) bool { return a[i].IpAddressUint32 < a[j].IpAddressUint32 } +type ByIpSorter []*network.Endpoint + +func (a ByIpSorter) Len() int { return len(a) } +func (a ByIpSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByIpSorter) Less(i, j int) bool { + return a[i].IpAddressUint32 < a[j].IpAddressUint32 +} + +type ByMacSorter []*network.Endpoint + +func (a ByMacSorter) Len() int { return len(a) } +func (a ByMacSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByMacSorter) Less(i, j int) bool { + return a[i].HwAddress < a[j].HwAddress +} + type BySeenSorter []*network.Endpoint func (a BySeenSorter) Len() int { return len(a) } func (a BySeenSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a BySeenSorter) Less(i, j int) bool { return a[i].LastSeen.After(a[j].LastSeen) } +func (a BySeenSorter) Less(i, j int) bool { return a[i].LastSeen.Before(a[j].LastSeen) } type BySentSorter []*network.Endpoint @@ -43,7 +59,7 @@ func (a BySentSorter) Less(i, j int) bool { bTraffic = &packets.Traffic{} } - return bTraffic.Sent < aTraffic.Sent + return bTraffic.Sent > aTraffic.Sent } type ByRcvdSorter []*network.Endpoint @@ -66,5 +82,5 @@ func (a ByRcvdSorter) Less(i, j int) bool { bTraffic = &packets.Traffic{} } - return bTraffic.Received < aTraffic.Received + return bTraffic.Received > aTraffic.Received } diff --git a/modules/view_selector.go b/modules/view_selector.go new file mode 100644 index 00000000..008b438d --- /dev/null +++ b/modules/view_selector.go @@ -0,0 +1,93 @@ +package modules + +import ( + "fmt" + "regexp" + "strings" + + "github.com/bettercap/bettercap/session" +) + +type ViewSelector struct { + owner *session.SessionModule + + Filter string + filterName string + filterPrev string + Expression *regexp.Regexp + + SortBy string + sortBys map[string]bool + sortByName string + + Sort string + sortName string + + Limit int + limitName string +} + +func ViewSelectorFor(m *session.SessionModule, prefix string, sortBys []string, defSortBy string) *ViewSelector { + s := &ViewSelector{ + owner: m, + filterName: prefix + ".filter", + filterPrev: "", + sortByName: prefix + ".sort_by", + sortBys: make(map[string]bool), + sortName: prefix + ".sort", + limitName: prefix + ".limit", + + SortBy: defSortBy, + Sort: "asc", + Limit: 0, + Filter: "", + Expression: nil, + } + + for _, sb := range sortBys { + s.sortBys[sb] = true + } + + m.AddParam(session.NewStringParameter(s.filterName, "", "", "Defines a regular expression filter for "+prefix)) + m.AddParam(session.NewStringParameter(s.sortByName, defSortBy, "", "Defines sorting field for "+prefix+", available: "+strings.Join(sortBys, ", "))) + m.AddParam(session.NewStringParameter(s.sortName, "asc", "", "Defines sorting direction for "+prefix)) + m.AddParam(session.NewIntParameter(s.limitName, "0", "Defines limit for "+prefix)) + return s +} + +func (s *ViewSelector) Update() (err error) { + if err, s.Filter = s.owner.StringParam(s.filterName); err != nil { + return + } + + if s.Filter != "" { + if s.Filter != s.filterPrev { + if s.Expression, err = regexp.Compile(s.Filter); err != nil { + return + } + } + } else { + s.Expression = nil + } + s.filterPrev = s.Filter + + if err, s.SortBy = s.owner.StringParam(s.sortByName); err != nil { + return + } else if s.SortBy != "" { + if _, found := s.sortBys[s.SortBy]; !found { + return fmt.Errorf("'%s' is not valid for %s", s.SortBy, s.sortByName) + } + } + + if err, s.Sort = s.owner.StringParam(s.sortName); err != nil { + return + } else if s.Sort != "asc" && s.Sort != "desc" { + return fmt.Errorf("'%s' is not valid for %s", s.Sort, s.sortName) + } + + if err, s.Limit = s.owner.IntParam(s.limitName); err != nil { + return + } + + return +}