diff options
author | Sridhar Samudrala <sri@us.ibm.com> | 2009-07-09 04:10:04 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2009-07-12 17:29:29 -0400 |
commit | ba73542585a4a3c8a708f502e62e6e63dd74b66c (patch) | |
tree | 5dbb997e5f2f0eeb1cbc03000f8121b322b254cd /net | |
parent | 7ea2f2c5a66e4e9a8d96296ac47ad895c467ee1d (diff) |
udpv6: Handle large incoming UDP/IPv6 packets and support software UFO
- validate and forward GSO UDP/IPv6 packets from untrusted sources.
- do software UFO if the outgoing device doesn't support UFO.
Signed-off-by: Sridhar Samudrala <sri@us.ibm.com>
Acked-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net')
-rw-r--r-- | net/ipv6/af_inet6.c | 20 | ||||
-rw-r--r-- | net/ipv6/udp.c | 93 |
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 | ||
813 | out: | 827 | out: |
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 | ||
1081 | static 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 | |||
1100 | static 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 | |||
1168 | out: | ||
1169 | return segs; | ||
1170 | } | ||
1171 | |||
1081 | static struct inet6_protocol udpv6_protocol = { | 1172 | static 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 | ||