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.c93
1 files changed, 93 insertions, 0 deletions
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
index f31b1b9b0e0e..d79fa6724451 100644
--- a/net/ipv6/udp.c
+++ b/net/ipv6/udp.c
@@ -1078,9 +1078,102 @@ int compat_udpv6_getsockopt(struct sock *sk, int level, int optname,
1078} 1078}
1079#endif 1079#endif
1080 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
1081static struct inet6_protocol udpv6_protocol = { 1172static struct inet6_protocol udpv6_protocol = {
1082 .handler = udpv6_rcv, 1173 .handler = udpv6_rcv,
1083 .err_handler = udpv6_err, 1174 .err_handler = udpv6_err,
1175 .gso_send_check = udp6_ufo_send_check,
1176 .gso_segment = udp6_ufo_fragment,
1084 .flags = INET6_PROTO_NOPOLICY|INET6_PROTO_FINAL, 1177 .flags = INET6_PROTO_NOPOLICY|INET6_PROTO_FINAL,
1085}; 1178};
1086 1179