Chapter 4. Browsing for Services

Once you are on a network where you have a working IP address and hostname, you are in a position to begin doing some useful networking. Your IP address may change over time, particularly if you are using IPv4 link-local addressing, but your Multicast DNS hostname generally won’t. People on the local network can access services running on your machine using your mDNS hostname anywhere a conventional hostname would be used, automatically connecting to your current address, even if your address changed since the last time they connected. People can use your mDNS hostname on the command line to connect with FTP or SSH commands. If your machine is running a web server, others can connec[t to it by entering your mDNS hostname into their web browser. Note that web servers can take many forms apart from the conventional collection of static pages: if you have a typical network-connected camera with Multicast DNS, you can connect by typing its name (e.g., netcam.local) into your web browser. This is, of course, a big improvement over having to know the IP address to type, but in some ways we’ve merely moved the problem, not solved it. Instead of having to know what IP address to type, you now have to know what name to type. In the case of IPv6 addresses, which are 20-40 characters long, a short, memorable hostname is definitely an improvement, but imagine how much better it would be if you didn’t have to know the name at all, and your web browser could simply instruct the network, “Show me the list of services that I know how to talk to.”

This chapter introduces DNS Service Discovery (DNS-SD), the mechanism in Zeroconf that lets you discover what services are available on the network without having to know device or service names in advance via some other means. DNS Service Discovery is accomplished by building on existing standard DNS queries and resource record types, not by creating a new set of technologies and hoping they will be adopted over time. Enhancing and extending existing technologies is one of the things that has helped lead to the quick adoption of DNS-based service discovery.

The first two legs of Zeroconf allow you to fully participate in a local network in the absence of what we would have traditionally considered the enabling technologies of DHCP and DNS. Obtaining an IP address is nothing new. At work or at a café offering wireless Internet access, your computer has likely been assigned an IP address by a DHCP server. The first leg of Zeroconf provides a way to obtain a link-local IP address without a DHCP server, so that common and necessary step can now be accomplished in a new situation. In Chapter 3, you saw that however you obtained an IP address, it was desirable to obtain a locally unique hostname. One alternative is conventional unicast DNS, but setting up DNS is a lot of work, and all that work really should not be necessary if you just need to transfer a file or print a one-page document. Multicast DNS is a lightweight alternative that gives you DNS-like functionality on the local network without all the overhead and effort of conventional unicast DNS.

Thus far, Zeroconf has not provided anything that you could not, in principle, have obtained through other means. What Zeroconf has provided are alternative ways of doing the same things, ways that work when the conventional mechanisms let us down. In addition, at each step, Zeroconf is not concerned with how you accomplished the previous step. Step one: obtain an IP address by using DHCP, manual assignment, or self-assigned link-local addressing. Step two: obtain a meaningful hostname by using Multicast DNS in cases where conventional Unicast DNS is not appropriate. Step two requires that a device has obtained a working IP address but is not concerned with how. Step three: browse for the services that you need. Step three requires the availability of working DNS-like functionality, but it could be link-local Multicast DNS, global Unicast DNS, or both.

Regardless of whether Multicast DNS or Unicast DNS is being used, new services coming onto the network announce their presence via Multicast DNS, or they use DNS Dynamic Update to update a Unicast DNS server with their information; thus, clients looking for services of that type are all informed that a new instance is now available. When a service goes away gracefully (as opposed to crashing, having the network cable cut, or suffering a power failure), it sends a Multicast DNS goodbye packet or uses DNS Dynamic Update to remove its information from the Unicast DNS server, so that clients can be informed that that particular named service instance is no longer available.

In this chapter, you will see how Zeroconf’s service discovery works. As the DNS-based Service Discovery Internet-Draft explains, the prime directive for the service discovery protocol is that it “should be so simple to implement that virtually any device capable of implementing IP should not have any trouble implementing the service discovery software as well.”

The service discovery software has two main responsibilities: enumerating the list of names of services on the network of a given type and translating from any given name on the list to the IP address and other information necessary to connect and use it. The service instance names should be under user control but relatively persistent, so that tomorrow, the same service instance name logically identifies the same conceptual service being offered, even if the IP address has changed or the TCP port number the server is listening on has changed. Even if the hardware has been replaced or the software has been upgraded, clients should still be able to connect to that service using the same name.

You will notice a recurring theme in this chapter: you are browsing for services, not devices. The importance of browsing for services instead of devices is a lesson that was learned from the old AppleTalk Name Binding Protocol, a protocol that enjoyed two decades of success in the marketplace, and it is an important lesson. The difference between discovering services and discovering devices is subtle. The concept of a service, as a pure abstract entity in its own right, divorced from whatever hardware may be providing that service, is a fairly counterintuitive concept. Humans can touch, see, buy, and sell hardware, so from a human perspective, it seems natural that the computer would also see the network as a collection of bits of hardware. However, from a computer and network protocol perspective, the useful question to ask the network is not “What hardware do you have?” but “What can you do for me?” From a human perspective, the user thinks in terms of finding a printer, but from a protocol perspective, the software is looking for a network service that it can use to print. The difference is subtle but important. Discovering hardware is no use if you don’t know how to talk to it. Discovering a service you can use is what’s useful, and from a protocol perspective, it doesn’t really matter what kind of hardware is providing that service.

Zero Configuration Operation

Finding services should be as easy as turning on a lamp. If technological devices continue to be unreasonably hard to set up and use, the market for those devices is going to be stifled because the buying public simply won’t be willing to expend the time and effort it takes to get them to work.

Consider a table lamp. The customer needs to plug the lamp into a live AC outlet, the lamp needs to have a working bulb properly in place, and the customer needs to locate and operate the switch. When a customer flicks a switch and the light does not come on, there are not many things that could have gone wrong. The tech support script is pretty basic: “You say the light doesn’t come on. Did you try the bulb in a different lamp to make sure the bulb is good? Did you try connecting some other appliance to the outlet to make sure it’s providing power? What? I see. You hadn’t actually plugged the lamp into a power outlet? That may be your problem. Sure, I’ll hold while you try that. Works now? Great. No, really it’s no problem, your service contract allows you unlimited calls for the first year you own your lamp.” This scenario is comical because, of course, no one has technical support service contracts for table lamps. We need to arrive at a world where we think of consumer electronics and networked devices the same way we think of table lamps.

Finding Services, Not Devices

In the world of networked devices, it does you no good to locate a device with which you cannot communicate. We commonly anthropomorphize devices in ways that are not quite correct. We say that we “pinged a server,” though, in fact, what we pinged was a piece of software on the server that answers ICMP echo request packets. If you take away that software, it stops answering ping requests, even though the server is still there and may still be performing other functions perfectly well.

When designing a service discovery system, it’s important to remember that what network software clients need to discover are software entities with which they can communicate, not pieces of hardware. The difference between discovering services and discovering hardware may seem small and subtle, but it makes all the difference in actual use. In a print dialog, you want to see the list of things you can print to. In iTunes, you want to see the list of music sources you can play. In iPhoto, you want to see the list of photo albums you can view. In a web browser, you want to see a list of offered web pages you can view. Any given piece of hardware on the network may offer zero, one, or more of each of these kinds of resources. What you want to see is the list of resources you can use, not a list of the hardware where they may or may not reside.

Knowing the Protocol

A visitor staying in Paris wants to know whether she should take her umbrella on her travels around the city. Standing in the hotel lobby in the morning, she sees a local newspaper, which has the day’s weather forecast, but it is in French. She doesn’t read French. Also in the hotel lobby is a copy of an English newspaper, which she can read, but the English newspaper doesn’t include a weather forecast for Paris. This illustrates one of the challenges of network software. To be useful, a service has to (1) provide the conceptual service the client wants (e.g., a weather forecast) and (2) provide it using a language (protocol) the client can speak and understand (e.g., English). Because of this, a Zeroconf service type name conveys not just the “what” of a service but also the “how.” For example, the Zeroconf service type _ipp encodes both the “what”—printing—and the “how” via Internet Printing Protocol.

When an IPP printing client browses for services of type _ipp, it is not looking for printers in a broad, fuzzy, not-very-precisely defined human sense. It is looking specifically for printers it can talk to. It is looking specifically for printers that implement IPP, the Internet Printing Protocol. There may be an old AppleTalk printer nearby, which may be a printer as far as human beings are concerned, but from the point of view of an IPP printing client that has no way to communicate with an AppleTalk printer, it may as well not exist. From the point of view of IPP printing client software, it’s only useful to discover things that it can actually use. This is one of the reasons that proliferation of network protocols is a bad thing. While we may

embrace the richness of variety in human languages, the same is not so desirable in network protocols. When there are 10 different ways of doing basically the same thing, there’s much opportunity for incompatibility. The server may offer the conceptual service that the client wants, but if they have no protocol in common that they both speak, it may as well not exist.

The converse is also true. Finding entities that implement a given protocol is only appropriate when the semantic service being offered is also appropriate. It is common to borrow an existing protocol and repurpose it for a new task. This is an entirely sensible and sound engineering practice, but that doesn’t mean that the new protocol is providing the same semantic service as the old one, even if it uses the same message formats. For example, the local network music-playing protocol implemented by iTunes on Macintosh and Windows is built using HTTP GET commands. However, that does not mean that it is sensible or useful to try to access one of these music servers by connecting to it with a standard web browser. The data that is being fetched via those HTTP GET commands is compact binary machine-readable data, not HTML text that a normal web browser could interpret and display as a page on the screen. If iTunes were to advertise the _http service, that would cause iTunes servers to show up in conventional web browsers like Safari and Internet Explorer, which is of little use since an iTunes server offers no pages containing human-readable content. Similarly, if iTunes were to browse for _http service, it would find generic web servers, such as the embedded web servers in devices such as printers, which is of little use since printers generally don’t have much music to offer. Consequently, the DNS-SD service advertised (and browsed for) by iTunes is the Digital Audio Access Protocol, or _daap, which conveys both the “what” of the service (a collection of music) and also the “how” (read using HTTP GET commands).

Building on DNS

As early as the 1980s, AppleTalk had an effective service discovery mechanism. Many attempts have been made to replicate that on IP, but none have been a resounding success. Zeroconf took an unconventional approach to solving this problem. Rather than inventing an entirely new protocol from scratch, it built on an existing ubiquitous standard, DNS. A scalable service discovery mechanism needs to work both on small networks, operating peer-to-peer with no infrastructure, and on large networks, where peer-to-peer multicast would be too inefficient and, instead, service discovery data needs to be stored at some central aggregation point. As pointed out in the Internet Draft on DNS Service Discovery, DNS and its related protocols already provide the properties we need:

Service discovery requires a central aggregation server

DNS already has one: it’s called a DNS server.

Service discovery requires a service registration protocol

DNS already has one: it’s called DNS Dynamic Update.

Service discovery requires a query protocol

DNS already has one: it’s called DNS.

Service discovery requires security mechanisms

DNS already has security mechanisms: they’re called DNSSEC.

Service discovery requires a multicast mode for ad-hoc networks

Zeroconf environments already require a multicast-based, DNS-like name lookup protocol for mapping hostnames to addresses, so it makes sense to let one multicast-based protocol do both jobs.

By building on an existing protocol, many of the deployment and adoption problems are already solved. Just about every large company already runs a DNS server, so the required hardware and software is already in place. DNS delegation means that if the network operators don’t want to support service discovery functions on their current DNS server, they can choose to delegate that responsibility to some other machine. Whether running on the company’s main DNS server or delegated to some other piece of hardware, the DNS technology is familiar and the software well understood. We don’t have some entirely new, unfamiliar piece of software to be installed, learned, configured, and maintained.

In the remainder of this chapter, you will see how DNS-SD builds on what exists in DNS.

Browsing for Services

The DNS protocol family already defines a record type called SRV for service discovery, specified in RFC 2782, “A DNS RR for specifying the location of services (DNS SRV).” (The letters “SRV” are not initials that stand for something; it’s just a simple contraction of the word “service.”) SRV records give us a new way of finding services for a given domain. Today, to find the web server for domain http://example.com, you would look up the address associated with the pseudo-hostname http://www.example.com. The reason we call http://www.example.com a pseudo-hostname is because www is not really the name of a host; it is really the name of a service. The user typing www doesn’t know or care what host they’re connecting to, what they care about is that the host has web pages on it. This puts us in the odd situation where some DNS names are hostnames, and others are really service names, but the distinction is blurred and vague; for any given name, it’s not always clear whether it’s intended to be the name of a logical service or the name of a particular piece of hardware. This is why, in 1996, a new DNS record type was defined, the SRV record. Using the new SRV mechanism, you would do a DNS query for the SRV record with the name http://_http._tcp.example.com. This is explicitly and unambiguously not the name of a piece of hardware. What you’re asking for with this query is HTTP service (i.e., web pages) for the domain http://example.com.

The _tcp part of the name is there for largely historical reasons. It suggests that the service usually runs by default over TCP, not UDP, though it is only a loose suggestion, and in retrospect perhaps it should have been omitted from the specification. However, the inclusion of the transport protocol label in the SRV record name does give us an accidental benefit—a DNS server operator can easily offload all the service-discovery workload from the main server by simply delegating the _tcp and _udp subdomains to some other machine.

The result of our SRV query tells us the hostname of the machine and the port number of the process on that machine offering HTTP service for the http://example.com domain. Some sites might have multiple servers running for fault tolerance reasons, in which case, we would get multiple SRV records in the response. The client then picks one of the SRV records at random. It doesn’t matter which one, since all the servers are offering the same pages.

So far, we’ve described SRV records as specified in RFC 2782. As specified there, SRV records work for finding a company’s main web page but are less useful for other kinds of service. If an employee wants to print, and there are 50 printers available at the company, then having the printing client simply pick one at random is not likely to be very useful. What DNS-SD adds to RFC 2782 is the ability to present a list to the user, so she can choose which printer she wants to use.

There’s an old joke that the answer to every problem in computer science is to add one more level of indirection. In this case, that joke offers us the answer to our problem. Instead of having 50 DNS SRV records with the name http://_ipp._tcp.example.com., we have 50 DNS pointer (PTR) records, each pointing to a differently named SRV record describing that printing service. By performing a PTR lookup for a name of the form ServiceType . Domain, you get a list of individual named instances of that service from which the client can choose. This is the key refinement that DNS-SD adds to vanilla SRV records.

Service Instance Names

When you perform a PTR lookup for a service type in a domain, you will receive zero or more PTR records containing service instance names . A service instance name adds a third piece to the name contained in your PTR lookup. Your lookup sent the name ServiceType . Domain and returned PTR records that contain service instance names consisting of Instance.ServiceType . Domain. For example, a query for http://_ipp._tcp.example.com may return the service instance names http://Sales._ipp._tcp.example.com and http://Bullpen._ipp._tcp.example.com .

The Instance portion of a service instance name is not restricted to US-ASCII characters. Any Unicode characters may be used, up to a total of 63 bytes of UTF-8 encoded text. Of course, you are free to name your services how you choose; you can use names containing only US-ASCII if you wish, but you shouldn’t feel compelled to keep names short to make them easy to type. Users select Zeroconf services by picking from an onscreen list, not by memorizing names and typing them in, so there’s really little benefit in making names terse and easy to type. You can use long names, including capital letters, spaces, punctuation, and other characters, to make them more descriptive.

You can think of the ServiceType . Domain name structure as being analogous to a directory hierarchy containing instance names. So, the example http://_ipp._tcp.example.com would correspond to the directory /com/example/_tcp/_ipp, as shown in Figure 4-1.

The directory metaphor for service instance names
Figure 4-1. The directory metaphor for service instance names

Inside of this directory, you can imagine aliases or soft links to actual instances of services of the specific type. If you wanted to select the Bullpen printer, you would double-click on its alias. In the actual case of a service instance name, when a user selects the service name in a service browser, a DNS query will be sent for the SRV record with the selected name. In response, the client receives an SRV record with the host and port information for the service. Notice that this means that a host is able to allocate its available port numbers dynamically to services that need them, instead of restricting each service to run on one predetermined, “well-known” port.

In the directory analogy, you see that the most significant part of the Instance . ServiceType . Domain triple is the domain, with the service coming in second. The idea is that within a domain, there may or may not be services offered. For a given service type within a domain, there may or may not be instances of that type. The key in this structure is that the instances are the leaves in this tree you are navigating. In a graphical user interface, typically only the instance portion of the service instance name is displayed. In principle, the service type and domain of a discovered instance don’t have to match the service type and domain of the PTR query that returned them, but in practice, they almost always do. Still, it’s good programming practice to store the full name, type, and domain of each discovered service, rather than just storing the name and assuming the other two will necessarily be the expected values.

What You See Is What You Get

One design decision in DNS-SD was that the user-visible name of a service instance is also the primary identifier for that instance. They are one and the same. If you change the name, it is conceptually a different instance. If you replace defective hardware with new hardware but continue to advertise the service using the old name, then it is conceptually the same service being offered.

There are other service discovery systems that don’t work like this. In those systems, the primary identifier for a service is some hidden binary unique ID, like the MAC address of the Ethernet interface or some other globally unique ID (GUID). These identifiers are long and cryptic and practically impossible for humans to remember. Because the unique IDs are not intended to be user-friendly, a user-visible name is also associated with the service, a mere transient ephemeral attribute, changeable at any time. On paper, this flexibility might sound attractive: you can change the “name” of a service at any time without really changing its identity. Identity is defined solely by the unchangeable unique IDs, which are hidden and supposedly never seen by human users. In practice, once you use a system like that for a while, you find the flexibility is not always the benefit it seemed. If the name does not define the identity, then two things with different names might actually be the same service. Two things with apparently the same name might really be different. When problems occur, as they frequently do with networked devices, the veil is pierced. Users are forced to start being aware of the supposedly hidden unique IDs in order to diagnose what’s really going on and solve the problem. With DNS-SD, in contrast, there is complete naming transparency. The true identifiers are not cryptic, secret, and hidden. What you see is what you get.

Flagship Service Types

Normally, the namespaces for different service types are separate. For example, you could have a file server called Home Office, a printer called Home Office, and an Ethernet-attached security camera called Home Office, and there’s no confusion because they all offer clearly different services.

The difficulty arises when there are several different protocols that offer conceptually similar services. For example, there are at least four different ways of printing over TCP/IP:

  • Old-fashioned Unix LPR printing. The data transferred is often, but not necessarily, postscript. The DNS-SD service type name is _printer._tcp.

  • Proprietary printer-specific command set, usually sent to TCP port 9100. The DNS-SD service type name is _pdl-datastream._tcp.

  • IETF-Standard Internet Printing Protocol. The DNS-SD service type name is _ipp._tcp.

  • Remote USB port emulation. The DNS-SD service type name is _riousbprint._tcp.

Suppose you have a printing client like Mac OS X’s printing client that speaks all four protocols. It browses for all four DNS-SD service types. Suppose it finds, for each type, a service instance called Home Office. Should it assume that it has found four different printers that each speak one protocol or found a single printer that speaks all four and is offering four logical services on the network?

The DNS-SD convention is that it should assume it has found one single printer that speaks all four protocols. To make this assumption safe, we want to ensure that, if there actually are four different printers on the network, they don’t pick the same name. Normally, for entities offering the same service type, Multicast DNS’s built-in name conflict detection will ensure that two services can’t have the same name. However, how should DNS-SD know that you can have a file server and a network security camera with the same name, but you should not have a service of type _pdl-datastream._tcp along with another service of type _riousbprint._tcp advertising the same name on the network at the same time? The answer is flagship service types. For each group of protocols that offer conceptually similar services, one of the protocols, usually the oldest, is nominated as the flagship of the fleet of protocols. In the case of printing protocols, the flagship protocol is Unix LPR printing (_printer._tcp). Any device advertising any protocol of the fleet must also advertise the flagship protocol. If the device speaks the flagship protocol, then it advertises it as a normal service it offers, and the usual name conflict detection ensures that there aren’t two instances of this protocol with the same name at the same time. If the device does not speak the flagship protocol, then it advertises a special empty SRV record, where the target hostname is the device’s hostname, but the target port number is zero. This constitutes an assertion that “I claim ownership of this name, but I don’t offer the actual service.” This solves the problem of ensuring mutual name uniqueness among a set of related protocols. The existence of the flagship SRV record means that attempts by other devices to create other SRV records with the same service name will register a conflict, but the absence of a PTR record advertising that service means that clients browsing for that particular service type won’t inadvertently discover our non-service and mistake it for a real offered service. In other words, the device has reserved the name in that particular namespace, preventing others from accidentally using it, without having to actually offer or advertise a real service of that type.

Flagship protocols are used when there are two or more protocols that perform effectively the same or similar functions from the user’s point of view. From our earlier example, DAAP and HTTP are not viewed as protocols in the same fleet because, even though they share a common design foundation, the functions they perform from the user’s point of view are most definitely not interchangeable.

The determination of what constitutes a fleet of protocols is not something that the software can do automatically. That determination is made by the human protocol designers. Typically the way things evolve is that initially, a first protocol is created (e.g., LPR). At this point there is no fleet, because there’s only one. Later, an improved protocol is invented (e.g., IPP), and because it does roughly the same thing as the earlier protocol, when the new service is advertised by some new device, the device also advertises the older protocol as the flagship of the newly created fleet (of two). Devices advertising only the older protocol don’t need to know this—they just continue to advertise the older protocol as they always did. As subsequent new protocols are invented that perform roughly the same function, as long as each one is specified to advertise the same original flagship protocol, then that original flagship protocol becomes the conceptual rendezvous point of the whole family of protocols for name conflict detection purposes. Eventually, many years later, it’s possible to arrive at the situation where the original protocol is obsolete and no longer used by anyone at all, but it retains its role as the non-service that every device registers, to ensure that different devices, advertising different protocols that perform roughly the same function, conceptually bump into one another if they try to advertise the same name.

Subtypes of Service Types

The design of Zeroconf was intentionally kept simple, because in network design, simplicity is the best way to achieve reliability, with products from different vendors all interoperating and working correctly with one another. For this reason, DNS-SD intentionally does not include a complicated query language allowing arbitrarily elaborate queries. What it does include is a very simple filtering capability, which can be useful for some cases. Subtypes are a useful way to advertise a service when some clients will want to find all instances of that service type, but others will only be interested in finding some subset.

Subtypes are best illustrated with an example. Suppose a game developer makes a network game. The commercial version of the game supports both open games that anyone can join and password-protected games. The game developer also makes a free version of the game client available, but the free version can only join open games without a password. In this case, the full version wants to find all available games on the network it might join, whereas the free version wants to find only open games without a password, since it can’t join password-protected games.

This selectivity can be achieved using subtypes . Suppose the DNS-SD service type for the game is _mynetgame._tcp. When starting a password-protected game, the service type _mynetgame._tcp is advertised. When starting an open game, the subtype open is used to convey that this game is open to all clients. In Apple’s Bonjour APIs, subtypes are introduced by placing them after a comma following the main type, like this: _mynetgame._tcp, open.

When a full client browses for games to join, it simply browses for the main type _mynetgame._tcp and finds all advertised instances on the network, both open and password-protected. When a restricted client browses for games and wants to find only open games, it browses for the subtype _mynetgame._tcp, open and finds only those games that were advertised with this subtype.

When advertising a service, zero, one, or more subtypes may be added as a comma-separated list after the main type. When browsing for services, at most one subtype may be specified. If a client wishes to find more than one subtype, it needs to start a separate browsing operation for each one.

In the on-the-wire packet format, subtypes are implemented by registering additional PTR records. In our example above, an open game is advertised with two PTR records, one with the name _mynetgame._tcp and another with the name open._sub._mynetgame._tcp. When the full client browses for _mynetgame._tcp, it finds all games, both open and password-protected. When a restricted client browses for open._sub._mynetgame._tcp, it finds only those instances that were advertised with this additional PTR record.

Note that, in both cases, the type of the discovered service remains the same: _mynetgame._tcp. Subtypes perform a filtering operation so that only a subset of the instances is discovered, but they don’t change the type being discovered.

Whether to use subtypes is a design decision for each protocol. Sometimes, subtypes are appropriate. Other times, it may be more appropriate to define two entirely separate types, with clients browsing for one or other or both as appropriate, and servers advertising one or other or both as appropriate.

To date, few DNS-SD protocols have specified any subtypes, and it remains to be seen how useful this mechanism will be. The most common use of subtypes so far has been for defining programmatic mappings from other communication schemes (e.g., Jini, UPnP, and web services) onto DNS-SD, to allow software written using those programming models to get the benefits of Zeroconf not offered by those other mechanisms, including pure peer-to-peer discovery that works even when no infrastructure is present and planet-wide discovery using wide-area DNS Service Discovery.

Late Binding

Sometimes, when a user chooses a service from a list, it is for immediate use.

Other times, a user makes a choice, like picking a default printer, which may be used repeatedly in the coming hours, days, or weeks. In the latter case, it’s important that the client software store the chosen service name, type, and domain, instead of resolving the named service to an IP address and storing that. This is because IP addresses and port numbers can change, whereas service names are the intended stable identifier for a given logical service instance. As long as the client resolves the service name at printing time, it will be sure to get the current address and port number, even if they have changed in the time since the service was first discovered.

DNS-SD TXT Records

In many cases, all a client needs to know to contact and use a service are the hostname or IP address where that service resides and the port number on that host.

There are other cases where more information is required. For example, a print server may advertise three LPR printers. All three logical printing services are being offered on the same host. All are being offered via the LPR port. What distinguishes them is the LPR queue name. How does a client, having discovered an advertised printer, know what LPR queue name to specify when contacting the machine hosting that service? If it doesn’t specify the right LPR queue name, its output may not go to the right physical printer. The answer is the DNS TXT record. In addition to the SRV record, every DNS-SD service has a TXT record, optionally containing additional parameters and attributes of interest to clients. DNS-SD uses the DNS TXT record to store a series of key/value pair attributes in the form “key=value.” The TXT record is a standard DNS record type, but DNS-SD establishes some conventions about how it is used for DNS-SD service types. Those conventions are described in this section.

Tip

The TXT record should not duplicate information that is stored elsewhere—for example, the host and port number for the service—since those are obtained from the SRV record.

Format for DNS TXT Records

Since DNS-SD uses standard DNS TXT records , these records must conform to format rules. In particular, the data consists of one or more strings , each of which consists of a single length byte followed by 0-255 bytes of text. An example of such a string is:

    | 0x08 | p | a | p | e | r | = | A | 4 |

In this diagram, the first byte of data is a binary byte with value 8. It is then followed by eight more bytes of data, each containing the ASCII (or UTF-8) codes for the character indicated. For example, the second byte contains the value 0x70, the ASCII code for lowercase P; the third byte contains the value 0x61, the ASCII code for lowercase A. Note that there is no terminating zero at the end of the string, as there conventionally is with strings in the C programming language.

According to the DNS specification (RFC 1035), a TXT record must contain at least one string. An empty TXT record with zero strings is not allowed. Because of this, you’ll often see DNS-SD services advertised with a TXT record containing a single empty string (a single zero length byte, followed by no data).

The total size of a typical DNS-SD TXT record is intended to be small—200 bytes or less. If large amounts of data need to be transferred, making this part of the client protocol is better than using a large TXT record.

However, there are some cases in which we are dealing with a legacy protocol like LPR, and we are not at liberty to change the client protocol. In this case, it is sometimes necessary to use TXT records of around 400 bytes to provide sufficient information to the client. Keeping the total size under 400 bytes should allow it to fit in a single standard 512-byte DNS message. (This standard DNS message size is defined in RFC 1035.)

In extreme cases where even 400 bytes is not enough, keeping the size of the TXT record below 1,300 bytes allows it to fit in a single 1,500-byte Ethernet packet. Using TXT records larger than 1,300 bytes becomes much less efficient on the network and is not recommended.

Content of DNS-SD TXT Records

Each component string in a DNS-SD TXT record consists of a key/value pair preceded by a byte giving the length of the string containing this information. The example given above was:

    | x08 | p | a | p | e | r | = | A | 4 |

In this example, the key is paper, the value is A4, and the length of the string “paper=A4” is eight bytes, which is given by the initial length byte x08. The key component is interpreted without regard for case, so paper, Paper, and PAPER are seen as identical. Spaces are significant in keys, so the strings “Papersize” and “Paper size” are distinct. Note that this means that if you insert a space before the equals sign, it is interpreted as a trailing space in the key. So paper=A4 and paper =A4 are distinct key/value pairs. The moral is: don’t add unintended spaces.

The key must consist of at least one character, while the value may be absent. The way the key/value pair is parsed is that everything after the length byte until the first equals sign is the key, and everything following the first equals sign to the end of the string is the value. This means that a key cannot contain an equals sign as one of its characters. The key is allowed to contain any printable US-ASCII character other than = (0x3D). Other UTF-8 values are not permitted in key names because they complicate things without increasing the expressive power of the protocol—key names are not intended to be user-visible. They just need to be unique identifiers—such as C variable names—that are used by the software.

If the string contains no equals sign, then the entire string is the key, which is interpreted conceptually as a Boolean attribute; it exists but has no assigned value. In general, for a key that is used to indicate a Boolean value, if the key is present the Boolean is true, and if the key is absent the Boolean is false.

A value is made up of any eight-bit binary values. In the case of textual data, UTF-8 encoding is strongly recommended, but TXT record values don’t have to be readable text. If you have some binary data to store, it is much more efficient to store it as binary data than to convert it to text using hexadecimal or Base-64 encoding. For example, an IPv4 address is just 4 bytes as binary data but up to 15 when written as text (e.g., “192.168.108.221”).

A string beginning with an equals sign will be ignored, as it would have to be interpreted as a key/value pair with an empty key, which is not allowed. If any key appears more than once in a TXT record, any appearances other than the first are silently ignored.

Interpreting DNS-SD TXT Records

When examining a TXT record for a given named attribute, there are four types of results :

  • The attribute is not present. For an attribute that takes a Boolean value, this would indicate that the value of the attribute is false.

  • The key of an attribute is present but no equals sign or value appears. For an attribute that takes a Boolean value, this indicates that the value of the attribute is true.

  • The attribute is present with an empty value (there’s an equals sign, but nothing following).

  • The attribute is present with a non-empty value (there’s an equals sign, with one or more bytes following).

The specification for a given DNS-SD service specifies how these four states are to be interpreted. For example, for some keys, there may be a natural true/false Boolean interpretation:

  • Present implies true.

  • Absent implies false.

For other keys it may be sensible to define other semantics, such as value/no value/unknown.

Clients should ignore unknown keys they find in TXT records. This allows the protocol to be enhanced over time, adding new keys with new meanings, without breaking compatibility with older clients.

To further support possible changes to the specification of a particular service type, authors are encouraged to include a version attribute of the form txtvers= xxx. Even if you don’t anticipate future versions of your specification, you may still find in the future that you need to make a correction or addition to fix a mistake, or to address an unanticipated condition in the use of your service. Version numbers allow a client to ignore TXT records with versions newer than the highest txtvers number that the client knows how to interpret. The initial value of txtvers should be 1. Then, at a later time, if changes have to be made that result in a TXT record that is fundamentally incompatible with older clients, which they have no hope of reading correctly, then incrementing the txtvers to the next number signals to those older clients that they shouldn’t even bother trying to parse this TXT record data. Such incompatible changes are best avoided if at all possible, but it is still good to have a mechanism available so that if incompatible changes are unavoidable, it is at least possible to make the change safely, without confusing old clients or causing them to behave incorrectly.

Summary

DNS Service Discovery using Multicast DNS provides a simple, efficient, lightweight way to discover what services of a given type are available to you on the local network. Next, Chapter 5 shows how DNS Service Discovery using Unicast DNS takes the same elegant, simple concepts and scales literally to the entire planet, using the existing hierarchy of DNS servers that’s already in place and well understood at just about all large companies, universities, and other similar organizations around the world.

Get Zero Configuration Networking: The Definitive Guide now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.