Commit ffcebb163c6ddba11abd2e8aabc7a8a88982e4f4
Committed by
David S. Miller
1 parent
09a2c3c0d3
Exists in
master
and in
7 other branches
l2tp: fix UDP checksum support
The pppol2tp driver has had broken UDP checksum code for a long time. This patch fixes it. If UDP checksums are enabled in the tunnel's UDP socket, the L2TP driver now properly validates the checksum on receive and fills in the checksum on transmit. If the network device has hardware checksum support and is enabled, it is used instead of generating/checking the checksum in software. Signed-off-by: James Chapman <jchapman@katalix.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Showing 1 changed file with 82 additions and 12 deletions Side-by-side Diff
drivers/net/pppol2tp.c
... | ... | @@ -489,6 +489,30 @@ |
489 | 489 | spin_unlock_bh(&session->reorder_q.lock); |
490 | 490 | } |
491 | 491 | |
492 | +static inline int pppol2tp_verify_udp_checksum(struct sock *sk, | |
493 | + struct sk_buff *skb) | |
494 | +{ | |
495 | + struct udphdr *uh = udp_hdr(skb); | |
496 | + u16 ulen = ntohs(uh->len); | |
497 | + struct inet_sock *inet; | |
498 | + __wsum psum; | |
499 | + | |
500 | + if (sk->sk_no_check || skb_csum_unnecessary(skb) || !uh->check) | |
501 | + return 0; | |
502 | + | |
503 | + inet = inet_sk(sk); | |
504 | + psum = csum_tcpudp_nofold(inet->saddr, inet->daddr, ulen, | |
505 | + IPPROTO_UDP, 0); | |
506 | + | |
507 | + if ((skb->ip_summed == CHECKSUM_COMPLETE) && | |
508 | + !csum_fold(csum_add(psum, skb->csum))) | |
509 | + return 0; | |
510 | + | |
511 | + skb->csum = psum; | |
512 | + | |
513 | + return __skb_checksum_complete(skb); | |
514 | +} | |
515 | + | |
492 | 516 | /* Internal receive frame. Do the real work of receiving an L2TP data frame |
493 | 517 | * here. The skb is not on a list when we get here. |
494 | 518 | * Returns 0 if the packet was a data packet and was successfully passed on. |
... | ... | @@ -509,6 +533,9 @@ |
509 | 533 | if (tunnel == NULL) |
510 | 534 | goto no_tunnel; |
511 | 535 | |
536 | + if (tunnel->sock && pppol2tp_verify_udp_checksum(tunnel->sock, skb)) | |
537 | + goto discard_bad_csum; | |
538 | + | |
512 | 539 | /* UDP always verifies the packet length. */ |
513 | 540 | __skb_pull(skb, sizeof(struct udphdr)); |
514 | 541 | |
... | ... | @@ -725,6 +752,14 @@ |
725 | 752 | |
726 | 753 | return 0; |
727 | 754 | |
755 | +discard_bad_csum: | |
756 | + LIMIT_NETDEBUG("%s: UDP: bad checksum\n", tunnel->name); | |
757 | + UDP_INC_STATS_USER(&init_net, UDP_MIB_INERRORS, 0); | |
758 | + tunnel->stats.rx_errors++; | |
759 | + kfree_skb(skb); | |
760 | + | |
761 | + return 0; | |
762 | + | |
728 | 763 | error: |
729 | 764 | /* Put UDP header back */ |
730 | 765 | __skb_push(skb, sizeof(struct udphdr)); |
... | ... | @@ -851,7 +886,7 @@ |
851 | 886 | static const unsigned char ppph[2] = { 0xff, 0x03 }; |
852 | 887 | struct sock *sk = sock->sk; |
853 | 888 | struct inet_sock *inet; |
854 | - __wsum csum = 0; | |
889 | + __wsum csum; | |
855 | 890 | struct sk_buff *skb; |
856 | 891 | int error; |
857 | 892 | int hdr_len; |
... | ... | @@ -859,6 +894,8 @@ |
859 | 894 | struct pppol2tp_tunnel *tunnel; |
860 | 895 | struct udphdr *uh; |
861 | 896 | unsigned int len; |
897 | + struct sock *sk_tun; | |
898 | + u16 udp_len; | |
862 | 899 | |
863 | 900 | error = -ENOTCONN; |
864 | 901 | if (sock_flag(sk, SOCK_DEAD) || !(sk->sk_state & PPPOX_CONNECTED)) |
... | ... | @@ -870,7 +907,8 @@ |
870 | 907 | if (session == NULL) |
871 | 908 | goto error; |
872 | 909 | |
873 | - tunnel = pppol2tp_sock_to_tunnel(session->tunnel_sock); | |
910 | + sk_tun = session->tunnel_sock; | |
911 | + tunnel = pppol2tp_sock_to_tunnel(sk_tun); | |
874 | 912 | if (tunnel == NULL) |
875 | 913 | goto error_put_sess; |
876 | 914 | |
877 | 915 | |
... | ... | @@ -893,11 +931,12 @@ |
893 | 931 | skb_reset_transport_header(skb); |
894 | 932 | |
895 | 933 | /* Build UDP header */ |
896 | - inet = inet_sk(session->tunnel_sock); | |
934 | + inet = inet_sk(sk_tun); | |
935 | + udp_len = hdr_len + sizeof(ppph) + total_len; | |
897 | 936 | uh = (struct udphdr *) skb->data; |
898 | 937 | uh->source = inet->sport; |
899 | 938 | uh->dest = inet->dport; |
900 | - uh->len = htons(hdr_len + sizeof(ppph) + total_len); | |
939 | + uh->len = htons(udp_len); | |
901 | 940 | uh->check = 0; |
902 | 941 | skb_put(skb, sizeof(struct udphdr)); |
903 | 942 | |
... | ... | @@ -919,8 +958,22 @@ |
919 | 958 | skb_put(skb, total_len); |
920 | 959 | |
921 | 960 | /* Calculate UDP checksum if configured to do so */ |
922 | - if (session->tunnel_sock->sk_no_check != UDP_CSUM_NOXMIT) | |
923 | - csum = udp_csum_outgoing(sk, skb); | |
961 | + if (sk_tun->sk_no_check == UDP_CSUM_NOXMIT) | |
962 | + skb->ip_summed = CHECKSUM_NONE; | |
963 | + else if (!(skb->dst->dev->features & NETIF_F_V4_CSUM)) { | |
964 | + skb->ip_summed = CHECKSUM_COMPLETE; | |
965 | + csum = skb_checksum(skb, 0, udp_len, 0); | |
966 | + uh->check = csum_tcpudp_magic(inet->saddr, inet->daddr, | |
967 | + udp_len, IPPROTO_UDP, csum); | |
968 | + if (uh->check == 0) | |
969 | + uh->check = CSUM_MANGLED_0; | |
970 | + } else { | |
971 | + skb->ip_summed = CHECKSUM_PARTIAL; | |
972 | + skb->csum_start = skb_transport_header(skb) - skb->head; | |
973 | + skb->csum_offset = offsetof(struct udphdr, check); | |
974 | + uh->check = ~csum_tcpudp_magic(inet->saddr, inet->daddr, | |
975 | + udp_len, IPPROTO_UDP, 0); | |
976 | + } | |
924 | 977 | |
925 | 978 | /* Debug */ |
926 | 979 | if (session->send_seq) |
927 | 980 | |
... | ... | @@ -1008,13 +1061,14 @@ |
1008 | 1061 | struct sock *sk = (struct sock *) chan->private; |
1009 | 1062 | struct sock *sk_tun; |
1010 | 1063 | int hdr_len; |
1064 | + u16 udp_len; | |
1011 | 1065 | struct pppol2tp_session *session; |
1012 | 1066 | struct pppol2tp_tunnel *tunnel; |
1013 | 1067 | int rc; |
1014 | 1068 | int headroom; |
1015 | 1069 | int data_len = skb->len; |
1016 | 1070 | struct inet_sock *inet; |
1017 | - __wsum csum = 0; | |
1071 | + __wsum csum; | |
1018 | 1072 | struct udphdr *uh; |
1019 | 1073 | unsigned int len; |
1020 | 1074 | int old_headroom; |
... | ... | @@ -1060,6 +1114,8 @@ |
1060 | 1114 | /* Setup L2TP header */ |
1061 | 1115 | pppol2tp_build_l2tp_header(session, __skb_push(skb, hdr_len)); |
1062 | 1116 | |
1117 | + udp_len = sizeof(struct udphdr) + hdr_len + sizeof(ppph) + data_len; | |
1118 | + | |
1063 | 1119 | /* Setup UDP header */ |
1064 | 1120 | inet = inet_sk(sk_tun); |
1065 | 1121 | __skb_push(skb, sizeof(*uh)); |
1066 | 1122 | |
... | ... | @@ -1067,13 +1123,9 @@ |
1067 | 1123 | uh = udp_hdr(skb); |
1068 | 1124 | uh->source = inet->sport; |
1069 | 1125 | uh->dest = inet->dport; |
1070 | - uh->len = htons(sizeof(struct udphdr) + hdr_len + sizeof(ppph) + data_len); | |
1126 | + uh->len = htons(udp_len); | |
1071 | 1127 | uh->check = 0; |
1072 | 1128 | |
1073 | - /* *BROKEN* Calculate UDP checksum if configured to do so */ | |
1074 | - if (sk_tun->sk_no_check != UDP_CSUM_NOXMIT) | |
1075 | - csum = udp_csum_outgoing(sk_tun, skb); | |
1076 | - | |
1077 | 1129 | /* Debug */ |
1078 | 1130 | if (session->send_seq) |
1079 | 1131 | PRINTK(session->debug, PPPOL2TP_MSG_DATA, KERN_DEBUG, |
... | ... | @@ -1107,6 +1159,24 @@ |
1107 | 1159 | dst_release(skb->dst); |
1108 | 1160 | skb->dst = dst_clone(__sk_dst_get(sk_tun)); |
1109 | 1161 | pppol2tp_skb_set_owner_w(skb, sk_tun); |
1162 | + | |
1163 | + /* Calculate UDP checksum if configured to do so */ | |
1164 | + if (sk_tun->sk_no_check == UDP_CSUM_NOXMIT) | |
1165 | + skb->ip_summed = CHECKSUM_NONE; | |
1166 | + else if (!(skb->dst->dev->features & NETIF_F_V4_CSUM)) { | |
1167 | + skb->ip_summed = CHECKSUM_COMPLETE; | |
1168 | + csum = skb_checksum(skb, 0, udp_len, 0); | |
1169 | + uh->check = csum_tcpudp_magic(inet->saddr, inet->daddr, | |
1170 | + udp_len, IPPROTO_UDP, csum); | |
1171 | + if (uh->check == 0) | |
1172 | + uh->check = CSUM_MANGLED_0; | |
1173 | + } else { | |
1174 | + skb->ip_summed = CHECKSUM_PARTIAL; | |
1175 | + skb->csum_start = skb_transport_header(skb) - skb->head; | |
1176 | + skb->csum_offset = offsetof(struct udphdr, check); | |
1177 | + uh->check = ~csum_tcpudp_magic(inet->saddr, inet->daddr, | |
1178 | + udp_len, IPPROTO_UDP, 0); | |
1179 | + } | |
1110 | 1180 | |
1111 | 1181 | /* Queue the packet to IP for output */ |
1112 | 1182 | len = skb->len; |