From: Guy Harris Date: Sun, 22 May 2011 02:59:06 +0000 (-0700) Subject: Some forms of offloading mean we can't trust the MTU. X-Git-Tag: libpcap-1.2.1~52 X-Git-Url: https://git.tcpdump.org/libpcap/commitdiff_plain/b783ed2b762173a96bcb5dcb976aa685f9da9e11 Some forms of offloading mean we can't trust the MTU. If we're doing segmentation/fragmentation offloading, or reassembly offloading, we might see reassembled/un-{frag,seg}mented "packets" bigger than the MTU, so don't shrink the ring buffer slot size. --- diff --git a/config.h.in b/config.h.in index 54e5be0f..d8128203 100644 --- a/config.h.in +++ b/config.h.in @@ -64,6 +64,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_COMPILER_H +/* Define to 1 if you have the header file. */ +#undef HAVE_LINUX_ETHTOOL_H + /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_NET_TSTAMP_H diff --git a/configure b/configure index c4c03d73..26bddecf 100755 --- a/configure +++ b/configure @@ -7487,6 +7487,146 @@ fi fi + +for ac_header in linux/ethtool.h +do +as_ac_Header=`echo "ac_cv_header_$ac_header" | $as_tr_sh` +if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then + { echo "$as_me:$LINENO: checking for $ac_header" >&5 +echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6; } +if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then + echo $ECHO_N "(cached) $ECHO_C" >&6 +fi +ac_res=`eval echo '${'$as_ac_Header'}'` + { echo "$as_me:$LINENO: result: $ac_res" >&5 +echo "${ECHO_T}$ac_res" >&6; } +else + # Is the header compilable? +{ echo "$as_me:$LINENO: checking $ac_header usability" >&5 +echo $ECHO_N "checking $ac_header usability... $ECHO_C" >&6; } +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +$ac_includes_default +#include <$ac_header> +_ACEOF +rm -f conftest.$ac_objext +if { (ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 + (eval "$ac_compile") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then + ac_header_compiler=yes +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_header_compiler=no +fi + +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +{ echo "$as_me:$LINENO: result: $ac_header_compiler" >&5 +echo "${ECHO_T}$ac_header_compiler" >&6; } + +# Is the header present? +{ echo "$as_me:$LINENO: checking $ac_header presence" >&5 +echo $ECHO_N "checking $ac_header presence... $ECHO_C" >&6; } +cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +#include <$ac_header> +_ACEOF +if { (ac_try="$ac_cpp conftest.$ac_ext" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 + (eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } >/dev/null && { + test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" || + test ! -s conftest.err + }; then + ac_header_preproc=yes +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_header_preproc=no +fi + +rm -f conftest.err conftest.$ac_ext +{ echo "$as_me:$LINENO: result: $ac_header_preproc" >&5 +echo "${ECHO_T}$ac_header_preproc" >&6; } + +# So? What about this header? +case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in + yes:no: ) + { echo "$as_me:$LINENO: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&5 +echo "$as_me: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&2;} + { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the compiler's result" >&5 +echo "$as_me: WARNING: $ac_header: proceeding with the compiler's result" >&2;} + ac_header_preproc=yes + ;; + no:yes:* ) + { echo "$as_me:$LINENO: WARNING: $ac_header: present but cannot be compiled" >&5 +echo "$as_me: WARNING: $ac_header: present but cannot be compiled" >&2;} + { echo "$as_me:$LINENO: WARNING: $ac_header: check for missing prerequisite headers?" >&5 +echo "$as_me: WARNING: $ac_header: check for missing prerequisite headers?" >&2;} + { echo "$as_me:$LINENO: WARNING: $ac_header: see the Autoconf documentation" >&5 +echo "$as_me: WARNING: $ac_header: see the Autoconf documentation" >&2;} + { echo "$as_me:$LINENO: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&5 +echo "$as_me: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&2;} + { echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the preprocessor's result" >&5 +echo "$as_me: WARNING: $ac_header: proceeding with the preprocessor's result" >&2;} + { echo "$as_me:$LINENO: WARNING: $ac_header: in the future, the compiler will take precedence" >&5 +echo "$as_me: WARNING: $ac_header: in the future, the compiler will take precedence" >&2;} + + ;; +esac +{ echo "$as_me:$LINENO: checking for $ac_header" >&5 +echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6; } +if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then + echo $ECHO_N "(cached) $ECHO_C" >&6 +else + eval "$as_ac_Header=\$ac_header_preproc" +fi +ac_res=`eval echo '${'$as_ac_Header'}'` + { echo "$as_me:$LINENO: result: $ac_res" >&5 +echo "${ECHO_T}$ac_res" >&6; } + +fi +if test `eval echo '${'$as_ac_Header'}'` = yes; then + cat >>confdefs.h <<_ACEOF +#define `echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +fi + +done + { echo "$as_me:$LINENO: checking if if_packet.h has tpacket_stats defined" >&5 echo $ECHO_N "checking if if_packet.h has tpacket_stats defined... $ECHO_C" >&6; } if test "${ac_cv_lbl_tpacket_stats+set}" = set; then diff --git a/configure.in b/configure.in index 9d25a84d..e2c49639 100644 --- a/configure.in +++ b/configure.in @@ -463,6 +463,7 @@ linux) ]) fi + AC_CHECK_HEADERS(linux/ethtool.h) AC_LBL_TPACKET_STATS AC_LBL_LINUX_TPACKET_AUXDATA_TP_VLAN_TCI ;; diff --git a/pcap-linux.c b/pcap-linux.c index fea9797a..60f4e9e8 100644 --- a/pcap-linux.c +++ b/pcap-linux.c @@ -163,6 +163,14 @@ static const char rcsid[] _U_ = #include #endif /* HAVE_LIBNL */ +/* + * Got ethtool support? + */ +#ifdef HAVE_LINUX_ETHTOOL_H +#include +#include +#endif /* HAVE_LINUX_ETHTOOL_H */ + #include "pcap-int.h" #include "pcap/sll.h" #include "pcap/vlan.h" @@ -336,7 +344,7 @@ static void pcap_oneshot_mmap(u_char *user, const struct pcap_pkthdr *h, */ #ifdef HAVE_PF_PACKET_SOCKETS static int iface_get_id(int fd, const char *device, char *ebuf); -#endif +#endif /* HAVE_PF_PACKET_SOCKETS */ static int iface_get_mtu(int fd, const char *device, char *ebuf); static int iface_get_arptype(int fd, const char *device, char *ebuf); #ifdef HAVE_PF_PACKET_SOCKETS @@ -347,6 +355,7 @@ static int has_wext(int sock_fd, const char *device, char *ebuf); static int enter_rfmon_mode(pcap_t *handle, int sock_fd, const char *device); #endif /* HAVE_PF_PACKET_SOCKETS */ +static int iface_get_offload(pcap_t *handle); static int iface_bind_old(int fd, const char *device, char *ebuf); #ifdef SO_ATTACH_FILTER @@ -360,7 +369,7 @@ static struct sock_filter total_insn = BPF_STMT(BPF_RET | BPF_K, 0); static struct sock_fprog total_fcode = { 1, &total_insn }; -#endif +#endif /* SO_ATTACH_FILTER */ pcap_t * pcap_create(const char *device, char *ebuf) @@ -3230,19 +3239,31 @@ create_ring(pcap_t *handle, int *status) * interface is just being used for passive snooping, the driver * might set the size of buffers in the receive ring based on * the MTU, so that the MTU limits the maximum size of packets - * that we can receive.) */ + * that we can receive.) + * + * We don't do that if segmentation/fragmentation or receive + * offload are enabled, so we don't get rudely surprised by + * "packets" bigger than the MTU. */ frame_size = handle->snapshot; if (handle->linktype == DLT_EN10MB) { int mtu; - - mtu = iface_get_mtu(handle->fd, handle->opt.source, - handle->errbuf); - if (mtu == -1) { + int offload; + + offload = iface_get_offload(handle); + if (offload == -1) { *status = PCAP_ERROR; return -1; } - if (frame_size > mtu + 18) - frame_size = mtu + 18; + if (!offload) { + mtu = iface_get_mtu(handle->fd, handle->opt.source, + handle->errbuf); + if (mtu == -1) { + *status = PCAP_ERROR; + return -1; + } + if (frame_size > mtu + 18) + frame_size = mtu + 18; + } } /* NOTE: calculus matching those in tpacket_rcv() @@ -4682,6 +4703,88 @@ enter_rfmon_mode(pcap_t *handle, int sock_fd, const char *device) return 0; } +/* + * Find out if we have any form of fragmentation/reassembly offloading. + */ +#ifdef SIOCETHTOOL +static int +iface_ethtool_ioctl(pcap_t *handle, int cmd) +{ + struct ifreq ifr; + struct ethtool_value eval; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, handle->opt.source, sizeof(ifr.ifr_name)); + eval.cmd = cmd; + ifr.ifr_data = (caddr_t)&eval; + if (ioctl(handle->fd, SIOCETHTOOL, &ifr) == -1) { + snprintf(handle->errbuf, PCAP_ERRBUF_SIZE, + "%s: 0x%08x ioctl failed: %s", handle->opt.source, + cmd, strerror(errno)); + return -1; + } + return eval.data; +} + +static int +iface_get_offload(pcap_t *handle) +{ + int ret; + + ret = iface_ethtool_ioctl(handle, ETHTOOL_GTSO); + if (ret == -1) + return -1; + if (ret) + return 1; /* TCP segmentation offloading on */ + + ret = iface_ethtool_ioctl(handle, ETHTOOL_GUFO); + if (ret == -1) + return -1; + if (ret) + return 1; /* UDP fragmentation offloading on */ + + /* + * XXX - will this cause large unsegmented packets to be + * handed to PF_PACKET sockets on transmission? If not, + * this need not be checked. + */ + ret = iface_ethtool_ioctl(handle, ETHTOOL_GGSO); + if (ret == -1) + return -1; + if (ret) + return 1; /* generic segmentation offloading on */ + + ret = iface_ethtool_ioctl(handle, ETHTOOL_GFLAGS); + if (ret == -1) + return -1; + if (ret & ETH_FLAG_LRO) + return 1; /* large receive offloading on */ + + /* + * XXX - will this cause large reassembled packets to be + * handed to PF_PACKET sockets on receipt? If not, + * this need not be checked. + */ + ret = iface_ethtool_ioctl(handle, ETHTOOL_GGRO); + if (ret == -1) + return -1; + if (ret) + return 1; /* generic (large) receive offloading on */ + + return 0; +} +#else /* SIOCETHTOOL */ +static int +iface_get_offload(pcap_t *handle _U_) +{ + /* + * XXX - do we need to get this information if we don't + * have the ethtool ioctls? If so, how do we do that? + */ + return 0; +} +#endif /* SIOCETHTOOL */ + #endif /* HAVE_PF_PACKET_SOCKETS */ /* ===== Functions to interface to the older kernels ================== */