如何根据MTU值发送数据包
How to send packets according to the MTU value
我正在尝试通过 UDP 实现我自己的协议。
正如 Internet 上许多手册所建议的那样,最好通过发送大小小于 MTU 的数据包来避免 IP 碎片。
我想知道获得最佳邮件大小的最佳方法是什么?我应该以某种方式获取 MTU 值(例如 this),还是应该将其设置为 smth,如 1300 或 1400,并希望它不会随着时间的推移而变小或改变?
我听说获取 MTU 值 (https://en.wikipedia.org/wiki/Path_MTU_Discovery) 存在一些问题,据我所知,这在很大程度上取决于当前路由和其他可能随时间变化的因素。
IPv4 UDP 的推荐大小为 576 个八位字节。每个 Internet 路由器都应该保证至少该大小的 IPv4 MTU,并且由于 UDP 是一种无连接、即发即弃、尽力而为、无保证交付协议,因此每个数据包可能会冒更少的数据风险丢失,并且 将 丢失数据包。
IPv6 的最低 MTU 要求为 1280 个八位字节,并且路径中没有碎片。
为了在您的接口中获取 MTU,而不是为了路径 MTU 发现,您有 struct ifreq。它的字段之一是 ifr_mtu,此字段将为您提供 MTU。您使用 ioctl 和 SIOCGIFMTU 读取此字段以获得正确的接口。
(http://man7.org/linux/man-pages/man7/netdevice.7.html)
struct ifreq {
char ifr_name[IFNAMSIZ]; /* Interface name */
union {
struct sockaddr ifr_addr;
struct sockaddr ifr_dstaddr;
struct sockaddr ifr_broadaddr;
struct sockaddr ifr_netmask;
struct sockaddr ifr_hwaddr;
short ifr_flags;
int ifr_ifindex;
int ifr_metric;
int ifr_mtu;
struct ifmap ifr_map;
char ifr_slave[IFNAMSIZ];
char ifr_newname[IFNAMSIZ];
char *ifr_data;
};
};
示例:
#include <sys/socket.h>
#include <sys/types.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
int main(void)
{
int sock;
char *name = "enp0s3";
struct ifreq ifr;
if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
printf("Creating socket: %d\n", errno);
exit(-1);
}
ifr.ifr_addr.sa_family = AF_INET;
strcpy(ifr.ifr_name, name);
if (ioctl(sock, SIOCGIFMTU, (caddr_t)&ifr) < 0) {
printf("Error ioctl: %d\n", errno);
exit(-2);
}
printf("MTU is %d.\n", ifr.ifr_mtu);
close(sock);
return 0;
}
今天,除非您使用蓝牙或 Zigbee 等特定连接技术,否则通常您可以信任 Internet 中 1,500 的 MTU。您可以使用 Path MTU Discovery 并使用 ACK 实现基于 UDP 的协议,以检查另一方是否已收到消息。现在,实现面向连接协议的 ACK 和特性与使用 TCP 不同。如果你可以用 UDP 做任何事情,它比 TCP 更轻。
编辑:
要使用 Path MTU Discovery,您还可以使用带有选项 IP_PMTUDISC_DO:
的 getsockopt
IP_MTU_DISCOVER (since Linux 2.2)
Set or receive the Path MTU Discovery setting for a socket.
When enabled, Linux will perform Path MTU Discovery as defined
in RFC 1191 on SOCK_STREAM sockets. For non-SOCK_STREAM
sockets, IP_PMTUDISC_DO forces the don't-fragment flag to be
set on all outgoing packets. It is the user's responsibility
to packetize the data in MTU-sized chunks and to do the
retransmits if necessary. The kernel will reject (with
EMSGSIZE) datagrams that are bigger than the known path MTU.
IP_PMTUDISC_WANT will fragment a datagram if needed according
to the path MTU, or will set the don't-fragment flag
otherwise.
我正在尝试通过 UDP 实现我自己的协议。
正如 Internet 上许多手册所建议的那样,最好通过发送大小小于 MTU 的数据包来避免 IP 碎片。
我想知道获得最佳邮件大小的最佳方法是什么?我应该以某种方式获取 MTU 值(例如 this),还是应该将其设置为 smth,如 1300 或 1400,并希望它不会随着时间的推移而变小或改变?
我听说获取 MTU 值 (https://en.wikipedia.org/wiki/Path_MTU_Discovery) 存在一些问题,据我所知,这在很大程度上取决于当前路由和其他可能随时间变化的因素。
IPv4 UDP 的推荐大小为 576 个八位字节。每个 Internet 路由器都应该保证至少该大小的 IPv4 MTU,并且由于 UDP 是一种无连接、即发即弃、尽力而为、无保证交付协议,因此每个数据包可能会冒更少的数据风险丢失,并且 将 丢失数据包。
IPv6 的最低 MTU 要求为 1280 个八位字节,并且路径中没有碎片。
为了在您的接口中获取 MTU,而不是为了路径 MTU 发现,您有 struct ifreq。它的字段之一是 ifr_mtu,此字段将为您提供 MTU。您使用 ioctl 和 SIOCGIFMTU 读取此字段以获得正确的接口。 (http://man7.org/linux/man-pages/man7/netdevice.7.html)
struct ifreq {
char ifr_name[IFNAMSIZ]; /* Interface name */
union {
struct sockaddr ifr_addr;
struct sockaddr ifr_dstaddr;
struct sockaddr ifr_broadaddr;
struct sockaddr ifr_netmask;
struct sockaddr ifr_hwaddr;
short ifr_flags;
int ifr_ifindex;
int ifr_metric;
int ifr_mtu;
struct ifmap ifr_map;
char ifr_slave[IFNAMSIZ];
char ifr_newname[IFNAMSIZ];
char *ifr_data;
};
};
示例:
#include <sys/socket.h>
#include <sys/types.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
int main(void)
{
int sock;
char *name = "enp0s3";
struct ifreq ifr;
if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
printf("Creating socket: %d\n", errno);
exit(-1);
}
ifr.ifr_addr.sa_family = AF_INET;
strcpy(ifr.ifr_name, name);
if (ioctl(sock, SIOCGIFMTU, (caddr_t)&ifr) < 0) {
printf("Error ioctl: %d\n", errno);
exit(-2);
}
printf("MTU is %d.\n", ifr.ifr_mtu);
close(sock);
return 0;
}
今天,除非您使用蓝牙或 Zigbee 等特定连接技术,否则通常您可以信任 Internet 中 1,500 的 MTU。您可以使用 Path MTU Discovery 并使用 ACK 实现基于 UDP 的协议,以检查另一方是否已收到消息。现在,实现面向连接协议的 ACK 和特性与使用 TCP 不同。如果你可以用 UDP 做任何事情,它比 TCP 更轻。
编辑: 要使用 Path MTU Discovery,您还可以使用带有选项 IP_PMTUDISC_DO:
的 getsockoptIP_MTU_DISCOVER (since Linux 2.2)
Set or receive the Path MTU Discovery setting for a socket.
When enabled, Linux will perform Path MTU Discovery as defined
in RFC 1191 on SOCK_STREAM sockets. For non-SOCK_STREAM
sockets, IP_PMTUDISC_DO forces the don't-fragment flag to be
set on all outgoing packets. It is the user's responsibility
to packetize the data in MTU-sized chunks and to do the
retransmits if necessary. The kernel will reject (with
EMSGSIZE) datagrams that are bigger than the known path MTU.
IP_PMTUDISC_WANT will fragment a datagram if needed according
to the path MTU, or will set the don't-fragment flag
otherwise.