Browsing like it's 1994

By Connor Taffe | Published .

Before the ubiquity of the Internet, before WiFi, even before Ethernet was affordable, there was the LocalTalk physical layer and cabling system and its companion suite of protocols called AppleTalk1. A network ahead of its time in terms of plug-and-play2, but not quite as fast as 10mbit/s Ethernet at 230.4 kbit/s.

A few weeks ago, I found a Macintosh SE on Facebook Marketplace. It turned out to be running System 7.1, and had Microsoft Word 5 installed. Years prior, I had recapped an Apple IIGS and brought it back to life, and attempted to network it using LocalTalk and an ImageWriter II with a LocalTalk Option card, but was unsuccessful. With the Macintosh, I was finally able to use my ImageWriter II over AppleTalk!

Off to a good start, I wanted to expand my LocalTalk network. I swapped the ImageWriter II for an AsanteTalk and discovered that my HP LaserJet 4100N from 2004 with a 635n EIO networking card spoke EtherTalk and advertised itself as a LaserWriter. I was able to print the same document on the LaserJet, using the built-in PostScript driver for the LaserWriter -- the result was beautiful crisp text. In fact, there's another EIO card in the LaserJet; it provides USB connectivity alongside a LocalTalk port, so it could become part of my LocalTalk network as well.

HP JetDirect Cards
HP JetDirect Cards

Next, I ordered some LocalTalk adapters from eBay to convert my 8 pin DIN to 3 pin locking LocalTalk ports which work with LocalTalk cabling. Each adapter has two ports, which supports chaining devices together. Unfortunately, LocalTalk cabling is expensive (and PhoneNet was used more often at the time), so my LocalTalk network is limited to the Macintosh and AsanteTalk for the moment. With the AsanteTalk3, we open up the possibility of interfacing with a wider Ethernet and IP network.

As we enter the early 90s and the Internet becomes more widely available, these older Macintosh computers were used to access it. MacTCP and the AsanteTalk helped to enable this, and that's what this post is about.


This post is broken down into several sections:

Printing over LocalTalk

Printing from a Macintosh SE to an ImageWriter is easy with the LocalTalk option card.

Netatalk 2.x

Netatalk is the Linux implementation of several Apple protocols including AppleShare. Before 3.x, it supported AppleTalk, the protocol that Apple used before the switch to IP, importantly for us this is the protocol used over the LocalTalk and EtherTalk physical layers. There are several forks of Netatalk 2.x maintained by the retrocomputing community:

In the 5 years since the release of Netatalk 2.2.6, an impressive number of forks and projects with their own downstream patchset to keep Netatalk running have emerged. Here are a few of the major ones that I encountered:

Last year, Daniel Markstedt (handles rdmark or slipperygrey) released a new Netatalk 2.x fork which can be compiled on modern Linux and includes systemd services. I'll be installing it on a Fedora Server VM running on ESXi.


To get netatalk-2.x installed and serving AFP and AppleTalk, we need to compile it. First, we'll install some dependencies:

; sudo dnf install openssl-devel libgcrypt-devel libdb-devel automake libtool avahi-devel cups-devel
Dependency Feature
avahi-devel Zeroconf (Bonjour) service discovery in Mac OS X 10.2 or later
cups-devel papd printer server support
libgcrypt-devel DHX2 authentication support, required for Mac OS X 10.2 or later

Then we'll need the appletalk kernel module for AppleTalk network support. On Fedora this is provided by kernel-modules-extra, but not on Fedora 35:

; sudo dnf install kernel-modules-extra

On Fedora, the appletalk module is blacklisted. To allow it, edit the file /etc/modprobe.d/appletalk-blacklist.conf and comment out the last line:

# This kernel module can be automatically loaded by non-root users. To
# enhance system security, the module is blacklisted by default to ensure
# system administrators make the module available for use as needed.
# See for more details.
# Remove the blacklist by adding a comment # at the start of the line.
#blacklist appletalk

Then have the module load automatically4, we need to add the file /etc/modules-load.d/appletalk.conf:

# Load appletalk.ko at boot

which configures the systemd-modules-load.service service. You can test it with:

; sudo systemctl start systemd-modules-load.service
; journalctl -n 10 -u systemd-modules-load.service
Aug 05 01:25:44 systemd[1]: Starting systemd-modules-load.service - Load Kernel Modules...
Aug 05 01:25:44 systemd-modules-load[1433]: Inserted module 'appletalk'

Upon reboot, the module should be automatically loaded. To test the module is loaded:

; lsmod | grep '^appletalk'

and to manually load the module:

; sudo modprobe appletalk

To compile netatalk-2.x, first clone the repo:

; git clone

Then run the boostrap script5.

; ./bootstrap

Now run the configure script, options are described in this post.

; ./configure --enable-systemd --enable-ddp --enable-a2boot --enable-cups --enable-timelord --enable-zeroconf --disable-quota --sysconfdir=/etc --with-uams-path=/usr/lib/netatalk

Finally, run make then make install as root.

; make
; sudo make install


Now you should have the systemd services in place for atalkd.service and afpd.service among others. First let's set up some minimal config files under /etc/netatalk/:

To configure atalkd.conf, you'll need the name of the interface that an AppleTalk network will be present on (a LAN):

; ip addr
1: lo: ...
2: ens160: ...

In my case my VM's interface is named ens160, so my /etc/netatalk/atalkd.conf file ends with the line:

ens160 -router -phase 2 -net 1 -addr 1.41 -zone "office"

Next is Apple Filing Protocol, which is configured in /etc/netatalk/afpd.conf:

"Office" -transall -uamlist,,

I use "Office" instead of - because I want a friendly name instead of the VM hostname. transall enables both DSI (Data Stream Interface6) over TCP and DDP (Datagram Delivery Protocol7) aka EtherTalk, the AppleTalk data link layer on the Ethernet physical layer. The modules listed enable both DDP and DSI, the guest UAM for anonymous read-only access, the clrtxt UAM for Classic Mac OS authentication, and DHX2 UAM for Mac OS X / macOS authentication. The guest login only allows read-only access to shares, and System 7's AppleTalk interface in Chooser limits passwords to 8 characters. Netatalk authenticates against system users, so I created a new macintosh user with an 8-character password to allow logins.

; sudo useradd macintosh
; sudo passwd macintosh

Next is the AppleVolumes.default file, which defines the volumes available to connecting systems. By default a user's home directory is exposed as a share with this line


but we can also add other shares:

/srv/appletalk "Share" options:prodos

This creates a share at /srv/appletalk, named Share, with the prodos option which allows the Apple IIGS to use the share or boot from it.

You can now enable the services:

; sudo systemctl enable --now atalkd.service
; sudo systemctl enable --now afpd.service


To ensure we can access these shares over TCP using afpovertcp from a modern mac, we need to open the firewall. I created a new service for the port and enabled it:

; sudo firewall-cmd --permanent --new-service=afpovertcp
; sudo firewall-cmd --permanent --service=afpovertcp --add-port=548/tcp
; sudo firewall-cmd --permanent --add-service=afpovertcp
; sudo firewall-cmd --reload


Netatalk also includes a Printer Access Protocol daemon called papd which integrates with CUPS and provides bidirectional printing support.

Apple ImageWriter II
Apple ImageWriter II

Macintosh to CUPS

Next we'll edit /etc/netatalk/papd.conf to expose our CUPS printers to the AppleTalk network, see these directions:


The documentation tells us:

If used as the first entry in papd.conf this will share all CUPS printers via papd. type/zone settings as well as other parameters assigned to this special printer share will apply to all CUPS printers. Unless the pd option is set, the CUPS PPDs will be used. To overwrite these global settings for individual printers simply add them subsequently to papd.conf and assign different settings.

We should now enable the service

; sudo systemctl enable --now papd.service

CUPS to ImageWriter II

To have CUPS print to an AppleTalk printer, we need a pap backend, there are good directions here8. By default, the backend only looks for LaserWriter devices, edit /usr/lib/cups/backend/pap so that devicetypes reflects this or set it to devicetypes="=" to find all devices.


With the pap backend in place, we should see our printer here:

lpinfo -v
network pap://office/HP%20LaserJet%204100%20Series/LaserWriter
network pap://office/ImageWriter/ImageWriter

If it doesn't show up, ensure your printer is shared over AppleTalk:

; nbplkup
    AsantéTalk 94B02967:Asant�Talk                         1.111:252
            ImageWriter:ImageWriter                        1.113:138

We also need to update our /etc/cups/cupsd.conf file with:

BrowseOrder allow,deny
BrowseAllow all
BrowseRemoteProtocols CUPS dnssd pap
BrowseAddress @LOCAL
BrowseLocalProtocols CUPS dnssd pap

and restart the services:

; sudo systemctl restart cups.service

Now in the CUPS UI, under Administration > Add a Printer, you should see the AppleTalk Devices via pap option. Select it and continue, then copy the URL from lpinfo -v or constructed from nbplkup info into the form, name the printer, and upload the ImageWriter II PPD file.

We need to patch9 our Netatalk 2.x distribution so that the status check doesn't error on ImageWriter IIs. Apply this patch to your netatalk-2.x directory:

; curl -s | git apply -
; make
; sudo make install
; sudo sytemctl restart papd.service

Now from the CUPS UI, you can print a test page and it should print from the ImageWriter II:

ImageWriter II printing the CUPS test page
ImageWriter II printing the CUPS test page

By adding a Avahi service file, we can even print via AirPrint:

<?xml version="1.0" ?>
<!DOCTYPE service-group  SYSTEM 'avahi-service.dtd'>
	<name>ImageWriter II</name>
		<txt-record>product=(ImageWriter II)</txt-record>

The final flow is:

%3cluster_vmVMcluster_iwImageWriter IIiphoneiPhonecupsCUPSiphone->cupsPDF over IPPdriverGhostScript iwhi drivercups->driverFilterspapPAP Backenddriver->papRaster dataasanteAsanteTalkpap->asanteEtherTalk card LocalTalk Option Card asante->card LocalTalk printer Printer card->printer

Adding files

In each share, Netatalk creats metadata stores. Files in the share only represent only part of a file, the metadata is maintained in these databaes. If you add a Macintosh file in Linux, or even if you copy an existing file to a new name, it'll show up to the Macintosh as an unknown file -- the metadata about how to open it has been lost. This presents quite a problem: without a Macintosh with the file, how can I add files to my share? I've come up with a couple of options:

When using slirp with Basilisk II, the IP configuration (using OpenTransport) are not the settings from your network, but the network within Basilisk II:

Setting Value
IP Address
Subnet mask
Router address
Name server address

I couldn't get slirp to work, but I discovered that the shared directory between macOS and System 7 on Basilisk II can hold the StuffIt files going into Basilisk II and the resulting decompressed files. And macOS can handle Macintosh files without disrupting the metadata. We can mount the Netatalk AFP server to our modern Mac via afpovertcp, then copy the un-stuffed program files from Basilisk II from the share folder to our AFP folder. Or, we can make the AFP share folder our Basilisk II share folder and skip the extra copy. I was able to copy the StuffIt program file this way from Basilisk II to my Macintosh SE.

%3cluster_laptopLaptopcluster_macMacintosh SEbasiliskBasilisk IItcpshareAFP mountbasilisk->tcpshareshare foldernetatalkNetatalk 2.xtcpshare->netatalkTCPshareAFP mountshare->netatalkAppleTalk

There are several file formats used for old files:

Sometimes an .img file may be StuffIt compressed, to deal with that:

Here is an update list, and here's another for System 7.1.

Getting Online

Mac IP Gateway

To get our Macintosh online, we need either OpenTransport10 (for newer versions of System 7) or MacTCP. They both work by proxying IP packets over AppleTalk where a gateway, (originally a newer Mac running Apple IP Gateway11) translates them to IP on Ethernet.

Using macipgw, which was originally written for FreeBSD but has now been ported to Linux, we can provide this gateway. The AppleTalk packets themselves are copied from LocalTalk to Ethernet by an AsanteTalk, and since EtherTalk is routed over Ethernet and not IP, any system or VM running this software or Netatalk 2.x must have a physical Ethernet connection. Unfortunately, macipgw requires a kernel with CONFIG_IPDDP disabled:

Your kernel must be configured with the CONFIG_IPDDP option disabled completely. It is not sufficient to compile it as a module -- in order to support the module, the kernel is modified to intercept all MacIP traffic, so userspace applications such as macipgw cannot handle it.

And my Federa 37 kernel on the VM where I run Netatalk has it configured as a module:

; cat /boot/config-$(uname -r) | grep CONFIG_IPDDP

There is a ready made ISO image from based on Tiny Core Linux, which I can run on ESXi with 512MB of memory and no hard disk (it's a live CD).


Using MacWeb 0.98 and MacTCP configured with the IPs provided by tinymacipgw, I was able to access the local network and load this blog's index page from the Kubernetes cluster in my office closet. I attempted to use Netscape Navigator and iCab based on the list from here to no avail, Netscape Navigator crashed and iCab reported that it didn't have enough memory (the Macintosh has 4MB of RAM which is the maximum configurable).

MacWeb 0.98 on a Macintosh SE running System 7.1
MacWeb 0.98 on a Macintosh SE running System 7.1

Below is a diagram of the path from the Mac to the internet:

%3cluster_macMacintosh SEcluster_vmsESXimacwebMacWeb 0.98mactcpMacTCPmacweb->mactcpasanteAsanteTalkmactcp->asanteLocalTalkmacipMacIP Gatewayasante->macipEtherTalkrouterpfSensemacip->routerIPinternetInternet router->internet

Here's what an HTTP request from MacWeb looks like:

; nc -vv -l -p 8000
Connection from
GET / HTTP/1.0
Accept: application/mac-binhex40 q=0.500
Accept: audio/basic q=0.500
Accept: image/gif q=0.500
Accept: image/jpeg q=0.500
Accept: image/pict q=0.500
Accept: image/x-xbitmap q=0.500
Accept: video/mpeg q=0.500
Accept: video/quicktime q=0.500
Accept: www/source q=0.300
Accept: www/unknown q=0.300
Accept: application/octet-stream q=0.100
Accept: text/plain
Accept: text/html
User-Agent:  MacWeb/libwww/2.13  libwww/unknown

The Accept header syntax has some quirks when compared to the standard. Instead of separating the options with , on a single line, each option gets its own line; and instead of separating each option and its q= with a ;, there is a space. The browser also places the most preferred format at the end, instead of at the beginning as would be expected within a single line to distinguish between multiple values with no q. And, it has no support for application/xhtml+xml, a MIME type registered in 2002 well after its release. After some adjustments, this website is now viewable on MacWeb 0.98, although all pages except the index seem to hang (I assume because they're too large).

There are also some interesting MIME types in the request, like BinHex for applications or audio/basic for audio (the digital audio encoding introduced by the telephone system)

The content of the "audio/basic" subtype is single channel audio encoded using 8bit ISDN mu-law [PCM] at a sample rate of 8000 Hz.

It also advertises image/pict for the PICT graphics format:

PICT is a file format that was developed by Apple Computer in 1984 as the native format for Macintosh graphics. PICT files are encoded in QuickDraw commands. The PICT file format is a meta-format that can be used for both bitmap images and vector images.

There's also www/source and www/unknown, an artifact of its use of libwww, now available on GitHub. I found some information in the MIT WWW Library HTFormat docs. It seems to predate MIME:

The www/xxx ones are of course not MIME standard.

star/star is an output format which leaves the input untouched. It is useful for diagnostics, and for users who want to see the original, whatever it is.

#define WWW_SOURCE	HTAtom_for("*/*")      /* Whatever it was originally */

www/present represents the user's perception of the document. If you convert to www/present, you present the material to the user.

#define WWW_PRESENT	HTAtom_for("www/present")   /* The user's perception */

The message/rfc822 format means a MIME message or a plain text message with no MIME header. This is what is returned by an HTTP server.

#define WWW_MIME	HTAtom_for("www/mime")		   /* A MIME message */

www/print is like www/present except it represents a printed copy.

#define WWW_PRINT	HTAtom_for("www/print")		   /* A printed copy */

www/unknown is a really unknown type. Some default action is appropriate.

#define WWW_UNKNOWN     HTAtom_for("www/unknown")

NCSA Mosaic

Based on a comment on Hacker News, I also gave NCSA Mosaic 1.0.3 a try using this copy. It works! Mosiac 1.x is the last to work on the Macintosh SE, since Mosaic 2.0.1 asks for 5MB of memory.

NCSA Mosiac 1.0.3 on a Macintosh SE running System 7.1
NCSA Mosiac 1.0.3 on a Macintosh SE running System 7.1

The historical relevance of Mosaic can't be understated. Marc Andreessen led the NCSA Mosaic project, and went on the found Netscape, eventually co-founding the VC firm Andreessen Horowitz. Netscape went on to become Mozilla Firefox, one of today's major browsers. In 1995, Internet Explorer had its start when Microsoft licensed Spyglass Mosaic (which shared no code with NCSA Mosaic but licensed the name).

%3ncsaNCSA MosaicnetscapeNetscapencsa->netscapespyglassSpyglass Mosaicncsa->spyglassfirefoxMozilla Firefoxnetscape->firefoxahAndreessen Horowitznetscape->ahieInternet Explorerspyglass->ie

Mosaic's creation was enabled by Al Gore's bill High Performance Computing and Communication Act of 1991, Andreessen had this to say:

If it had been left to private industry, it wouldn't have happened. At least, not until years later.

It accounts for the origins (at least in name) of two major browsers, of which only Firefox is still relevant today since the switch from IE to Edge. As for the other two major browsers: Safari was originally derived from the KDE project's Konqueror browser engine, KHTML (and JavaScript runtime KJS); the WebCore component of Safari's browser engine, WebKit, was forked into Blink, Chrome's browser engine. Chrome's open source project, Chromium, is the basis for several other browsers including Brave, Vivaldi, and Opera.


So, we can trace the lineage of every major browser today back to an early browser written in the 1990s.

And here's what a request looks like from NCSA Mosaic:

; nc -vv -l -p 8000
Connection from
GET / HTTP/1.0
Accept: text/plain
Accept: application/x-html
Accept: application/html
Accept: text/x-html
Accept: text/html
Accept: text/richtext
Accept: application/octet-stream
Accept: application/postscript
Accept: application/mac-binhex40
Accept: application/zip
Accept: application/macwriteii
Accept: application/msword
Accept: image/gif
Accept: image/jpeg
Accept: image/x-pict
Accept: image/tiff
Accept: image/x-xbm
Accept: audio/x-aiff
Accept: audio/basic
Accept: video/mpeg
Accept: video/quicktime
Accept: application/macbinary
Accept: */*
User-Agent:  MacMosaicB6  libwww2.09

There is an option to use HTTP 0.9, which simply sends:

; nc -vv -l -p 8000
Connection from

There are some interesting non-standard MIME types here as well, such as application/x-html and text/x-html. There's also application/macbinary for the MacBinary (.bin) format (similar to BinHex referenced above with application/mac-binhex40) used to transfer both data and resource forks of Macintosh application file across the network. We also see formats specific to Microsoft Word and MacWrite II.

  1. The best resource for AppleTalk is Inside AppleTalk, second edition. Cisco also has detailed instructions on Configuring AppleTalk routing.

    AppleTalk Network ↩︎

  2. As Apple shifted to IP, it helped to create Bonjour aka Multicast DNS (mDNS) or DNS Service Discovery (DNS-SD) which allows computers on a network to advertise services they provide on the network. The ZeroConf standardized IPv4LL (IPv4 Link Local addressing), a requirement for fully plug-and-play networking without a DHCP server:

    The IETF Zeroconf Working Group was chartered September 1999 and held its first official meeting at the 46th IETF in Washington, D.C., in November 1999. By the time the Working Group completed its work on Dynamic Configuration of IPv4 Link-Local Addresses and wrapped up in July 2003, IPv4LL was implemented and shipping in Mac OS (9 & X), Microsoft Windows (98, ME, 2000, XP, 2003), in every network printer from every major printer vendor, and in many assorted network devices from a variety of vendors. IPv4LL is available for Linux and for embedded operating systems. If you’re making a networked device today, there’s no excuse not to include IPv4 Link-Local Addressing.

    The specification for IPv4 Link-Local Addressing is complete, but the work to improve network ease-of-use (Zero Configuration Networking) continues. That means making it possible to take two laptop computers, and connect them with a crossover Ethernet cable, and have them communicate usefully using IP, without needing a man in a white lab coat to set it all up for you. Zeroconf is not limited to networks with just two hosts, but as we scale up our technologies to larger networks, we always have to be sure we haven’t forgotten the two-devices (and no DHCP server) case.

    Historically, AppleTalk handled this very well. Back in the 1980s if you took a group of Macs and connected them together with LocalTalk cabling, you had a working AppleTalk network, without any expert intervention, without needing to set up special servers like a DHCP server or a DNS server. In the 1990s the same was true using Ethernet — if you took a group of Macs and plugged them into an Ethernet hub, you had a working AppleTalk network, using AppleTalk-over-Ethernet. Now that it’s common for computers to have IEEE 802.11 ("AirPort") networking built-in, you don’t even need cables or a hub.

  3. The AsanteTalk is a small metal box with a wall-wart power supply, Ethernet port, and 8-pin DIN port which can either be connected directly to the printer port of a Macintosh, Apple IIGS, or to a printer, or to a LocalTalk 3-pin DIN adapter and used with locking LocalTalk cabling as part of a wider LocalTalk network. The Farallon PhoneNet adapters were popular in this era, and you can use these adapters as well along with standard 4-wire RJ11 terminated phone lines. The Macintosh Troubleshooting Pocket Guide from 2002 answers this question:

    How do I connect my LocalTalk printer to my USB Mac? Printers like the LaserWriter IINT, NTX, F, Personal LaserWriter NT, NTR, 320, LaserWriter Pro 600, 4/600 PS, Select 360, Color StyleWriter 6500, or an HP LaserJet with "M" or "MP" in its name?

    To connect these printers to a new Mac, you must use an Ethernet to LocalTalk Bridge:

    1. The AsanteTalk Ethernet to LocalTalk Bridge includes everything you need to connect a LocalTalk printer to a new Mac. It works with existing drivers.
    2. If the printer is already connected to a LocalTalk network, you can use Farallon's iPrint LT. The iPrint LT is similar to the AsanteTalk, except that it has a PhoneNet jack instead of a LoclTalk DIN-8 jack. If your existing LocalTalk network has more than eight LocalTalk devices on it, you need a much more expensive bridge, and are better off upgrading to Ethernet all around.

    One gotcha: Mac OS 10.2 and later no longer support PostScript Level 1 printers -- only PostScript 2 & 3. So your old LaserWriter II NTX and other PostScript Level 1 printers will not work from 10.2 at all.

    More information on the AsanteTalk is available in the User Manual. I found this manual on Marushin, a website for a Japanese shop which focuses on old Macintosh computers.

    I can't help but fix it. This is the spirit of the 65-year-old shopkeeper.

  4. See Persistent Module Loading in the Fedora docs. ↩︎

  5. This StackOverflow answer has more info ↩︎

  6. DSI was introduced with MacTCP to enable AppleTalk over TCP and enable IP networking. I've found this PDF of chapter 5 on ADSP of Inside Macintosh: Networking↩︎

  7. The excellent book Inside AppleTalk covers DDP in chapter 4. I've also found this PDF of chapter 7 on DDP of Inside Macintosh: Networking↩︎

  8. As documented in this footnote, there are several pap backends based on the work of Rupi, which I first discovered reading How CUPS talks to Print Servers, Print Clients and Printers. Its link to the pap backend is only available via the Way Back Machine↩︎

  9. I adapted the approach taken by Carpentier Pierre-Francois in thier fork. In futher testing, it seems the patch to papstatus.c is unnecessary and reports incorrect status information, the Netatalk 2.x version already returns a human-readable string. There is a bug where CUPS displays discovered network printers, PAP printers will display the status strangely:

    ImageWriter II@office (pap) (%%[ status: Processing... ]%%)
  10. OpenTransport was an Apple implentation of the UNIX STREAMS networking API, described in Tech Note 1117. Dennis Ritchie wrote in A Stream Input Output System:

    Patchwork solutions to specific problems were destroying the modularity of this part of the system. The time was ripe to redo the whole thing. This paper describes the new organization.

    I think this was in response to approachs such as BSD Sockets. STREAMS was further iterated on in Plan 9↩︎

  11. A copy of the original software is available here ↩︎

See comments on Hacker News.