aboutsummaryrefslogtreecommitdiff
path: root/sys/netinet/ip_output.c
diff options
context:
space:
mode:
authorJonathan Lemon <jlemon@FreeBSD.org>2000-03-27 19:14:27 +0000
committerJonathan Lemon <jlemon@FreeBSD.org>2000-03-27 19:14:27 +0000
commitdb4f9cc70389b2004594fea6f910e5091855ddf8 (patch)
tree18fe751adcccc8aec961339214bd6bf346ee66d4 /sys/netinet/ip_output.c
parent07b065a591b026c05dbb40e715987592c28db5cd (diff)
downloadsrc-db4f9cc70389b2004594fea6f910e5091855ddf8.tar.gz
src-db4f9cc70389b2004594fea6f910e5091855ddf8.zip
Add support for offloading IP/TCP/UDP checksums to NIC hardware which
supports them.
Notes
Notes: svn path=/head/; revision=58698
Diffstat (limited to 'sys/netinet/ip_output.c')
-rw-r--r--sys/netinet/ip_output.c104
1 files changed, 87 insertions, 17 deletions
diff --git a/sys/netinet/ip_output.c b/sys/netinet/ip_output.c
index ee361ce401c9..b44765f909a3 100644
--- a/sys/netinet/ip_output.c
+++ b/sys/netinet/ip_output.c
@@ -96,6 +96,7 @@ static MALLOC_DEFINE(M_IPMOPTS, "ip_moptions", "internet multicast options");
u_short ip_id;
+static void in_delayed_cksum(struct mbuf *m);
static struct mbuf *ip_insertoptions __P((struct mbuf *, struct mbuf *, int *));
static void ip_mloopback
__P((struct ifnet *, struct mbuf *, struct sockaddr_in *, int));
@@ -132,7 +133,7 @@ ip_output(m0, opt, ro, flags, imo)
int len, off, error = 0;
struct sockaddr_in *dst;
struct in_ifaddr *ia;
- int isbroadcast;
+ int isbroadcast, sw_csum;
#ifdef IPSEC
struct route iproute;
struct socket *so = NULL;
@@ -692,6 +693,15 @@ pass:
state.ro = ro;
state.dst = (struct sockaddr *)dst;
+ /*
+ * XXX
+ * delayed checksums are not currently compatible with IPsec
+ */
+ if (m->m_pkthdr.csum_flags & CSUM_DELAY_DATA) {
+ in_delayed_cksum(m);
+ m->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA;
+ }
+
error = ipsec4_output(&state, sp, flags);
m = state.m;
@@ -754,17 +764,29 @@ pass:
skip_ipsec:
#endif /*IPSEC*/
+ sw_csum = m->m_pkthdr.csum_flags | CSUM_IP;
+ m->m_pkthdr.csum_flags = sw_csum & ifp->if_hwassist;
+ sw_csum &= ~ifp->if_hwassist;
+ if (sw_csum & CSUM_DELAY_DATA) {
+ in_delayed_cksum(m);
+ sw_csum &= ~CSUM_DELAY_DATA;
+ }
+
/*
- * If small enough for interface, can just send directly.
+ * If small enough for interface, or the interface will take
+ * care of the fragmentation for us, can just send directly.
*/
- if ((u_short)ip->ip_len <= ifp->if_mtu) {
+ if ((u_short)ip->ip_len <= ifp->if_mtu ||
+ ifp->if_hwassist & CSUM_FRAGMENT) {
ip->ip_len = htons((u_short)ip->ip_len);
ip->ip_off = htons((u_short)ip->ip_off);
ip->ip_sum = 0;
- if (ip->ip_vhl == IP_VHL_BORING) {
- ip->ip_sum = in_cksum_hdr(ip);
- } else {
- ip->ip_sum = in_cksum(m, hlen);
+ if (sw_csum & CSUM_DELAY_IP) {
+ if (ip->ip_vhl == IP_VHL_BORING) {
+ ip->ip_sum = in_cksum_hdr(ip);
+ } else {
+ ip->ip_sum = in_cksum(m, hlen);
+ }
}
error = (*ifp->if_output)(ifp, m,
(struct sockaddr *)dst, ro->ro_rt);
@@ -797,9 +819,20 @@ skip_ipsec:
goto bad;
}
+ /*
+ * if the interface will not calculate checksums on
+ * fragmented packets, then do it here.
+ */
+ if (m->m_pkthdr.csum_flags & CSUM_DELAY_DATA &&
+ (ifp->if_hwassist & CSUM_IP_FRAGS) == 0) {
+ in_delayed_cksum(m);
+ m->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA;
+ }
+
{
int mhlen, firstlen = len;
struct mbuf **mnext = &m->m_nextpkt;
+ int nfrags = 1;
/*
* Loop through length of segment after first fragment,
@@ -814,7 +847,7 @@ skip_ipsec:
ipstat.ips_odropped++;
goto sendorfree;
}
- m->m_flags |= (m0->m_flags & M_MCAST);
+ m->m_flags |= (m0->m_flags & M_MCAST) | M_FRAG;
m->m_data += max_linkhdr;
mhip = mtod(m, struct ip *);
*mhip = *ip;
@@ -840,17 +873,27 @@ skip_ipsec:
}
m->m_pkthdr.len = mhlen + len;
m->m_pkthdr.rcvif = (struct ifnet *)0;
+ m->m_pkthdr.csum_flags = m0->m_pkthdr.csum_flags;
mhip->ip_off = htons((u_short)mhip->ip_off);
mhip->ip_sum = 0;
- if (mhip->ip_vhl == IP_VHL_BORING) {
- mhip->ip_sum = in_cksum_hdr(mhip);
- } else {
- mhip->ip_sum = in_cksum(m, mhlen);
+ if (sw_csum & CSUM_DELAY_IP) {
+ if (mhip->ip_vhl == IP_VHL_BORING) {
+ mhip->ip_sum = in_cksum_hdr(mhip);
+ } else {
+ mhip->ip_sum = in_cksum(m, mhlen);
+ }
}
*mnext = m;
mnext = &m->m_nextpkt;
- ipstat.ips_ofragments++;
+ nfrags++;
}
+ ipstat.ips_ofragments += nfrags;
+
+ /* set first/last markers for fragment chain */
+ m->m_flags |= M_LASTFRAG;
+ m0->m_flags |= M_FIRSTFRAG | M_FRAG;
+ m0->m_pkthdr.csum_data = nfrags;
+
/*
* Update first fragment by trimming what's been copied out
* and updating header, then send each fragment (in order).
@@ -861,10 +904,12 @@ skip_ipsec:
ip->ip_len = htons((u_short)m->m_pkthdr.len);
ip->ip_off = htons((u_short)(ip->ip_off | IP_MF));
ip->ip_sum = 0;
- if (ip->ip_vhl == IP_VHL_BORING) {
- ip->ip_sum = in_cksum_hdr(ip);
- } else {
- ip->ip_sum = in_cksum(m, hlen);
+ if (sw_csum & CSUM_DELAY_IP) {
+ if (ip->ip_vhl == IP_VHL_BORING) {
+ ip->ip_sum = in_cksum_hdr(ip);
+ } else {
+ ip->ip_sum = in_cksum(m, hlen);
+ }
}
sendorfree:
for (m = m0; m; m = m0) {
@@ -898,6 +943,31 @@ bad:
goto done;
}
+static void
+in_delayed_cksum(struct mbuf *m)
+{
+ struct ip *ip;
+ u_short csum, offset;
+
+ ip = mtod(m, struct ip *);
+ offset = IP_VHL_HL(ip->ip_vhl) << 2 ;
+ csum = in_cksum_skip(m, ip->ip_len, offset);
+ offset += m->m_pkthdr.csum_data; /* checksum offset */
+
+ if (offset + sizeof(u_short) > m->m_len) {
+ printf("delayed m_pullup, m->len: %d off: %d p: %d\n",
+ m->m_len, offset, ip->ip_p);
+ /*
+ * XXX
+ * this shouldn't happen, but if it does, the
+ * correct behavior may be to insert the checksum
+ * in the existing chain instead of rearranging it.
+ */
+ m = m_pullup(m, offset + sizeof(u_short));
+ }
+ *(u_short *)(m->m_data + offset) = csum;
+}
+
/*
* Insert IP options into preformed packet.
* Adjust IP destination as required for IP source routing,