aboutsummaryrefslogtreecommitdiffstats
path: root/net/ipv6/udp.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/ipv6/udp.c')
-rw-r--r--net/ipv6/udp.c141
1 files changed, 140 insertions, 1 deletions
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
index 33b59bd92c4d..d79fa6724451 100644
--- a/net/ipv6/udp.c
+++ b/net/ipv6/udp.c
@@ -638,6 +638,47 @@ static void udp_v6_flush_pending_frames(struct sock *sk)
638 } 638 }
639} 639}
640 640
641/**
642 * udp6_hwcsum_outgoing - handle outgoing HW checksumming
643 * @sk: socket we are sending on
644 * @skb: sk_buff containing the filled-in UDP header
645 * (checksum field must be zeroed out)
646 */
647static void udp6_hwcsum_outgoing(struct sock *sk, struct sk_buff *skb,
648 const struct in6_addr *saddr,
649 const struct in6_addr *daddr, int len)
650{
651 unsigned int offset;
652 struct udphdr *uh = udp_hdr(skb);
653 __wsum csum = 0;
654
655 if (skb_queue_len(&sk->sk_write_queue) == 1) {
656 /* Only one fragment on the socket. */
657 skb->csum_start = skb_transport_header(skb) - skb->head;
658 skb->csum_offset = offsetof(struct udphdr, check);
659 uh->check = ~csum_ipv6_magic(saddr, daddr, len, IPPROTO_UDP, 0);
660 } else {
661 /*
662 * HW-checksum won't work as there are two or more
663 * fragments on the socket so that all csums of sk_buffs
664 * should be together
665 */
666 offset = skb_transport_offset(skb);
667 skb->csum = skb_checksum(skb, offset, skb->len - offset, 0);
668
669 skb->ip_summed = CHECKSUM_NONE;
670
671 skb_queue_walk(&sk->sk_write_queue, skb) {
672 csum = csum_add(csum, skb->csum);
673 }
674
675 uh->check = csum_ipv6_magic(saddr, daddr, len, IPPROTO_UDP,
676 csum);
677 if (uh->check == 0)
678 uh->check = CSUM_MANGLED_0;
679 }
680}
681
641/* 682/*
642 * Sending 683 * Sending
643 */ 684 */
@@ -668,7 +709,11 @@ static int udp_v6_push_pending_frames(struct sock *sk)
668 709
669 if (is_udplite) 710 if (is_udplite)
670 csum = udplite_csum_outgoing(sk, skb); 711 csum = udplite_csum_outgoing(sk, skb);
671 else 712 else if (skb->ip_summed == CHECKSUM_PARTIAL) { /* UDP hardware csum */
713 udp6_hwcsum_outgoing(sk, skb, &fl->fl6_src, &fl->fl6_dst,
714 up->len);
715 goto send;
716 } else
672 csum = udp_csum_outgoing(sk, skb); 717 csum = udp_csum_outgoing(sk, skb);
673 718
674 /* add protocol-dependent pseudo-header */ 719 /* add protocol-dependent pseudo-header */
@@ -677,6 +722,7 @@ static int udp_v6_push_pending_frames(struct sock *sk)
677 if (uh->check == 0) 722 if (uh->check == 0)
678 uh->check = CSUM_MANGLED_0; 723 uh->check = CSUM_MANGLED_0;
679 724
725send:
680 err = ip6_push_pending_frames(sk); 726 err = ip6_push_pending_frames(sk);
681out: 727out:
682 up->len = 0; 728 up->len = 0;
@@ -1032,9 +1078,102 @@ int compat_udpv6_getsockopt(struct sock *sk, int level, int optname,
1032} 1078}
1033#endif 1079#endif
1034 1080
1081static int udp6_ufo_send_check(struct sk_buff *skb)
1082{
1083 struct ipv6hdr *ipv6h;
1084 struct udphdr *uh;
1085
1086 if (!pskb_may_pull(skb, sizeof(*uh)))
1087 return -EINVAL;
1088
1089 ipv6h = ipv6_hdr(skb);
1090 uh = udp_hdr(skb);
1091
1092 uh->check = ~csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr, skb->len,
1093 IPPROTO_UDP, 0);
1094 skb->csum_start = skb_transport_header(skb) - skb->head;
1095 skb->csum_offset = offsetof(struct udphdr, check);
1096 skb->ip_summed = CHECKSUM_PARTIAL;
1097 return 0;
1098}
1099
1100static struct sk_buff *udp6_ufo_fragment(struct sk_buff *skb, int features)
1101{
1102 struct sk_buff *segs = ERR_PTR(-EINVAL);
1103 unsigned int mss;
1104 unsigned int unfrag_ip6hlen, unfrag_len;
1105 struct frag_hdr *fptr;
1106 u8 *mac_start, *prevhdr;
1107 u8 nexthdr;
1108 u8 frag_hdr_sz = sizeof(struct frag_hdr);
1109 int offset;
1110 __wsum csum;
1111
1112 mss = skb_shinfo(skb)->gso_size;
1113 if (unlikely(skb->len <= mss))
1114 goto out;
1115
1116 if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {
1117 /* Packet is from an untrusted source, reset gso_segs. */
1118 int type = skb_shinfo(skb)->gso_type;
1119
1120 if (unlikely(type & ~(SKB_GSO_UDP | SKB_GSO_DODGY) ||
1121 !(type & (SKB_GSO_UDP))))
1122 goto out;
1123
1124 skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss);
1125
1126 segs = NULL;
1127 goto out;
1128 }
1129
1130 /* Do software UFO. Complete and fill in the UDP checksum as HW cannot
1131 * do checksum of UDP packets sent as multiple IP fragments.
1132 */
1133 offset = skb->csum_start - skb_headroom(skb);
1134 csum = skb_checksum(skb, offset, skb->len- offset, 0);
1135 offset += skb->csum_offset;
1136 *(__sum16 *)(skb->data + offset) = csum_fold(csum);
1137 skb->ip_summed = CHECKSUM_NONE;
1138
1139 /* Check if there is enough headroom to insert fragment header. */
1140 if ((skb_headroom(skb) < frag_hdr_sz) &&
1141 pskb_expand_head(skb, frag_hdr_sz, 0, GFP_ATOMIC))
1142 goto out;
1143
1144 /* Find the unfragmentable header and shift it left by frag_hdr_sz
1145 * bytes to insert fragment header.
1146 */
1147 unfrag_ip6hlen = ip6_find_1stfragopt(skb, &prevhdr);
1148 nexthdr = *prevhdr;
1149 *prevhdr = NEXTHDR_FRAGMENT;
1150 unfrag_len = skb_network_header(skb) - skb_mac_header(skb) +
1151 unfrag_ip6hlen;
1152 mac_start = skb_mac_header(skb);
1153 memmove(mac_start-frag_hdr_sz, mac_start, unfrag_len);
1154
1155 skb->mac_header -= frag_hdr_sz;
1156 skb->network_header -= frag_hdr_sz;
1157
1158 fptr = (struct frag_hdr *)(skb_network_header(skb) + unfrag_ip6hlen);
1159 fptr->nexthdr = nexthdr;
1160 fptr->reserved = 0;
1161 ipv6_select_ident(fptr);
1162
1163 /* Fragment the skb. ipv6 header and the remaining fields of the
1164 * fragment header are updated in ipv6_gso_segment()
1165 */
1166 segs = skb_segment(skb, features);
1167
1168out:
1169 return segs;
1170}
1171
1035static struct inet6_protocol udpv6_protocol = { 1172static struct inet6_protocol udpv6_protocol = {
1036 .handler = udpv6_rcv, 1173 .handler = udpv6_rcv,
1037 .err_handler = udpv6_err, 1174 .err_handler = udpv6_err,
1175 .gso_send_check = udp6_ufo_send_check,
1176 .gso_segment = udp6_ufo_fragment,
1038 .flags = INET6_PROTO_NOPOLICY|INET6_PROTO_FINAL, 1177 .flags = INET6_PROTO_NOPOLICY|INET6_PROTO_FINAL,
1039}; 1178};
1040 1179