diff --git a/modules/events_stream/events_view.go b/modules/events_stream/events_view.go index c6e1b397..c83e0913 100644 --- a/modules/events_stream/events_view.go +++ b/modules/events_stream/events_view.go @@ -134,8 +134,8 @@ func (mod *EventsStream) Render(output io.Writer, e session.Event) { mod.viewUpdateEvent(output, e) } else if e.Tag == "gateway.change" { mod.viewGatewayEvent(output, e) - } else if e.Tag == "mdns.service" { - mod.viewMDNSEvent(output, e) + } else if strings.HasPrefix(e.Tag, "zeroconf.") { + mod.viewZeroConfEvent(output, e) } else if e.Tag != "tick" && e.Tag != "session.started" && e.Tag != "session.stopped" { fmt.Fprintf(output, "[%s] [%s] %v\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), e) } diff --git a/modules/events_stream/events_view_mdns.go b/modules/events_stream/events_view_mdns.go deleted file mode 100644 index a02331db..00000000 --- a/modules/events_stream/events_view_mdns.go +++ /dev/null @@ -1,23 +0,0 @@ -package events_stream - -import ( - "fmt" - "io" - - "github.com/bettercap/bettercap/v2/modules/zerogod" - "github.com/bettercap/bettercap/v2/session" - "github.com/evilsocket/islazy/tui" -) - -func (mod *EventsStream) viewMDNSEvent(output io.Writer, e session.Event) { - event := e.Data.(zerogod.ServiceDiscoveryEvent) - fmt.Fprintf(output, "[%s] [%s] service %s detected for %s (%s):%d with %d records\n", - e.Time.Format(mod.timeFormat), - tui.Green(e.Tag), - tui.Bold(event.Service.ServiceInstanceName()), - event.Service.AddrIPv4, - tui.Dim(event.Service.HostName), - event.Service.Port, - len(event.Service.Text), - ) -} diff --git a/modules/events_stream/events_view_zeroconf.go b/modules/events_stream/events_view_zeroconf.go new file mode 100644 index 00000000..3141c94d --- /dev/null +++ b/modules/events_stream/events_view_zeroconf.go @@ -0,0 +1,62 @@ +package events_stream + +import ( + "fmt" + "io" + "strings" + + "github.com/bettercap/bettercap/v2/modules/zerogod" + "github.com/bettercap/bettercap/v2/session" + "github.com/evilsocket/islazy/tui" +) + +func (mod *EventsStream) viewZeroConfEvent(output io.Writer, e session.Event) { + if e.Tag == "zeroconf.service" { + event := e.Data.(zerogod.ServiceDiscoveryEvent) + fmt.Fprintf(output, "[%s] [%s] service %s detected for %s (%s):%d with %d records\n", + e.Time.Format(mod.timeFormat), + tui.Green(e.Tag), + tui.Bold(event.Service.ServiceInstanceName()), + event.Service.AddrIPv4, + tui.Dim(event.Service.HostName), + event.Service.Port, + len(event.Service.Text), + ) + } else if e.Tag == "zeroconf.browsing" { + event := e.Data.(zerogod.BrowsingEvent) + source := event.Source + if event.Endpoint != nil { + source = event.Endpoint.ShortString() + } + + services := make([]string, 0) + for _, q := range event.Query.Questions { + services = append(services, tui.Yellow(string(q.Name))) + } + /* + instances := make([]string, 0) + answers := append(event.Query.Answers, event.Query.Additionals...) + for _, answer := range answers { + if answer.Class == layers.DNSClassIN && answer.Type == layers.DNSTypePTR { + instances = append(instances, tui.Green(string(answer.PTR))) + } else { + instances = append(instances, tui.Green(answer.String())) + } + } + + advPart := "" + if len(instances) > 0 { + advPart = fmt.Sprintf(" and advertising %s", strings.Join(instances, ", ")) + } + */ + + fmt.Fprintf(output, "[%s] [%s] %s is browsing for services %s\n", + e.Time.Format(mod.timeFormat), + tui.Green(e.Tag), + source, + strings.Join(services, ", "), + ) + } else { + fmt.Fprintf(output, "[%s] [%s] %v\n", e.Time.Format(mod.timeFormat), tui.Green(e.Tag), e) + } +} diff --git a/zeroconf/client.go b/modules/zerogod/zeroconf/client.go similarity index 100% rename from zeroconf/client.go rename to modules/zerogod/zeroconf/client.go diff --git a/zeroconf/connection.go b/modules/zerogod/zeroconf/connection.go similarity index 100% rename from zeroconf/connection.go rename to modules/zerogod/zeroconf/connection.go diff --git a/zeroconf/doc.go b/modules/zerogod/zeroconf/doc.go similarity index 100% rename from zeroconf/doc.go rename to modules/zerogod/zeroconf/doc.go diff --git a/zeroconf/server.go b/modules/zerogod/zeroconf/server.go similarity index 100% rename from zeroconf/server.go rename to modules/zerogod/zeroconf/server.go diff --git a/zeroconf/service.go b/modules/zerogod/zeroconf/service.go similarity index 100% rename from zeroconf/service.go rename to modules/zerogod/zeroconf/service.go diff --git a/zeroconf/utils.go b/modules/zerogod/zeroconf/utils.go similarity index 100% rename from zeroconf/utils.go rename to modules/zerogod/zeroconf/utils.go diff --git a/modules/zerogod/zerogod.go b/modules/zerogod/zerogod.go index eaad55d5..0444a76d 100644 --- a/modules/zerogod/zerogod.go +++ b/modules/zerogod/zerogod.go @@ -3,10 +3,14 @@ package zerogod import ( "github.com/bettercap/bettercap/v2/session" "github.com/bettercap/bettercap/v2/tls" + "github.com/google/gopacket" + "github.com/google/gopacket/pcap" ) type ZeroGod struct { session.SessionModule + sniffer *pcap.Handle + snifferCh chan gopacket.Packet browser *Browser advertiser *Advertiser } @@ -14,8 +18,6 @@ type ZeroGod struct { func NewZeroGod(s *session.Session) *ZeroGod { mod := &ZeroGod{ SessionModule: session.NewSessionModule("zerogod", s), - browser: nil, - advertiser: nil, } mod.SessionModule.Requires("net.recon") @@ -115,12 +117,6 @@ func (mod *ZeroGod) Configure() (err error) { return session.ErrAlreadyStarted(mod.Name()) } - if mod.browser != nil { - mod.browser.Stop(false) - } - - mod.browser = NewBrowser() - return } @@ -130,7 +126,7 @@ func (mod *ZeroGod) Start() (err error) { } // start the root discovery - if err = mod.startResolver(DNSSD_DISCOVERY_SERVICE); err != nil { + if err = mod.startDiscovery(DNSSD_DISCOVERY_SERVICE); err != nil { return err } @@ -144,14 +140,6 @@ func (mod *ZeroGod) Start() (err error) { func (mod *ZeroGod) Stop() error { return mod.SetRunning(false, func() { mod.stopAdvertiser() - if mod.browser != nil { - mod.Debug("stopping discovery") - - mod.browser.Stop(true) - - mod.Debug("stopped") - - mod.browser = nil - } + mod.stopDiscovery() }) } diff --git a/modules/zerogod/zerogod_acceptor.go b/modules/zerogod/zerogod_acceptor.go index 76fe2897..09679cca 100644 --- a/modules/zerogod/zerogod_acceptor.go +++ b/modules/zerogod/zerogod_acceptor.go @@ -7,6 +7,7 @@ import ( "net" "strings" + "github.com/evilsocket/islazy/ops" "github.com/evilsocket/islazy/tui" ) @@ -30,10 +31,12 @@ var TCP_HANDLERS = map[string]Handler{ type Acceptor struct { mod *ZeroGod srvHost string + proto string port uint16 service string tlsConfig *tls.Config - listener net.Listener + tcpListener net.Listener + udpListener *net.UDPConn running bool context context.Context ctxCancel context.CancelFunc @@ -53,9 +56,12 @@ type HandlerContext struct { func NewAcceptor(mod *ZeroGod, service string, srvHost string, port uint16, tlsConfig *tls.Config, ippAttributes map[string]string) *Acceptor { context, ctcCancel := context.WithCancel(context.Background()) + proto := ops.Ternary(strings.Contains(service, "_tcp"), "tcp", "udp").(string) + acceptor := Acceptor{ mod: mod, port: port, + proto: proto, service: service, context: context, ctxCancel: ctcCancel, @@ -72,36 +78,34 @@ func NewAcceptor(mod *ZeroGod, service string, srvHost string, port uint16, tlsC } if acceptor.handler.Handle == nil { - mod.Warning("no protocol handler found for service %s, using generic dump handler", tui.Yellow(service)) + mod.Warning("no protocol handler found for service %s, using generic %s dump handler", tui.Yellow(service), proto) acceptor.handler.Handle = handleGenericTCP } else { - mod.Info("found %s protocol handler", tui.Green(service)) + mod.Info("found %s %s protocol handler", proto, tui.Green(service)) } return &acceptor } -func (a *Acceptor) Start() (err error) { +func (a *Acceptor) startTCP() (err error) { var lc net.ListenConfig - - if a.listener, err = lc.Listen(a.context, "tcp", fmt.Sprintf("0.0.0.0:%d", a.port)); err != nil { + if a.tcpListener, err = lc.Listen(a.context, "tcp", fmt.Sprintf("0.0.0.0:%d", a.port)); err != nil { return err } - if a.tlsConfig != nil { - a.listener = tls.NewListener(a.listener, a.tlsConfig) + a.tcpListener = tls.NewListener(a.tcpListener, a.tlsConfig) } a.running = true go func() { - a.mod.Debug("tcp listener for port %d (%s) started", a.port, tui.Green(a.service)) + a.mod.Debug("%s listener for port %d (%s) started", a.proto, a.port, tui.Green(a.service)) for a.running { - if conn, err := a.listener.Accept(); err != nil { + if conn, err := a.tcpListener.Accept(); err != nil { if a.running { a.mod.Error("%v", err) } } else { - a.mod.Debug("accepted connection for service %s (port %d): %v", tui.Green(a.service), a.port, conn.RemoteAddr()) + a.mod.Debug("accepted %s connection for service %s (port %d): %v", a.proto, tui.Green(a.service), a.port, conn.RemoteAddr()) go a.handler.Handle(&HandlerContext{ service: a.service, mod: a.mod, @@ -113,17 +117,60 @@ func (a *Acceptor) Start() (err error) { }) } } - a.mod.Debug("tcp listener for port %d (%s) stopped", a.port, tui.Green(a.service)) + a.mod.Debug("%s listener for port %d (%s) stopped", a.proto, a.port, tui.Green(a.service)) }() return nil } -func (a *Acceptor) Stop() { - a.mod.Debug("stopping tcp listener for port %d", a.port) - a.running = false - a.ctxCancel() - <-a.context.Done() - a.listener.Close() - a.mod.Debug("tcp listener for port %d stopped", a.port) +func (a *Acceptor) startUDP() (err error) { + if udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", a.port)); err != nil { + return err + } else if a.udpListener, err = net.ListenUDP("udp", udpAddr); err != nil { + return err + } else { + a.running = true + go func() { + var buffer [4096]byte + + a.mod.Info("%s listener for port %d (%s) started", a.proto, a.port, tui.Green(a.service)) + + for a.running { + if n, addr, err := a.udpListener.ReadFromUDP(buffer[0:]); err != nil { + a.mod.Warning("error reading udp packet: %v", err) + } else if n <= 0 { + a.mod.Info("empty read") + } else { + a.mod.Info("%v:\n%s", addr, Dump(buffer[0:n])) + } + } + + a.mod.Info("%s listener for port %d (%s) stopped", a.proto, a.port, tui.Green(a.service)) + }() + } + + return nil +} + +func (a *Acceptor) Start() (err error) { + if a.proto == "tcp" { + return a.startTCP() + } else { + return a.startUDP() + } +} + +func (a *Acceptor) Stop() { + a.mod.Debug("stopping %s listener for port %d", a.proto, a.port) + a.running = false + + if a.proto == "tcp" { + a.ctxCancel() + <-a.context.Done() + a.tcpListener.Close() + } else { + a.udpListener.Close() + } + + a.mod.Debug("%s listener for port %d stopped", a.proto, a.port) } diff --git a/modules/zerogod/zerogod_advertise.go b/modules/zerogod/zerogod_advertise.go index cd7f1129..0876a973 100644 --- a/modules/zerogod/zerogod_advertise.go +++ b/modules/zerogod/zerogod_advertise.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "io/ioutil" + "math/rand" + "net" "os" "strings" "time" @@ -20,6 +22,27 @@ type Advertiser struct { Acceptors []*Acceptor } +func isPortAvailable(port int) bool { + address := fmt.Sprintf("127.0.0.1:%d", port) + if conn, err := net.DialTimeout("tcp", address, 10*time.Millisecond); err != nil { + return true + } else if conn == nil { + return true + } else { + conn.Close() + return false + } +} + +func isPortRequested(svc *ServiceData, services []*ServiceData) bool { + for _, other := range services { + if svc != other && svc.Port == other.Port { + return true + } + } + return false +} + func (mod *ZeroGod) loadTLSConfig() (*tls.Config, error) { var certFile string var keyFile string @@ -104,6 +127,21 @@ func (mod *ZeroGod) startAdvertiser(fileName string) error { Acceptors: make([]*Acceptor, 0), } + // fix ports + for _, svc := range advertiser.Services { + // if no external responder has been specified, check if port is available + if svc.Responder == "" { + for svc.Port == 0 || !isPortAvailable(svc.Port) || isPortRequested(svc, services) { + newPort := (rand.Intn(65535-1024) + 1024) + mod.Warning("port %d for service %s is not avaialble, trying %d ...", + svc.Port, + svc.FullName(), + newPort) + svc.Port = newPort + } + } + } + // paralleize initialization svcChan := make(chan error, numServices) for _, svc := range advertiser.Services { diff --git a/modules/zerogod/zerogod_browser.go b/modules/zerogod/zerogod_browser.go index 1511fe31..98c87bca 100644 --- a/modules/zerogod/zerogod_browser.go +++ b/modules/zerogod/zerogod_browser.go @@ -4,7 +4,7 @@ import ( "context" "sort" - "github.com/bettercap/bettercap/v2/zeroconf" + "github.com/bettercap/bettercap/v2/modules/zerogod/zeroconf" "github.com/evilsocket/islazy/tui" ) diff --git a/modules/zerogod/zerogod_discovery.go b/modules/zerogod/zerogod_discovery.go index 25ea4dff..2beefc98 100644 --- a/modules/zerogod/zerogod_discovery.go +++ b/modules/zerogod/zerogod_discovery.go @@ -1,19 +1,31 @@ package zerogod import ( + "net" "strings" + "time" + "github.com/bettercap/bettercap/v2/modules/zerogod/zeroconf" "github.com/bettercap/bettercap/v2/network" "github.com/bettercap/bettercap/v2/session" - "github.com/bettercap/bettercap/v2/zeroconf" "github.com/evilsocket/islazy/tui" + "github.com/google/gopacket" + "github.com/google/gopacket/layers" ) +// a service has been discovered type ServiceDiscoveryEvent struct { Service zeroconf.ServiceEntry `json:"service"` Endpoint *network.Endpoint `json:"endpoint"` } +// an endpoint is browsing for specific services +type BrowsingEvent struct { + Source string `json:"source"` + Query layers.DNS `json:"service"` + Endpoint *network.Endpoint `json:"endpoint"` +} + func (mod *ZeroGod) onServiceDiscovered(svc *zeroconf.ServiceEntry) { mod.Debug("%++v", *svc) @@ -21,8 +33,15 @@ func (mod *ZeroGod) onServiceDiscovered(svc *zeroconf.ServiceEntry) { svcName := strings.Replace(svc.Instance, ".local", "", 1) if !mod.browser.HasResolverFor(svcName) { mod.Debug("discovered service %s", tui.Green(svcName)) - if err := mod.startResolver(svcName); err != nil { + if ch, err := mod.browser.StartBrowsing(svcName, "local.", mod); err != nil { mod.Error("%v", err) + } else { + // start listening on this channel + go func() { + for entry := range ch { + mod.onServiceDiscovered(entry) + } + }() } } return @@ -58,17 +77,116 @@ func (mod *ZeroGod) onServiceDiscovered(svc *zeroconf.ServiceEntry) { mod.Debug("got mdns entry for unknown ip: %++v", *svc) } - session.I.Events.Add("mdns.service", event) + session.I.Events.Add("zeroconf.service", event) session.I.Refresh() } -func (mod *ZeroGod) startResolver(service string) error { +func (mod *ZeroGod) onPacket(pkt gopacket.Packet) { + mod.Debug("%++v", pkt) + + netLayer := pkt.NetworkLayer() + if netLayer == nil { + mod.Warning("not network layer in packet %+v", pkt) + return + } + var srcIP net.IP + // var dstIP net.IP + switch netLayer.LayerType() { + case layers.LayerTypeIPv4: + ip := netLayer.(*layers.IPv4) + srcIP = ip.SrcIP + // dstIP = ip.DstIP + case layers.LayerTypeIPv6: + ip := netLayer.(*layers.IPv6) + srcIP = ip.SrcIP + // dstIP = ip.DstIP + default: + mod.Warning("unexpected network layer type %v in packet %+v", netLayer.LayerType(), pkt) + return + } + + // not interested in packet generated by us + if srcIP.Equal(mod.Session.Interface.IP) || srcIP.Equal(mod.Session.Interface.IPv6) { + mod.Debug("skipping local packet") + return + } + + udp := pkt.Layer(layers.LayerTypeUDP) + if udp == nil { + mod.Warning("not udp layer in packet %+v", pkt) + return + } + + dns := layers.DNS{} + if err := dns.DecodeFromBytes(udp.LayerPayload(), gopacket.NilDecodeFeedback); err != nil { + mod.Warning("could not decode DNS (%v) in packet %+v", err, pkt) + return + } + + // since the browser is already checking for these, we are only interested in queries + numQs := len(dns.Questions) + if numQs == 0 { + mod.Debug("skipping answers only packet") + return + } + + event := BrowsingEvent{ + Source: srcIP.String(), + Query: dns, + Endpoint: mod.Session.Lan.GetByIp(srcIP.String()), + } + + if event.Endpoint == nil { + // TODO: if nil, this is probably an IPv6 only record, try to somehow check which known IPv4 it is + // TODO: make configurable? + mod.Debug("got mdns packet from unknown ip %s: %++v", srcIP, dns) + return + } + + session.I.Events.Add("zeroconf.browsing", event) + session.I.Refresh() +} + +func (mod *ZeroGod) startDiscovery(service string) (err error) { mod.Debug("starting resolver for service %s", tui.Yellow(service)) + // create passive sniffer + if mod.sniffer != nil { + mod.sniffer.Close() + } + + readTimeout := 500 * time.Millisecond + if mod.sniffer, err = network.CaptureWithTimeout(mod.Session.Interface.Name(), readTimeout); err != nil { + return err + } else if err = mod.sniffer.SetBPFFilter("udp and port 5353"); err != nil { + return err + } + // prepare source and start listening for packets + src := gopacket.NewPacketSource(mod.sniffer, mod.sniffer.LinkType()) + mod.snifferCh = src.Packets() + // start listening for new packets + go func() { + mod.Debug("sniffer started") + for pkt := range mod.snifferCh { + if !mod.Running() { + mod.Debug("end pkt loop (pkt=%v)", pkt) + break + } + mod.onPacket(pkt) + } + mod.Debug("sniffer stopped") + }() + + // create service browser + if mod.browser != nil { + mod.browser.Stop(false) + } + mod.browser = NewBrowser() + // start active browsing if ch, err := mod.browser.StartBrowsing(service, "local.", mod); err != nil { return err } else { - // start listening + // start listening for new services go func() { for entry := range ch { mod.onServiceDiscovered(entry) @@ -78,3 +196,21 @@ func (mod *ZeroGod) startResolver(service string) error { return nil } + +func (mod *ZeroGod) stopDiscovery() { + if mod.browser != nil { + mod.Debug("stopping discovery") + mod.browser.Stop(true) + mod.browser = nil + mod.Debug("discovery stopped") + } + + if mod.sniffer != nil { + mod.Debug("stopping sniffer") + mod.snifferCh <- nil + mod.sniffer.Close() + mod.sniffer = nil + mod.snifferCh = nil + mod.Debug("sniffer stopped") + } +} diff --git a/modules/zerogod/zerogod_endpoint_update.go b/modules/zerogod/zerogod_endpoint_update.go index 634af366..4e06b3f1 100644 --- a/modules/zerogod/zerogod_endpoint_update.go +++ b/modules/zerogod/zerogod_endpoint_update.go @@ -5,8 +5,8 @@ import ( "strings" "github.com/bettercap/bettercap/v2/modules/syn_scan" + "github.com/bettercap/bettercap/v2/modules/zerogod/zeroconf" "github.com/bettercap/bettercap/v2/network" - "github.com/bettercap/bettercap/v2/zeroconf" "github.com/evilsocket/islazy/str" ) diff --git a/modules/zerogod/zerogod_generic_handler.go b/modules/zerogod/zerogod_generic_tcp_handler.go similarity index 100% rename from modules/zerogod/zerogod_generic_handler.go rename to modules/zerogod/zerogod_generic_tcp_handler.go diff --git a/modules/zerogod/zerogod_ipp_handler.go b/modules/zerogod/zerogod_ipp_handler.go index f1c9865a..9526abea 100644 --- a/modules/zerogod/zerogod_ipp_handler.go +++ b/modules/zerogod/zerogod_ipp_handler.go @@ -50,6 +50,7 @@ func ippClientHandler(ctx *HandlerContext) { read, err := ctx.client.Read(buf) if err != nil { if err == io.EOF { + ctx.mod.Debug("EOF, client %s disconnected", clientIP) return } ctx.mod.Warning("error while reading from %v: %v", clientIP, err) @@ -67,12 +68,12 @@ func ippClientHandler(ctx *HandlerContext) { reader := bufio.NewReader(bytes.NewReader(raw_req)) http_req, err := http.ReadRequest(reader) if err != nil { - ctx.mod.Error("error while parsing http request from %v: %v", clientIP, err) + ctx.mod.Error("error while parsing http request from %v: %v\n%s", clientIP, err, Dump(raw_req)) return } clientUA := http_req.UserAgent() - ctx.mod.Debug("%v -> %s", clientIP, tui.Green(clientUA)) + ctx.mod.Info("%v -> %s", clientIP, tui.Green(clientUA)) ipp_body, err := ippReadRequestBody(ctx, http_req) if err != nil { @@ -92,8 +93,14 @@ func ippClientHandler(ctx *HandlerContext) { ipp_op_name = name } - ctx.mod.Info("%s <- %s (%s) %s", + reqUsername := tui.Dim("") + if value, found := ipp_req.OperationAttributes["requesting-user-name"]; found { + reqUsername = tui.Blue(value.(string)) + } + + ctx.mod.Info("%s <- %s@%s (%s) %s", tui.Yellow(ctx.service), + reqUsername, clientIP, tui.Green(clientUA), tui.Bold(ipp_op_name)) diff --git a/modules/zerogod/zerogod_save.go b/modules/zerogod/zerogod_save.go index d95c0fe1..a0c7eb85 100644 --- a/modules/zerogod/zerogod_save.go +++ b/modules/zerogod/zerogod_save.go @@ -5,7 +5,7 @@ import ( "fmt" "io/ioutil" - "github.com/bettercap/bettercap/v2/zeroconf" + "github.com/bettercap/bettercap/v2/modules/zerogod/zeroconf" "github.com/evilsocket/islazy/str" yaml "gopkg.in/yaml.v3" ) diff --git a/modules/zerogod/zerogod_service.go b/modules/zerogod/zerogod_service.go index 785b005a..200e22fe 100644 --- a/modules/zerogod/zerogod_service.go +++ b/modules/zerogod/zerogod_service.go @@ -5,7 +5,7 @@ import ( "net" "strings" - "github.com/bettercap/bettercap/v2/zeroconf" + "github.com/bettercap/bettercap/v2/modules/zerogod/zeroconf" "github.com/evilsocket/islazy/tui" ) diff --git a/modules/zerogod/zerogod_show.go b/modules/zerogod/zerogod_show.go index 44093c7d..92140d14 100644 --- a/modules/zerogod/zerogod_show.go +++ b/modules/zerogod/zerogod_show.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "github.com/evilsocket/islazy/ops" "github.com/evilsocket/islazy/str" "github.com/evilsocket/islazy/tui" ) @@ -20,7 +21,10 @@ func (mod *ZeroGod) show(filter string, withData bool) error { for _, entry := range entries { if endpoint := mod.Session.Lan.GetByIp(entry.Address); endpoint != nil { - fmt.Fprintf(mod.Session.Events.Stdout, "* %s (%s)\n", tui.Bold(endpoint.IpAddress), tui.Dim(endpoint.Vendor)) + fmt.Fprintf(mod.Session.Events.Stdout, "* %s (%s)%s\n", + tui.Bold(endpoint.IpAddress), + tui.Dim(endpoint.Vendor), + ops.Ternary(endpoint.Hostname == "", "", " "+tui.Bold(endpoint.Hostname))) } else { fmt.Fprintf(mod.Session.Events.Stdout, "* %s\n", tui.Bold(entry.Address)) } diff --git a/network/lan_endpoint.go b/network/lan_endpoint.go index 15925928..e90681cc 100644 --- a/network/lan_endpoint.go +++ b/network/lan_endpoint.go @@ -161,6 +161,27 @@ func (t *Endpoint) String() string { return fmt.Sprintf("%s%s (%s) - %s", ipPart, t.HwAddress, t.Vendor, tui.Bold(name)) } +func (t *Endpoint) ShortString() string { + parts := []string{ + t.IpAddress, + } + + if t.Vendor != "" { + parts = append(parts, tui.Dim(fmt.Sprintf("(%s)", t.Vendor))) + } + + name := t.Hostname + if t.Alias != "" { + name = t.Alias + } + + if name != "" { + parts = append(parts, tui.Bold(name)) + } + + return strings.Join(parts, " ") +} + func (t *Endpoint) OnMeta(meta map[string]string) { host := "" for k, v := range meta {