diff --git a/share/man/man4/bridge.4 b/share/man/man4/bridge.4 --- a/share/man/man4/bridge.4 +++ b/share/man/man4/bridge.4 @@ -36,7 +36,7 @@ .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE .\" POSSIBILITY OF SUCH DAMAGE. .\" -.Dd October 13, 2025 +.Dd January 29, 2026 .Dt IF_BRIDGE 4 .Os .Sh NAME @@ -144,7 +144,7 @@ If the MTU of a bridge is changed after its creation, the MTU of all member interfaces is also changed to match. .Pp -The TOE, TSO, TXCSUM and TXCSUM6 capabilities on all interfaces added to the +The TOE and TSO capabilities on all interfaces added to the bridge are disabled if any of the interfaces do not support/enable them. The LRO capability is always disabled. All the capabilities are restored when the interface is removed from the bridge. diff --git a/sys/net/if_bridge.c b/sys/net/if_bridge.c --- a/sys/net/if_bridge.c +++ b/sys/net/if_bridge.c @@ -78,6 +78,7 @@ #include "opt_inet.h" #include "opt_inet6.h" +#include "opt_sctp.h" #define EXTERR_CATEGORY EXTERR_CAT_BRIDGE @@ -137,6 +138,10 @@ #include +#if defined(SCTP) || defined(SCTP_SUPPORT) +#include +#endif + /* * At various points in the code we need to know if we're hooked into the INET * and/or INET6 pfil. Define some macros to do that based on which IP versions @@ -195,8 +200,7 @@ /* * List of capabilities to possibly mask on the member interface. */ -#define BRIDGE_IFCAPS_MASK (IFCAP_TOE|IFCAP_TSO|IFCAP_TXCSUM|\ - IFCAP_TXCSUM_IPV6|IFCAP_MEXTPG) +#define BRIDGE_IFCAPS_MASK (IFCAP_TOE|IFCAP_TSO|IFCAP_MEXTPG) /* * List of capabilities to strip @@ -841,6 +845,10 @@ return (bifa->bif_sc == bifb->bif_sc); } +#define BRIDGE_CSUM_IP4_ALL (CSUM_IP | CSUM_IP_SCTP | CSUM_IP_TCP | CSUM_IP_UDP) +#define BRIDGE_CSUM_IP6_ALL (CSUM_IP6_SCTP | CSUM_IP6_TCP | CSUM_IP6_UDP) +#define BRIDGE_CSUM_ALL (BRIDGE_CSUM_IP4_ALL | BRIDGE_CSUM_IP6_ALL) + /* * bridge_clone_create: * @@ -871,7 +879,9 @@ ifp->if_softc = sc; if_initname(ifp, bridge_name, ifd->unit); ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST; - ifp->if_capabilities = ifp->if_capenable = IFCAP_VLAN_HWTAGGING; + ifp->if_capabilities = ifp->if_capenable = IFCAP_VLAN_HWTAGGING | + IFCAP_TXCSUM | IFCAP_TXCSUM_IPV6 | IFCAP_VLAN_HWCSUM; + ifp->if_hwassist = BRIDGE_CSUM_ALL; ifp->if_ioctl = bridge_ioctl; #ifdef ALTQ ifp->if_start = bridge_altq_start; @@ -998,7 +1008,7 @@ } args; struct ifdrv *ifd = (struct ifdrv *) data; const struct bridge_control *bc; - int error = 0, oldmtu; + int error = 0, oldmtu, mask; BRIDGE_LOCK(sc); @@ -1125,6 +1135,20 @@ sc->sc_ifp->if_mtu = ifr->ifr_mtu; } break; + case SIOCSIFCAP: + mask = ifr->ifr_reqcap ^ if_getcapenable(ifp); + if (mask & IFCAP_TXCSUM) { + if_togglecapenable(ifp, IFCAP_TXCSUM); + if_togglehwassist(ifp, BRIDGE_CSUM_IP4_ALL); + } + if (mask & IFCAP_TXCSUM_IPV6) { + if_togglecapenable(ifp, IFCAP_TXCSUM_IPV6); + if_togglehwassist(ifp, BRIDGE_CSUM_IP6_ALL); + } + if (mask & IFCAP_VLAN_HWCSUM) { + if_togglecapenable(ifp, IFCAP_VLAN_HWCSUM); + } + break; default: /* * drop the lock as ether_ioctl() will call bridge_start() and @@ -2373,6 +2397,134 @@ ifp->if_drv_flags &= ~IFF_DRV_RUNNING; } +#if defined(INET) || defined(INET6) + +/* + * Depending on what is set in the given csum_req, compute and insert the IP, + * SCTP, TCP, or UDP checksum for the given mbuf chain m. + * Return the modified mbuf chain, or NULL if the mbuf chain was destroyed. + */ +static struct mbuf * +bridge_delayed_cksum(struct mbuf *m, uint32_t csum_req) +{ + struct ether_header *eh; + uint16_t ether_type, inner_ether_type, iph_offset; + + KASSERT((csum_req & BRIDGE_CSUM_ALL) != 0, + ("%s: got an mbuf without csum offloading flag set.", __func__)); + + m = m_pullup(m, ETHER_HDR_LEN + 2 * ETHER_VLAN_ENCAP_LEN); + if (m == NULL) { + return (NULL); + } + /* determine ethernet header length */ + eh = mtod(m, struct ether_header *); + ether_type = ntohs(eh->ether_type); + switch (ether_type) { + case ETHERTYPE_IP: + case ETHERTYPE_IPV6: + iph_offset = ETHER_HDR_LEN; + inner_ether_type = ether_type; + break; + case ETHERTYPE_VLAN: + iph_offset = ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN; + inner_ether_type = + ntohs(((struct ether_vlan_header *)eh)->evl_proto); + break; + case ETHERTYPE_QINQ: + iph_offset = ETHER_HDR_LEN + 2 * ETHER_VLAN_ENCAP_LEN; + /* QINQ header adds four 2-byte fields before the ether_type */ + inner_ether_type = ntohs(*(&(eh->ether_type) + 4)); + break; + default: + KASSERT(0, ("%s: got the unexpected ether_type %d.\n", + __func__, ether_type)); + return (m); + } + +#if defined(INET) + if (csum_req & BRIDGE_CSUM_IP4_ALL) { + struct ip *ip; + + if (inner_ether_type != ETHERTYPE_IP) { + KASSERT(0, ("%s: got an mbuf with an IPv4 csum offloading flag set that contains no IPv4 packet.", + __func__)); + return (m); + } + /* + * pullup enough to access the ihl, total length, and checksum + * fields in the IP header. + */ + m = m_pullup(m, iph_offset + offsetof(struct ip, ip_src)); + if (m == NULL) { + return (NULL); + } + ip = (struct ip *)mtodo(m, iph_offset); + if (csum_req & CSUM_IP) { + ip->ip_sum = 0; + ip->ip_sum = in_cksum_skip(m, + iph_offset + (ip->ip_hl << 2), iph_offset); + if (m->m_flags & M_PKTHDR) { + m->m_pkthdr.csum_flags &= ~CSUM_IP; + } + } + if (csum_req & (CSUM_IP_TCP | CSUM_IP_UDP)) { + in_delayed_cksum_o(m, iph_offset); + if (m->m_flags & M_PKTHDR) { + m->m_pkthdr.csum_flags &= + ~(CSUM_IP_TCP | CSUM_IP_UDP); + } + } +#if defined(SCTP) || defined(SCTP_SUPPORT) + else if (csum_req & CSUM_IP_SCTP) { + sctp_delayed_cksum(m, (uint32_t)(iph_offset + + (ip->ip_hl << 2))); + if (m->m_flags & M_PKTHDR) { + m->m_pkthdr.csum_flags &= ~CSUM_IP_SCTP; + } + } +#endif /* defined(SCTP) || defined(SCTP_SUPPORT) */ + } +#endif /* defined(INET) */ +#if defined(INET6) + if (csum_req & BRIDGE_CSUM_IP6_ALL) { + int ip6p_offset; + + if (inner_ether_type != ETHERTYPE_IPV6) { + KASSERT(0, ("%s: got an mbuf with an IPv6 csum offloading flag set that contains no IPv6 packet.", + __func__)); + return (m); + } + ip6p_offset = ip6_lasthdr(m, iph_offset, IPPROTO_IPV6, NULL); + if ((ip6p_offset < iph_offset + sizeof(struct ip6_hdr)) || + (m->m_flags & M_PKTHDR && ip6p_offset > m->m_pkthdr.len)) { + KASSERT(0, ("%s: offset to the IPv6 payload seems incorrect.", + __func__)); + return (m); + } + + if (csum_req & (CSUM_IP6_TCP | CSUM_IP6_UDP)) { + in6_delayed_cksum(m, m->m_pkthdr.len - ip6p_offset, + ip6p_offset); + if (m->m_flags & M_PKTHDR) { + m->m_pkthdr.csum_flags &= + ~(CSUM_IP6_TCP | CSUM_IP6_UDP); + } + } +#if defined(SCTP) || defined(SCTP_SUPPORT) + else { /* csum_req & CSUM_IP6_SCTP */ + sctp_delayed_cksum(m, ip6p_offset); + if (m->m_flags & M_PKTHDR) { + m->m_pkthdr.csum_flags &= ~CSUM_IP6_SCTP; + } + } +#endif /* defined(SCTP) || defined(SCTP_SUPPORT) */ + } +#endif /* defined(INET6) */ + return (m); +} +#endif /* defined(INET) || defined(INET6) */ + /* * bridge_enqueue: * @@ -2442,6 +2594,32 @@ } M_ASSERTPKTHDR(m); /* We shouldn't transmit mbuf without pkthdr */ + +#if defined(INET) || defined(INET6) + uint32_t csum_req; + + /* + * If IP, SCTP, TCP, or UDP header still needs a valid checksum + * and the outgoing interface will not compute it for us, + * do it here. + */ + csum_req = m->m_pkthdr.csum_flags & BRIDGE_CSUM_ALL & + ~dst_ifp->if_hwassist; + if (__predict_false(csum_req != 0)) { + m = bridge_delayed_cksum(m, csum_req); + if (m == NULL) { + /* + * The mbuf was destroyed during the attempt to + * compute and insert the checksum. + */ + if_printf(dst_ifp, + "error while computing and inserting IP/SCTP/TCP/UDP checksum\n"); + if_inc_counter(dst_ifp, IFCOUNTER_OERRORS, 1); + continue; + } + } +#endif /* defined(INET) || defined(INET6) */ + /* * XXXZL: gif(4) requires the af to be saved in csum_data field * so that gif_transmit() routine can pull it back.