Skip to main content

14.14 Quick Reference (The Wrenches and Screwdrivers in Your Toolbox)

And so, our journey truly comes to an end.

Looking back, we covered a lot of ground in this chapter:

  • We started with Namespaces, establishing an isolated perspective.
  • We used Cgroups to put a leash on resources.
  • We ventured into the wireless worlds of Bluetooth, 802.15.4, and NFC to examine those quirky protocols.
  • We dove into the kernel's Notification Chains and PCI subsystems.
  • Finally, we took a look at complex cases like PPPoE and Android that wrap low-level technologies into upper-level platforms.

Remember what we said at the beginning of this chapter? The Linux Network Stack is a massive machine. Every section in this chapter—whether it's multi-core RPS or Android's HAL—is really answering one question: As this machine grows more complex, more multi-core, and more mobile, how do we tame it?

Now we've reached the last page. Linux networking is a vast ocean, with new features constantly flowing into the mainline kernel. We hope this book hasn't just taught you a few APIs or code snippets, but has given you a map. The next time you're staring at ethtool output or a kernel crash log, you'll know where to look and what questions to ask.

Happy debugging!


To make things easier for your future code explorations, I've compiled the core kernel methods and APIs we got our hands on in this chapter into this quick reference. Think of it as the manual for your toolbox—not meant to be read cover to cover, but to be there when you're in the middle of writing code and suddenly can't remember "what was that function that increments the reference count called again?" so you can instantly find the right wrench.

Namespace Management

Namespaces are the lenses through which we view our isolated worlds. This group of methods is responsible for creating, switching, and destroying those lenses.

void switch_task_namespaces(struct task_struct *p, struct nsproxy *new);

What it does: Fits a specified process (p) with a new pair of "glasses" (nsproxy). Essence: Once this code executes, the process completely enters a new namespace view—the network devices, hostname, and PIDs it sees will all become those pointed to by the new nsproxy.

struct nsproxy *create_nsproxy(void);

What it does: Allocates a new nsproxy structure. Details: It doesn't just allocate memory; it also initializes the reference count to 1. This is the first step in creating a new namespace view.

void free_nsproxy(struct nsproxy *ns);

What it does: Releases the resources occupied by a specified nsproxy object. Note: Typically called when the reference count drops to zero.

struct nsproxy *task_nsproxy(struct task_struct *tsk);

What it does: Retrieves the "glasses" currently worn by a specified process. Use case: When you have a process's task_struct but don't know which namespace it belongs to, use this function.

void get_nsproxy(struct nsproxy *ns);

What it does: Increments the reference count of a nsproxy by 1. Purpose: Prevents someone else from freeing it while you're using it.

void put_nsproxy(struct nsproxy *ns);

What it does: Decrements the reference count of a nsproxy by 1. Trigger: If it drops to 0, the kernel calls free_nsproxy() to thoroughly clean it up.


Network Namespaces

This is the most core isolation mechanism in the network subsystem. Every function below operates on struct net—the object that represents an entire Network Stack view.

struct net *dev_net(const struct net_device *dev);

What it does: Answers "Which network namespace does this network device belong to?". Returns: The value of the nd_net member in net_device.

void dev_net_set(struct net_device *dev, struct net *net);

What it does: Moves a specified network device into a specified network namespace. Essence: Modifies the dev->nd_net pointer.

struct net *sock_net(const struct sock *sk);

What it does: Answers "Which network namespace does this Socket belong to?". Returns: The value of sk_net in the sock structure.

void sock_net_set(struct sock *sk, struct net *net);

What it does: Assigns a belonging network namespace to a Socket. Scenario: When a Socket is created, it must be attached to a network namespace; otherwise, the kernel won't know which routing table to use.

int net_eq(const struct net *net1, const struct net *net2);

What it does: Checks if two network namespace pointers are the same. Why we need it: Because network namespace pointers can sometimes be &init_net, a quick equality check avoids unnecessary lookups.

struct net *net_alloc(void);

What it does: Allocates a new struct net object. Location: It's an internal implementation detail of copy_net_ns().

struct net *copy_net_ns(unsigned long flags, struct user_namespace *user_ns, struct net *old_net);

What it does: This is the "master switch" for creating a new network namespace. Logic:

  1. Checks if CLONE_NEWNET is present in flags.
  2. If yes, calls net_alloc() to allocate a new object, initializes it with setup_net(), and finally hangs it on the global list net_namespace_list.
  3. If no, returns old_net (everyone shares one). Note: If the kernel doesn't have CONFIG_NET_NS enabled, this function becomes an empty shell—it just checks flags and directly returns the old namespace.

int setup_net(struct net *net, struct user_namespace *user_ns);

What it does: Initializes a newly allocated network namespace object. Details: Sets user_ns, sets the reference count to 1, and executes all initialization callbacks registered to pernet_operations.

struct net *get_net_ns_by_pid(pid_t pid);

What it does: Finds the network namespace a process resides in via its PID. Use case: When you only want to operate on "the network namespace where a certain process lives," this lookup step is necessary.

struct net *get_net_ns_by_fd(int fd);

What it does: Finds the corresponding network namespace via a file descriptor. Principle: The file descriptor might point to a special file representing a network namespace (such as an open file descriptor from /proc/[pid]/ns/net).

void put_net(struct net *net);

What it does: Decrements the network namespace's reference count. Consequence: If the count drops to zero, calls __put_net() to reclaim resources.

struct net *get_net(struct net *net);

What it does: Increments the network namespace's reference count and returns the pointer. Pattern: A typical "get and use" pattern that prevents the object from disappearing while in use.

int dev_change_net_namespace(struct net_device *dev, struct net *net, const char *pat);

What it does: Physically moves a running network device to another network namespace. Condition: The caller must hold the rtnl semaphore (the lock for network configuration operations). Gotcha: If the device's features have NETIF_F_NETNS_LOCAL set (meaning "I'm a local device, I can't be moved"), this function will directly return -EINVAL. Some virtual devices do not allow cross-namespace migration.


Network Namespace Subsystem Registration

When you write a new kernel module and want to do something every time a network namespace is created (like initializing some data structures), you need these registration functions.

int register_pernet_device(struct pernet_operations *ops);

What it does: Registers a device-level network namespace callback. Characteristic: Ordered later; used for modules that depend on other basic devices already being initialized.

void unregister_pernet_device(struct pernet_operations *ops);

What it does: Unregisters the above callback.

int register_pernet_subsys(struct pernet_operations *ops);

What it does: Registers a subsystem-level network namespace callback. Characteristic: Ordered earlier; used for core subsystems (like routing tables, the protocol stack).

void unregister_pernet_subsys(struct pernet_operations *ops);

What it does: Unregisters the above callback.


UTS Namespace

The UTS namespace holds the hostname and domain name. This is one of the simplest namespaces.

struct new_utsname *utsname(void);

What it does: Gets the UTS name structure of the current process. Shortcut: Equivalent to getting current->nsproxy->uts_ns->name.

struct uts_namespace *clone_uts_ns(struct user_namespace *user_ns, struct uts_namespace *old_ns);

What it does: Clones a UTS namespace. Logic: Calls create_uts_ns() to allocate, then copies the hostname from the old namespace over.

struct uts_namespace *copy_utsname(unsigned long flags, struct user_namespace *user_ns, struct uts_namespace *old_ns);

What it does: Handles UTS namespace creation during the clone system call. Logic: If flags has CLONE_NEWUTS, it calls clone_uts_ns() to create a new one; otherwise, it returns the old old_ns.


Proc Filesystem and Inode

Every namespace has a unique ID number under /proc.

int proc_alloc_inum(unsigned int *inum);

What it does: Allocates a proc inode number. Range: Between 0xf0000000 and 0xffffffff. Use case: Used to identify a namespace instance, ensuring it is unique within /proc.

struct pid_namespace *ns_of_pid(struct pid *pid);

What it does: Returns the PID namespace to which a specified PID belongs. Use case: Figuring out which "box" a PID was generated in.


Cgroup Helpers

Cgroups are the cages for resource limits. Here is some handling for when processes are released.

void cgroup_release_agent(struct work_struct *work);

What it does: Called when a Cgroup is released. Essence: It spawns a helper process in user space (via call_usermodehelper()) to handle cleanup work.

int call_usermodehelper(char * path, char ** argv, char ** envp, int wait);

What it does: Starts a user-space program from kernel space. Capability: This is how the kernel lets you run a script.


Bluetooth Protocol Stack

The complex layers of Bluetooth are glued together by these methods.

int bacmp(bdaddr_t *ba1, bdaddr_t *ba2);

What it does: Compares two Bluetooth addresses. Returns: 0 means they are equal.

void bacpy(bdaddr_t *dst, bdaddr_t *src);

What it does: Copies a Bluetooth address from src to dst.

int hci_send_frame(struct sk_buff *skb);

What it does: Sends an HCI-layer packet (command or data). Status: The "main gate" for Bluetooth transmission.

int hci_register_dev(struct hci_dev *hdev);

What it does: Registers an HCI device with the kernel. Gotcha: If the open() or close() callbacks in hdev are undefined, it directly returns -EINVAL. It also sets the HCI_SETUP flag and creates the corresponding sysfs entries.

void hci_unregister_dev(struct hci_dev *hdev);

What it does: Unregisters an HCI device. Action: Sets the HCI_UNREGISTER flag and removes the sysfs entries.

int hci_event_packet(struct hci_dev *hdev, struct sk_buff *skb);

What it does: Handles event packets received from the HCI layer. Trigger: Called by hci_rx_work(); this is the entry point for the event handler.


6LoWPAN

Running IPv6 over low-power wireless networks relies on this adaptation layer.

int lowpan_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev);

What it does: The main receive processing function for 6LoWPAN. Characteristic: The Ethernet type for these packets is 0x00F6.


PCI Subsystem

The vast majority of network devices are PCI devices. These are the fundamentals for drivers interacting with the PCI bus.

void pci_unregister_driver(struct pci_driver *dev);

What it does: Unregisters a PCI driver. Location: Usually written in the module's exit function.

int pci_enable_device(struct pci_dev *dev);

What it does: Wakes up and initializes a PCI device. Timing: Must be called before the driver actually starts using the device.

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);

What it does: Registers an Interrupt Handler. Binding: Hangs your handler function handler on the interrupt line irq.

void free_irq(unsigned int irq, void *dev_id);

What it does: Frees an interrupt line. Counterpart: The inverse operation of request_irq.


NFC (Near Field Communication)

Initialization and device registration for the NFC subsystem.

int nfc_init(void);

What it does: The master initialization for the NFC subsystem. Action: Registers the Generic Netlink family, and initializes the Raw Socket, LLCP Socket, and AF_NFC protocol.

int nfc_register_device(struct nfc_dev *dev);

What it does: Registers an NFC device with the NFC core layer.

int nfc_hci_register_device(struct nfc_hci_dev *hdev);

What it does: Registers an NFC HCI device.

int nci_register_device(struct nci_dev *ndev);

What it does: Registers an NFC NCI device.


PPPoE (Point-to-Point Protocol over Ethernet)

This is the protocol behind DSL connections. Below are its core operations.

static int __init pppoe_init(void);

What it does: Entry initialization for the PPPoE layer. Action: Registers the protocol handler, Socket type, network notification chain, and /proc entries.

struct pppoe_hdr *pppoe_hdr(const struct sk_buff *skb);

What it does: Extracts the PPPoE header from a Socket buffer.

static int pppoe_create(struct net *net, struct socket *sock);

What it does: Creates a PPPoE Socket. Error: If sk_alloc() allocation fails, returns -ENOMEM.

int __set_item(struct pppoe_net *pn, struct pppox_sock *po);

What it does: Inserts a PPPoE Socket into a hash table. Hash key: Calculated based on the Session ID and the peer's MAC address.

void delete_item(struct pppoe_net *pn, __be16 sid, char *addr, int ifindex);

What it does: Removes a PPPoE Socket from the hash table. Locating: Positioned jointly by the Session ID, MAC address, and network device index.

bool stage_session(__be16 sid);

What it does: Checks if a Session ID is valid (non-zero).


Notification Chains

This is the kernel's mechanism for "shouting so everyone can hear." As we saw earlier, network device state changes rely entirely on it.

int notifier_chain_register(struct notifier_block **nl, struct notifier_block *n);

What it does: Hangs a callback block n onto the list nl. Note: This is a low-level primitive; wrapper functions are usually used in practice.

int notifier_chain_unregister(struct notifier_block **nl, struct notifier_block *n);

What it does: Removes a callback block from the list.

int register_netdevice_notifier(struct notifier_block *nb);

What it does: Registers with the network device notification chain (netdev_chain). Wrapper: Internally calls raw_notifier_chain_register().

int unregister_netdevice_notifier(struct notifier_block *nb);

What it does: Unregisters from the network device notification chain.

int register_inet6addr_notifier(struct notifier_block *nb);

What it does: Registers with the IPv6 address notification chain.

int unregister_inet6addr_notifier(struct notifier_block *nb);

What it does: Unregisters from the IPv6 address notification chain.

int register_netevent_notifier(struct notifier_block *nb);

What it does: Registers with the network event notification chain.

int unregister_netevent_notifier(struct notifier_block *nb);

What it does: Unregisters from the network event notification chain.

int __kprobes notifier_call_chain(struct notifier_block **nl, unsigned long val, void *v, int nr_to_call, int nr_calls);

What it does: Triggers a notification chain, causing all attached callback functions to run once. Low-level: This is the engine and is usually not called directly.

int call_netdevice_notifiers(unsigned long val, struct net_device *dev);

What it does: Specifically sends a notification for a network device. Action: Calls raw_notifier_call_chain().

int blocking_notifier_call_chain(struct blocking_notifier_head *nh, unsigned long val, void *v);

What it does: A locked notification call. Scenario: Used in contexts that might sleep.

int __atomic_notifier_call_chain(struct atomic_notifier_head *nh,unsigned long val, void *v, int nr_to_call, int nr_calls);

What it does: An atomic context notification call (cannot sleep).


Finally, returning to that question about "taming complexity":

Looking at this list, you'll notice that all the APIs revolve around a few core actions: register, unregister, associate, and lookup. No matter how massive the kernel network subsystem gets, it essentially boils down to these actions playing out repeatedly across countless layers and objects.

When you truly understand the tools in this chapter—knowing when they are called and why they are designed this way—you're no longer just "using" Linux networking; you're "understanding" it.

May your next debugging session be free of confusion.