tcp: Add TSO support for IPv4
authorSepherosa Ziehau <sephe@dragonflybsd.org>
Fri, 27 Jul 2012 06:07:27 +0000 (14:07 +0800)
committerSepherosa Ziehau <sephe@dragonflybsd.org>
Mon, 30 Jul 2012 06:26:15 +0000 (14:26 +0800)
It is implemented mainly according to NetBSD's TSO implementation.

Following stuffs are only in DragonFly
- Add comment about devices' expected behaviour upon PUSH and FIN flags
  Obtained-from: Microsoft's LSO online document
- Don't use TSO, if there are SACK or DSACK blocks to report
- Don't use TSO, if congestion window needs validation
- Don't use TSO, if URG flag is to be set
- Take IP and TCP header sizes into consideration when calculate the
  large TCP segment size
- Pseudo checksum for the large TCP segment is calculated using only
  source address, destination address and IPPROTO_TCP according to
  Microsoft's LSO online document.  This fashion of pseudo checksum
  calculation seems to be adopted by several NIC chips.

Several driver helper functions are added:
- tcp_tso_pullup(), which extracts IPv4 and TCP header's location and
  length.  And make sure that IPv4 and TCP headers are in contiguous
  memory.
- ether_tso_pullup(), in addition to what tcp_tso_pullup() does, it
  also extracts ethernet header's length and make sure that ethernet,
  IPv4 and TCP headers are in contiguous memory.

Sysctl node net.inet.tcp.tso could be used to globally disable TSO.
TSO is by default on.

tso/-tso are added to ifconfig(8), which could be used to enable or
disable TSO on the specific interface.

sbin/ifconfig/ifconfig.8
sbin/ifconfig/ifconfig.c
sys/net/if.h
sys/net/if_ethersubr.c
sys/net/if_var.h
sys/netinet/ip_output.c
sys/netinet/tcp_output.c
sys/netinet/tcp_subr.c
sys/netinet/tcp_var.h
sys/sys/mbuf.h

index 6bbaad4..e36df75 100644 (file)
@@ -336,6 +336,12 @@ support, the exact level of offloading varies between drivers.
 If the driver supports user-configurable checksum offloading,
 disable receive (or transmit) checksum offloading on the interface.
 These settings may not always be independent of each other.
+.It Cm tso
+If the driver supports TCP segmentation offloading,
+enable TCP segmentation offloading on the interface.
+.It Fl tso
+If the driver supports TCP segmentation offloading,
+disable TCP segmentation offloading on the interface.
 .It Cm vlanmtu , vlanhwtag
 If the driver offers user-configurable VLAN support, enable
 reception of extended frames or tag processing in hardware,
index 3a62669..2d79d1c 100644 (file)
@@ -889,7 +889,8 @@ rt_xaddrs(caddr_t cp, caddr_t cplim, struct rt_addrinfo *rtinfo)
 "\20MULTICAST\21POLLING\22PPROMISC\23MONITOR\24STATICARP\25NPOLLING"
 
 #define        IFCAPBITS \
-"\020\1RXCSUM\2TXCSUM\3NETCONS\4VLAN_MTU\5VLAN_HWTAGGING\6JUMBO_MTU\7RSS"
+"\020\1RXCSUM\2TXCSUM\3NETCONS\4VLAN_MTU\5VLAN_HWTAGGING\6JUMBO_MTU\7RSS" \
+"\10VLAN_HWCSUM\11TSO"
 
 /*
  * Print the status of the interface.  If an address family was
@@ -1131,6 +1132,8 @@ static struct cmd basic_cmds[] = {
        DEF_CMD("-netcons",     -IFCAP_NETCONS, setifcap),
        DEF_CMD("rss",          IFCAP_RSS,      setifcap),
        DEF_CMD("-rss",         -IFCAP_RSS,     setifcap),
+       DEF_CMD("tso",          IFCAP_TSO,      setifcap),
+       DEF_CMD("-tso",         -IFCAP_TSO,     setifcap),
        DEF_CMD("normal",       -IFF_LINK0,     setifflags),
        DEF_CMD("compress",     IFF_LINK0,      setifflags),
        DEF_CMD("noicmp",       IFF_LINK1,      setifflags),
index e8575e3..e0a890a 100644 (file)
@@ -162,6 +162,7 @@ struct if_data {
 #define IFCAP_JUMBO_MTU                0x00020 /* 9000 byte MTU support */
 #define IFCAP_RSS              0x00040 /* Receive Side Scaling for IPv4 */
 #define IFCAP_VLAN_HWCSUM      0x00080 /* can do IFCAP_HWCSUM on VLANs */
+#define IFCAP_TSO              0x00100 /* can offload TCP segementation */
 
 #define IFCAP_HWCSUM   (IFCAP_RXCSUM | IFCAP_TXCSUM)
 
index bbfc880..256bfa1 100644 (file)
 #include <net/bpf.h>
 #include <net/ethernet.h>
 #include <net/vlan/if_vlan_ether.h>
+#include <net/vlan/if_vlan_var.h>
 #include <net/netmsg2.h>
 
 #if defined(INET) || defined(INET6)
 #include <netinet/in.h>
 #include <netinet/ip_var.h>
+#include <netinet/tcp_var.h>
 #include <netinet/if_ether.h>
 #include <netinet/ip_flow.h>
 #include <net/ipfw/ip_fw.h>
@@ -1594,4 +1596,49 @@ ether_demux(struct mbuf *m)
        lwkt_sendmsg(cpu_portfn(m->m_pkthdr.hash), &pmsg->base.lmsg);
 }
 
+boolean_t
+ether_tso_pullup(struct mbuf **mp, int *hoff0, struct ip **ip, int *iphlen,
+    struct tcphdr **th, int *thoff)
+{
+       struct mbuf *m = *mp;
+       struct ether_header *eh;
+       uint16_t type;
+       int hoff;
+
+       KASSERT(M_WRITABLE(m), ("not writable"));
+
+       hoff = ETHER_HDR_LEN;
+       if (m->m_len < hoff) {
+               m = m_pullup(m, hoff);
+               if (m == NULL)
+                       goto failed;
+       }
+       eh = mtod(m, struct ether_header *);
+       type = eh->ether_type;
+
+       if (type == htons(ETHERTYPE_VLAN)) {
+               struct ether_vlan_header *evh;
+
+               hoff += EVL_ENCAPLEN;
+               if (m->m_len < hoff) {
+                       m = m_pullup(m, hoff);
+                       if (m == NULL)
+                               goto failed;
+               }
+               evh = mtod(m, struct ether_vlan_header *);
+               type = evh->evl_proto;
+       }
+       KASSERT(type == htons(ETHERTYPE_IP), ("not IP %d", ntohs(type)));
+
+       *mp = m;
+       *hoff0 = hoff;
+       return tcp_tso_pullup(mp, hoff, ip, iphlen, th, thoff);
+
+failed:
+       if (m != NULL)
+               m_freem(m);
+       *mp = NULL;
+       return FALSE;
+}
+
 MODULE_VERSION(ether, 1);
index 288d9a5..f1535f6 100644 (file)
@@ -828,6 +828,9 @@ extern      int ifqmaxlen;
 extern struct ifnet loif[];
 extern int if_index;
 
+struct ip;
+struct tcphdr;
+
 void   ether_ifattach(struct ifnet *, uint8_t *, struct lwkt_serialize *);
 void   ether_ifattach_bpf(struct ifnet *, uint8_t *, u_int, u_int,
                        struct lwkt_serialize *);
@@ -838,6 +841,8 @@ void        ether_reinput_oncpu(struct ifnet *, struct mbuf *, int);
 void   ether_input_pkt(struct ifnet *, struct mbuf *, const struct pktinfo *);
 int    ether_output_frame(struct ifnet *, struct mbuf *);
 int    ether_ioctl(struct ifnet *, u_long, caddr_t);
+boolean_t ether_tso_pullup(struct mbuf **, int *, struct ip **, int *,
+           struct tcphdr **, int *);
 struct ifnet *ether_bridge_interface(struct ifnet *ifp);
 uint32_t       ether_crc32_le(const uint8_t *, size_t);
 uint32_t       ether_crc32_be(const uint8_t *, size_t);
index 72678c8..042a9b4 100644 (file)
@@ -911,20 +911,26 @@ pass:
                }
        }
 
-       m->m_pkthdr.csum_flags |= CSUM_IP;
-       sw_csum = m->m_pkthdr.csum_flags & ~ifp->if_hwassist;
-       if (sw_csum & CSUM_DELAY_DATA) {
-               in_delayed_cksum(m);
-               sw_csum &= ~CSUM_DELAY_DATA;
+       if ((m->m_pkthdr.csum_flags & CSUM_TSO) == 0) {
+               m->m_pkthdr.csum_flags |= CSUM_IP;
+               sw_csum = m->m_pkthdr.csum_flags & ~ifp->if_hwassist;
+               if (sw_csum & CSUM_DELAY_DATA) {
+                       in_delayed_cksum(m);
+                       sw_csum &= ~CSUM_DELAY_DATA;
+               }
+               m->m_pkthdr.csum_flags &= ifp->if_hwassist;
+       } else {
+               sw_csum = 0;
        }
-       m->m_pkthdr.csum_flags &= ifp->if_hwassist;
 
        /*
         * If small enough for interface, or the interface will take
-        * care of the fragmentation for us, can just send directly.
+        * care of the fragmentation or segmentation for us, can just
+        * send directly.
         */
-       if (ip->ip_len <= ifp->if_mtu || ((ifp->if_hwassist & CSUM_FRAGMENT) &&
-           !(ip->ip_off & IP_DF))) {
+       if (ip->ip_len <= ifp->if_mtu ||
+           ((ifp->if_hwassist & CSUM_FRAGMENT) && !(ip->ip_off & IP_DF)) ||
+           (m->m_pkthdr.csum_flags & CSUM_TSO)) {
                ip->ip_len = htons(ip->ip_len);
                ip->ip_off = htons(ip->ip_off);
                ip->ip_sum = 0;
index 2b3e664..6c9c5aa 100644 (file)
@@ -85,6 +85,7 @@
 #include <sys/thread.h>
 #include <sys/globaldata.h>
 
+#include <net/if_var.h>
 #include <net/route.h>
 
 #include <netinet/in.h>
@@ -149,8 +150,14 @@ static int tcp_idle_restart = 1;
 SYSCTL_INT(_net_inet_tcp, OID_AUTO, idle_restart, CTLFLAG_RW,
     &tcp_idle_restart, 0, "Reset congestion window after idle period");
 
+static int tcp_do_tso = 1;
+SYSCTL_INT(_net_inet_tcp, OID_AUTO, tso, CTLFLAG_RW,
+    &tcp_do_tso, 0, "Enable TCP Segmentation Offload (TSO)");
+
 static void    tcp_idle_cwnd_validate(struct tcpcb *);
 
+static int     tcp_tso_getsize(struct tcpcb *tp, u_int *segsz, u_int *hlen);
+
 /*
  * Tcp output routine: figure out what should be sent and send it.
  */
@@ -171,7 +178,7 @@ tcp_output(struct tcpcb *tp)
        struct tcphdr *th;
        u_char opt[TCP_MAXOLEN];
        unsigned int ipoptlen, optlen, hdrlen;
-       int idle, idle_cwv = 0;
+       int idle;
        boolean_t sendalot;
        struct ip6_hdr *ip6;
 #ifdef INET6
@@ -179,6 +186,9 @@ tcp_output(struct tcpcb *tp)
 #else
        const boolean_t isipv6 = FALSE;
 #endif
+       boolean_t can_tso = FALSE, use_tso;
+       boolean_t report_sack, idle_cwv = FALSE;
+       u_int segsz, tso_hlen;
 
        KKASSERT(so->so_port == &curthread->td_msgport);
 
@@ -198,7 +208,7 @@ tcp_output(struct tcpcb *tp)
         */
        if (tp->snd_max == tp->snd_una &&
            (ticks - tp->snd_last) >= tp->t_rxtcur && tcp_idle_restart)
-               idle_cwv = 1;
+               idle_cwv = TRUE;
 
        /*
         * Calculate whether the transmit stream was previously idle 
@@ -214,6 +224,34 @@ tcp_output(struct tcpcb *tp)
            !IN_FASTRECOVERY(tp))
                nsacked = tcp_sack_bytes_below(&tp->scb, tp->snd_nxt);
 
+       /*
+        * Find out whether TSO could be used or not
+        *
+        * For TSO capable devices, the following assumptions apply to
+        * the processing of TCP flags:
+        * - If FIN is set on the large TCP segment, the device must set
+        *   FIN on the last segment that it creates from the large TCP
+        *   segment.
+        * - If PUSH is set on the large TCP segment, the device must set
+        *   PUSH on the last segment that it creates from the large TCP
+        *   segment.
+        */
+#if !defined(IPSEC) && !defined(FAST_IPSEC)
+       if (tcp_do_tso
+#ifdef TCP_SIGNATURE
+           && (tp->t_flags & TF_SIGNATURE) == 0
+#endif
+       ) {
+               if (!isipv6) {
+                       struct rtentry *rt = inp->inp_route.ro_rt;
+
+                       if (rt != NULL && (rt->rt_flags & RTF_UP) &&
+                           (rt->rt_ifp->if_hwassist & CSUM_TSO))
+                               can_tso = TRUE;
+               }
+       }
+#endif /* !IPSEC && !FAST_IPSEC */
+
 again:
        m = NULL;
        ip = NULL;
@@ -221,6 +259,14 @@ again:
        th = NULL;
        ip6 = NULL;
 
+       if ((tp->t_flags & (TF_SACK_PERMITTED | TF_NOOPT)) ==
+               TF_SACK_PERMITTED &&
+           (!TAILQ_EMPTY(&tp->t_segq) ||
+            tp->reportblk.rblk_start != tp->reportblk.rblk_end))
+               report_sack = TRUE;
+       else
+               report_sack = FALSE;
+
        /* Make use of SACK information when slow-starting after a RTO. */
        if (TCP_DO_SACK(tp) && tp->snd_nxt != tp->snd_max &&
            !IN_FASTRECOVERY(tp)) {
@@ -404,12 +450,64 @@ again:
        }
 
        /*
-        * Truncate to the maximum segment length and ensure that FIN is
-        * removed if the length no longer contains the last data byte.
+        * Don't use TSO, if:
+        * - Congestion window needs validation
+        * - There are SACK blocks to report
+        * - RST or SYN flags is set
+        * - URG will be set
+        *
+        * XXX
+        * Checking for SYN|RST looks overkill, just to be safe than sorry
+        */
+       use_tso = can_tso;
+       if (report_sack || idle_cwv || (flags & (TH_RST | TH_SYN)))
+               use_tso = FALSE;
+       if (use_tso) {
+               tcp_seq ugr_nxt = tp->snd_nxt;
+
+               if ((flags & TH_FIN) && (tp->t_flags & TF_SENTFIN) &&
+                   tp->snd_nxt == tp->snd_max)
+                       --ugr_nxt;
+
+               if (SEQ_GT(tp->snd_up, ugr_nxt))
+                       use_tso = FALSE;
+       }
+
+       if (use_tso) {
+               /*
+                * Find out segment size and header length for TSO
+                */
+               error = tcp_tso_getsize(tp, &segsz, &tso_hlen);
+               if (error)
+                       use_tso = FALSE;
+       }
+       if (!use_tso) {
+               segsz = tp->t_maxseg;
+               tso_hlen = 0; /* not used */
+       }
+
+       /*
+        * Truncate to the maximum segment length if not TSO, and ensure that
+        * FIN is removed if the length no longer contains the last data byte.
         */
-       if (len > tp->t_maxseg) {
-               len = tp->t_maxseg;
+       if (len > segsz) {
+               if (!use_tso) {
+                       len = segsz;
+               } else {
+                       /*
+                        * Truncate TSO transfers to (IP_MAXPACKET - iphlen -
+                        * thoff), and make sure that we send equal size
+                        * transfers down the stack (rather than big-small-
+                        * big-small-...).
+                        */
+                       len = (min(len, (IP_MAXPACKET - tso_hlen)) / segsz) *
+                           segsz;
+                       if (len <= segsz)
+                               use_tso = FALSE;
+               }
                sendalot = TRUE;
+       } else {
+               use_tso = FALSE;
        }
        if (SEQ_LT(tp->snd_nxt + len, tp->snd_una + so->so_snd.ssb_cc))
                flags &= ~TH_FIN;
@@ -429,7 +527,7 @@ again:
         *      - we need to retransmit
         */
        if (len) {
-               if (len == tp->t_maxseg)
+               if (len >= segsz)
                        goto send;
                /*
                 * NOTE! on localhost connections an 'ack' from the remote
@@ -488,7 +586,7 @@ again:
                 */
                if (avoid_pure_win_update == 0 ||
                    (tp->t_flags & TF_RXRESIZED)) {
-                       if (adv >= (long) (2 * tp->t_maxseg)) {
+                       if (adv >= (long) (2 * segsz)) {
                                goto send;
                        }
                }
@@ -630,10 +728,7 @@ send:
         * If this is a SACK connection and we have a block to report,
         * fill in the SACK blocks in the TCP options.
         */
-       if ((tp->t_flags & (TF_SACK_PERMITTED | TF_NOOPT)) ==
-               TF_SACK_PERMITTED &&
-           (!TAILQ_EMPTY(&tp->t_segq) ||
-            tp->reportblk.rblk_start != tp->reportblk.rblk_end))
+       if (report_sack)
                tcp_sack_fill_report(tp, opt, &optlen);
 
 #ifdef TCP_SIGNATURE
@@ -675,29 +770,39 @@ send:
        ipoptlen += ipsec_hdrsiz_tcp(tp);
 #endif
 
-       /*
-        * Adjust data length if insertion of options will bump the packet
-        * length beyond the t_maxopd length.  Clear FIN to prevent premature
-        * closure since there is still more data to send after this (now
-        * truncated) packet.
-        *
-        * If just the options do not fit we are in a no-win situation and
-        * we treat it as an unreachable host.
-        */
-       if (len + optlen + ipoptlen > tp->t_maxopd) {
-               if (tp->t_maxopd <= optlen + ipoptlen) {
-                       static time_t last_optlen_report;
+       if (use_tso) {
+               KASSERT(len > segsz,
+                   ("invalid TSO len %ld, segsz %u", len, segsz));
+       } else {
+               KASSERT(len <= segsz,
+                   ("invalid len %ld, segsz %u", len, segsz));
 
-                       if (last_optlen_report != time_second) {
-                               last_optlen_report = time_second;
-                               kprintf("tcpcb %p: MSS (%d) too small to hold options!\n", tp, tp->t_maxopd);
+               /*
+                * Adjust data length if insertion of options will bump
+                * the packet length beyond the t_maxopd length.  Clear
+                * FIN to prevent premature closure since there is still
+                * more data to send after this (now truncated) packet.
+                *
+                * If just the options do not fit we are in a no-win
+                * situation and we treat it as an unreachable host.
+                */
+               if (len + optlen + ipoptlen > tp->t_maxopd) {
+                       if (tp->t_maxopd <= optlen + ipoptlen) {
+                               static time_t last_optlen_report;
+
+                               if (last_optlen_report != time_second) {
+                                       last_optlen_report = time_second;
+                                       kprintf("tcpcb %p: MSS (%d) too "
+                                           "small to hold options!\n",
+                                           tp, tp->t_maxopd);
+                               }
+                               error = EHOSTUNREACH;
+                               goto out;
+                       } else {
+                               flags &= ~TH_FIN;
+                               len = tp->t_maxopd - optlen - ipoptlen;
+                               sendalot = TRUE;
                        }
-                       error = EHOSTUNREACH;
-                       goto out;
-               } else {
-                       flags &= ~TH_FIN;
-                       len = tp->t_maxopd - optlen - ipoptlen;
-                       sendalot = TRUE;
                }
        }
 
@@ -729,7 +834,7 @@ send:
                        tcpstat.tcps_sndbyte += len;
                }
                if (idle_cwv) {
-                       idle_cwv = 0;
+                       idle_cwv = FALSE;
                        tcp_idle_cwnd_validate(tp);
                }
                /* Update last send time after CWV */
@@ -806,13 +911,13 @@ send:
        if (isipv6) {
                ip6 = mtod(m, struct ip6_hdr *);
                th = (struct tcphdr *)(ip6 + 1);
-               tcp_fillheaders(tp, ip6, th);
+               tcp_fillheaders(tp, ip6, th, use_tso);
        } else {
                ip = mtod(m, struct ip *);
                ipov = (struct ipovly *)ip;
                th = (struct tcphdr *)(ip + 1);
                /* this picks up the pseudo header (w/o the length) */
-               tcp_fillheaders(tp, ip, th);
+               tcp_fillheaders(tp, ip, th, use_tso);
        }
 after_th:
        /*
@@ -857,7 +962,7 @@ after_th:
         * window is less then one segment.
         */
        if (recvwin < (long)(so->so_rcv.ssb_hiwat / 4) &&
-           recvwin < (long)tp->t_maxseg)
+           recvwin < (long)segsz)
                recvwin = 0;
        if (recvwin < (tcp_seq_diff_t)(tp->rcv_adv - tp->rcv_nxt))
                recvwin = (tcp_seq_diff_t)(tp->rcv_adv - tp->rcv_nxt);
@@ -881,6 +986,7 @@ after_th:
                th->th_win = htons((u_short) (recvwin>>tp->rcv_scale));
 
        if (SEQ_GT(tp->snd_up, tp->snd_nxt)) {
+               KASSERT(!use_tso, ("URG with TSO"));
                if (th != NULL) {
                        th->th_urp = htons((u_short)(tp->snd_up - tp->snd_nxt));
                        th->th_flags |= TH_URG;
@@ -917,11 +1023,16 @@ after_th:
                            sizeof(struct ip6_hdr),
                            sizeof(struct tcphdr) + optlen + len);
                } else {
-                       m->m_pkthdr.csum_flags = CSUM_TCP;
                        m->m_pkthdr.csum_data = offsetof(struct tcphdr, th_sum);
-                       if (len + optlen) {
-                               th->th_sum = in_addword(th->th_sum,
-                                   htons((u_short)(optlen + len)));
+                       if (use_tso) {
+                               m->m_pkthdr.csum_flags = CSUM_TSO;
+                               m->m_pkthdr.segsz = segsz;
+                       } else {
+                               m->m_pkthdr.csum_flags = CSUM_TCP;
+                               if (len + optlen) {
+                                       th->th_sum = in_addword(th->th_sum,
+                                           htons((u_short)(optlen + len)));
+                               }
                        }
 
                        /*
@@ -1227,3 +1338,46 @@ tcp_idle_cwnd_validate(struct tcpcb *tp)
        /* Restart ABC counting during congestion avoidance */
        tp->snd_wacked = 0;
 }
+
+static int
+tcp_tso_getsize(struct tcpcb *tp, u_int *segsz, u_int *hlen0)
+{
+       struct inpcb * const inp = tp->t_inpcb;
+#ifdef INET6
+       const boolean_t isipv6 = (inp->inp_vflag & INP_IPV6) != 0;
+#else
+       const boolean_t isipv6 = FALSE;
+#endif
+       unsigned int ipoptlen, optlen;
+       u_int hlen;
+
+       hlen = sizeof(struct ip) + sizeof(struct tcphdr);
+
+       if (isipv6) {
+               ipoptlen = ip6_optlen(inp);
+       } else {
+               if (inp->inp_options) {
+                       ipoptlen = inp->inp_options->m_len -
+                           offsetof(struct ipoption, ipopt_list);
+               } else {
+                       ipoptlen = 0;
+               }
+       }
+#ifdef IPSEC
+       ipoptlen += ipsec_hdrsiz_tcp(tp);
+#endif
+       hlen += ipoptlen;
+
+       optlen = 0;
+       if ((tp->t_flags & (TF_REQ_TSTMP | TF_NOOPT)) == TF_REQ_TSTMP &&
+           (tp->t_flags & TF_RCVD_TSTMP))
+               optlen += TCPOLEN_TSTAMP_APPA;
+       hlen += optlen;
+
+       if (tp->t_maxopd <= optlen + ipoptlen)
+               return EHOSTUNREACH;
+
+       *segsz = tp->t_maxopd - optlen - ipoptlen;
+       *hlen0 = hlen;
+       return 0;
+}
index 8deb272..6383fff 100644 (file)
@@ -441,7 +441,7 @@ tcp_willblock(void)
  * of the tcpcb each time to conserve mbufs.
  */
 void
-tcp_fillheaders(struct tcpcb *tp, void *ip_ptr, void *tcp_ptr)
+tcp_fillheaders(struct tcpcb *tp, void *ip_ptr, void *tcp_ptr, boolean_t tso)
 {
        struct inpcb *inp = tp->t_inpcb;
        struct tcphdr *tcp_hdr = (struct tcphdr *)tcp_ptr;
@@ -464,6 +464,7 @@ tcp_fillheaders(struct tcpcb *tp, void *ip_ptr, void *tcp_ptr)
 #endif
        {
                struct ip *ip = (struct ip *) ip_ptr;
+               u_int plen;
 
                ip->ip_vhl = IP_VHL_BORING;
                ip->ip_tos = 0;
@@ -475,9 +476,13 @@ tcp_fillheaders(struct tcpcb *tp, void *ip_ptr, void *tcp_ptr)
                ip->ip_p = IPPROTO_TCP;
                ip->ip_src = inp->inp_laddr;
                ip->ip_dst = inp->inp_faddr;
+
+               if (tso)
+                       plen = htons(IPPROTO_TCP);
+               else
+                       plen = htons(sizeof(struct tcphdr) + IPPROTO_TCP);
                tcp_hdr->th_sum = in_pseudo(ip->ip_src.s_addr,
-                                   ip->ip_dst.s_addr,
-                                   htons(sizeof(struct tcphdr) + IPPROTO_TCP));
+                   ip->ip_dst.s_addr, plen);
        }
 
        tcp_hdr->th_sport = inp->inp_lport;
@@ -503,7 +508,7 @@ tcp_maketemplate(struct tcpcb *tp)
 
        if ((tmp = mpipe_alloc_nowait(&tcptemp_mpipe)) == NULL)
                return (NULL);
-       tcp_fillheaders(tp, &tmp->tt_ipgen, &tmp->tt_t);
+       tcp_fillheaders(tp, &tmp->tt_ipgen, &tmp->tt_t, FALSE);
        return (tmp);
 }
 
@@ -1815,7 +1820,7 @@ ipsec_hdrsiz_tcp(struct tcpcb *tp)
                th = (struct tcphdr *)(ip6 + 1);
                m->m_pkthdr.len = m->m_len =
                    sizeof(struct ip6_hdr) + sizeof(struct tcphdr);
-               tcp_fillheaders(tp, ip6, th);
+               tcp_fillheaders(tp, ip6, th, FALSE);
                hdrsiz = ipsec6_hdrsiz(m, IPSEC_DIR_OUTBOUND, inp);
        } else
 #endif
@@ -1823,7 +1828,7 @@ ipsec_hdrsiz_tcp(struct tcpcb *tp)
                ip = mtod(m, struct ip *);
                th = (struct tcphdr *)(ip + 1);
                m->m_pkthdr.len = m->m_len = sizeof(struct tcpiphdr);
-               tcp_fillheaders(tp, ip, th);
+               tcp_fillheaders(tp, ip, th, FALSE);
                hdrsiz = ipsec4_hdrsiz(m, IPSEC_DIR_OUTBOUND, inp);
        }
 
@@ -2227,3 +2232,67 @@ tcpsignature_apply(void *fstate, void *data, unsigned int len)
        return (0);
 }
 #endif /* TCP_SIGNATURE */
+
+boolean_t
+tcp_tso_pullup(struct mbuf **mp, int hoff, struct ip **ip0, int *iphlen0,
+    struct tcphdr **th0, int *thoff0)
+{
+       struct mbuf *m = *mp;
+       struct ip *ip;
+       int len, iphlen;
+       struct tcphdr *th;
+       int thoff;
+
+       len = hoff + sizeof(struct ip);
+
+       /* The fixed IP header must reside completely in the first mbuf. */
+       if (m->m_len < len) {
+               m = m_pullup(m, len);
+               if (m == NULL)
+                       goto fail;
+       }
+
+       ip = mtodoff(m, struct ip *, hoff);
+       iphlen = IP_VHL_HL(ip->ip_vhl) << 2;
+
+       /* The full IP header must reside completely in the one mbuf. */
+       if (m->m_len < hoff + iphlen) {
+               m = m_pullup(m, hoff + iphlen);
+               if (m == NULL)
+                       goto fail;
+               ip = mtodoff(m, struct ip *, hoff);
+       }
+
+       KASSERT(ip->ip_p == IPPROTO_TCP, ("not tcp %d", ip->ip_p));
+
+       if (m->m_len < hoff + iphlen + sizeof(struct tcphdr)) {
+               m = m_pullup(m, hoff + iphlen + sizeof(struct tcphdr));
+               if (m == NULL)
+                       goto fail;
+               ip = mtodoff(m, struct ip *, hoff);
+       }
+
+       th = (struct tcphdr *)((caddr_t)ip + iphlen);
+       thoff = th->th_off << 2;
+
+       if (m->m_len < hoff + iphlen + thoff) {
+               m = m_pullup(m, hoff + iphlen + thoff);
+               if (m == NULL)
+                       goto fail;
+               ip = mtodoff(m, struct ip *, hoff);
+               th = (struct tcphdr *)((caddr_t)ip + iphlen);
+       }
+
+       *mp = m;
+       *ip0 = ip;
+       *iphlen0 = iphlen;
+       *th0 = th;
+       *thoff0 = thoff;
+       return TRUE;
+
+fail:
+       if (m != NULL)
+               m_freem(m);
+       *mp = NULL;
+       return FALSE;
+}
index c1691d8..b5fffc2 100644 (file)
@@ -624,6 +624,7 @@ extern      int tcp_minmss;
 extern int tcp_delack_enabled;
 extern int path_mtu_discovery;
 
+struct ip;
 union netmsg;
 
 int     tcp_addrcpu(in_addr_t faddr, in_port_t fport,
@@ -686,7 +687,7 @@ void         tcp_revert_congestion_state(struct tcpcb *tp);
 void    tcp_setpersist (struct tcpcb *);
 struct tcptemp *tcp_maketemplate (struct tcpcb *);
 void    tcp_freetemplate (struct tcptemp *);
-void    tcp_fillheaders (struct tcpcb *, void *, void *);
+void    tcp_fillheaders (struct tcpcb *, void *, void *, boolean_t);
 struct lwkt_port *
         tcp_soport(struct socket *, struct sockaddr *, struct mbuf **);
 struct lwkt_port *
@@ -698,6 +699,9 @@ void         tcp_trace (short, short, struct tcpcb *, void *, struct tcphdr *,
 void    tcp_xmit_bandwidth_limit(struct tcpcb *tp, tcp_seq ack_seq);
 u_long  tcp_initial_window(struct tcpcb *tp);
 void    tcp_timer_keep_activity(struct tcpcb *tp, int thflags);
+boolean_t
+        tcp_tso_pullup(struct mbuf **mp, int hoff, struct ip **ip, int *iphlen,
+               struct tcphdr **th, int *thoff);
 void    syncache_init(void);
 void    syncache_unreach(struct in_conninfo *, struct tcphdr *);
 int     syncache_expand(struct in_conninfo *, struct tcphdr *,
index 2d5f8fb..2040ff1 100644 (file)
@@ -174,6 +174,7 @@ struct pkthdr {
        uint16_t hash;                  /* packet hash */
 
        uint16_t wlan_seqno;            /* IEEE 802.11 seq no. */
+       uint16_t segsz;                 /* TSO segment size */
 };
 
 /*
@@ -279,6 +280,7 @@ struct mbuf {
                                                 * NB: This flag is only used
                                                 * by IP defragmenter.
                                                 */
+#define CSUM_TSO               0x2000          /* will do TCP segmentation */
 
 #define        CSUM_DELAY_DATA         (CSUM_TCP | CSUM_UDP)
 #define        CSUM_DELAY_IP           (CSUM_IP)       /* XXX add ipv6 here too? */