Skip to content

Instantly share code, notes, and snippets.

@VinDuv
Last active November 3, 2024 18:07
Show Gist options
  • Save VinDuv/4db433b6dce39d51a5b7847ee749b2a4 to your computer and use it in GitHub Desktop.
Save VinDuv/4db433b6dce39d51a5b7847ee749b2a4 to your computer and use it in GitHub Desktop.
AppleTalk on Linux issues and fixes

AppleTalk on Linux

The appletalk Linux module provides support for AppleTalk sockets and routing, allowing the Netatalk tools to work properly. There are, however, some issues with the current implementation.

Note: All issues are now fixed; this document is left as reference.

Module loading issue (Linux kernel v5.1-rc1 to 5.8, fixed in 5.9)

Part of the appletalk module initialization code was refactored during development of the 5.1 version of the kernel. Unfortunately, a return statement was missed in a function, resulting in the function always falling.

If you have atalkd complaining that it can’t open an AppleTalk socket because the protocol is not supported, try loading the appletalk module manually, by running modprobe appletalk. If you get the following error:

modprobe: ERROR: could not insert 'appletalk': Cannot allocate memory

it means your kernel is affected. To fix it, you need to apply the following patch:

Fix atalk_proc_init success code path
--- linux/net/appletalk/atalk_proc.c 2020-06-01 01:49:15.000000000 +0200
+++ linux/net/appletalk/atalk_proc.c 2020-06-05 07:44:42.748258347 +0200
@@ -229,6 +229,8 @@
                                     sizeof(struct aarp_iter_state), NULL))
                goto out;
 
+       return 0;
+
 out:
        remove_proc_subtree("atalk", init_net.proc_net);
        return -ENOMEM;

(Note that you normally don’t need to load the module before starting atalkd; it will be automatically loaded once an AppleTalk socket is opened)

Broadcast packets handling issue (fixed in 6.9, all prior versions probably affected)

This issue only affects the use of atalkd in multi-interface configuration (i.e. you have two Ethernet interfaces on the machine running atalkd, and both of them are connected to Mac computers using AppleTalk). In this configuration, atalkd is supposed to announce the network settings to the computers so they get correct AppleTalk addresses assigned, and then the kernel will route AppleTalk packets between interfaces so Macs on the first network can communicate with Macs on the second network.

When AppleTalk is enabled on a Mac, it first tries to discover the network configuration by sending a network information request packet on the AppleTalk broadcast address (0.255), port 6; the local AppleTalk router is supposed to send back a packet indicating which AppleTalk network number(s) is/are associated to this network. The Mac will then choose a random address in the network, check that it does not conflict with another machine, and assign it to itself.

When started, atalkd configures the AppleTalk addresses on the machine, then waits for network information requests to be received. Suppose that you have the following AppleTalk configuration:

Interface AppleTalk network number Local AppleTalk address
eth1 1 1.1
eth2 2 2.1

In this configuration, after setting up the interfaces, atalkd will create two sockets, listening respectively on 1.1 port 6, and 2.1 port 6. When receiving a network information request on the 1.1 socket, it will respond “This is network 1” on the same socket; same thing for socket 2.

The problem is that Linux does not handle AppleTalk broadcast packets the way atalkd expects. When a broadcast packet is received on port 6, Linux will look at any AppleTalk socket listening on port 6, and give the packet to the first one it finds. (In practice, the packet will be given to the most-recently created socket listening on the correct port).

In practice, that means that atalkd will see all network information requests come from the same socket, and will send replies on this socket, so they will all come out on the same interface, which is not necessarily the one which received the packet initially.

To fix this issue, the following patch modifies the socket selection process that is used when a broadcast packet is received; it will prioritize sockets listening on the address associated with the interface that received the packet (and has the right port). If no such socket is found, another random socket (listening on the right port) will be used, as before.

Prioritize sockets listening on the interface when receiving broadcast packets.
--- linux-5.7.0.orig/net/appletalk/ddp.c        2020-06-01 01:49:15.000000000 +0200
+++ linux-5.7.0/net/appletalk/ddp.c     2020-06-05 07:44:17.363132984 +0200
@@ -88,6 +88,7 @@
                                        struct atalk_iface *atif)
 {
        struct sock *s;
+       struct sock *default_socket = NULL;
 
        read_lock_bh(&atalk_sockets_lock);
        sk_for_each(s, &atalk_sockets) {
@@ -96,9 +97,20 @@
                if (to->sat_port != at->src_port)
                        continue;
 
+               /* Prioritize reception of broadcast packets on the socket whose
+                * address matches the interface's address */
                if (to->sat_addr.s_net == ATADDR_ANYNET &&
-                   to->sat_addr.s_node == ATADDR_BCAST)
-                       goto found;
+                       to->sat_addr.s_node == ATADDR_BCAST) {
+                       /* Any socket with the right port can receive broadcast
+                        * packets... */
+                       default_socket = s;
+
+                       /* ... but the socket whose address matches the interface's
+                        * address (if any) is preferred */
+                       if (atif->address.s_node == at->src_node &&
+                               atif->address.s_net == at->src_net)
+                               goto found;
+               }
 
                if (to->sat_addr.s_net == at->src_net &&
                    (to->sat_addr.s_node == at->src_node ||
@@ -115,7 +127,8 @@
                        goto found;
                }
        }
-       s = NULL;
+       
+       s = default_socket;
 found:
        read_unlock_bh(&atalk_sockets_lock);
        return s;

With this patch, you should be able to configure atalkd to use multiple interfaces and be able to route packets between these interfaces. Here is a basic atalkd.conf that you can use as long as no more than 252 Macs are attached to each interface (which is likely):

some-interface -router -phase 2 -net 1 -addr 1.1 -zone "My Net"
some-other-iface -router -phase 2 -net 2 -addr 2.1 -zone "My Net"
etc...

Once atalkd is started, any Mac on the network should join the right network when AppleTalk is activated. The zone name My Net should be displayed in the AppleTalk or Network control panel, and the AppleTalk address should match the network (you can see advanced information in the AppleTalk control panel by switching to Advanced mode in the Edit menu)

How do I fix this?

If you want to patch the AppleTalk module on your Linux system, you can do it without having to rebuild the whole kernel as long as your distribution enables appletalk as a module (i.e has CONFIG_ATALK=m in its kernel configuration); fortunately, this is the most common case.

The first step is to download the appletalk module source:

curl https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.tar.xz | \
    tar -xJ --strip-components=2 linux-6.1/net/appletalk/

(You should ideally use a version of the AppleTalk module that matches the one from your distribution, but since the module has not changed since 2020 you are probably fine)

Go into the appletalk module folder, then patch ddp.c as indicated above.

Then: (you will need to do the following each time your kernel is updated):

  • Run make -C /lib/modules/$(uname -r)/build M=$PWD clean appletalk.ko
  • Load the current AppleTalk module, then unload it to make sure its dependencies are loaded: sudo modprobe appletalk && sudo rmmod appletalk (If the module fails to unload, stop atalkd and try again)
  • Load your patched module: sudo insmod appletalk.ko
  • Make sure everything works
  • To make the change permanent (until the next kernel update), replace the file /lib/modules/$(uname -r)/kernel/net/appletalk/appletalk.ko with your patched module.

Changelog

  • 2024-04-27: Updated the module loading section to indicate which version fixed the issue, and hide it by default. Added some info on how to patch a system.
  • 2024-07-30: Indicate that all issues are now fixed.
@NJRoadfan
Copy link

I've already applied the patch, which appears to solve some of the issues. Others are likely bugs in Netatalk that I have to investigate. I hear you about getting a kernel patch submitted. I read the drama about getting the kernel driver patched after it was broken. I think that same maintainer is the one that was pushing for all LocalTalk support to be removed a few months back. I also recall someone trying to get AppleTalk removed entirely from the kernel, which would be a big blow to the Netatalk project now that it is actively being maintained again.

@zoggins
Copy link

zoggins commented Apr 26, 2024

I have been racking my brain for days trying to figure out why multi-subnet routing wasn't working consistently!! This patch saved my sanity!

@dougg3
Copy link

dougg3 commented May 5, 2024

I just resubmitted the patch to the networking maintainers, fixing the issues they mentioned. Crossing my fingers that it'll be accepted this time. If not, I'll keep trying.

@dougg3
Copy link

dougg3 commented May 12, 2024

The broadcast packets handling fix was just released with Linux 6.9.

@NJRoadfan
Copy link

I'm running into weird behavior with the broadcast bug patch (Debian Trixie comes with Linux 6.9). Sometimes the kernel sends out responses to broadcast packets with a source address of 0.0 instead of the originating host's proper address. Seems to frequently occur with NBP Lookups. A debug log of this happening can be found here: lampmerchant/tashrouter#8

@NJRoadfan
Copy link

Further testing shows that this is possibly a bug in Netatalk's NBP Reply function. Not entirely out of the question that fixing the kernel stack exposed an old bug.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment