-
Notifications
You must be signed in to change notification settings - Fork 54
Added support for Hijack UDP #83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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)| |
| 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" | ||
|
|
@@ -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 | ||
|
|
@@ -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) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Linux style guidelines require the 's' in 'struct' to align under the 's' in 'struct' of the preceding line. |
||
| { | ||
| /* 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); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
|
@@ -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). | ||
|
|
@@ -74,14 +80,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 | ||
|
|
@@ -93,6 +101,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. | ||
|
|
@@ -108,10 +124,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); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is too cryptic; please use C structure definitions to manage the UDP header fields (e.g. all the info needed for UDP hijacking should be clearly visible, symbolically, in homa_wire.h). |
||
|
|
||
| /* 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); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Functions should be listed in alphabetical order. |
||
|
|
||
| #endif /* _HOMA_HIJACK_H */ | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of adding this sentence here, rework the comment at the beginning of homa_hijack.h so that it describes both TCP and UDP hijacking in a coordinated way.