The previous check only tested whether a new fragment's endpoints fell
inside an existing range and treated any such match as a fragment to be
dropped on its own. This missed the case where a new fragment fully
contains an existing one, and it conflated two cases that the kernel and
RFCs 815, 5722 and 8200 handle differently:
- An exact duplicate (same byte range as an existing fragment) may be
dropped on its own while the rest of the fragment list is kept for
later reassembly (RFC 8200).
- An overlapping (but not duplicate) fragment means the whole datagram
must be silently discarded along with all of its fragments
(RFC 5722 / RFC 8200).
Detect these two cases separately using closed-interval overlap: drop an
exact duplicate on its own, and tear down the entire fragment list on a
real overlap.
Assisted-by: composer-2.5-fast, Cursor
Fixes: 4ea96698f667 ("Userspace datapath: Add fragmentation handling.")
Signed-off-by: Eli Britstein <[email protected]>
---
lib/dpctl.c | 4 ++
lib/dpif-provider.h | 1 +
lib/ipf.c | 52 +++++++++++++++----
lib/ipf.h | 1 +
tests/ofproto-dpif.at | 85 ++++++++++++++++++++++++++++++--
tests/system-userspace-macros.at | 8 +++
6 files changed, 138 insertions(+), 13 deletions(-)
diff --git a/lib/dpctl.c b/lib/dpctl.c
index 48afb8549..76b219e2c 100644
--- a/lib/dpctl.c
+++ b/lib/dpctl.c
@@ -2629,6 +2629,8 @@ dpctl_ct_ipf_get_status(int argc, const char *argv[],
dpif_ipf_status.v4.nfrag_too_large);
dpctl_print(dpctl_p, " v4 frags overlapped: %"PRIu64"\n",
dpif_ipf_status.v4.nfrag_overlap);
+ dpctl_print(dpctl_p, " v4 frags duplicate: %"PRIu64"\n",
+ dpif_ipf_status.v4.nfrag_duplicate);
dpctl_print(dpctl_p, " v4 frags purged: %"PRIu64"\n",
dpif_ipf_status.v4.nfrag_purged);
@@ -2646,6 +2648,8 @@ dpctl_ct_ipf_get_status(int argc, const char *argv[],
dpif_ipf_status.v6.nfrag_too_large);
dpctl_print(dpctl_p, " v6 frags overlapped: %"PRIu64"\n",
dpif_ipf_status.v6.nfrag_overlap);
+ dpctl_print(dpctl_p, " v6 frags duplicate: %"PRIu64"\n",
+ dpif_ipf_status.v6.nfrag_duplicate);
dpctl_print(dpctl_p, " v6 frags purged: %"PRIu64"\n",
dpif_ipf_status.v6.nfrag_purged);
} else {
diff --git a/lib/dpif-provider.h b/lib/dpif-provider.h
index b3dd58e9d..7c9cd003e 100644
--- a/lib/dpif-provider.h
+++ b/lib/dpif-provider.h
@@ -131,6 +131,7 @@ struct dpif_ipf_proto_status {
uint64_t nfrag_too_small;
uint64_t nfrag_too_large;
uint64_t nfrag_overlap;
+ uint64_t nfrag_duplicate;
uint64_t nfrag_purged;
unsigned int min_frag_size;
bool enabled;
diff --git a/lib/ipf.c b/lib/ipf.c
index 858d28109..7bbfeeda3 100644
--- a/lib/ipf.c
+++ b/lib/ipf.c
@@ -84,6 +84,7 @@ enum ipf_counter_type {
IPF_NFRAGS_TOO_SMALL,
IPF_NFRAGS_TOO_LARGE,
IPF_NFRAGS_OVERLAP,
+ IPF_NFRAGS_DUPLICATE,
IPF_NFRAGS_PURGED,
IPF_NFRAGS_NUM_CNTS,
};
@@ -856,16 +857,38 @@ ipf_list_key_lookup(struct ipf *ipf, const struct
ipf_list_key *key,
return NULL;
}
+/* Returns true if the new fragment is an exact duplicate of an existing
+ * fragment, i.e. it covers the same byte range. Per RFC 8200, an exact
+ * duplicate may be dropped on its own while keeping the rest of the
+ * fragment list for later reassembly. */
static bool
ipf_is_frag_duped(const struct ipf_frag *frag_list, int last_inuse_idx,
size_t start_data_byte, size_t end_data_byte)
/* OVS_REQUIRES(ipf_lock) */
{
for (int i = 0; i <= last_inuse_idx; i++) {
- if ((start_data_byte >= frag_list[i].start_data_byte &&
- start_data_byte <= frag_list[i].end_data_byte) ||
- (end_data_byte >= frag_list[i].start_data_byte &&
- end_data_byte <= frag_list[i].end_data_byte)) {
+ if (start_data_byte == frag_list[i].start_data_byte &&
+ end_data_byte == frag_list[i].end_data_byte) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* Returns true if the new fragment overlaps any existing fragment without
+ * being an exact duplicate. Uses closed-interval overlap:
+ * start_a <= end_b && end_a >= start_b. */
+static bool
+ipf_is_frag_overlap(const struct ipf_frag *frag_list, int last_inuse_idx,
+ size_t start_data_byte, size_t end_data_byte)
+ /* OVS_REQUIRES(ipf_lock) */
+{
+ for (int i = 0; i <= last_inuse_idx; i++) {
+ if (start_data_byte <= frag_list[i].end_data_byte &&
+ end_data_byte >= frag_list[i].start_data_byte &&
+ !(start_data_byte == frag_list[i].start_data_byte &&
+ end_data_byte == frag_list[i].end_data_byte)) {
return true;
}
}
@@ -888,10 +911,10 @@ ipf_drop_frag_chain(struct ipf *ipf, struct ipf_list
*ipf_list)
ipf_list_clean(&ipf->frag_lists, ipf_list);
}
-/* Adds a fragment to a list of fragments, if the fragment does not overlap
- * an existing fragment. If it overlaps, the whole fragment list is dropped.
- * (see ipf_drop_frag_chain()), avoiding the work that conntrack would
- * otherwise do to mark the fragments as invalid. */
+/* Adds a fragment to a list of fragments. An exact duplicate fragment is
+ * dropped on its own, keeping the rest of the list (RFC 8200). An
+ * overlapping (but not duplicate) fragment causes the whole fragment list to
+ * be dropped (see ipf_drop_frag_chain(); RFC 5722 / RFC 8200). */
static bool
ipf_process_frag(struct ipf *ipf, struct ipf_list *ipf_list,
struct dp_packet *pkt, uint16_t start_data_byte,
@@ -901,9 +924,11 @@ ipf_process_frag(struct ipf *ipf, struct ipf_list
*ipf_list,
{
bool duped_frag = ipf_is_frag_duped(ipf_list->frag_list,
ipf_list->last_inuse_idx, start_data_byte, end_data_byte);
+ bool overlap_frag = ipf_is_frag_overlap(ipf_list->frag_list,
+ ipf_list->last_inuse_idx, start_data_byte, end_data_byte);
int last_inuse_idx = ipf_list->last_inuse_idx;
- if (!duped_frag) {
+ if (!duped_frag && !overlap_frag) {
if (last_inuse_idx < ipf_list->size - 1) {
struct ipf_frag *frag = &ipf_list->frag_list[last_inuse_idx + 1];
frag->pkt = pkt;
@@ -916,6 +941,11 @@ ipf_process_frag(struct ipf *ipf, struct ipf_list
*ipf_list,
} else {
OVS_NOT_REACHED();
}
+ } else if (duped_frag) {
+ /* RFC 8200: an exact duplicate may be dropped while keeping the rest
+ * of the fragment list for later reassembly. */
+ ipf_count(ipf, v6, IPF_NFRAGS_DUPLICATE);
+ dp_packet_delete(pkt);
} else {
ipf_count(ipf, v6, IPF_NFRAGS_OVERLAP);
dp_packet_delete(pkt);
@@ -1515,6 +1545,8 @@ ipf_get_status(struct ipf *ipf, struct ipf_status
*ipf_status)
&ipf_status->v4.nfrag_too_large);
atomic_read_relaxed(&ipf->n4frag_cnt[IPF_NFRAGS_OVERLAP],
&ipf_status->v4.nfrag_overlap);
+ atomic_read_relaxed(&ipf->n4frag_cnt[IPF_NFRAGS_DUPLICATE],
+ &ipf_status->v4.nfrag_duplicate);
atomic_read_relaxed(&ipf->n4frag_cnt[IPF_NFRAGS_PURGED],
&ipf_status->v4.nfrag_purged);
@@ -1533,6 +1565,8 @@ ipf_get_status(struct ipf *ipf, struct ipf_status
*ipf_status)
&ipf_status->v6.nfrag_too_large);
atomic_read_relaxed(&ipf->n6frag_cnt[IPF_NFRAGS_OVERLAP],
&ipf_status->v6.nfrag_overlap);
+ atomic_read_relaxed(&ipf->n6frag_cnt[IPF_NFRAGS_DUPLICATE],
+ &ipf_status->v6.nfrag_duplicate);
atomic_read_relaxed(&ipf->n6frag_cnt[IPF_NFRAGS_PURGED],
&ipf_status->v6.nfrag_purged);
return 0;
diff --git a/lib/ipf.h b/lib/ipf.h
index 2ac3c9658..775acbf73 100644
--- a/lib/ipf.h
+++ b/lib/ipf.h
@@ -29,6 +29,7 @@ struct ipf_proto_status {
uint64_t nfrag_too_small;
uint64_t nfrag_too_large;
uint64_t nfrag_overlap;
+ uint64_t nfrag_duplicate;
uint64_t nfrag_purged;
unsigned int min_frag_size;
bool enabled;
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index b9a3f06b5..a427765d5 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -5682,8 +5682,9 @@ AT_CHECK([ovs-ofctl -O OpenFlow11 replace-flows br0
flows.txt])
dnl Send the same fragment twice. It is a last fragment (MF=0) at offset 400,
dnl so it bypasses the minimum fragment size check and is admitted, yet it
dnl cannot complete reassembly on its own (bytes 0..399 are missing). The
-dnl second copy is a duplicate and must be dropped, rather than marked CT
-dnl invalid and returned to the datapath, so it is never forwarded.
+dnl second copy is an exact duplicate: per RFC 8200 it is dropped on its own
+dnl while the first fragment is kept for later reassembly, rather than being
+dnl marked CT invalid and returned to the datapath, so it is never forwarded.
dnl
dnl Last fragment carrying bytes 400..799 (MF clear).
dnl Ethernet II, Src: 50:54:00:00:00:09, Dst: 50:54:00:00:00:0a
@@ -5710,9 +5711,10 @@ AT_CHECK([ovs-appctl netdev-dummy/receive p90 "$packet"])
AT_CHECK([ovs-appctl netdev-dummy/receive p90 "$packet"])
AT_CHECK([ovs-appctl dpctl/ipf-get-status -m \
-| grep -E 'v4 frags accepted:|v4 frags overlapped:'], [], [dnl
+| grep -E 'num frag:|v4 frags accepted:|v4 frags duplicate:'], [], [dnl
+ num frag: 1
v4 frags accepted: 1
- v4 frags overlapped: 1
+ v4 frags duplicate: 1
])
dnl The duplicate fragment is dropped, not marked invalid and forwarded, so
@@ -5769,6 +5771,81 @@ AT_CHECK([ovs-appctl dpctl/ipf-get-status -m \
OVS_VSWITCHD_STOP
AT_CLEANUP
+AT_SETUP([ofproto-dpif - fragment handling - reject overlapped fragment])
+OVS_VSWITCHD_START
+add_of_ports br0 1 90
+
+AT_DATA([flows.txt], [dnl
+table=0 in_port=90,ip actions=ct(commit),output:1
+])
+AT_CHECK([ovs-ofctl -O OpenFlow11 replace-flows br0 flows.txt])
+
+dnl The minimum fragment size is clamped to 400 bytes, so both fragments must
+dnl be at least that large to be admitted for reassembly.
+AT_CHECK([ovs-appctl dpctl/ipf-set-min-frag v4 400], [], [dnl
+setting minimum fragment size successful
+])
+
+dnl First admit a fragment carrying bytes 400..799, then send a new fragment
+dnl covering bytes 0..1199 that fully contains the previously admitted range.
+dnl The overlap causes the entire fragment list to be dropped (RFC 5722 /
+dnl RFC 8200), so no fragments remain tracked afterwards.
+dnl
+dnl Packet 1 (admitted). Middle fragment carrying bytes 400..799 (MF set).
+dnl Ethernet II, Src: 50:54:00:00:00:09, Dst: 50:54:00:00:00:0a
+dnl Type: IPv4 (0x0800)
+dnl Internet Protocol Version 4, Src: 10.1.1.1, Dst: 10.1.1.2
+dnl 0100 .... = Version: 4
+dnl .... 0101 = Header Length: 20 bytes (5)
+dnl Differentiated Services Field: 0x00 (DSCP: CS0, ECN: Not-ECT)
+dnl Total Length: 420
+dnl Identification: 0x0020 (32)
+dnl 001. .... = Flags: 0x1 (More fragments)
+dnl ...0 0000 0011 0010 = Fragment Offset: 400
+dnl Time to Live: 64
+dnl Protocol: UDP (17)
+dnl Header Checksum: 0x42f3
+dnl Data (400 bytes)
+eth="50 54 00 00 00 0a 50 54 00 00 00 09 08 00"
+ip1="45 00 01 a4 00 20 20 32 40 11 42 f3"
+addrs="0a 01 01 01 0a 01 01 02"
+data1=$(printf '%0*d' 800 0)
+packet1="${eth}${ip1}${addrs}${data1}"
+
+dnl Packet 2 (rejected as overlap). First fragment carrying bytes 0..1199
+dnl (MF set), fully containing packet 1's byte range.
+dnl Ethernet II, Src: 50:54:00:00:00:09, Dst: 50:54:00:00:00:0a
+dnl Type: IPv4 (0x0800)
+dnl Internet Protocol Version 4, Src: 10.1.1.1, Dst: 10.1.1.2
+dnl 0100 .... = Version: 4
+dnl .... 0101 = Header Length: 20 bytes (5)
+dnl Differentiated Services Field: 0x00 (DSCP: CS0, ECN: Not-ECT)
+dnl Total Length: 1220
+dnl Identification: 0x0020 (32)
+dnl 001. .... = Flags: 0x1 (More fragments)
+dnl ...0 0000 0000 0000 = Fragment Offset: 0
+dnl Time to Live: 64
+dnl Protocol: UDP (17)
+dnl Header Checksum: 0x4005
+dnl Data (1200 bytes)
+ip2="45 00 04 c4 00 20 20 00 40 11 40 05"
+data2=$(printf '%0*d' 2400 0)
+packet2="${eth}${ip2}${addrs}${data2}"
+
+AT_CHECK([ovs-appctl netdev-dummy/receive p90 "$packet1"])
+AT_CHECK([ovs-appctl netdev-dummy/receive p90 "$packet2"])
+
+AT_CHECK([ovs-appctl dpctl/ipf-get-status -m \
+| grep -E 'num frag:|v4 frags accepted:|v4 frags completed:|v4 frags
overlapped:'], [], [dnl
+ num frag: 0
+ v4 frags accepted: 1
+ v4 frags completed: 0
+ v4 frags overlapped: 1
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
AT_SETUP([ofproto-dpif - handling of malformed TCP packets])
OVS_VSWITCHD_START
add_of_ports br0 1 90
diff --git a/tests/system-userspace-macros.at b/tests/system-userspace-macros.at
index 10ce3e746..218cfe8c3 100644
--- a/tests/system-userspace-macros.at
+++ b/tests/system-userspace-macros.at
@@ -180,6 +180,7 @@ AT_CHECK([ovs-appctl dpctl/ipf-get-status], [], [dnl
v4 frags too small: 0
v4 frags too large: 0
v4 frags overlapped: 0
+ v4 frags duplicate: 0
v4 frags purged: 0
min v6 frag size: 1280
v6 frags accepted: 0
@@ -188,6 +189,7 @@ AT_CHECK([ovs-appctl dpctl/ipf-get-status], [], [dnl
v6 frags too small: 0
v6 frags too large: 0
v6 frags overlapped: 0
+ v6 frags duplicate: 0
v6 frags purged: 0
])
])
@@ -212,6 +214,7 @@ AT_CHECK([ovs-appctl dpctl/ipf-get-status --more], [], [dnl
v4 frags too small: 0
v4 frags too large: 0
v4 frags overlapped: 0
+ v4 frags duplicate: 0
v4 frags purged: 0
min v6 frag size: 1280
v6 frags accepted: 0
@@ -220,6 +223,7 @@ AT_CHECK([ovs-appctl dpctl/ipf-get-status --more], [], [dnl
v6 frags too small: 0
v6 frags too large: 0
v6 frags overlapped: 0
+ v6 frags duplicate: 0
v6 frags purged: 0
Fragment Lists:
@@ -247,6 +251,7 @@ AT_CHECK([ovs-appctl dpctl/ipf-get-status --more], [], [dnl
v4 frags too small: 0
v4 frags too large: 0
v4 frags overlapped: 0
+ v4 frags duplicate: 0
v4 frags purged: 0
min v6 frag size: 1280
v6 frags accepted: 30
@@ -255,6 +260,7 @@ AT_CHECK([ovs-appctl dpctl/ipf-get-status --more], [], [dnl
v6 frags too small: 0
v6 frags too large: 0
v6 frags overlapped: 0
+ v6 frags duplicate: 0
v6 frags purged: 0
Fragment Lists:
@@ -289,6 +295,7 @@ AT_CHECK([ovs-appctl dpctl/ipf-get-status -m |
FORMAT_FRAG_LIST()], [], [dnl
v4 frags too small: 0
v4 frags too large: 0
v4 frags overlapped: 0
+ v4 frags duplicate: 0
v4 frags purged: 0
min v6 frag size: 1280
v6 frags accepted: 0
@@ -297,6 +304,7 @@ AT_CHECK([ovs-appctl dpctl/ipf-get-status -m |
FORMAT_FRAG_LIST()], [], [dnl
v6 frags too small: 0
v6 frags too large: 0
v6 frags overlapped: 0
+ v6 frags duplicate: 0
v6 frags purged: 0
Fragment Lists:
--
2.43.0
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev