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.c158
1 files changed, 150 insertions, 8 deletions
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
index 33b59bd92c4d..164040613c2e 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,13 +722,20 @@ 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);
727 if (err) {
728 if (err == -ENOBUFS && !inet6_sk(sk)->recverr) {
729 UDP6_INC_STATS_USER(sock_net(sk),
730 UDP_MIB_SNDBUFERRORS, is_udplite);
731 err = 0;
732 }
733 } else
734 UDP6_INC_STATS_USER(sock_net(sk),
735 UDP_MIB_OUTDATAGRAMS, is_udplite);
681out: 736out:
682 up->len = 0; 737 up->len = 0;
683 up->pending = 0; 738 up->pending = 0;
684 if (!err)
685 UDP6_INC_STATS_USER(sock_net(sk),
686 UDP_MIB_OUTDATAGRAMS, is_udplite);
687 return err; 739 return err;
688} 740}
689 741
@@ -900,11 +952,8 @@ do_udp_sendmsg:
900 hlimit = ip6_dst_hoplimit(dst); 952 hlimit = ip6_dst_hoplimit(dst);
901 } 953 }
902 954
903 if (tclass < 0) { 955 if (tclass < 0)
904 tclass = np->tclass; 956 tclass = np->tclass;
905 if (tclass < 0)
906 tclass = 0;
907 }
908 957
909 if (msg->msg_flags&MSG_CONFIRM) 958 if (msg->msg_flags&MSG_CONFIRM)
910 goto do_confirm; 959 goto do_confirm;
@@ -1032,9 +1081,102 @@ int compat_udpv6_getsockopt(struct sock *sk, int level, int optname,
1032} 1081}
1033#endif 1082#endif
1034 1083
1084static int udp6_ufo_send_check(struct sk_buff *skb)
1085{
1086 struct ipv6hdr *ipv6h;
1087 struct udphdr *uh;
1088
1089 if (!pskb_may_pull(skb, sizeof(*uh)))
1090 return -EINVAL;
1091
1092 ipv6h = ipv6_hdr(skb);
1093 uh = udp_hdr(skb);
1094
1095 uh->check = ~csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr, skb->len,
1096 IPPROTO_UDP, 0);
1097 skb->csum_start = skb_transport_header(skb) - skb->head;
1098 skb->csum_offset = offsetof(struct udphdr, check);
1099 skb->ip_summed = CHECKSUM_PARTIAL;
1100 return 0;
1101}
1102
1103static struct sk_buff *udp6_ufo_fragment(struct sk_buff *skb, int features)
1104{
1105 struct sk_buff *segs = ERR_PTR(-EINVAL);
1106 unsigned int mss;
1107 unsigned int unfrag_ip6hlen, unfrag_len;
1108 struct frag_hdr *fptr;
1109 u8 *mac_start, *prevhdr;
1110 u8 nexthdr;
1111 u8 frag_hdr_sz = sizeof(struct frag_hdr);
1112 int offset;
1113 __wsum csum;
1114
1115 mss = skb_shinfo(skb)->gso_size;
1116 if (unlikely(skb->len <= mss))
1117 goto out;
1118
1119 if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {
1120 /* Packet is from an untrusted source, reset gso_segs. */
1121 int type = skb_shinfo(skb)->gso_type;
1122
1123 if (unlikely(type & ~(SKB_GSO_UDP | SKB_GSO_DODGY) ||
1124 !(type & (SKB_GSO_UDP))))
1125 goto out;
1126
1127 skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss);
1128
1129 segs = NULL;
1130 goto out;
1131 }
1132
1133 /* Do software UFO. Complete and fill in the UDP checksum as HW cannot
1134 * do checksum of UDP packets sent as multiple IP fragments.
1135 */
1136 offset = skb->csum_start - skb_headroom(skb);
1137 csum = skb_checksum(skb, offset, skb->len- offset, 0);
1138 offset += skb->csum_offset;
1139 *(__sum16 *)(skb->data + offset) = csum_fold(csum);
1140 skb->ip_summed = CHECKSUM_NONE;
1141
1142 /* Check if there is enough headroom to insert fragment header. */
1143 if ((skb_headroom(skb) < frag_hdr_sz) &&
1144 pskb_expand_head(skb, frag_hdr_sz, 0, GFP_ATOMIC))
1145 goto out;
1146
1147 /* Find the unfragmentable header and shift it left by frag_hdr_sz
1148 * bytes to insert fragment header.
1149 */
1150 unfrag_ip6hlen = ip6_find_1stfragopt(skb, &prevhdr);
1151 nexthdr = *prevhdr;
1152 *prevhdr = NEXTHDR_FRAGMENT;
1153 unfrag_len = skb_network_header(skb) - skb_mac_header(skb) +
1154 unfrag_ip6hlen;
1155 mac_start = skb_mac_header(skb);
1156 memmove(mac_start-frag_hdr_sz, mac_start, unfrag_len);
1157
1158 skb->mac_header -= frag_hdr_sz;
1159 skb->network_header -= frag_hdr_sz;
1160
1161 fptr = (struct frag_hdr *)(skb_network_header(skb) + unfrag_ip6hlen);
1162 fptr->nexthdr = nexthdr;
1163 fptr->reserved = 0;
1164 ipv6_select_ident(fptr);
1165
1166 /* Fragment the skb. ipv6 header and the remaining fields of the
1167 * fragment header are updated in ipv6_gso_segment()
1168 */
1169 segs = skb_segment(skb, features);
1170
1171out:
1172 return segs;
1173}
1174
1035static struct inet6_protocol udpv6_protocol = { 1175static struct inet6_protocol udpv6_protocol = {
1036 .handler = udpv6_rcv, 1176 .handler = udpv6_rcv,
1037 .err_handler = udpv6_err, 1177 .err_handler = udpv6_err,
1178 .gso_send_check = udp6_ufo_send_check,
1179 .gso_segment = udp6_ufo_fragment,
1038 .flags = INET6_PROTO_NOPOLICY|INET6_PROTO_FINAL, 1180 .flags = INET6_PROTO_NOPOLICY|INET6_PROTO_FINAL,
1039}; 1181};
1040 1182