Skip to content
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

IP fragmentation send path: added TCP/UDP packet splitting to fit within MTU #2380

Merged
merged 1 commit into from
Sep 12, 2023

Conversation

robertc2000
Copy link
Collaborator

No description provided.

@robertc2000 robertc2000 requested review from cpq and scaprile September 8, 2023 12:07
Copy link
Collaborator

@scaprile scaprile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach seems to look fine for TCP.
However, for UDP, it will only work if the user calls mg_print*() functions that call mg_io_send(). Otherwise, calls to mg_send() will bypass your strategy and be sent without being fragmented. Please write a test that proves or refutes my point and, if I'm right, then solve it.

mongoose/src/net_builtin.c

Lines 1075 to 1088 in d23664a

bool mg_send(struct mg_connection *c, const void *buf, size_t len) {
struct mg_tcpip_if *ifp = (struct mg_tcpip_if *) c->mgr->priv;
bool res = false;
uint32_t rem_ip;
memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
if (ifp->ip == 0 || ifp->state != MG_TCPIP_STATE_READY) {
mg_error(c, "net down");
} else if (c->is_udp) {
struct connstate *s = (struct connstate *) (c + 1);
tx_udp(ifp, s->mac, ifp->ip, c->loc.port, rem_ip, c->rem.port, buf, len);
res = true;
} else {
res = mg_iobuf_add(&c->send, c->send.len, buf, len);
}

In case we would need to mg_io_add for UDP, I wouldn't want to delay UDP packets that would fit the MTU, so we should not just buffer, as buffered datagrams would be sent on next poll, not now. For UDP, whatever fits the MTU we should try to send it now. (*)
If I'm right, this fragmentation stuff will have the side effect that we'll now be able to "send UDP datagrams larger than an IP datagram", which will cause them to the splitted into several datagrams and that IMO wouldn't be correct. Please check that, too.

(*): How would this interact with an ARP lookup ? Now our first UDP that requires an ARP lookup is lost, can we make use of this in advance and save the datagram for later ? (If the ISO police OSI enforcement unit comes for you, just tell them I told you to do it)

And now that I think of it again... We are not actually sending fragmented IP, are we ? So if a user requests a 1500-byte UDP datagram and our MTU is 750, we'll actually send two UDP datagrams breaking the very logic foundation of UDP datagrams. I think we should either reject this (force the users to break their messages) and (or at least) log it. Otherwise, we should send IP fragments but we will not receive our own data and that looks bad...
Our current logic is to follow down the path and the Ethernet layer will drop too-long packets.
I think this is turning into a long rambling, if that is the case, let's please follow up at the chat.

src/net_builtin.c Outdated Show resolved Hide resolved
src/net_builtin.c Outdated Show resolved Hide resolved
src/net.h Outdated
@@ -54,6 +54,8 @@ struct mg_connection {
void *pfn_data; // Protocol-specific function parameter
char data[MG_DATA_SIZE]; // Arbitrary connection data
void *tls; // TLS specific data
#define MTU_DEFAULT_VALUE 1500
uint16_t mtu; // MTU for this connection
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be moved to the tcp/ip stack implementation. Specifically, to the struct connstate -

struct connstate {
uint32_t seq, ack; // TCP seq/ack counters
. The MTU_DEFAULT_VALUE I'd rename to MG_DEFAULT_MTU, to keep our defines prefixed with MG_ in order to avoid conflicts with customer code.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I second, missed that, that is ifp-> stuff not c-> ...

if (len + max_headers_len > ifp->tx.len) {
len = ifp->tx.len - max_headers_len;
}
if (len + max_headers_len - eth_h_len > c->mtu) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, mtu must not be too small - it should be bigger that the size of headers. Shall we check that mtu is not less than a minimum possible here, or somewhere else?

Copy link
Member

@cpq cpq left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move mtu setting to the struct connstate

@robertc2000 robertc2000 force-pushed the ip-fragmentation-2 branch 2 times, most recently from 08bbd07 to 6410d85 Compare September 11, 2023 11:10
Copy link
Collaborator

@scaprile scaprile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address those nitty comments of mine.

Comment on lines 1093 to 1096
size_t max_udp_header_len = 32;
if (s->mtu < max_udp_header_len) {
s->mtu = MIP_MTU_DEFAULT;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above (RFC-791)

Comment on lines 564 to 567
if (s->mtu < max_headers_len - eth_h_len) {
s->mtu = MIP_MTU_DEFAULT;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem correct. The user is trying to lower the MTU and we go full throttle to 1500.
I think we should limit to the smallest allowed, IIUC it is 68 bytes per RFC-791: "Every internet module must be able to forward a datagram of 68 octets without further fragmentation"
However, for TCP, we should perhaps limit to the max header value.

if (len + max_headers_len - eth_h_len > s->mtu) {
len = s->mtu - max_headers_len + eth_h_len;
if (c->is_udp)
MG_ERROR(("UDP datagram exceeds MTU"));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please tell the datagram has been truncated, something like
"Truncated UDP datagram exceeding MTU"

}
if (len + max_udp_header_len > s->mtu) {
len = s->mtu - max_udp_header_len;
MG_ERROR(("UDP datagram exceeds MTU"));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as above (truncation)

Copy link
Collaborator

@scaprile scaprile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes bring more changes...

Comment on lines 1094 to 1108
struct connstate *s = (struct connstate *) (c + 1);
size_t min_mtu = 68 /* RFC-791 */;
if (ifp->mtu < min_mtu) {
MG_ERROR(("MTU is lower than minimum possible value. Setting it to %d.", min_mtu));
ifp->mtu = (uint16_t) min_mtu;
}
if (len + max_udp_header_len > ifp->mtu) {
len = ifp->mtu - max_udp_header_len;
MG_ERROR(("UDP datagram exceeds MTU. Truncating it."));
}
tx_udp(ifp, s->mac, ifp->ip, c->loc.port, rem_ip, c->rem.port, buf, len);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that the MTU is checked against ifp->mtu I think it makes sense moving the UDP case into tx_udp(). This also avoids duplicated code and texts.

Comment on lines 555 to 569
size_t eth_h_len = 14, ip_max_h_len = 24, tcp_max_h_len = 60, udp_h_len = 8;
size_t max_headers_len = eth_h_len + ip_max_h_len +
(c->is_udp ? udp_h_len : tcp_max_h_len);
size_t min_mtu = c->is_udp ? 68 /* RFC-791 */ : max_headers_len - eth_h_len;
memcpy(&rem_ip, c->rem.ip, sizeof(uint32_t));
if (len + max_headers_len > ifp->tx.len) {
len = ifp->tx.len - max_headers_len;
}
if (ifp->mtu < min_mtu) {
MG_ERROR(("MTU is lower than minimum possible value. Setting it to %d.", min_mtu));
ifp->mtu = (uint16_t) min_mtu;
}
if (len + max_headers_len - eth_h_len > ifp->mtu) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment (mg_send())

@cpq cpq merged commit 53baa18 into master Sep 12, 2023
72 of 73 checks passed
@cpq cpq deleted the ip-fragmentation-2 branch September 12, 2023 11:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants