Skip to content

Instantly share code, notes, and snippets.

@VinDuv
Last active May 12, 2024 21:39
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.

Issues resolved in current versions

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 (probably all versions)

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.

@NJRoadfan
Copy link

Any luck getting the broadcast packet issue patched? I'm running into weird problems with multiple interfaces and ZIP GetNetInfo packets not going out the right interface. Also some wonky NBP lookups (objects are showing up as being on the last interface configured).

@VinDuv
Copy link
Author

VinDuv commented Apr 22, 2024

Hi, I think the issue you’re having with GetNetInfo is the one fixed by my second patch. (I’m pretty sure it’s the issue that prompted me to diagnose the whole thing)
I actually submitted both patches to the LKML a long time ago, but unfortunately the reaction from the developper handling this part of the kernel… dissuaded me to make further submissions (This is quite common from what I understand).
In the end, only the first part of the patch was merged. From what I see, in current Linux, the broadcast packet issue still exists (the code seems pretty much unmodified from Linux 5.7).
So unless you want to take a shot at submitting the broadcast fix patch to LKML again (or know someone willing to do it), you’ll need to either rebuild a Linux kernel with the patch applied, or patch atalkd to work around the issue (by making it reply to GetNetInfo messages — and maybe others? — on all router sockets instead of just the originator socket).

Note that if the Linux distribution you’re using distributes appletalk as a kernel module (which is likely) you don’t need to rebuild the whole Linux kernel just to patch it. You can also just rebuild the module:

  1. Install the kernel headers from your distribution
  2. Get the full Linux source, whose version roughly matches the one you’re running
  3. Go into linux-x.y.z/net/appletalk
  4. Patch ddp.c
  5. Run make -C /lib/modules/$(uname -r)/build M=$PWD
  6. Run insmod appletalk.ko to load your rebuilt module (you may need to do rmmod appletalk first if the appletalk module from your distribution is already loaded)
  7. If that works, you may permanently replace /lib/modules/$(uname -r)/kernel/net/appletalk/appletalk.ko with your module.

@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.

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