From 91d360327aacfb1e3cc20d70fe0ae43abbab7112 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Thu, 19 Sep 2024 16:33:44 +0200 Subject: [PATCH] it works! --- go.mod | 2 + go.sum | 9 + modules/events_stream/events_view_mdns.go | 10 +- modules/mdns/client.go | 475 ---------------------- modules/mdns/mdns_advertise.go | 93 +++-- modules/mdns/mdns_discovery.go | 172 +++++--- modules/mdns/mdns_show.go | 15 +- modules/mdns/server.go | 306 -------------- modules/mdns/service.go | 317 --------------- network/lan.go | 6 +- printer.yml | 210 ++++------ printer2.yml | 187 +++++++++ 12 files changed, 446 insertions(+), 1356 deletions(-) delete mode 100644 modules/mdns/client.go delete mode 100644 modules/mdns/server.go delete mode 100644 modules/mdns/service.go create mode 100644 printer2.yml diff --git a/go.mod b/go.mod index 8620fff3..71d834bb 100644 --- a/go.mod +++ b/go.mod @@ -40,12 +40,14 @@ require ( require ( github.com/antchfx/xpath v1.3.1 // indirect + github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/chzyer/logex v1.2.1 // indirect github.com/elazarl/goproxy/ext v0.0.0-20210110162100-a92cc753f88e // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/mock v1.6.0 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-querystring v1.1.0 // indirect + github.com/grandcat/zeroconf v1.0.0 // indirect github.com/josharian/native v1.1.0 // indirect github.com/kr/binarydist v0.1.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect diff --git a/go.sum b/go.sum index fd09f526..40606672 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/bettercap/readline v0.0.0-20210228151553-655e48bcb7bf h1:pwGPRc5PIp4K github.com/bettercap/readline v0.0.0-20210228151553-655e48bcb7bf/go.mod h1:03rWiUf60r1miMVzMEtgtkq7RdZniecZFw3/Zgvyxcs= github.com/bettercap/recording v0.0.0-20190408083647-3ce1dcf032e3 h1:pC4ZAk7UtDIbrRKzMMiIL1TVkiKlgtgcJodqKB53Rl4= github.com/bettercap/recording v0.0.0-20190408083647-3ce1dcf032e3/go.mod h1:kqVwnx6DKuOHMZcBnzsgp2Lq2JZHDtFtm92b5hxdRaM= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= @@ -53,6 +55,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE= +github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs= github.com/hashicorp/go-bexpr v0.1.14 h1:uKDeyuOhWhT1r5CiMTjdVY4Aoxdxs6EtwgTGnlosyp4= github.com/hashicorp/go-bexpr v0.1.14/go.mod h1:gN7hRKB3s7yT+YvTdnhZVLTENejvhlkZ8UE4YVBS+Q8= github.com/inconshreveable/go-vhost v1.0.0 h1:IK4VZTlXL4l9vz2IZoiSFbYaaqUW7dXJAiPriUN5Ur8= @@ -88,6 +92,7 @@ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mgutz/logxi v0.0.0-20161027140823-aebf8a7d67ab h1:n8cgpHzJ5+EDyDri2s/GC7a9+qK3/YEGnBsd0uS/8PY= github.com/mgutz/logxi v0.0.0-20161027140823-aebf8a7d67ab/go.mod h1:y1pL58r5z2VvAjeG1VLGc8zOQgSOzbKN7kMHPvFXJ+8= +github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/miekg/dns v1.1.61 h1:nLxbwF3XxhwVSm8g9Dghm9MHPaUZuqhPiGL+675ZmEs= github.com/miekg/dns v1.1.61/go.mod h1:mnAarhS3nWaW+NVP2wTkYVIZyHNJ098SJZUki3eykwQ= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -133,6 +138,8 @@ golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190310074541-c10a0554eabf/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -144,6 +151,7 @@ golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -167,6 +175,7 @@ golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= diff --git a/modules/events_stream/events_view_mdns.go b/modules/events_stream/events_view_mdns.go index dffbb158..c0cd70a9 100644 --- a/modules/events_stream/events_view_mdns.go +++ b/modules/events_stream/events_view_mdns.go @@ -11,13 +11,13 @@ import ( func (mod *EventsStream) viewMDNSEvent(output io.Writer, e session.Event) { event := e.Data.(mdns.ServiceDiscoveryEvent) - fmt.Fprintf(output, "[%s] [%s] service %s detected for %s (%s):%d : %s\n", + 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.Name), - event.Service.AddrV4.String(), - tui.Dim(event.Service.Host), + tui.Bold(event.Service.ServiceInstanceName()), + event.Service.AddrIPv4, + tui.Dim(event.Service.HostName), event.Service.Port, - event.Service.Info, + len(event.Service.Text), ) } diff --git a/modules/mdns/client.go b/modules/mdns/client.go deleted file mode 100644 index 02f4200a..00000000 --- a/modules/mdns/client.go +++ /dev/null @@ -1,475 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MIT - -package mdns - -import ( - "context" - "fmt" - "log" - "net" - "strings" - "sync/atomic" - "time" - - "github.com/miekg/dns" - "golang.org/x/net/ipv4" - "golang.org/x/net/ipv6" -) - -// ServiceEntry is returned after we query for a service -type ServiceEntry struct { - Name string - Host string - AddrV4 net.IP - AddrV6 net.IP // @Deprecated - AddrV6IPAddr *net.IPAddr - Port int - Info string - InfoFields []string - - Addr net.IP // @Deprecated - - hasTXT bool - sent bool -} - -// complete is used to check if we have all the info we need -func (s *ServiceEntry) complete() bool { - return (s.AddrV4 != nil || s.AddrV6 != nil || s.Addr != nil) && s.Port != 0 && s.hasTXT -} - -// QueryParam is used to customize how a Lookup is performed -type QueryParam struct { - Module *MDNSModule - Service string // Service to lookup - Domain string // Lookup domain, default "local" - Timeout time.Duration // Lookup timeout, default 1 second - Interface *net.Interface // Multicast interface to use - Entries chan<- *ServiceEntry // Entries Channel - WantUnicastResponse bool // Unicast response desired, as per 5.4 in RFC - DisableIPv4 bool // Whether to disable usage of IPv4 for MDNS operations. Does not affect discovered addresses. - DisableIPv6 bool // Whether to disable usage of IPv6 for MDNS operations. Does not affect discovered addresses. - Logger *log.Logger // Optionally provide a *log.Logger to better manage log output. -} - -// DefaultParams is used to return a default set of QueryParam's -func DefaultParams(service string) *QueryParam { - return &QueryParam{ - Service: service, - Domain: "local", - Timeout: time.Second, - Entries: make(chan *ServiceEntry), - WantUnicastResponse: false, // TODO(reddaly): Change this default. - DisableIPv4: false, - DisableIPv6: false, - } -} - -// Query looks up a given service, in a domain, waiting at most -// for a timeout before finishing the query. The results are streamed -// to a channel. Sends will not block, so clients should make sure to -// either read or buffer. -func Query(params *QueryParam) error { - return QueryContext(context.Background(), params) -} - -// QueryContext looks up a given service, in a domain, waiting at most -// for a timeout before finishing the query. The results are streamed -// to a channel. Sends will not block, so clients should make sure to -// either read or buffer. QueryContext will attempt to stop the query -// on cancellation. -func QueryContext(ctx context.Context, params *QueryParam) error { - if params.Logger == nil { - params.Logger = log.Default() - } - // Create a new client - client, err := newClient(!params.DisableIPv4, !params.DisableIPv6, params.Logger) - if err != nil { - return err - } - defer client.Close() - - go func() { - select { - case <-ctx.Done(): - client.Close() - case <-client.closedCh: - return - } - }() - - // Set the multicast interface - if params.Interface != nil { - if err := client.setInterface(params.Interface); err != nil { - return err - } - } - - // Ensure defaults are set - if params.Domain == "" { - params.Domain = "local" - } - if params.Timeout == 0 { - params.Timeout = time.Second - } - - // Run the query - return client.query(params) -} - -// Lookup is the same as Query, however it uses all the default parameters -func Lookup(service string, entries chan<- *ServiceEntry) error { - params := DefaultParams(service) - params.Entries = entries - return Query(params) -} - -// Client provides a query interface that can be used to -// search for service providers using mDNS -type client struct { - use_ipv4 bool - use_ipv6 bool - - ipv4UnicastConn *net.UDPConn - ipv6UnicastConn *net.UDPConn - - ipv4MulticastConn *net.UDPConn - ipv6MulticastConn *net.UDPConn - - closed int32 - closedCh chan struct{} // TODO(reddaly): This doesn't appear to be used. - - log *log.Logger -} - -// NewClient creates a new mdns Client that can be used to query -// for records -func newClient(v4 bool, v6 bool, logger *log.Logger) (*client, error) { - if !v4 && !v6 { - return nil, fmt.Errorf("Must enable at least one of IPv4 and IPv6 querying") - } - - // TODO(reddaly): At least attempt to bind to the port required in the spec. - // Create a IPv4 listener - var uconn4 *net.UDPConn - var uconn6 *net.UDPConn - var mconn4 *net.UDPConn - var mconn6 *net.UDPConn - var err error - - // Establish unicast connections - if v4 { - uconn4, err = net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) - if err != nil { - logger.Printf("[ERR] mdns: Failed to bind to udp4 port: %v", err) - } - } - if v6 { - uconn6, err = net.ListenUDP("udp6", &net.UDPAddr{IP: net.IPv6zero, Port: 0}) - if err != nil { - logger.Printf("[ERR] mdns: Failed to bind to udp6 port: %v", err) - } - } - if uconn4 == nil && uconn6 == nil { - return nil, fmt.Errorf("failed to bind to any unicast udp port") - } - - // Establish multicast connections - if v4 { - mconn4, err = net.ListenMulticastUDP("udp4", nil, ipv4Addr) - if err != nil { - logger.Printf("[ERR] mdns: Failed to bind to udp4 port: %v", err) - } - } - if v6 { - mconn6, err = net.ListenMulticastUDP("udp6", nil, ipv6Addr) - if err != nil { - logger.Printf("[ERR] mdns: Failed to bind to udp6 port: %v", err) - } - } - if mconn4 == nil && mconn6 == nil { - return nil, fmt.Errorf("failed to bind to any multicast udp port") - } - - // Check that unicast and multicast connections have been made for IPv4 and IPv6 - // and disable the respective protocol if not. - if uconn4 == nil || mconn4 == nil { - logger.Printf("[INFO] mdns: Failed to listen to both unicast and multicast on IPv4") - uconn4 = nil - mconn4 = nil - v4 = false - } - if uconn6 == nil || mconn6 == nil { - logger.Printf("[INFO] mdns: Failed to listen to both unicast and multicast on IPv6") - uconn6 = nil - mconn6 = nil - v6 = false - } - if !v4 && !v6 { - return nil, fmt.Errorf("at least one of IPv4 and IPv6 must be enabled for querying") - } - - c := &client{ - use_ipv4: v4, - use_ipv6: v6, - ipv4MulticastConn: mconn4, - ipv6MulticastConn: mconn6, - ipv4UnicastConn: uconn4, - ipv6UnicastConn: uconn6, - closedCh: make(chan struct{}), - log: logger, - } - return c, nil -} - -// Close is used to cleanup the client -func (c *client) Close() error { - if !atomic.CompareAndSwapInt32(&c.closed, 0, 1) { - // something else already closed it - return nil - } - - c.log.Printf("[INFO] mdns: Closing client %v", *c) - close(c.closedCh) - - if c.ipv4UnicastConn != nil { - c.ipv4UnicastConn.Close() - } - if c.ipv6UnicastConn != nil { - c.ipv6UnicastConn.Close() - } - if c.ipv4MulticastConn != nil { - c.ipv4MulticastConn.Close() - } - if c.ipv6MulticastConn != nil { - c.ipv6MulticastConn.Close() - } - - return nil -} - -// setInterface is used to set the query interface, uses system -// default if not provided -func (c *client) setInterface(iface *net.Interface) error { - if c.use_ipv4 { - p := ipv4.NewPacketConn(c.ipv4UnicastConn) - if err := p.SetMulticastInterface(iface); err != nil { - return err - } - p = ipv4.NewPacketConn(c.ipv4MulticastConn) - if err := p.SetMulticastInterface(iface); err != nil { - return err - } - } - if c.use_ipv6 { - p2 := ipv6.NewPacketConn(c.ipv6UnicastConn) - if err := p2.SetMulticastInterface(iface); err != nil { - return err - } - p2 = ipv6.NewPacketConn(c.ipv6MulticastConn) - if err := p2.SetMulticastInterface(iface); err != nil { - return err - } - } - return nil -} - -// msgAddr carries the message and source address from recv to message processing. -type msgAddr struct { - msg *dns.Msg - src *net.UDPAddr -} - -// query is used to perform a lookup and stream results -func (c *client) query(params *QueryParam) error { - // Create the service name - serviceAddr := fmt.Sprintf("%s.%s.", trimDot(params.Service), trimDot(params.Domain)) - - // Start listening for response packets - msgCh := make(chan *msgAddr, 32) - if c.use_ipv4 { - go c.recv(c.ipv4UnicastConn, msgCh) - go c.recv(c.ipv4MulticastConn, msgCh) - } - if c.use_ipv6 { - go c.recv(c.ipv6UnicastConn, msgCh) - go c.recv(c.ipv6MulticastConn, msgCh) - } - - // Send the query - m := new(dns.Msg) - m.SetQuestion(serviceAddr, dns.TypePTR) - // RFC 6762, section 18.12. Repurposing of Top Bit of qclass in Question - // Section - // - // In the Question Section of a Multicast DNS query, the top bit of the qclass - // field is used to indicate that unicast responses are preferred for this - // particular question. (See Section 5.4.) - if params.WantUnicastResponse { - m.Question[0].Qclass |= 1 << 15 - } - m.RecursionDesired = true - if err := c.sendQuery(m); err != nil { - return err - } - - // Map the in-progress responses - inprogress := make(map[string]*ServiceEntry) - - // Listen until we reach the timeout - finish := time.After(params.Timeout) - for { - select { - case resp := <-msgCh: - var inp *ServiceEntry - for _, answer := range append(resp.msg.Answer, resp.msg.Extra...) { - // TODO(reddaly): Check that response corresponds to serviceAddr? - switch rr := answer.(type) { - case *dns.PTR: - // Create new entry for this - inp = ensureName(inprogress, rr.Ptr) - - case *dns.SRV: - // Check for a target mismatch - if rr.Target != rr.Hdr.Name { - alias(inprogress, rr.Hdr.Name, rr.Target) - } - - // Get the port - inp = ensureName(inprogress, rr.Hdr.Name) - inp.Host = rr.Target - inp.Port = int(rr.Port) - - case *dns.TXT: - // Pull out the txt - inp = ensureName(inprogress, rr.Hdr.Name) - inp.Info = strings.Join(rr.Txt, "|") - inp.InfoFields = rr.Txt - inp.hasTXT = true - - case *dns.A: - // Pull out the IP - inp = ensureName(inprogress, rr.Hdr.Name) - inp.Addr = rr.A // @Deprecated - inp.AddrV4 = rr.A - - case *dns.AAAA: - // Pull out the IP - inp = ensureName(inprogress, rr.Hdr.Name) - inp.Addr = rr.AAAA // @Deprecated - inp.AddrV6 = rr.AAAA // @Deprecated - inp.AddrV6IPAddr = &net.IPAddr{IP: rr.AAAA} - // link-local IPv6 addresses must be qualified with a zone (interface). Zone is - // specific to this machine/network-namespace and so won't be carried in the - // mDNS message itself. We borrow the zone from the source address of the UDP - // packet, as the link-local address should be valid on that interface. - if rr.AAAA.IsLinkLocalUnicast() || rr.AAAA.IsLinkLocalMulticast() { - inp.AddrV6IPAddr.Zone = resp.src.Zone - } - } - - if inp == nil { - params.Module.Debug("no inp for %v", answer) - continue - } - - // Check if this entry is complete - if inp.complete() { - if inp.sent { - continue - } - inp.sent = true - select { - case params.Entries <- inp: - default: - } - } else { - // Fire off a node specific query - params.Module.Debug("sending query for service %s", inp.Name) - m := new(dns.Msg) - m.SetQuestion(inp.Name, dns.TypePTR) - m.RecursionDesired = true - if err := c.sendQuery(m); err != nil { - params.Module.Error("failed to query instance %s: %v", inp.Name, err) - } - time.Sleep(time.Duration(1) * time.Millisecond) - } - } - case <-finish: - return nil - } - } -} - -// sendQuery is used to multicast a query out -func (c *client) sendQuery(q *dns.Msg) error { - buf, err := q.Pack() - if err != nil { - return err - } - if c.ipv4UnicastConn != nil { - _, err = c.ipv4UnicastConn.WriteToUDP(buf, ipv4Addr) - if err != nil { - return err - } - } - if c.ipv6UnicastConn != nil { - _, err = c.ipv6UnicastConn.WriteToUDP(buf, ipv6Addr) - if err != nil { - return err - } - } - return nil -} - -// recv is used to receive until we get a shutdown -func (c *client) recv(l *net.UDPConn, msgCh chan *msgAddr) { - if l == nil { - return - } - buf := make([]byte, 65536) - for atomic.LoadInt32(&c.closed) == 0 { - n, addr, err := l.ReadFromUDP(buf) - - if atomic.LoadInt32(&c.closed) == 1 { - return - } - - if err != nil { - c.log.Printf("[ERR] mdns: Failed to read packet: %v", err) - continue - } - msg := new(dns.Msg) - if err := msg.Unpack(buf[:n]); err != nil { - c.log.Printf("[ERR] mdns: Failed to unpack packet: %v", err) - continue - } - select { - case msgCh <- &msgAddr{ - msg: msg, - src: addr, - }: - case <-c.closedCh: - return - } - } -} - -// ensureName is used to ensure the named node is in progress -func ensureName(inprogress map[string]*ServiceEntry, name string) *ServiceEntry { - if inp, ok := inprogress[name]; ok { - return inp - } - inp := &ServiceEntry{ - Name: name, - } - inprogress[name] = inp - return inp -} - -// alias is used to setup an alias between two entries -func alias(inprogress map[string]*ServiceEntry, src, dst string) { - srcEntry := ensureName(inprogress, src) - inprogress[dst] = srcEntry -} diff --git a/modules/mdns/mdns_advertise.go b/modules/mdns/mdns_advertise.go index ce4884de..893a1dc7 100644 --- a/modules/mdns/mdns_advertise.go +++ b/modules/mdns/mdns_advertise.go @@ -4,34 +4,57 @@ import ( "errors" "fmt" "io/ioutil" - "net" "os" "strings" - "github.com/miekg/dns" + "github.com/evilsocket/islazy/tui" + "github.com/grandcat/zeroconf" yaml "gopkg.in/yaml.v3" ) +/* type multiService struct { + mod *MDNSModule services []*MDNSService } func (m multiService) Records(q dns.Question) []dns.RR { records := make([]dns.RR, 0) - for _, svc := range m.services { - records = append(records, svc.Records(q)...) + m.mod.Debug("QUESTION: %+v", q) + + if strings.HasPrefix(q.Name, "_services._dns-sd._udp.") { + for _, svc := range m.services { + records = append(records, svc.Records(q)...) + } + } else { + for _, svc := range m.services { + if svcRecords := svc.Records(q); len(svcRecords) > 0 { + records = svcRecords + break + } + } + } + + if num := len(records); num == 0 { + m.mod.Debug("unhandled service %+v", q) + } else { + m.mod.Info("responding to query %s with %d records", tui.Green(q.Name), num) + if q.Name == "_services._dns-sd._udp.local." { + for _, r := range records { + m.mod.Info(" %+v", r) + } + } } return records } +*/ type Advertiser struct { Filename string - Mapping map[string]ServiceEntry - - Service multiService - Server *Server + Mapping map[string]zeroconf.ServiceEntry + Servers map[string]*zeroconf.Server } func (mod *MDNSModule) startAdvertiser(fileName string) error { @@ -44,7 +67,7 @@ func (mod *MDNSModule) startAdvertiser(fileName string) error { return fmt.Errorf("could not read %s: %v", fileName, err) } - mapping := make(map[string]ServiceEntry) + mapping := make(map[string]zeroconf.ServiceEntry) if err = yaml.Unmarshal(data, &mapping); err != nil { return fmt.Errorf("could not deserialize %s: %v", fileName, err) } @@ -53,54 +76,41 @@ func (mod *MDNSModule) startAdvertiser(fileName string) error { if err != nil { return fmt.Errorf("could not get hostname: %v", err) } - if !strings.HasSuffix(hostName, ".") { hostName += "." } - mod.Info("loaded %d services from %s, advertising with: host=%s ipv4=%s ipv6=%s", + ifName := mod.Session.Interface.Name() + /* + iface, err := net.InterfaceByName(ifName) + if err != nil { + return fmt.Errorf("error getting interface %s: %v", ifName, err) + } + */ + + mod.Info("loaded %d services from %s, advertising with host=%s iface=%s ipv4=%s ipv6=%s", len(mapping), fileName, hostName, + ifName, mod.Session.Interface.IpAddress, mod.Session.Interface.Ip6Address) advertiser := &Advertiser{ Filename: fileName, Mapping: mapping, - Service: multiService{ - services: make([]*MDNSService, 0), - }, + Servers: make(map[string]*zeroconf.Server), } - for _, svcData := range mapping { - svcParts := strings.SplitN(svcData.Name, ".", 2) - svcInstance := svcParts[0] - svcService := strings.Replace(svcParts[1], ".local.", "", 1) - - // TODO: patch UUID - - service, err := NewMDNSService( - mod, - svcInstance, - svcService, - "local.", - hostName, - svcData.Port, - []net.IP{ - mod.Session.Interface.IP, - mod.Session.Interface.IPv6, - }, - svcData.InfoFields) + for key, svc := range mapping { + server, err := zeroconf.Register(svc.Instance, svc.Service, svc.Domain, svc.Port, svc.Text, nil) if err != nil { - return fmt.Errorf("could not create service %s: %v", svcData.Name, err) + return fmt.Errorf("could not create service %s: %v", svc.Instance, err) } - advertiser.Service.services = append(advertiser.Service.services, service) - } + mod.Info("advertising service %s", tui.Yellow(svc.Service)) - if advertiser.Server, err = NewServer(mod, &Config{Zone: advertiser.Service}); err != nil { - return fmt.Errorf("could not create server: %v", err) + advertiser.Servers[key] = server } mod.advertiser = advertiser @@ -117,7 +127,12 @@ func (mod *MDNSModule) stopAdvertiser() error { mod.Info("stopping %d services ...", len(mod.advertiser.Mapping)) - mod.advertiser.Server.Shutdown() + for key, server := range mod.advertiser.Servers { + mod.Info("stopping %s ...", key) + server.Shutdown() + } + + mod.Info("all services stopped") mod.advertiser = nil return nil diff --git a/modules/mdns/mdns_discovery.go b/modules/mdns/mdns_discovery.go index 6fbfabb6..5c3fe437 100644 --- a/modules/mdns/mdns_discovery.go +++ b/modules/mdns/mdns_discovery.go @@ -1,31 +1,34 @@ package mdns import ( + "context" "fmt" "strings" - "time" "github.com/bettercap/bettercap/v2/modules/syn_scan" "github.com/bettercap/bettercap/v2/network" "github.com/bettercap/bettercap/v2/session" "github.com/evilsocket/islazy/str" "github.com/evilsocket/islazy/tui" + + "github.com/grandcat/zeroconf" ) type MDNSModule struct { session.SessionModule - advertiser *Advertiser - discoChannel chan *ServiceEntry - mapping map[string]map[string]*ServiceEntry + advertiser *Advertiser + rootContext context.Context + rootCancel context.CancelFunc + resolvers map[string]*zeroconf.Resolver + mapping map[string]map[string]*zeroconf.ServiceEntry } func NewMDNSModule(s *session.Session) *MDNSModule { mod := &MDNSModule{ SessionModule: session.NewSessionModule("mdns", s), - discoChannel: make(chan *ServiceEntry), - mapping: make(map[string]map[string]*ServiceEntry), - advertiser: nil, + mapping: make(map[string]map[string]*zeroconf.ServiceEntry), + resolvers: make(map[string]*zeroconf.Resolver), } mod.SessionModule.Requires("net.recon") @@ -108,42 +111,47 @@ func (mod *MDNSModule) Configure() (err error) { return session.ErrAlreadyStarted(mod.Name()) } - if mod.discoChannel != nil { - close(mod.discoChannel) + if mod.rootContext != nil { + mod.rootCancel() } - mod.discoChannel = make(chan *ServiceEntry) - mod.mapping = make(map[string]map[string]*ServiceEntry) + + mod.mapping = make(map[string]map[string]*zeroconf.ServiceEntry) + mod.resolvers = make(map[string]*zeroconf.Resolver) + mod.rootContext, mod.rootCancel = context.WithCancel(context.Background()) return } type ServiceDiscoveryEvent struct { - Service ServiceEntry `json:"service"` - Endpoint *network.Endpoint `json:"endpoint"` + Service zeroconf.ServiceEntry `json:"service"` + Endpoint *network.Endpoint `json:"endpoint"` } -func (mod *MDNSModule) updateEndpointMeta(address string, endpoint *network.Endpoint, svc *ServiceEntry) { +func (mod *MDNSModule) updateEndpointMeta(address string, endpoint *network.Endpoint, svc *zeroconf.ServiceEntry) { mod.Debug("found endpoint %s for address %s", endpoint.HwAddress, address) + // TODO: this is shit and needs to be refactored + // update mdns metadata meta := make(map[string]string) - svcType := strings.SplitN(svc.Name, ".", 2)[1] + svcType := svc.Service - meta[fmt.Sprintf("mdns:%s:name", svcType)] = svc.Name - meta[fmt.Sprintf("mdns:%s:hostname", svcType)] = svc.Host + meta[fmt.Sprintf("mdns:%s:name", svcType)] = svc.ServiceName() + meta[fmt.Sprintf("mdns:%s:hostname", svcType)] = svc.HostName - if svc.AddrV4 != nil { - meta[fmt.Sprintf("mdns:%s:ipv4", svcType)] = svc.AddrV4.String() + // TODO: include all + if len(svc.AddrIPv4) > 0 { + meta[fmt.Sprintf("mdns:%s:ipv4", svcType)] = svc.AddrIPv4[0].String() } - if svc.AddrV6 != nil { - meta[fmt.Sprintf("mdns:%s:ipv6", svcType)] = svc.AddrV6.String() + if len(svc.AddrIPv6) > 0 { + meta[fmt.Sprintf("mdns:%s:ipv6", svcType)] = svc.AddrIPv6[0].String() } meta[fmt.Sprintf("mdns:%s:port", svcType)] = fmt.Sprintf("%d", svc.Port) - for _, field := range svc.InfoFields { + for _, field := range svc.Text { field = str.Trim(field) if len(field) == 0 { continue @@ -180,86 +188,122 @@ func (mod *MDNSModule) updateEndpointMeta(address string, endpoint *network.Endp endpoint.Meta.Set("ports", ports) } -func (mod *MDNSModule) onServiceDiscovered(svc *ServiceEntry) { - mod.Debug("discovered service %s (%s) [%v / %v]:%d", tui.Green(svc.Name), tui.Dim(svc.Host), svc.AddrV4, svc.AddrV6, svc.Port) +func (mod *MDNSModule) onServiceDiscovered(svc *zeroconf.ServiceEntry) { + mod.Debug("%++v", *svc) + + if svc.Service == "_services._dns-sd._udp" && len(svc.AddrIPv4) == 0 && len(svc.AddrIPv6) == 0 { + svcName := strings.Replace(svc.Instance, ".local", "", 1) + if _, found := mod.resolvers[svcName]; !found { + mod.Debug("discovered service %s", tui.Green(svcName)) + if err := mod.startResolver(svcName); err != nil { + mod.Error("%v", err) + } + } + return + } + + mod.Debug("discovered instance %s (%s) [%v / %v]:%d", + tui.Green(svc.ServiceInstanceName()), + tui.Dim(svc.HostName), + svc.AddrIPv4, + svc.AddrIPv6, + svc.Port) event := ServiceDiscoveryEvent{ Service: *svc, Endpoint: nil, } - addresses := []string{} - if svc.AddrV4 != nil { - addresses = append(addresses, svc.AddrV4.String()) - } - if svc.AddrV6 != nil { - addresses = append(addresses, svc.AddrV6.String()) - } + addresses := append(svc.AddrIPv4, svc.AddrIPv6...) - for _, address := range addresses { + for _, ip := range addresses { + address := ip.String() if event.Endpoint = mod.Session.Lan.GetByIp(address); event.Endpoint != nil { // update endpoint metadata mod.updateEndpointMeta(address, event.Endpoint, svc) // update internal module mapping if ipServices, found := mod.mapping[address]; found { - ipServices[svc.Name] = svc + ipServices[svc.ServiceInstanceName()] = svc } else { - mod.mapping[address] = map[string]*ServiceEntry{ - svc.Name: svc, + mod.mapping[address] = map[string]*zeroconf.ServiceEntry{ + svc.ServiceInstanceName(): svc, } } break - } else { - mod.Warning("got mdns entry for unknown ip %s", svc.AddrV4) } } + if event.Endpoint == nil { + // TODO: this is probably an IPv6 only record, try to somehow check which known IPv4 it is + mod.Debug("got mdns entry for unknown ip: %++v", *svc) + } + session.I.Events.Add("mdns.service", event) session.I.Refresh() } +func (mod *MDNSModule) startResolver(service string) error { + mod.Debug("starting resolver for service %s", tui.Yellow(service)) + + resolver, err := zeroconf.NewResolver(nil) + if err != nil { + return err + } + + // start listening + channel := make(chan *zeroconf.ServiceEntry) + go func() { + for entry := range channel { + mod.onServiceDiscovered(entry) + } + }() + + // start browsing + go func() { + err = resolver.Browse(mod.rootContext, service, "local.", channel) + if err != nil { + mod.Error("%v", err) + } + mod.Debug("resolver for service %s stopped", tui.Yellow(service)) + }() + + mod.resolvers[service] = resolver + + return nil +} + func (mod *MDNSModule) Start() (err error) { if err = mod.Configure(); err != nil { return err } - // start the discovery - service := "_services._dns-sd._udp" - params := DefaultParams(service) - - params.Module = mod - params.Service = service - params.Domain = "local" - params.Entries = mod.discoChannel - params.DisableIPv6 = true // https://github.com/hashicorp/mdns/issues/35 - params.Timeout = time.Duration(10) * time.Minute - - go func() { - mod.Info("starting query routine ...") - if err := Query(params); err != nil { - mod.Error("service discovery query: %v", err) - } - mod.Info("stopping query routine ...") - }() + // start the root discovery + if err = mod.startResolver("_services._dns-sd._udp"); err != nil { + return err + } return mod.SetRunning(true, func() { - mod.Info("mDNS service discovery started") + mod.Info("service discovery started") - for entry := range mod.discoChannel { - mod.onServiceDiscovered(entry) - } + <-mod.rootContext.Done() - mod.Info("mDNS service discovery stopped") + mod.Info("service discovery stopped") }) } func (mod *MDNSModule) Stop() error { return mod.SetRunning(false, func() { - if mod.discoChannel != nil { - mod.Info("closing mDNS discovery channel") - close(mod.discoChannel) - mod.discoChannel = nil + if mod.rootCancel != nil { + mod.Debug("stopping mDNS discovery") + + mod.rootCancel() + <-mod.rootContext.Done() + + mod.Debug("stopped") + + mod.rootContext = nil + mod.rootCancel = nil } }) } diff --git a/modules/mdns/mdns_show.go b/modules/mdns/mdns_show.go index fa5c7a15..3ad238bb 100644 --- a/modules/mdns/mdns_show.go +++ b/modules/mdns/mdns_show.go @@ -6,11 +6,12 @@ import ( "github.com/evilsocket/islazy/str" "github.com/evilsocket/islazy/tui" + "github.com/grandcat/zeroconf" ) type entry struct { ip string - services map[string]*ServiceEntry + services map[string]*zeroconf.ServiceEntry } func (mod *MDNSModule) show(filter string, withData bool) error { @@ -30,7 +31,7 @@ func (mod *MDNSModule) show(filter string, withData bool) error { for _, entry := range entries { if endpoint := mod.Session.Lan.GetByIp(entry.ip); endpoint != nil { - fmt.Fprintf(mod.Session.Events.Stdout, "* %s (%s)\n", endpoint.IpAddress, tui.Dim(endpoint.Vendor)) + fmt.Fprintf(mod.Session.Events.Stdout, "* %s (%s)\n", tui.Bold(endpoint.IpAddress), tui.Dim(endpoint.Vendor)) } else { fmt.Fprintf(mod.Session.Events.Stdout, "* %s\n", tui.Bold(entry.ip)) } @@ -38,16 +39,16 @@ func (mod *MDNSModule) show(filter string, withData bool) error { for name, svc := range entry.services { fmt.Fprintf(mod.Session.Events.Stdout, " %s (%s) [%v / %v]:%s\n", tui.Green(name), - tui.Dim(svc.Host), - svc.AddrV4, - svc.AddrV6, + tui.Dim(svc.HostName), + svc.AddrIPv4, + svc.AddrIPv6, tui.Red(fmt.Sprintf("%d", svc.Port)), ) - numFields := len(svc.InfoFields) + numFields := len(svc.Text) if withData { if numFields > 0 { - for _, field := range svc.InfoFields { + for _, field := range svc.Text { if field = str.Trim(field); len(field) > 0 { fmt.Fprintf(mod.Session.Events.Stdout, " %s\n", field) } diff --git a/modules/mdns/server.go b/modules/mdns/server.go deleted file mode 100644 index 66a49184..00000000 --- a/modules/mdns/server.go +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MIT - -package mdns - -import ( - "fmt" - "net" - "strings" - "sync/atomic" - - "github.com/miekg/dns" -) - -const ( - ipv4mdns = "224.0.0.251" - ipv6mdns = "ff02::fb" - mdnsPort = 5353 - forceUnicastResponses = false -) - -var ( - ipv4Addr = &net.UDPAddr{ - IP: net.ParseIP(ipv4mdns), - Port: mdnsPort, - } - ipv6Addr = &net.UDPAddr{ - IP: net.ParseIP(ipv6mdns), - Port: mdnsPort, - } -) - -// Config is used to configure the mDNS server -type Config struct { - // Zone must be provided to support responding to queries - Zone Zone - - // Iface if provided binds the multicast listener to the given - // interface. If not provided, the system default multicase interface - // is used. - Iface *net.Interface - - // LogEmptyResponses indicates the server should print an informative message - // when there is an mDNS query for which the server has no response. - LogEmptyResponses bool -} - -// mDNS server is used to listen for mDNS queries and respond if we -// have a matching local record -type Server struct { - mod *MDNSModule - config *Config - - ipv4List *net.UDPConn - ipv6List *net.UDPConn - - shutdown int32 - shutdownCh chan struct{} -} - -// NewServer is used to create a new mDNS server from a config -func NewServer(mod *MDNSModule, config *Config) (*Server, error) { - // Create the listeners - ipv4List, err := net.ListenMulticastUDP("udp4", config.Iface, ipv4Addr) - if err != nil { - return nil, err - } - ipv6List, _ := net.ListenMulticastUDP("udp6", config.Iface, ipv6Addr) - - // Check if we have any listener - if ipv4List == nil && ipv6List == nil { - return nil, fmt.Errorf("no multicast listeners could be started") - } - - s := &Server{ - mod: mod, - config: config, - ipv4List: ipv4List, - ipv6List: ipv6List, - shutdownCh: make(chan struct{}), - } - - if ipv4List != nil { - mod.Info("starting ipv4 receiver for %v", s.ipv4List) - go s.recv(s.ipv4List) - } - - if ipv6List != nil { - mod.Info("starting ipv6 receiver for %v", s.ipv6List) - go s.recv(s.ipv6List) - } - - return s, nil -} - -// Shutdown is used to shutdown the listener -func (s *Server) Shutdown() error { - if !atomic.CompareAndSwapInt32(&s.shutdown, 0, 1) { - // something else already closed us - return nil - } - - close(s.shutdownCh) - - if s.ipv4List != nil { - s.ipv4List.Close() - } - if s.ipv6List != nil { - s.ipv6List.Close() - } - return nil -} - -// recv is a long running routine to receive packets from an interface -func (s *Server) recv(c *net.UDPConn) { - if c == nil { - return - } - buf := make([]byte, 65536) - for atomic.LoadInt32(&s.shutdown) == 0 { - s.mod.Debug("receiving from %v ...", c) - - n, from, err := c.ReadFrom(buf) - if err != nil { - s.mod.Error("error while receiving datagram: %v", err) - continue - } - if err := s.parsePacket(buf[:n], from); err != nil { - s.mod.Debug("failed to handle query: %v", err) - } - } -} - -// parsePacket is used to parse an incoming packet -func (s *Server) parsePacket(packet []byte, from net.Addr) error { - var msg dns.Msg - if err := msg.Unpack(packet); err != nil { - s.mod.Error("failed to unpack packet: %v", err) - return err - } - return s.handleQuery(&msg, from) -} - -// handleQuery is used to handle an incoming query -func (s *Server) handleQuery(query *dns.Msg, from net.Addr) error { - if query.Opcode != dns.OpcodeQuery { - // "In both multicast query and multicast response messages, the OPCODE MUST - // be zero on transmission (only standard queries are currently supported - // over multicast). Multicast DNS messages received with an OPCODE other - // than zero MUST be silently ignored." Note: OpcodeQuery == 0 - return fmt.Errorf("mdns: received query with non-zero Opcode %v: %v", query.Opcode, *query) - } - if query.Rcode != 0 { - // "In both multicast query and multicast response messages, the Response - // Code MUST be zero on transmission. Multicast DNS messages received with - // non-zero Response Codes MUST be silently ignored." - return fmt.Errorf("mdns: received query with non-zero Rcode %v: %v", query.Rcode, *query) - } - - // TODO(reddaly): Handle "TC (Truncated) Bit": - // In query messages, if the TC bit is set, it means that additional - // Known-Answer records may be following shortly. A responder SHOULD - // record this fact, and wait for those additional Known-Answer records, - // before deciding whether to respond. If the TC bit is clear, it means - // that the querying host has no additional Known Answers. - if query.Truncated { - return fmt.Errorf("[ERR] mdns: support for DNS requests with high truncated bit not implemented: %v", *query) - } - - var unicastAnswer, multicastAnswer []dns.RR - - // Handle each question - for _, q := range query.Question { - mrecs, urecs := s.handleQuestion(q) - multicastAnswer = append(multicastAnswer, mrecs...) - unicastAnswer = append(unicastAnswer, urecs...) - } - - // See section 18 of RFC 6762 for rules about DNS headers. - resp := func(unicast bool) *dns.Msg { - // 18.1: ID (Query Identifier) - // 0 for multicast response, query.Id for unicast response - id := uint16(0) - if unicast { - id = query.Id - } - - var answer []dns.RR - if unicast { - answer = unicastAnswer - } else { - answer = multicastAnswer - } - if len(answer) == 0 { - return nil - } - - return &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Id: id, - - // 18.2: QR (Query/Response) Bit - must be set to 1 in response. - Response: true, - - // 18.3: OPCODE - must be zero in response (OpcodeQuery == 0) - Opcode: dns.OpcodeQuery, - - // 18.4: AA (Authoritative Answer) Bit - must be set to 1 - Authoritative: true, - - // The following fields must all be set to 0: - // 18.5: TC (TRUNCATED) Bit - // 18.6: RD (Recursion Desired) Bit - // 18.7: RA (Recursion Available) Bit - // 18.8: Z (Zero) Bit - // 18.9: AD (Authentic Data) Bit - // 18.10: CD (Checking Disabled) Bit - // 18.11: RCODE (Response Code) - }, - // 18.12 pertains to questions (handled by handleQuestion) - // 18.13 pertains to resource records (handled by handleQuestion) - - // 18.14: Name Compression - responses should be compressed (though see - // caveats in the RFC), so set the Compress bit (part of the dns library - // API, not part of the DNS packet) to true. - Compress: true, - - Answer: answer, - } - } - - if s.config.LogEmptyResponses && len(multicastAnswer) == 0 && len(unicastAnswer) == 0 { - questions := make([]string, len(query.Question)) - for i, q := range query.Question { - questions[i] = q.Name - } - s.mod.Warning("no responses for query with questions: %s", strings.Join(questions, ", ")) - } - - if mresp := resp(false); mresp != nil { - if err := s.sendResponse(mresp, from, false); err != nil { - return fmt.Errorf("mdns: error sending multicast response: %v", err) - } - } - if uresp := resp(true); uresp != nil { - if err := s.sendResponse(uresp, from, true); err != nil { - return fmt.Errorf("mdns: error sending unicast response: %v", err) - } - } - return nil -} - -// handleQuestion is used to handle an incoming question -// -// The response to a question may be transmitted over multicast, unicast, or -// both. The return values are DNS records for each transmission type. -func (s *Server) handleQuestion(q dns.Question) (multicastRecs, unicastRecs []dns.RR) { - records := s.config.Zone.Records(q) - - if len(records) == 0 { - return nil, nil - } - - s.mod.Info("%+v :", q) - for _, rec := range records { - s.mod.Info(" %+v", rec) - } - - // Handle unicast and multicast responses. - // TODO(reddaly): The decision about sending over unicast vs. multicast is not - // yet fully compliant with RFC 6762. For example, the unicast bit should be - // ignored if the records in question are close to TTL expiration. For now, - // we just use the unicast bit to make the decision, as per the spec: - // RFC 6762, section 18.12. Repurposing of Top Bit of qclass in Question - // Section - // - // In the Question Section of a Multicast DNS query, the top bit of the - // qclass field is used to indicate that unicast responses are preferred - // for this particular question. (See Section 5.4.) - if q.Qclass&(1<<15) != 0 || forceUnicastResponses { - return nil, records - } - return records, nil -} - -// sendResponse is used to send a response packet -func (s *Server) sendResponse(resp *dns.Msg, from net.Addr, unicast bool) error { - s.mod.Debug("sending response=%v from=%v", *resp, from) - - // TODO(reddaly): Respect the unicast argument, and allow sending responses - // over multicast. - buf, err := resp.Pack() - if err != nil { - return err - } - - // Determine the socket to send from - addr := from.(*net.UDPAddr) - if addr.IP.To4() != nil { - _, err = s.ipv4List.WriteToUDP(buf, addr) - return err - } else { - _, err = s.ipv6List.WriteToUDP(buf, addr) - return err - } -} diff --git a/modules/mdns/service.go b/modules/mdns/service.go deleted file mode 100644 index 24078762..00000000 --- a/modules/mdns/service.go +++ /dev/null @@ -1,317 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MIT - -package mdns - -import ( - "fmt" - "net" - "os" - "strings" - - "github.com/miekg/dns" -) - -const ( - // defaultTTL is the default TTL value in returned DNS records in seconds. - defaultTTL = 120 -) - -// Zone is the interface used to integrate with the server and -// to serve records dynamically -type Zone interface { - // Records returns DNS records in response to a DNS question. - Records(q dns.Question) []dns.RR -} - -// MDNSService is used to export a named service by implementing a Zone -type MDNSService struct { - mod *MDNSModule - - Instance string // Instance name (e.g. "hostService name") - Service string // Service name (e.g. "_http._tcp.") - Domain string // If blank, assumes "local" - HostName string // Host machine DNS name (e.g. "mymachine.net.") - Port int // Service Port - IPs []net.IP // IP addresses for the service's host - TXT []string // Service TXT records - - serviceAddr string // Fully qualified service address - instanceAddr string // Fully qualified instance address - enumAddr string // _services._dns-sd._udp. -} - -// validateFQDN returns an error if the passed string is not a fully qualified -// hdomain name (more specifically, a hostname). -func validateFQDN(s string) error { - if len(s) == 0 { - return fmt.Errorf("FQDN must not be blank") - } - if s[len(s)-1] != '.' { - return fmt.Errorf("FQDN must end in period: %s", s) - } - // TODO(reddaly): Perform full validation. - - return nil -} - -// NewMDNSService returns a new instance of MDNSService. -// -// If domain, hostName, or ips is set to the zero value, then a default value -// will be inferred from the operating system. -// -// TODO(reddaly): This interface may need to change to account for "unique -// record" conflict rules of the mDNS protocol. Upon startup, the server should -// check to ensure that the instance name does not conflict with other instance -// names, and, if required, select a new name. There may also be conflicting -// hostName A/AAAA records. -func NewMDNSService(mod *MDNSModule, instance, service, domain, hostName string, port int, ips []net.IP, txt []string) (*MDNSService, error) { - // Sanity check inputs - if instance == "" { - return nil, fmt.Errorf("missing service instance name") - } - if service == "" { - return nil, fmt.Errorf("missing service name") - } - if port == 0 { - return nil, fmt.Errorf("missing service port") - } - - // Set default domain - if domain == "" { - domain = "local." - } - if err := validateFQDN(domain); err != nil { - return nil, fmt.Errorf("domain %q is not a fully-qualified domain name: %v", domain, err) - } - - // Get host information if no host is specified. - if hostName == "" { - var err error - hostName, err = os.Hostname() - if err != nil { - return nil, fmt.Errorf("could not determine host: %v", err) - } - hostName = fmt.Sprintf("%s.", hostName) - } - if err := validateFQDN(hostName); err != nil { - return nil, fmt.Errorf("hostName %q is not a fully-qualified domain name: %v", hostName, err) - } - - if len(ips) == 0 { - var err error - ips, err = net.LookupIP(hostName) - if err != nil { - // Try appending the host domain suffix and lookup again - // (required for Linux-based hosts) - tmpHostName := fmt.Sprintf("%s%s", hostName, domain) - - ips, err = net.LookupIP(tmpHostName) - - if err != nil { - return nil, fmt.Errorf("could not determine host IP addresses for %s", hostName) - } - } - } - for _, ip := range ips { - if ip.To4() == nil && ip.To16() == nil { - return nil, fmt.Errorf("invalid IP address in IPs list: %v", ip) - } - } - - mod.Debug("serviceAddr=%s.%s.", trimDot(service), trimDot(domain)) - mod.Debug("instanceAddr=%s.%s.%s.", instance, trimDot(service), trimDot(domain)) - mod.Debug("enumAddr=_services._dns-sd._udp.%s.", trimDot(domain)) - - return &MDNSService{ - mod: mod, - Instance: instance, - Service: service, - Domain: domain, - HostName: hostName, - Port: port, - IPs: ips, - TXT: txt, - serviceAddr: fmt.Sprintf("%s.%s.", trimDot(service), trimDot(domain)), - instanceAddr: fmt.Sprintf("%s.%s.%s.", instance, trimDot(service), trimDot(domain)), - enumAddr: fmt.Sprintf("_services._dns-sd._udp.%s.", trimDot(domain)), - }, nil -} - -// trimDot is used to trim the dots from the start or end of a string -func trimDot(s string) string { - return strings.Trim(s, ".") -} - -// Records returns DNS records in response to a DNS question. -func (m *MDNSService) Records(q dns.Question) []dns.RR { - switch q.Name { - case m.enumAddr: - return m.serviceEnum(q) - case m.serviceAddr: - return m.serviceRecords(q) - case m.instanceAddr: - return m.instanceRecords(q) - case m.HostName: - if q.Qtype == dns.TypeA || q.Qtype == dns.TypeAAAA { - return m.instanceRecords(q) - } - fallthrough - default: - return nil - } -} - -func (m *MDNSService) serviceEnum(q dns.Question) []dns.RR { - switch q.Qtype { - case dns.TypeANY: - fallthrough - case dns.TypePTR: - rr := &dns.PTR{ - Hdr: dns.RR_Header{ - Name: q.Name, - Rrtype: dns.TypePTR, - Class: dns.ClassINET, - Ttl: defaultTTL, - }, - Ptr: m.serviceAddr, - } - return []dns.RR{rr} - default: - return nil - } -} - -// serviceRecords is called when the query matches the service name -func (m *MDNSService) serviceRecords(q dns.Question) []dns.RR { - switch q.Qtype { - case dns.TypeANY: - fallthrough - case dns.TypePTR: - // Build a PTR response for the service - rr := &dns.PTR{ - Hdr: dns.RR_Header{ - Name: q.Name, - Rrtype: dns.TypePTR, - Class: dns.ClassINET, - Ttl: defaultTTL, - }, - Ptr: m.instanceAddr, - } - servRec := []dns.RR{rr} - - // Get the instance records - instRecs := m.instanceRecords(dns.Question{ - Name: m.instanceAddr, - Qtype: dns.TypeANY, - }) - - // Return the service record with the instance records - return append(servRec, instRecs...) - default: - return nil - } -} - -// serviceRecords is called when the query matches the instance name -func (m *MDNSService) instanceRecords(q dns.Question) []dns.RR { - switch q.Qtype { - case dns.TypeANY: - // Get the SRV, which includes A and AAAA - recs := m.instanceRecords(dns.Question{ - Name: m.instanceAddr, - Qtype: dns.TypeSRV, - }) - - // Add the TXT record - recs = append(recs, m.instanceRecords(dns.Question{ - Name: m.instanceAddr, - Qtype: dns.TypeTXT, - })...) - return recs - - case dns.TypeA: - var rr []dns.RR - for _, ip := range m.IPs { - if ip4 := ip.To4(); ip4 != nil { - rr = append(rr, &dns.A{ - Hdr: dns.RR_Header{ - Name: m.HostName, - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: defaultTTL, - }, - A: ip4, - }) - } - } - return rr - - case dns.TypeAAAA: - var rr []dns.RR - for _, ip := range m.IPs { - if ip.To4() != nil { - // TODO(reddaly): IPv4 addresses could be encoded in IPv6 format and - // putinto AAAA records, but the current logic puts ipv4-encodable - // addresses into the A records exclusively. Perhaps this should be - // configurable? - continue - } - - if ip16 := ip.To16(); ip16 != nil { - rr = append(rr, &dns.AAAA{ - Hdr: dns.RR_Header{ - Name: m.HostName, - Rrtype: dns.TypeAAAA, - Class: dns.ClassINET, - Ttl: defaultTTL, - }, - AAAA: ip16, - }) - } - } - return rr - - case dns.TypeSRV: - // Create the SRV Record - srv := &dns.SRV{ - Hdr: dns.RR_Header{ - Name: q.Name, - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - Ttl: defaultTTL, - }, - Priority: 10, - Weight: 1, - Port: uint16(m.Port), - Target: m.HostName, - } - recs := []dns.RR{srv} - - // Add the A record - recs = append(recs, m.instanceRecords(dns.Question{ - Name: m.instanceAddr, - Qtype: dns.TypeA, - })...) - - // Add the AAAA record - recs = append(recs, m.instanceRecords(dns.Question{ - Name: m.instanceAddr, - Qtype: dns.TypeAAAA, - })...) - return recs - - case dns.TypeTXT: - txt := &dns.TXT{ - Hdr: dns.RR_Header{ - Name: q.Name, - Rrtype: dns.TypeTXT, - Class: dns.ClassINET, - Ttl: defaultTTL, - }, - Txt: m.TXT, - } - return []dns.RR{txt} - } - return nil -} diff --git a/network/lan.go b/network/lan.go index c6db5fd2..990cdfd0 100644 --- a/network/lan.go +++ b/network/lan.go @@ -75,14 +75,14 @@ func (lan *LAN) GetByIp(ip string) *Endpoint { lan.Lock() defer lan.Unlock() - if ip == lan.iface.IpAddress { + if ip == lan.iface.IpAddress || ip == lan.iface.Ip6Address { return lan.iface - } else if ip == lan.gateway.IpAddress { + } else if ip == lan.gateway.IpAddress || ip == lan.gateway.Ip6Address { return lan.gateway } for _, e := range lan.hosts { - if e.IpAddress == ip { + if e.IpAddress == ip || e.Ip6Address == ip { return e } } diff --git a/printer.yml b/printer.yml index c891bcb1..e5fc9ae4 100644 --- a/printer.yml +++ b/printer.yml @@ -1,32 +1,29 @@ -EPSON\ XP-666\ Series._http._tcp.local.: - name: EPSON\ XP-666\ Series._http._tcp.local. - host: EPSON59F5BA.local. - addrv4: 192.168.50.21 - addrv6: fe80::46d2:44ff:fe59:f5ba - addrv6ipaddr: - ip: fe80::46d2:44ff:fe59:f5ba - zone: "" +_http: + servicerecord: + instance: EPSON\ XP-630\ Series + service: _http._tcp + domain: local. port: 80 - info: "" - infofields: - - "" - addr: fe80::46d2:44ff:fe59:f5ba -EPSON\ XP-666\ Series._ipp._tcp.local.: - name: EPSON\ XP-666\ Series._ipp._tcp.local. - host: EPSON59F5BA.local. - addrv4: 192.168.50.21 - addrv6: fe80::46d2:44ff:fe59:f5ba - addrv6ipaddr: - ip: fe80::46d2:44ff:fe59:f5ba - zone: "" + +_ipp: + servicerecord: + instance: EPSON\ XP-630\ Series + service: _ipp._tcp + domain: local. port: 631 - info: txtvers=1|ty=EPSON XP-666 Series|usb_MFG=EPSON|usb_MDL=XP-666 Series|product=(EPSON XP-666 Series)|pdl=application/octet-stream,image/pwg-raster,image/urf,image/jpeg|rp=ipp/print|qtotal=1|Color=T|Duplex=T|Scan=T|Fax=F|kind=document,envelope,label,photo|PaperMax=legal-A4|URF=CP1,MT1-3-5-8-10-11-12,PQ4-5,OB9,OFU0,RS360,SRGB24,W8,DM3,IS1-7,V1.4|mopria-certified=1.2|priority=30|adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR|note=|UUID=cff92100-67f4-11d4-a45f-44d24459f5ba|TLS=1.2 - infofields: + +_ipps: + servicerecord: + instance: EPSON\ XP-630\ Series + service: _ipps._tcp + domain: local. + port: 631 + text: - txtvers=1 - - ty=EPSON XP-666 Series + - ty=EPSON XP-630 Series - usb_MFG=EPSON - - usb_MDL=XP-666 Series - - product=(EPSON XP-666 Series) + - usb_MDL=XP-630 Series + - product=(EPSON XP-630 Series) - pdl=application/octet-stream,image/pwg-raster,image/urf,image/jpeg - rp=ipp/print - qtotal=1 @@ -41,159 +38,92 @@ EPSON\ XP-666\ Series._ipp._tcp.local.: - priority=30 - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR - note= - - UUID=cff92100-67f4-11d4-a45f-44d24459f5ba + - UUID=cfe92100-67c4-11d4-a45f-44d24459f5ba - TLS=1.2 - addr: fe80::46d2:44ff:fe59:f5ba -EPSON\ XP-666\ Series._ipps._tcp.local.: - name: EPSON\ XP-666\ Series._ipps._tcp.local. - host: EPSON59F5BA.local. - addrv4: 192.168.50.21 - addrv6: fe80::46d2:44ff:fe59:f5ba - addrv6ipaddr: - ip: fe80::46d2:44ff:fe59:f5ba - zone: "" - port: 631 - info: txtvers=1|ty=EPSON XP-666 Series|usb_MFG=EPSON|usb_MDL=XP-666 Series|product=(EPSON XP-666 Series)|pdl=application/octet-stream,image/pwg-raster,image/urf,image/jpeg|rp=ipp/print|qtotal=1|Color=T|Duplex=T|Scan=T|Fax=F|kind=document,envelope,label,photo|PaperMax=legal-A4|URF=CP1,MT1-3-5-8-10-11-12,PQ4-5,OB9,OFU0,RS360,SRGB24,W8,DM3,IS1-7,V1.4|mopria-certified=1.2|priority=30|adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR|note=|UUID=cff92100-67f4-11d4-a45f-44d24459f5ba|TLS=1.2 - infofields: - - txtvers=1 - - ty=EPSON XP-666 Series - - usb_MFG=EPSON - - usb_MDL=XP-666 Series - - product=(EPSON XP-666 Series) - - pdl=application/octet-stream,image/pwg-raster,image/urf,image/jpeg - - rp=ipp/print - - qtotal=1 - - Color=T - - Duplex=T - - Scan=T - - Fax=F - - kind=document,envelope,label,photo - - PaperMax=legal-A4 - - URF=CP1,MT1-3-5-8-10-11-12,PQ4-5,OB9,OFU0,RS360,SRGB24,W8,DM3,IS1-7,V1.4 - - mopria-certified=1.2 - - priority=30 - - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR - - note= - - UUID=cff92100-67f4-11d4-a45f-44d24459f5ba - - TLS=1.2 - addr: fe80::46d2:44ff:fe59:f5ba -EPSON\ XP-666\ Series._pdl-datastream._tcp.local.: - name: EPSON\ XP-666\ Series._pdl-datastream._tcp.local. - host: EPSON59F5BA.local. - addrv4: 192.168.50.21 - addrv6: fe80::46d2:44ff:fe59:f5ba - addrv6ipaddr: - ip: fe80::46d2:44ff:fe59:f5ba - zone: "" + +_pdl-datastream: + servicerecord: + instance: EPSON\ XP-630\ Series + service: _pdl-datastream._tcp + domain: local. port: 9100 - info: txtvers=1|priority=40|ty=EPSON XP-666 Series|usb_MFG=EPSON|usb_MDL=XP-666 Series|product=(EPSON XP-666 Series)|pdl=raw|qtotal=1|adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR|note= - infofields: + text: - txtvers=1 - priority=40 - - ty=EPSON XP-666 Series + - ty=EPSON XP-630 Series - usb_MFG=EPSON - - usb_MDL=XP-666 Series - - product=(EPSON XP-666 Series) + - usb_MDL=XP-630 Series + - product=(EPSON XP-630 Series) - pdl=raw - qtotal=1 - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR - note= - addr: fe80::46d2:44ff:fe59:f5ba -EPSON\ XP-666\ Series._printer._tcp.local.: - name: EPSON\ XP-666\ Series._printer._tcp.local. - host: EPSON59F5BA.local. - addrv4: 192.168.50.21 - addrv6: fe80::46d2:44ff:fe59:f5ba - addrv6ipaddr: - ip: fe80::46d2:44ff:fe59:f5ba - zone: "" + +_printer: + servicerecord: + instance: EPSON\ XP-630\ Series + service: _printer._tcp + domain: local. port: 515 - info: txtvers=1|priority=50|ty=EPSON XP-666 Series|usb_MFG=EPSON|usb_MDL=XP-666 Series|product=(EPSON XP-666 Series)|pdl=raw|rp=auto|qtotal=1|adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR|note= - infofields: + text: - txtvers=1 - priority=50 - - ty=EPSON XP-666 Series + - ty=EPSON XP-630 Series - usb_MFG=EPSON - - usb_MDL=XP-666 Series - - product=(EPSON XP-666 Series) + - usb_MDL=XP-630 Series + - product=(EPSON XP-630 Series) - pdl=raw - rp=auto - qtotal=1 - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR - note= - addr: fe80::46d2:44ff:fe59:f5ba -EPSON\ XP-666\ Series._privet._tcp.local.: - name: EPSON\ XP-666\ Series._privet._tcp.local. - host: EPSON59F5BA.local. - addrv4: 192.168.50.21 - addrv6: fe80::46d2:44ff:fe59:f5ba - addrv6ipaddr: - ip: fe80::46d2:44ff:fe59:f5ba - zone: "" + +_privet: + servicerecord: + instance: EPSON\ XP-630\ Series + service: _privet._tcp + domain: local. port: 80 - info: txtvers=1|ty=EPSON XP-666 Series (EPSON59F5BA)|url=https://www.google.com/cloudprint|type=printer|id=0936a89f-33d7-80f5-c1bc-7421d40a78b5|cs=offline - infofields: + text: - txtvers=1 - - ty=EPSON XP-666 Series (EPSON59F5BA) + - ty=EPSON XP-630 Series (EPSON59F5BA) - url=https://www.google.com/cloudprint - type=printer - id=0936a89f-33d7-80f5-c1bc-7421d40a78b5 - cs=offline - addr: fe80::46d2:44ff:fe59:f5ba -EPSON\ XP-666\ Series._scanner._tcp.local.: - name: EPSON\ XP-666\ Series._scanner._tcp.local. - host: EPSON59F5BA.local. - addrv4: 192.168.50.21 - addrv6: fe80::46d2:44ff:fe59:f5ba - addrv6ipaddr: - ip: fe80::46d2:44ff:fe59:f5ba - zone: "" + +_scanner: + servicerecord: + instance: EPSON\ XP-630\ Series + service: _scanner._tcp + domain: local. port: 1865 - info: txtvers=1|ty=EPSON XP-666 Series|adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR|mfg=EPSON|mdl=XP-666 Series|UUID=cff92100-67f4-11d4-a45f-44d24459f5ba|scannerAvailable=0|note= - infofields: + text: - txtvers=1 - - ty=EPSON XP-666 Series + - ty=EPSON XP-630 Series - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR - mfg=EPSON - - mdl=XP-666 Series - - UUID=cff92100-67f4-11d4-a45f-44d24459f5ba + - mdl=XP-630 Series + - UUID=cfe92100-67c4-11d4-a45f-44d24459f5ba - scannerAvailable=0 - note= - addr: fe80::46d2:44ff:fe59:f5ba -EPSON\ XP-666\ Series._smb._tcp.local.: - name: EPSON\ XP-666\ Series._smb._tcp.local. - host: EPSON59F5BA.local. - addrv4: 192.168.50.21 - addrv6: fe80::46d2:44ff:fe59:f5ba - addrv6ipaddr: - ip: fe80::46d2:44ff:fe59:f5ba - zone: "" - port: 445 - info: "" - infofields: - - "" - addr: fe80::46d2:44ff:fe59:f5ba -EPSON\ XP-666\ Series._uscan._tcp.local.: - name: EPSON\ XP-666\ Series._uscan._tcp.local. - host: EPSON59F5BA.local. - addrv4: 192.168.50.21 - addrv6: fe80::46d2:44ff:fe59:f5ba - addrv6ipaddr: - ip: fe80::46d2:44ff:fe59:f5ba - zone: "" + +_uscan: + servicerecord: + instance: EPSON\ XP-630\ Series + service: _uscan._tcp + domain: local. port: 443 - info: txtvers=1|vers=2.5|representation=/PRESENTATION/AIRPRINT/PRINTER_128.PNG|rs=eSCL|ty=EPSON XP-666 Series|pdl=application/pdf,image/jpeg|cs=color,grayscale,binary|is=platen|duplex=F|adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR|UUID=cff92100-67f4-11d4-a45f-44d24459f5ba|note= - infofields: + text: - txtvers=1 - vers=2.5 - representation=/PRESENTATION/AIRPRINT/PRINTER_128.PNG - rs=eSCL - - ty=EPSON XP-666 Series + - ty=EPSON XP-630 Series - pdl=application/pdf,image/jpeg - cs=color,grayscale,binary - is=platen - duplex=F - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR - - UUID=cff92100-67f4-11d4-a45f-44d24459f5ba + - UUID=cfe92100-67c4-11d4-a45f-44d24459f5ba - note= - addr: fe80::46d2:44ff:fe59:f5ba \ No newline at end of file diff --git a/printer2.yml b/printer2.yml new file mode 100644 index 00000000..545026a7 --- /dev/null +++ b/printer2.yml @@ -0,0 +1,187 @@ +EPSON\ XP-630\ Series-59F5BA._http._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA + service: _http._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 80 + text: + - "" + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA._ipp._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA + service: _ipp._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 631 + text: + - txtvers=1 + - ty=EPSON XP-630 Series + - usb_MFG=EPSON + - usb_MDL=XP-630 Series + - product=(EPSON XP-630 Series) + - pdl=application/octet-stream,image/pwg-raster,image/urf,image/jpeg + - rp=ipp/print + - qtotal=1 + - Color=T + - Duplex=T + - Scan=T + - Fax=F + - kind=document,envelope,label,photo + - PaperMax=legal-A4 + - URF=CP1,MT1-3-5-8-10-11-12,PQ4-5,OB9,OFU0,RS360,SRGB24,W8,DM3,IS1-7,V1.4 + - mopria-certified=1.2 + - priority=30 + - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR + - note= + - UUID=cfe92100-67c4-11d4-a45f-44d24459f5ba + - TLS=1.2 + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA._ipps._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA + service: _ipps._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 631 + text: [] + ttl: 120 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA._pdl-datastream._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA + service: _pdl-datastream._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 9100 + text: + - txtvers=1 + - priority=40 + - ty=EPSON XP-630 Series + - usb_MFG=EPSON + - usb_MDL=XP-630 Series + - product=(EPSON XP-630 Series) + - pdl=raw + - qtotal=1 + - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR + - note= + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA._printer._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA + service: _printer._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 515 + text: + - txtvers=1 + - priority=50 + - ty=EPSON XP-630 Series + - usb_MFG=EPSON + - usb_MDL=XP-630 Series + - product=(EPSON XP-630 Series) + - pdl=raw + - rp=auto + - qtotal=1 + - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR + - note= + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA._privet._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA + service: _privet._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 80 + text: + - txtvers=1 + - ty=EPSON XP-630 Series (EPSON59F5BA) + - url=https://www.google.com/cloudprint + - type=printer + - id=0936a89f-33d7-80f5-c1bc-7421d40a78b5 + - cs=offline + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA._scanner._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA + service: _scanner._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 1865 + text: + - txtvers=1 + - ty=EPSON XP-630 Series + - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR + - mfg=EPSON + - mdl=XP-630 Series + - UUID=cfe92100-67c4-11d4-a45f-44d24459f5ba + - scannerAvailable=0 + - note= + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA._smb._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA + service: _smb._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 445 + text: + - "" + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba +EPSON\ XP-630\ Series-59F5BA._uscan._tcp.local.: + servicerecord: + instance: EPSON\ XP-630\ Series-59F5BA + service: _uscan._tcp + domain: local. + hostname: EPSON59F5BA.local. + port: 443 + text: + - txtvers=1 + - vers=2.5 + - representation=/PRESENTATION/AIRPRINT/PRINTER_128.PNG + - rs=eSCL + - ty=EPSON XP-630 Series + - pdl=application/pdf,image/jpeg + - cs=color,grayscale,binary + - is=platen + - duplex=F + - adminurl=http://EPSON59F5BA.local.:80/PRESENTATION/BONJOUR + - UUID=cfe92100-67c4-11d4-a45f-44d24459f5ba + - note= + ttl: 4500 + addripv4: + - 192.168.50.21 + addripv6: + - fe80::46d2:44ff:fe59:f5ba