Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions UDP_HIJACK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# UDP Hijacking for Homa

## Overview

UDP hijacking is an optional mechanism that encapsulates Homa packets as UDP
datagrams, using `IPPROTO_UDP` instead of `IPPROTO_HOMA` as the IP protocol.
It works alongside the existing TCP hijacking feature — only one can be active
at a time on a given socket.

### Why UDP hijacking?

TCP hijacking uses `SYN+RST` flag combinations that never occur in real TCP
traffic. However, some firewalls (particularly on virtualized environments)
inspect TCP flags and drop packets with these "impossible" flag combinations.
UDP hijacking avoids this issue entirely since UDP has no flags for firewalls
to inspect.

### Trade-offs vs TCP hijacking

| Feature | TCP hijacking | UDP hijacking |
|---------------------|------------------------|------------------------|
| NIC TSO support | Yes (multi-segment) | No (single-segment) |
| Firewall friendly | No (SYN+RST blocked) | Yes |
| GSO segments/packet | Multiple | 1 (`segs_per_gso = 1`) |
| IP protocol | `IPPROTO_TCP` | `IPPROTO_UDP` |
| sysctl | `hijack_tcp` | `hijack_udp` |

Because NICs do not perform TSO on UDP packets the same way they do for TCP,
UDP hijacking forces `segs_per_gso = 1` (one segment per GSO packet). This
means each Homa data packet is sent individually rather than being batched
into large TSO super-packets.

## Configuration

Enable UDP hijacking at runtime via sysctl:

```bash
# Enable UDP hijacking (disable TCP hijacking first if it was on)
sudo sysctl net.homa.hijack_tcp=0
sudo sysctl net.homa.hijack_udp=1
```

To switch back to TCP hijacking:

```bash
sudo sysctl net.homa.hijack_udp=0
sudo sysctl net.homa.hijack_tcp=1
```

**Note:** If both `hijack_tcp` and `hijack_udp` are set, TCP hijacking takes
priority (sockets opened while both are set will use TCP).

## How It Works

### Sending (outgoing packets)

1. **Socket initialization** (`homa_hijack_sock_init`): When a new Homa socket
is created, if `hijack_udp` is set the socket's `sk_protocol` is set to
`IPPROTO_UDP`. The kernel then transmits packets with a UDP IP protocol.

2. **Header setup** (`homa_udp_hijack_set_hdr`): Before transmission, Homa
writes UDP-compatible header fields:
- `flags` is set to `HOMA_HIJACK_FLAGS` (6) — a marker value.
- `urgent` is set to `HOMA_HIJACK_URGENT` (0xb97d) — a second marker.
- Bytes 4-5 of the transport header are overwritten with the UDP length.
- Bytes 6-7 are set up for proper UDP checksum offload.
- Because the sequence field (bytes 4-7) is overwritten, the packet offset
is stored in `seg.offset` instead.

3. **GSO geometry**: With UDP hijacking, `segs_per_gso` is forced to 1 (no
multi-segment GSO batching).

### Receiving (incoming packets)

1. **GRO interception** (`homa_udp_hijack_gro_receive`): Homa hooks into the
UDP GRO pipeline. When a UDP packet arrives, Homa checks:
- At least 20 bytes of transport header are available.
- `flags == HOMA_HIJACK_FLAGS` and `urgent == HOMA_HIJACK_URGENT`.

2. If the packet is identified as a Homa-over-UDP packet, the IP protocol
is rewritten to `IPPROTO_HOMA` and the packet is handed to Homa's normal
GRO handler. Real UDP packets are passed through to the normal UDP stack.

### Qdisc support

The `is_homa_pkt()` function in `homa_qdisc.c` recognizes both TCP-hijacked
and UDP-hijacked packets, ensuring they receive proper Homa qdisc treatment.

## Files Modified

| File | Changes |
|-------------------|------------------------------------------------------------|
| `homa_wire.h` | No new defines needed (reuses `HOMA_HIJACK_FLAGS` and `HOMA_HIJACK_URGENT`) |
| `homa_impl.h` | Added `hijack_udp` field to `struct homa` |
| `homa_hijack.h` | Added `homa_udp_hijack_set_hdr()`, `homa_sock_udp_hijacked()`, `homa_skb_udp_hijacked()`; updated `homa_hijack_sock_init()` |
| `homa_hijack.c` | Added `homa_udp_hijack_init()`, `homa_udp_hijack_end()`, `homa_udp_hijack_gro_receive()` |
| `homa_outgoing.c` | Added `segs_per_gso=1` for UDP; added UDP header calls in xmit paths |
| `homa_plumbing.c` | Added `hijack_udp` sysctl; added UDP init/end calls |
| `homa_qdisc.c` | Added `IPPROTO_UDP` check in `is_homa_pkt()` |
| `util/homa_test.cc` | Added `udp_ping()`, `test_udp()`, "udp" test command |
| `util/server.cc` | Added `udp_server()` function |
| `util/cp_node.cc` | Added `udp_server` and `udp_client` classes, "udp" protocol option |

## Key Constants

| Constant | Value | Purpose |
|----------------------|----------|------------------------------------------------------|
| `HOMA_HIJACK_FLAGS` | 6 | Marker in the `flags` field (shared with TCP hijack) |
| `HOMA_HIJACK_URGENT` | 0xb97d | Marker in the `urgent` field (shared with TCP hijack)|
93 changes: 91 additions & 2 deletions homa_hijack.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// SPDX-License-Identifier: BSD-2-Clause or GPL-2.0+

/* This file implements TCP hijacking for Homa. See comments at the top of
* homa_hijack.h for an overview of TCP hijacking.
/* This file implements TCP and UDP hijacking for Homa. See comments at the
* top of homa_hijack.h for an overview of TCP hijacking. UDP hijacking works
* similarly but uses UDP as the IP protocol, which avoids issues with
* firewalls that inspect TCP flags.
*/

#include "homa_hijack.h"
Expand All @@ -20,6 +22,19 @@ static const struct net_offload *tcp6_net_offload;
static struct net_offload hook_tcp_net_offload;
static struct net_offload hook_tcp6_net_offload;

/* Pointers to UDP's net_offload structures. NULL means homa_udp_hijack_init
* hasn't been called yet.
*/
static const struct net_offload *udp_net_offload;
static const struct net_offload *udp6_net_offload;

/*
* Identical to *udp_net_offload except that the gro_receive function
* has been replaced with homa_udp_hijack_gro_receive.
*/
static struct net_offload hook_udp_net_offload;
static struct net_offload hook_udp6_net_offload;

/**
* homa_hijack_init() - Initializes the mechanism for TCP hijacking (allows
* incoming Homa packets encapsulated as TCP frames to be "stolen" back from
Expand Down Expand Up @@ -93,4 +108,78 @@ struct sk_buff *homa_hijack_gro_receive(struct list_head *held_list,
ip_hdr(skb)->protocol = IPPROTO_HOMA;
}
return homa_gro_receive(held_list, skb);
}

/**
* homa_udp_hijack_init() - Initializes the mechanism for UDP hijacking
* (allows incoming Homa packets encapsulated as UDP datagrams to be
* "stolen" back from the UDP pipeline and funneled through Homa).
*/
void homa_udp_hijack_init(void)
{
if (udp_net_offload)
return;

pr_notice("Homa setting up UDP hijacking\n");
rcu_read_lock();
udp_net_offload = rcu_dereference(inet_offloads[IPPROTO_UDP]);
hook_udp_net_offload = *udp_net_offload;
hook_udp_net_offload.callbacks.gro_receive = homa_udp_hijack_gro_receive;
inet_offloads[IPPROTO_UDP] = (struct net_offload __rcu *)
&hook_udp_net_offload;

udp6_net_offload = rcu_dereference(inet6_offloads[IPPROTO_UDP]);
hook_udp6_net_offload = *udp6_net_offload;
hook_udp6_net_offload.callbacks.gro_receive = homa_udp_hijack_gro_receive;
inet6_offloads[IPPROTO_UDP] = (struct net_offload __rcu *)
&hook_udp6_net_offload;
rcu_read_unlock();
}

/**
* homa_udp_hijack_end() - Reverses the effects of a previous call to
* homa_udp_hijack_init, so that incoming UDP packets are no longer checked
* to see if they are actually Homa frames.
*/
void homa_udp_hijack_end(void)
{
if (!udp_net_offload)
return;
pr_notice("Homa cancelling UDP hijacking\n");
inet_offloads[IPPROTO_UDP] = (struct net_offload __rcu *)
udp_net_offload;
udp_net_offload = NULL;
inet6_offloads[IPPROTO_UDP] = (struct net_offload __rcu *)
udp6_net_offload;
udp6_net_offload = NULL;
}

/**
* homa_udp_hijack_gro_receive() - Invoked instead of UDP's gro_receive
* function when UDP hijacking is enabled. Identifies Homa-over-UDP packets
* and passes them to Homa; sends real UDP packets to UDP's gro_receive.
* @held_list: Pointer to header for list of packets that are being
* held for possible GRO merging.
* @skb: The newly arrived packet.
*/
struct sk_buff *homa_udp_hijack_gro_receive(struct list_head *held_list,
struct sk_buff *skb)
{
/* Need at least 20 bytes of transport data to safely check the
* flags (offset 13) and urgent (offset 18-19) fields.
*/
if (skb_headlen(skb) >= skb_transport_offset(skb) + 20 &&
homa_skb_udp_hijacked(skb)) {
if (skb_is_ipv6(skb)) {
ipv6_hdr(skb)->nexthdr = IPPROTO_HOMA;
} else {
ip_hdr(skb)->check = ~csum16_add(
csum16_sub(~ip_hdr(skb)->check,
htons(ip_hdr(skb)->protocol)),
htons(IPPROTO_HOMA));
ip_hdr(skb)->protocol = IPPROTO_HOMA;
}
return homa_gro_receive(held_list, skb);
}
return udp_net_offload->callbacks.gro_receive(held_list, skb);
}
84 changes: 82 additions & 2 deletions homa_hijack.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "homa_peer.h"
#include "homa_sock.h"
#include "homa_wire.h"
#include <net/ip6_checksum.h>

/* Special value stored in the flags field of TCP headers to indicate that
* the packet is actually a Homa packet. It includes the SYN and RST flags
Expand All @@ -33,6 +34,11 @@
*/
#define HOMA_HIJACK_FLAGS 6

/* Special value stored in the flags field for UDP hijacking, distinct from
* the TCP hijack value.
*/
#define HOMA_UDP_FLAGS 5

/* Special value stored in the urgent pointer of a TCP header to indicate
* that the packet is actually a Homa packet (note that urgent pointer is
* set even though the URG flag is not set).
Expand Down Expand Up @@ -75,14 +81,16 @@ static inline void homa_hijack_set_hdr(struct sk_buff *skb,

/**
* homa_hijack_sock_init() - Perform socket initialization related to
* TCP hijacking (arrange for outgoing packets on the socket to use TCP,
* if the hijack_tcp option is set.)
* TCP/UDP hijacking (arrange for outgoing packets on the socket to use
* TCP or UDP, if the corresponding hijack option is set.)
* @hsk: New socket to initialize.
*/
static inline void homa_hijack_sock_init(struct homa_sock *hsk)
{
if (hsk->homa->hijack_tcp)
hsk->sock.sk_protocol = IPPROTO_TCP;
else if (hsk->homa->hijack_udp)
hsk->sock.sk_protocol = IPPROTO_UDP;
}

/* homa_sock_hijacked() - Returns true if outgoing packets on a socket
Expand All @@ -94,6 +102,14 @@ static inline bool homa_sock_hijacked(struct homa_sock *hsk)
return hsk->sock.sk_protocol == IPPROTO_TCP;
}

/* homa_sock_udp_hijacked() - Returns true if outgoing packets on a socket
* should use UDP hijacking.
*/
static inline bool homa_sock_udp_hijacked(struct homa_sock *hsk)
{
return hsk->sock.sk_protocol == IPPROTO_UDP;
}

/**
* homa_skb_hijacked() - Return true if the TCP header fields in a packet
* indicate that the packet is actually a Homa packet, false otherwise.
Expand All @@ -109,10 +125,74 @@ static inline bool homa_skb_hijacked(struct sk_buff *skb)
h->urgent == ntohs(HOMA_HIJACK_URGENT);
}

/**
* homa_udp_hijack_set_hdr() - Set all header fields needed for UDP hijacking
* in an outgoing Homa packet. Overwrites the sequence field (bytes 4-7) with
* UDP length and checksum, so the packet offset must be stored in seg.offset.
* @skb: Packet buffer in which to set fields.
* @peer: Peer that contains source and destination addresses for the packet.
* @ipv6: True means the packet is going to be sent via IPv6.
*/
static inline void homa_udp_hijack_set_hdr(struct sk_buff *skb,
struct homa_peer *peer,
bool ipv6)
{
struct homa_common_hdr *h;
int transport_len;

h = (struct homa_common_hdr *)skb_transport_header(skb);
h->flags = HOMA_UDP_FLAGS;
h->urgent = htons(HOMA_UDP_URGENT);

transport_len = skb->len - skb_transport_offset(skb);

/* Set UDP length at bytes 4-5 (overlaps high 16 bits of sequence). */
*((__be16 *)((u8 *)h + 4)) = htons(transport_len);

/* Arrange for proper UDP checksumming at bytes 6-7. */
skb->ip_summed = CHECKSUM_PARTIAL;
skb->csum_start = skb_transport_header(skb) - skb->head;
skb->csum_offset = 6;
if (ipv6)
*((__be16 *)((u8 *)h + 6)) = ~csum_ipv6_magic(
&peer->flow.u.ip6.saddr,
&peer->flow.u.ip6.daddr,
transport_len, IPPROTO_UDP, 0);
else
*((__be16 *)((u8 *)h + 6)) = ~csum_tcpudp_magic(
peer->flow.u.ip4.saddr,
peer->flow.u.ip4.daddr,
transport_len, IPPROTO_UDP, 0);
}

/**
* homa_skb_udp_hijacked() - Return true if the header fields in a UDP
* packet indicate that the packet is actually a Homa packet, false otherwise.
* @skb: Packet to check: must have an IP protocol of IPPROTO_UDP.
*/
static inline bool homa_skb_udp_hijacked(struct sk_buff *skb)
{
struct homa_common_hdr *h;

/* Need at least 20 bytes of transport data to safely check the
* flags (offset 13) and urgent (offset 18-19) fields.
*/
if (skb_headlen(skb) < skb_transport_offset(skb) + 20)
return false;
h = (struct homa_common_hdr *)skb_transport_header(skb);
return h->flags == HOMA_UDP_FLAGS &&
h->urgent == ntohs(HOMA_UDP_URGENT);
}

void homa_hijack_end(void);
struct sk_buff *
homa_hijack_gro_receive(struct list_head *held_list,
struct sk_buff *skb);
void homa_hijack_init(void);
void homa_udp_hijack_end(void);
struct sk_buff *
homa_udp_hijack_gro_receive(struct list_head *held_list,
struct sk_buff *skb);
void homa_udp_hijack_init(void);

#endif /* _HOMA_HIJACK_H */
8 changes: 8 additions & 0 deletions homa_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,14 @@ struct homa {
*/
int hijack_tcp;

/**
* @hijack_udp: Non-zero means encapsulate outgoing Homa packets
* as UDP packets (i.e. use UDP as the IP protocol). This provides
* network traversability similar to TCP hijacking but avoids issues
* with firewalls inspecting TCP flags. Set externally via sysctl.
*/
int hijack_udp;

/**
* @max_gro_skbs: Maximum number of socket buffers that can be
* aggregated by the GRO mechanism. Set externally via sysctl.
Expand Down
Loading