aboutsummaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
Diffstat (limited to 'net')
-rw-r--r--net/ipv6/af_inet6.c20
-rw-r--r--net/ipv6/udp.c93
2 files changed, 110 insertions, 3 deletions
diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c
index caa0278d30a9..bf85d5f97032 100644
--- a/net/ipv6/af_inet6.c
+++ b/net/ipv6/af_inet6.c
@@ -772,6 +772,11 @@ static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb, int features)
772 struct sk_buff *segs = ERR_PTR(-EINVAL); 772 struct sk_buff *segs = ERR_PTR(-EINVAL);
773 struct ipv6hdr *ipv6h; 773 struct ipv6hdr *ipv6h;
774 struct inet6_protocol *ops; 774 struct inet6_protocol *ops;
775 int proto;
776 struct frag_hdr *fptr;
777 unsigned int unfrag_ip6hlen;
778 u8 *prevhdr;
779 int offset = 0;
775 780
776 if (!(features & NETIF_F_V6_CSUM)) 781 if (!(features & NETIF_F_V6_CSUM))
777 features &= ~NETIF_F_SG; 782 features &= ~NETIF_F_SG;
@@ -791,10 +796,9 @@ static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb, int features)
791 __skb_pull(skb, sizeof(*ipv6h)); 796 __skb_pull(skb, sizeof(*ipv6h));
792 segs = ERR_PTR(-EPROTONOSUPPORT); 797 segs = ERR_PTR(-EPROTONOSUPPORT);
793 798
799 proto = ipv6_gso_pull_exthdrs(skb, ipv6h->nexthdr);
794 rcu_read_lock(); 800 rcu_read_lock();
795 ops = rcu_dereference(inet6_protos[ 801 ops = rcu_dereference(inet6_protos[proto]);
796 ipv6_gso_pull_exthdrs(skb, ipv6h->nexthdr)]);
797
798 if (likely(ops && ops->gso_segment)) { 802 if (likely(ops && ops->gso_segment)) {
799 skb_reset_transport_header(skb); 803 skb_reset_transport_header(skb);
800 segs = ops->gso_segment(skb, features); 804 segs = ops->gso_segment(skb, features);
@@ -808,6 +812,16 @@ static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb, int features)
808 ipv6h = ipv6_hdr(skb); 812 ipv6h = ipv6_hdr(skb);
809 ipv6h->payload_len = htons(skb->len - skb->mac_len - 813 ipv6h->payload_len = htons(skb->len - skb->mac_len -
810 sizeof(*ipv6h)); 814 sizeof(*ipv6h));
815 if (proto == IPPROTO_UDP) {
816 unfrag_ip6hlen = ip6_find_1stfragopt(skb, &prevhdr);
817 fptr = (struct frag_hdr *)(skb_network_header(skb) +
818 unfrag_ip6hlen);
819 fptr->frag_off = htons(offset);
820 if (skb->next != NULL)
821 fptr->frag_off |= htons(IP6_MF);
822 offset += (ntohs(ipv6h->payload_len) -
823 sizeof(struct frag_hdr));
824 }
811 } 825 }
812 826
813out: 827out:
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