From: Seonah Moon Date: Mon, 23 Sep 2019 05:33:26 +0000 (+0900) Subject: Imported Upstream version 1.4 X-Git-Tag: upstream/1.4~2 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=c9fb17e1cc63313b69dde33ee40ba89cb7ea2662;p=platform%2Fupstream%2Fclat.git Imported Upstream version 1.4 Change-Id: I230cabc6167ba04dce75ddd7a7a465088e8d1cb5 --- diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..5f2fe63 --- /dev/null +++ b/Android.mk @@ -0,0 +1,47 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:=clatd.c dump.c checksum.c translate.c icmp.c ipv4.c ipv6.c config.c dns64.c logging.c getaddr.c netlink_callbacks.c netlink_msg.c setif.c mtu.c tun.c ring.c + +LOCAL_CFLAGS := -Wall -Werror -Wunused-parameter +LOCAL_C_INCLUDES := external/libnl/include bionic/libc/dns/include +LOCAL_STATIC_LIBRARIES := libnl +LOCAL_SHARED_LIBRARIES := libcutils liblog libnetutils + +# The clat daemon. +LOCAL_MODULE := clatd + +include $(BUILD_EXECUTABLE) + + +# The configuration file. +include $(CLEAR_VARS) + +LOCAL_MODULE := clatd.conf +LOCAL_MODULE_CLASS := ETC +LOCAL_MODULE_PATH := $(TARGET_OUT)/etc +LOCAL_SRC_FILES := $(LOCAL_MODULE) + +include $(BUILD_PREBUILT) + + +# Unit tests. +include $(CLEAR_VARS) + +LOCAL_MODULE := clatd_test +LOCAL_CFLAGS := -Wall -Werror -Wunused-parameter +LOCAL_SRC_FILES := clatd_test.cpp checksum.c translate.c icmp.c ipv4.c ipv6.c logging.c config.c tun.c +LOCAL_MODULE_TAGS := eng tests +LOCAL_SHARED_LIBRARIES := liblog + +include $(BUILD_NATIVE_TEST) + +# Microbenchmark. +include $(CLEAR_VARS) + +LOCAL_MODULE := clatd_microbenchmark +LOCAL_CFLAGS := -Wall -Werror -Wunused-parameter +LOCAL_SRC_FILES := clatd_microbenchmark.c checksum.c tun.c +LOCAL_MODULE_TAGS := eng tests + +include $(BUILD_NATIVE_TEST) diff --git a/BUGS b/BUGS new file mode 100644 index 0000000..70aeb9f --- /dev/null +++ b/BUGS @@ -0,0 +1,5 @@ +known problems/assumptions: + - does not handle protocols other than ICMP, UDP, TCP and GRE + - assumes the handset has its own (routed) /64 ipv6 subnet + - assumes the /128 ipv6 subnet it generates can use the nat64 gateway + - assumes the nat64 gateway has the ipv4 address in the last 32 bits of the ipv6 address (that it uses a /96 plat subnet) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..5943b54 --- /dev/null +++ b/NOTICE @@ -0,0 +1,189 @@ + Copyright (c) 2010-2012, Daniel Drown + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/checksum.c b/checksum.c new file mode 100644 index 0000000..23a7c02 --- /dev/null +++ b/checksum.c @@ -0,0 +1,142 @@ +/* + * Copyright 2011 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * checksum.c - ipv4/ipv6 checksum calculation + */ +#include +#include +#include +#include +#include +#include +#include + +#include "checksum.h" + +/* function: ip_checksum_add + * adds data to a checksum + * current - the current checksum (or 0 to start a new checksum) + * data - the data to add to the checksum + * len - length of data + */ +uint32_t ip_checksum_add(uint32_t current, const void *data, int len) { + uint32_t checksum = current; + int left = len; + const uint16_t *data_16 = data; + + while(left > 1) { + checksum += *data_16; + data_16++; + left -= 2; + } + if(left) { + checksum += *(uint8_t *)data_16; + } + + return checksum; +} + +/* function: ip_checksum_fold + * folds a 32-bit partial checksum into 16 bits + * temp_sum - sum from ip_checksum_add + * returns: the folded checksum in network byte order + */ +uint16_t ip_checksum_fold(uint32_t temp_sum) { + while(temp_sum > 0xffff) + temp_sum = (temp_sum >> 16) + (temp_sum & 0xFFFF); + + return temp_sum; +} + +/* function: ip_checksum_finish + * folds and closes the checksum + * temp_sum - sum from ip_checksum_add + * returns: a header checksum value in network byte order + */ +uint16_t ip_checksum_finish(uint32_t temp_sum) { + return ~ip_checksum_fold(temp_sum); +} + +/* function: ip_checksum + * combined ip_checksum_add and ip_checksum_finish + * data - data to checksum + * len - length of data + */ +uint16_t ip_checksum(const void *data, int len) { + uint32_t temp_sum; + + temp_sum = ip_checksum_add(0,data,len); + return ip_checksum_finish(temp_sum); +} + +/* function: ipv6_pseudo_header_checksum + * calculate the pseudo header checksum for use in tcp/udp/icmp headers + * ip6 - the ipv6 header + * len - the transport length (transport header + payload) + * protocol - the transport layer protocol, can be different from ip6->ip6_nxt for fragments + */ +uint32_t ipv6_pseudo_header_checksum(const struct ip6_hdr *ip6, uint16_t len, uint8_t protocol) { + uint32_t checksum_len, checksum_next; + checksum_len = htonl((uint32_t) len); + checksum_next = htonl(protocol); + + uint32_t current = 0; + current = ip_checksum_add(current, &(ip6->ip6_src), sizeof(struct in6_addr)); + current = ip_checksum_add(current, &(ip6->ip6_dst), sizeof(struct in6_addr)); + current = ip_checksum_add(current, &checksum_len, sizeof(checksum_len)); + current = ip_checksum_add(current, &checksum_next, sizeof(checksum_next)); + + return current; +} + +/* function: ipv4_pseudo_header_checksum + * calculate the pseudo header checksum for use in tcp/udp headers + * ip - the ipv4 header + * len - the transport length (transport header + payload) + */ +uint32_t ipv4_pseudo_header_checksum(const struct iphdr *ip, uint16_t len) { + uint16_t temp_protocol, temp_length; + + temp_protocol = htons(ip->protocol); + temp_length = htons(len); + + uint32_t current = 0; + current = ip_checksum_add(current, &(ip->saddr), sizeof(uint32_t)); + current = ip_checksum_add(current, &(ip->daddr), sizeof(uint32_t)); + current = ip_checksum_add(current, &temp_protocol, sizeof(uint16_t)); + current = ip_checksum_add(current, &temp_length, sizeof(uint16_t)); + + return current; +} + +/* function: ip_checksum_adjust + * calculates a new checksum given a previous checksum and the old and new pseudo-header checksums + * checksum - the header checksum in the original packet in network byte order + * old_hdr_sum - the pseudo-header checksum of the original packet + * new_hdr_sum - the pseudo-header checksum of the translated packet + * returns: the new header checksum in network byte order + */ +uint16_t ip_checksum_adjust(uint16_t checksum, uint32_t old_hdr_sum, uint32_t new_hdr_sum) { + // Algorithm suggested in RFC 1624. + // http://tools.ietf.org/html/rfc1624#section-3 + checksum = ~checksum; + uint16_t folded_sum = ip_checksum_fold(checksum + new_hdr_sum); + uint16_t folded_old = ip_checksum_fold(old_hdr_sum); + if (folded_sum > folded_old) { + return ~(folded_sum - folded_old); + } else { + return ~(folded_sum - folded_old - 1); // end-around borrow + } +} diff --git a/checksum.h b/checksum.h new file mode 100644 index 0000000..d0af88e --- /dev/null +++ b/checksum.h @@ -0,0 +1,34 @@ +/* + * Copyright 2011 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * checksum.h - checksum functions + */ +#ifndef __CHECKSUM_H__ +#define __CHECKSUM_H__ + +#include +#include +#include + +uint32_t ip_checksum_add(uint32_t current, const void *data, int len); +uint16_t ip_checksum_finish(uint32_t temp_sum); +uint16_t ip_checksum(const void *data, int len); + +uint32_t ipv6_pseudo_header_checksum(const struct ip6_hdr *ip6, uint16_t len, uint8_t protocol); +uint32_t ipv4_pseudo_header_checksum(const struct iphdr *ip, uint16_t len); + +uint16_t ip_checksum_adjust(uint16_t checksum, uint32_t old_hdr_sum, uint32_t new_hdr_sum); + +#endif /* __CHECKSUM_H__ */ diff --git a/clatd.c b/clatd.c new file mode 100644 index 0000000..faeb679 --- /dev/null +++ b/clatd.c @@ -0,0 +1,526 @@ +/* + * Copyright 2012 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * clatd.c - tun interface setup and main event loop + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "translate.h" +#include "clatd.h" +#include "config.h" +#include "logging.h" +#include "resolv_netid.h" +#include "setif.h" +#include "mtu.h" +#include "getaddr.h" +#include "dump.h" +#include "tun.h" +#include "ring.h" + +#define DEVICEPREFIX "v4-" + +/* 40 bytes IPv6 header - 20 bytes IPv4 header + 8 bytes fragment header */ +#define MTU_DELTA 28 + +volatile sig_atomic_t running = 1; + +/* function: stop_loop + * signal handler: stop the event loop + */ +void stop_loop() { + running = 0; +} + +/* function: configure_packet_socket + * Binds the packet socket and attaches the receive filter to it. + * sock - the socket to configure + */ +int configure_packet_socket(int sock) { + struct sockaddr_ll sll = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_IPV6), + .sll_ifindex = if_nametoindex((char *) &Global_Clatd_Config.default_pdp_interface), + .sll_pkttype = PACKET_OTHERHOST, // The 464xlat IPv6 address is not assigned to the kernel. + }; + if (bind(sock, (struct sockaddr *) &sll, sizeof(sll))) { + logmsg(ANDROID_LOG_FATAL, "binding packet socket: %s", strerror(errno)); + return 0; + } + + uint32_t *ipv6 = Global_Clatd_Config.ipv6_local_subnet.s6_addr32; + struct sock_filter filter_code[] = { + // Load the first four bytes of the IPv6 destination address (starts 24 bytes in). + // Compare it against the first four bytes of our IPv6 address, in host byte order (BPF loads + // are always in host byte order). If it matches, continue with next instruction (JMP 0). If it + // doesn't match, jump ahead to statement that returns 0 (ignore packet). Repeat for the other + // three words of the IPv6 address, and if they all match, return PACKETLEN (accept packet). + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 24), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(ipv6[0]), 0, 7), + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 28), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(ipv6[1]), 0, 5), + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 32), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(ipv6[2]), 0, 3), + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 36), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(ipv6[3]), 0, 1), + BPF_STMT(BPF_RET | BPF_K, PACKETLEN), + BPF_STMT(BPF_RET | BPF_K, 0) + }; + struct sock_fprog filter = { + sizeof(filter_code) / sizeof(filter_code[0]), + filter_code + }; + + if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter))) { + logmsg(ANDROID_LOG_FATAL, "attach packet filter failed: %s", strerror(errno)); + return 0; + } + + return 1; +} + +/* function: configure_tun_ip + * configures the ipv4 and ipv6 addresses on the tunnel interface + * tunnel - tun device data + */ +void configure_tun_ip(const struct tun_data *tunnel) { + int status; + + // Pick an IPv4 address to use by finding a free address in the configured prefix. Technically, + // there is a race here - if another clatd calls config_select_ipv4_address after we do, but + // before we call add_address, it can end up having the same IP address as we do. But the time + // window in which this can happen is extremely small, and even if we end up with a duplicate + // address, the only damage is that IPv4 TCP connections won't be reset until both interfaces go + // down. + in_addr_t localaddr = config_select_ipv4_address(&Global_Clatd_Config.ipv4_local_subnet, + Global_Clatd_Config.ipv4_local_prefixlen); + if (localaddr == INADDR_NONE) { + logmsg(ANDROID_LOG_FATAL,"No free IPv4 address in %s/%d", + inet_ntoa(Global_Clatd_Config.ipv4_local_subnet), + Global_Clatd_Config.ipv4_local_prefixlen); + exit(1); + } + Global_Clatd_Config.ipv4_local_subnet.s_addr = localaddr; + + // Configure the interface before bringing it up. As soon as we bring the interface up, the + // framework will be notified and will assume the interface's configuration has been finalized. + status = add_address(tunnel->device4, AF_INET, &Global_Clatd_Config.ipv4_local_subnet, + 32, &Global_Clatd_Config.ipv4_local_subnet); + if(status < 0) { + logmsg(ANDROID_LOG_FATAL,"configure_tun_ip/if_address(4) failed: %s",strerror(-status)); + exit(1); + } + + char addrstr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &Global_Clatd_Config.ipv4_local_subnet, addrstr, sizeof(addrstr)); + logmsg(ANDROID_LOG_INFO, "Using IPv4 address %s on %s", addrstr, tunnel->device4); + + if((status = if_up(tunnel->device4, Global_Clatd_Config.ipv4mtu)) < 0) { + logmsg(ANDROID_LOG_FATAL,"configure_tun_ip/if_up(4) failed: %s",strerror(-status)); + exit(1); + } +} + +/* function: drop_root + * drops root privs but keeps the needed capability + */ +void drop_root() { + gid_t groups[] = { AID_INET, AID_VPN }; + if(setgroups(sizeof(groups)/sizeof(groups[0]), groups) < 0) { + logmsg(ANDROID_LOG_FATAL,"drop_root/setgroups failed: %s",strerror(errno)); + exit(1); + } + + prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); + + if(setgid(AID_CLAT) < 0) { + logmsg(ANDROID_LOG_FATAL,"drop_root/setgid failed: %s",strerror(errno)); + exit(1); + } + if(setuid(AID_CLAT) < 0) { + logmsg(ANDROID_LOG_FATAL,"drop_root/setuid failed: %s",strerror(errno)); + exit(1); + } + + struct __user_cap_header_struct header; + struct __user_cap_data_struct cap; + memset(&header, 0, sizeof(header)); + memset(&cap, 0, sizeof(cap)); + + header.version = _LINUX_CAPABILITY_VERSION; + header.pid = 0; // 0 = change myself + cap.effective = cap.permitted = (1 << CAP_NET_ADMIN); + + if(capset(&header, &cap) < 0) { + logmsg(ANDROID_LOG_FATAL,"drop_root/capset failed: %s",strerror(errno)); + exit(1); + } +} + +/* function: open_sockets + * opens a packet socket to receive IPv6 packets and a raw socket to send them + * tunnel - tun device data + * mark - the socket mark to use for the sending raw socket + */ +void open_sockets(struct tun_data *tunnel, uint32_t mark) { + int rawsock = socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_RAW); + if (rawsock < 0) { + logmsg(ANDROID_LOG_FATAL, "raw socket failed: %s", strerror(errno)); + exit(1); + } + + int off = 0; + if (setsockopt(rawsock, SOL_IPV6, IPV6_CHECKSUM, &off, sizeof(off)) < 0) { + logmsg(ANDROID_LOG_WARN, "could not disable checksum on raw socket: %s", strerror(errno)); + } + if (mark != MARK_UNSET && setsockopt(rawsock, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) < 0) { + logmsg(ANDROID_LOG_ERROR, "could not set mark on raw socket: %s", strerror(errno)); + } + + tunnel->write_fd6 = rawsock; + + tunnel->read_fd6 = ring_create(tunnel); + if (tunnel->read_fd6 < 0) { + exit(1); + } +} + +/* function: update_clat_ipv6_address + * picks the clat IPv6 address and configures packet translation to use it. + * tunnel - tun device data + * interface - uplink interface name + * returns: 1 on success, 0 on failure + */ +int update_clat_ipv6_address(const struct tun_data *tunnel, const char *interface) { + union anyip *interface_ip; + char addrstr[INET6_ADDRSTRLEN]; + + // TODO: check that the prefix length is /64. + interface_ip = getinterface_ip(interface, AF_INET6); + if (!interface_ip) { + logmsg(ANDROID_LOG_ERROR, "Unable to find an IPv6 address on interface %s", interface); + return 0; + } + + // If our prefix hasn't changed, do nothing. (If this is the first time we configure an IPv6 + // address, Global_Clatd_Config.ipv6_local_subnet will be ::, which won't match our new prefix.) + if (ipv6_prefix_equal(&interface_ip->ip6, &Global_Clatd_Config.ipv6_local_subnet)) { + free(interface_ip); + return 1; + } + + // Generate an interface ID. + config_generate_local_ipv6_subnet(&interface_ip->ip6); + inet_ntop(AF_INET6, &interface_ip->ip6, addrstr, sizeof(addrstr)); + + if (IN6_IS_ADDR_UNSPECIFIED(&Global_Clatd_Config.ipv6_local_subnet)) { + // Startup. + logmsg(ANDROID_LOG_INFO, "Using IPv6 address %s on %s", addrstr, interface); + } else { + // Prefix change. + char from_addr[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &Global_Clatd_Config.ipv6_local_subnet, from_addr, sizeof(from_addr)); + logmsg(ANDROID_LOG_INFO, "clat IPv6 address changed from %s to %s", from_addr, addrstr); + del_anycast_address(tunnel->write_fd6, &Global_Clatd_Config.ipv6_local_subnet); + } + + // Start translating packets to the new prefix. + Global_Clatd_Config.ipv6_local_subnet = interface_ip->ip6; + add_anycast_address(tunnel->write_fd6, &Global_Clatd_Config.ipv6_local_subnet, interface); + free(interface_ip); + + // Update our packet socket filter to reflect the new 464xlat IP address. + if (!configure_packet_socket(tunnel->read_fd6)) { + // Things aren't going to work. Bail out and hope we have better luck next time. + // We don't log an error here because configure_packet_socket has already done so. + exit(1); + } + + return 1; +} + +/* function: configure_interface + * reads the configuration and applies it to the interface + * uplink_interface - network interface to use to reach the ipv6 internet + * plat_prefix - PLAT prefix to use + * tunnel - tun device data + * net_id - NetID to use, NETID_UNSET indicates use of default network + */ +void configure_interface(const char *uplink_interface, const char *plat_prefix, struct tun_data *tunnel, unsigned net_id) { + int error; + + if(!read_config("/system/etc/clatd.conf", uplink_interface, plat_prefix, net_id)) { + logmsg(ANDROID_LOG_FATAL,"read_config failed"); + exit(1); + } + + if(Global_Clatd_Config.mtu > MAXMTU) { + logmsg(ANDROID_LOG_WARN,"Max MTU is %d, requested %d", MAXMTU, Global_Clatd_Config.mtu); + Global_Clatd_Config.mtu = MAXMTU; + } + if(Global_Clatd_Config.mtu <= 0) { + Global_Clatd_Config.mtu = getifmtu(Global_Clatd_Config.default_pdp_interface); + logmsg(ANDROID_LOG_WARN,"ifmtu=%d",Global_Clatd_Config.mtu); + } + if(Global_Clatd_Config.mtu < 1280) { + logmsg(ANDROID_LOG_WARN,"mtu too small = %d", Global_Clatd_Config.mtu); + Global_Clatd_Config.mtu = 1280; + } + + if(Global_Clatd_Config.ipv4mtu <= 0 || + Global_Clatd_Config.ipv4mtu > Global_Clatd_Config.mtu - MTU_DELTA) { + Global_Clatd_Config.ipv4mtu = Global_Clatd_Config.mtu - MTU_DELTA; + logmsg(ANDROID_LOG_WARN,"ipv4mtu now set to = %d",Global_Clatd_Config.ipv4mtu); + } + + error = tun_alloc(tunnel->device4, tunnel->fd4); + if(error < 0) { + logmsg(ANDROID_LOG_FATAL,"tun_alloc/4 failed: %s",strerror(errno)); + exit(1); + } + + error = set_nonblocking(tunnel->fd4); + if (error < 0) { + logmsg(ANDROID_LOG_FATAL, "set_nonblocking failed: %s", strerror(errno)); + exit(1); + } + + configure_tun_ip(tunnel); +} + +/* function: read_packet + * reads a packet from the tunnel fd and translates it + * read_fd - file descriptor to read original packet from + * write_fd - file descriptor to write translated packet to + * to_ipv6 - whether the packet is to be translated to ipv6 or ipv4 + */ +void read_packet(int read_fd, int write_fd, int to_ipv6) { + ssize_t readlen; + uint8_t buf[PACKETLEN], *packet; + + readlen = read(read_fd, buf, PACKETLEN); + + if(readlen < 0) { + if (errno != EAGAIN) { + logmsg(ANDROID_LOG_WARN,"read_packet/read error: %s", strerror(errno)); + } + return; + } else if(readlen == 0) { + logmsg(ANDROID_LOG_WARN,"read_packet/tun interface removed"); + running = 0; + return; + } + + struct tun_pi *tun_header = (struct tun_pi *) buf; + if (readlen < (ssize_t) sizeof(*tun_header)) { + logmsg(ANDROID_LOG_WARN,"read_packet/short read: got %ld bytes", readlen); + return; + } + + uint16_t proto = ntohs(tun_header->proto); + if (proto != ETH_P_IP) { + logmsg(ANDROID_LOG_WARN, "%s: unknown packet type = 0x%x", __func__, proto); + return; + } + + if(tun_header->flags != 0) { + logmsg(ANDROID_LOG_WARN, "%s: unexpected flags = %d", __func__, tun_header->flags); + } + + packet = (uint8_t *) (tun_header + 1); + readlen -= sizeof(*tun_header); + translate_packet(write_fd, to_ipv6, packet, readlen); +} + +/* function: event_loop + * reads packets from the tun network interface and passes them down the stack + * tunnel - tun device data + */ +void event_loop(struct tun_data *tunnel) { + time_t last_interface_poll; + struct pollfd wait_fd[] = { + { tunnel->read_fd6, POLLIN, 0 }, + { tunnel->fd4, POLLIN, 0 }, + }; + + // start the poll timer + last_interface_poll = time(NULL); + + while(running) { + if(poll(wait_fd, 2, NO_TRAFFIC_INTERFACE_POLL_FREQUENCY*1000) == -1) { + if(errno != EINTR) { + logmsg(ANDROID_LOG_WARN,"event_loop/poll returned an error: %s",strerror(errno)); + } + } else { + // Call read_packet if the socket has data to be read, but also if an + // error is waiting. If we don't call read() after getting POLLERR, a + // subsequent poll() will return immediately with POLLERR again, + // causing this code to spin in a loop. Calling read() will clear the + // socket error flag instead. + if (wait_fd[0].revents) { + ring_read(&tunnel->ring, tunnel->fd4, 0 /* to_ipv6 */); + } + if (wait_fd[1].revents) { + read_packet(tunnel->fd4, tunnel->write_fd6, 1 /* to_ipv6 */); + } + } + + time_t now = time(NULL); + if(last_interface_poll < (now - INTERFACE_POLL_FREQUENCY)) { + update_clat_ipv6_address(tunnel, Global_Clatd_Config.default_pdp_interface); + last_interface_poll = now; + } + } +} + +/* function: print_help + * in case the user is running this on the command line + */ +void print_help() { + printf("android-clat arguments:\n"); + printf("-i [uplink interface]\n"); + printf("-p [plat prefix]\n"); + printf("-n [NetId]\n"); + printf("-m [socket mark]\n"); +} + +/* function: parse_unsigned + * parses a string as a decimal/hex/octal unsigned integer + * str - the string to parse + * out - the unsigned integer to write to, gets clobbered on failure + */ +int parse_unsigned(const char *str, unsigned *out) { + char *end_ptr; + *out = strtoul(str, &end_ptr, 0); + return *str && !*end_ptr; +} + +/* function: main + * allocate and setup the tun device, then run the event loop + */ +int main(int argc, char **argv) { + struct tun_data tunnel; + int opt; + char *uplink_interface = NULL, *plat_prefix = NULL, *net_id_str = NULL, *mark_str = NULL; + unsigned net_id = NETID_UNSET; + uint32_t mark = MARK_UNSET; + unsigned len; + + while((opt = getopt(argc, argv, "i:p:n:m:h")) != -1) { + switch(opt) { + case 'i': + uplink_interface = optarg; + break; + case 'p': + plat_prefix = optarg; + break; + case 'n': + net_id_str = optarg; + break; + case 'm': + mark_str = optarg; + break; + case 'h': + print_help(); + exit(0); + default: + logmsg(ANDROID_LOG_FATAL, "Unknown option -%c. Exiting.", (char) optopt); + exit(1); + } + } + + if(uplink_interface == NULL) { + logmsg(ANDROID_LOG_FATAL, "clatd called without an interface"); + exit(1); + } + + if (net_id_str != NULL && !parse_unsigned(net_id_str, &net_id)) { + logmsg(ANDROID_LOG_FATAL, "invalid NetID %s", net_id_str); + exit(1); + } + + if (mark_str != NULL && !parse_unsigned(mark_str, &mark)) { + logmsg(ANDROID_LOG_FATAL, "invalid mark %s", mark_str); + exit(1); + } + + len = snprintf(tunnel.device4, sizeof(tunnel.device4), "%s%s", DEVICEPREFIX, uplink_interface); + if (len >= sizeof(tunnel.device4)) { + logmsg(ANDROID_LOG_FATAL, "interface name too long '%s'", tunnel.device4); + exit(1); + } + + logmsg(ANDROID_LOG_INFO, "Starting clat version %s on %s netid=%s mark=%s", + CLATD_VERSION, uplink_interface, + net_id_str ? net_id_str : "(none)", + mark_str ? mark_str : "(none)"); + + // open our raw sockets before dropping privs + open_sockets(&tunnel, mark); + + // run under a regular user + drop_root(); + + // we can create tun devices as non-root because we're in the VPN group. + tunnel.fd4 = tun_open(); + if(tunnel.fd4 < 0) { + logmsg(ANDROID_LOG_FATAL, "tun_open4 failed: %s", strerror(errno)); + exit(1); + } + + // When run from netd, the environment variable ANDROID_DNS_MODE is set to + // "local", but that only works for the netd process itself. + unsetenv("ANDROID_DNS_MODE"); + + configure_interface(uplink_interface, plat_prefix, &tunnel, net_id); + + update_clat_ipv6_address(&tunnel, uplink_interface); + + // Loop until someone sends us a signal or brings down the tun interface. + if(signal(SIGTERM, stop_loop) == SIG_ERR) { + logmsg(ANDROID_LOG_FATAL, "sigterm handler failed: %s", strerror(errno)); + exit(1); + } + + event_loop(&tunnel); + + logmsg(ANDROID_LOG_INFO,"Shutting down clat on %s", uplink_interface); + del_anycast_address(tunnel.write_fd6, &Global_Clatd_Config.ipv6_local_subnet); + + return 0; +} diff --git a/clatd.conf b/clatd.conf new file mode 100644 index 0000000..ff80975 --- /dev/null +++ b/clatd.conf @@ -0,0 +1,22 @@ +# Host IID to use as the source of CLAT traffic. +# This is a /128 taken out of the /64 on the parent interface. +# A host IID of :: means to generate a checksum-neutral, random IID. +ipv6_host_id :: + +# IPv4 address configuration to use when selecting a host address. The first +# clat daemon started will use the address specified by ipv4_local_subnet. If +# more than one daemon is run at the same time, subsequent daemons will use +# other addresses in the prefix of length ipv4_local prefixlen that contains +# ipv4_local_subnet. The default is to use the IANA-assigned range 192.0.0.0/29, +# which allows up to 8 clat daemons (.4, .5, .6, .7, .0, .1, .2, .3). +ipv4_local_subnet 192.0.0.4 +ipv4_local_prefixlen 29 + +# get the plat_subnet from dns lookups (requires DNS64) +plat_from_dns64 yes +# hostname to use to lookup plat subnet. must contain only A records +plat_from_dns64_hostname ipv4only.arpa + +# plat subnet to send ipv4 traffic to. This is a /96 subnet. +# This setting only makes sense with: plat_from_dns64 no +#plat_subnet 2001:db8:1:2:3:4:: diff --git a/clatd.h b/clatd.h new file mode 100644 index 0000000..f421f46 --- /dev/null +++ b/clatd.h @@ -0,0 +1,46 @@ +/* + * Copyright 2011 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * clatd.h - main system definitions + */ +#ifndef __CLATD_H__ +#define __CLATD_H__ + +#include + +#define MAXMTU 1500 +#define PACKETLEN (MAXMTU+sizeof(struct tun_pi)) +#define CLATD_VERSION "1.4" + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +// how frequently (in seconds) to poll for an address change while traffic is passing +#define INTERFACE_POLL_FREQUENCY 30 + +// how frequently (in seconds) to poll for an address change while there is no traffic +#define NO_TRAFFIC_INTERFACE_POLL_FREQUENCY 90 + +// A clat_packet is an array of iovec structures representing a packet that we are translating. +// The CLAT_POS_XXX constants represent the array indices within the clat_packet that contain +// specific parts of the packet. The packet_* functions operate on all the packet segments past a +// given position. +typedef enum { + CLAT_POS_TUNHDR, CLAT_POS_IPHDR, CLAT_POS_FRAGHDR, CLAT_POS_TRANSPORTHDR, + CLAT_POS_ICMPERR_IPHDR, CLAT_POS_ICMPERR_FRAGHDR, CLAT_POS_ICMPERR_TRANSPORTHDR, + CLAT_POS_PAYLOAD, CLAT_POS_MAX +} clat_packet_index; +typedef struct iovec clat_packet[CLAT_POS_MAX]; + +#endif /* __CLATD_H__ */ diff --git a/clatd_microbenchmark.c b/clatd_microbenchmark.c new file mode 100644 index 0000000..fed3100 --- /dev/null +++ b/clatd_microbenchmark.c @@ -0,0 +1,214 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * clatd_microbenchmark.c - micro-benchmark for clatd tun send path + * + * Run with: + * + * adb push {$ANDROID_PRODUCT_OUT,}/data/nativetest/clatd_microbenchmark/clatd_microbenchmark + * adb shell /data/nativetest/clatd_microbenchmark/clatd_microbenchmark + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "checksum.h" +#include "tun.h" + +#define DEVICENAME "clat4" + +#define PORT 51339 +#define PAYLOADSIZE (1280 - sizeof(struct iphdr) - sizeof(struct udphdr)) +#define NUMPACKETS 1000000 +#define SEC_TO_NANOSEC (1000 * 1000 * 1000) + +void init_sockaddr_in(struct sockaddr_in *sin, const char *addr) { + sin->sin_family = AF_INET; + sin->sin_port = 0; + sin->sin_addr.s_addr = inet_addr(addr); +} + +void die(const char *str) { + perror(str); + exit(1); +} + +int setup_tun() { + int fd = tun_open(); + if (fd == -1) die("tun_open"); + + char dev[IFNAMSIZ] = DEVICENAME; + int ret = tun_alloc(dev, fd); + if (ret == -1) die("tun_alloc"); + struct ifreq ifr = { + .ifr_name = DEVICENAME, + }; + + int s = socket(AF_INET, SOCK_DGRAM, 0); + init_sockaddr_in((struct sockaddr_in *) &ifr.ifr_addr, "192.0.0.4"); + if (ioctl(s, SIOCSIFADDR, &ifr) < 0) die("SIOCSIFADDR"); + init_sockaddr_in((struct sockaddr_in *) &ifr.ifr_addr, "255.255.255.248"); + if (ioctl(s, SIOCSIFNETMASK, &ifr) < 0) die("SIOCSIFNETMASK"); + if (ioctl(s, SIOCGIFFLAGS, &ifr) < 0) die("SIOCGIFFLAGS"); + ifr.ifr_flags |= (IFF_UP | IFF_RUNNING); + if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0) die("SIOCSIFFLAGS"); + return fd; +} + +int send_packet(int fd, uint8_t payload[], int len, uint32_t payload_checksum) { + struct tun_pi tun = { 0, htons(ETH_P_IP) }; + struct udphdr udp = { + .source = htons(1234), + .dest = htons(PORT), + .len = htons(len + sizeof(udp)), + .check = 0, + }; + struct iphdr ip = { + .version = 4, + .ihl = 5, + .tot_len = htons(len + sizeof(ip) + sizeof(udp)), + .frag_off = htons(IP_DF), + .ttl = 55, + .protocol = IPPROTO_UDP, + .saddr = htonl(0xc0000006), // 192.0.0.6 + .daddr = htonl(0xc0000004), // 192.0.0.4 + }; + clat_packet out = { + { &tun, sizeof(tun) }, // tun header + { &ip, sizeof(ip) }, // IP header + { NULL, 0 }, // Fragment header + { &udp, sizeof(udp) }, // Transport header + { NULL, 0 }, // ICMP error IP header + { NULL, 0 }, // ICMP error fragment header + { NULL, 0 }, // ICMP error transport header + { payload, len }, // Payload + }; + + ip.check = ip_checksum(&ip, sizeof(ip)); + + uint32_t sum; + sum = ipv4_pseudo_header_checksum(&ip, ntohs(udp.len)); + sum = ip_checksum_add(sum, &udp, sizeof(udp)); + sum += payload_checksum; + udp.check = ip_checksum_finish(sum); + + return send_tun(fd, out, sizeof(out) / sizeof(out[0])); +} + +double timedelta(const struct timespec tv1, const struct timespec tv2) { + struct timespec end = tv2; + if (end.tv_nsec < tv1.tv_nsec) { + end.tv_sec -= 1; + end.tv_nsec += SEC_TO_NANOSEC; + } + double seconds = (end.tv_sec - tv1.tv_sec); + seconds += (((double) (end.tv_nsec - tv1.tv_nsec)) / SEC_TO_NANOSEC); + return seconds; +} + +void benchmark(const char *name, int fd, int s, int num, int do_read, + uint8_t payload[], int len, uint32_t payload_sum) { + int i; + char buf[4096]; + struct timespec tv1, tv2; + int write_err = 0, read_err = 0; + clock_gettime(CLOCK_MONOTONIC, &tv1); + for (i = 0; i < num; i++) { + if (send_packet(fd, payload, len, payload_sum) == -1) write_err++; + if (do_read && recvfrom(s, buf, sizeof(buf), 0, NULL, NULL) == -1) { + read_err++; + if (errno == ETIMEDOUT) { + printf("Timed out after %d packets!\n", i); + break; + } + } + } + clock_gettime(CLOCK_MONOTONIC, &tv2); + double seconds = timedelta(tv1, tv2); + int pps = (int) (i / seconds); + double mbps = (i * PAYLOADSIZE / 1000000 * 8 / seconds); + printf("%s: %d packets in %.2fs (%d pps, %.2f Mbps), ", name, i, seconds, pps, mbps); + printf("read err %d (%.2f%%), write err %d (%.2f%%)\n", + read_err, (float) read_err / i * 100, + write_err, (float) write_err / i * 100); +} + +int open_socket() { + int sock = socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP); + + int on = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) die("SO_REUSEADDR"); + + struct timeval tv = { 1, 0 }; + if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) die("SO_RCVTIMEO"); + + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_port = ntohs(PORT), + .sin_addr = { INADDR_ANY } + }; + if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) == -1) die ("bind"); + + return sock; +} + +int main() { + int fd = setup_tun(); + int sock = open_socket(); + + int i; + uint8_t payload[PAYLOADSIZE]; + for (i = 0; i < (int) sizeof(payload); i++) { + payload[i] = (uint8_t) i; + } + uint32_t payload_sum = ip_checksum_add(0, payload, sizeof(payload)); + + // Check things are working. + char buf[4096]; + if (send_packet(fd, payload, sizeof(payload), payload_sum) == -1) die("send_packet"); + if (recvfrom(sock, buf, sizeof(buf), 0, NULL, NULL) == -1) die("recvfrom"); + + benchmark("Blocking", fd, sock, NUMPACKETS, 1, payload, sizeof(payload), payload_sum); + close(fd); + + fd = setup_tun(); + set_nonblocking(fd); + benchmark("No read", fd, sock, NUMPACKETS, 0, payload, sizeof(payload), payload_sum); + close(fd); + + fd = setup_tun(); + set_nonblocking(fd); + benchmark("Nonblocking", fd, sock, NUMPACKETS, 1, payload, sizeof(payload), payload_sum); + close(fd); + + return 0; +} diff --git a/clatd_test.cpp b/clatd_test.cpp new file mode 100644 index 0000000..7e218f0 --- /dev/null +++ b/clatd_test.cpp @@ -0,0 +1,936 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * clatd_test.cpp - unit tests for clatd + */ + +#include + +#include +#include +#include +#include + +#include + +extern "C" { +#include "checksum.h" +#include "translate.h" +#include "config.h" +#include "clatd.h" +} + +// For convenience. +#define ARRAYSIZE(x) sizeof((x)) / sizeof((x)[0]) + +// Default translation parameters. +static const char kIPv4LocalAddr[] = "192.0.0.4"; +static const char kIPv6LocalAddr[] = "2001:db8:0:b11::464"; +static const char kIPv6PlatSubnet[] = "64:ff9b::"; + +// Test packet portions. Defined as macros because it's easy to concatenate them to make packets. +#define IPV4_HEADER(p, c1, c2) \ + 0x45, 0x00, 0, 41, /* Version=4, IHL=5, ToS=0x80, len=41 */ \ + 0x00, 0x00, 0x40, 0x00, /* ID=0x0000, flags=IP_DF, offset=0 */ \ + 55, (p), (c1), (c2), /* TTL=55, protocol=p, checksum=c1,c2 */ \ + 192, 0, 0, 4, /* Src=192.0.0.4 */ \ + 8, 8, 8, 8, /* Dst=8.8.8.8 */ +#define IPV4_UDP_HEADER IPV4_HEADER(IPPROTO_UDP, 0x73, 0xb0) +#define IPV4_ICMP_HEADER IPV4_HEADER(IPPROTO_ICMP, 0x73, 0xc0) + +#define IPV6_HEADER(p) \ + 0x60, 0x00, 0, 0, /* Version=6, tclass=0x00, flowlabel=0 */ \ + 0, 21, (p), 55, /* plen=11, nxthdr=p, hlim=55 */ \ + 0x20, 0x01, 0x0d, 0xb8, /* Src=2001:db8:0:b11::464 */ \ + 0x00, 0x00, 0x0b, 0x11, \ + 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x04, 0x64, \ + 0x00, 0x64, 0xff, 0x9b, /* Dst=64:ff9b::8.8.8.8 */ \ + 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, \ + 0x08, 0x08, 0x08, 0x08, +#define IPV6_UDP_HEADER IPV6_HEADER(IPPROTO_UDP) +#define IPV6_ICMPV6_HEADER IPV6_HEADER(IPPROTO_ICMPV6) + +#define UDP_LEN 21 +#define UDP_HEADER \ + 0xc8, 0x8b, 0, 53, /* Port 51339->53 */ \ + 0x00, UDP_LEN, 0, 0, /* Length 21, checksum empty for now */ + +#define PAYLOAD 'H', 'e', 'l', 'l', 'o', ' ', 0x4e, 0xb8, 0x96, 0xe7, 0x95, 0x8c, 0x00 + +#define IPV4_PING \ + 0x08, 0x00, 0x88, 0xd0, /* Type 8, code 0, checksum 0x88d0 */ \ + 0xd0, 0x0d, 0x00, 0x03, /* ID=0xd00d, seq=3 */ + +#define IPV6_PING \ + 0x80, 0x00, 0xc3, 0x42, /* Type 128, code 0, checksum 0xc342 */ \ + 0xd0, 0x0d, 0x00, 0x03, /* ID=0xd00d, seq=3 */ + +// Macros to return pseudo-headers from packets. +#define IPV4_PSEUDOHEADER(ip, tlen) \ + ip[12], ip[13], ip[14], ip[15], /* Source address */ \ + ip[16], ip[17], ip[18], ip[19], /* Destination address */ \ + 0, ip[9], /* 0, protocol */ \ + ((tlen) >> 16) & 0xff, (tlen) & 0xff, /* Transport length */ + +#define IPV6_PSEUDOHEADER(ip6, protocol, tlen) \ + ip6[8], ip6[9], ip6[10], ip6[11], /* Source address */ \ + ip6[12], ip6[13], ip6[14], ip6[15], \ + ip6[16], ip6[17], ip6[18], ip6[19], \ + ip6[20], ip6[21], ip6[22], ip6[23], \ + ip6[24], ip6[25], ip6[26], ip6[27], /* Destination address */ \ + ip6[28], ip6[29], ip6[30], ip6[31], \ + ip6[32], ip6[33], ip6[34], ip6[35], \ + ip6[36], ip6[37], ip6[38], ip6[39], \ + ((tlen) >> 24) & 0xff, /* Transport length */ \ + ((tlen) >> 16) & 0xff, \ + ((tlen) >> 8) & 0xff, \ + (tlen) & 0xff, \ + 0, 0, 0, (protocol), + +// A fragmented DNS request. +static const uint8_t kIPv4Frag1[] = { + 0x45, 0x00, 0x00, 0x24, 0xfe, 0x47, 0x20, 0x00, 0x40, 0x11, + 0x8c, 0x6d, 0xc0, 0x00, 0x00, 0x04, 0x08, 0x08, 0x08, 0x08, + 0x14, 0x5d, 0x00, 0x35, 0x00, 0x29, 0x68, 0xbb, 0x50, 0x47, + 0x01, 0x00, 0x00, 0x01, 0x00, 0x00 +}; +static const uint8_t kIPv4Frag2[] = { + 0x45, 0x00, 0x00, 0x24, 0xfe, 0x47, 0x20, 0x02, 0x40, 0x11, + 0x8c, 0x6b, 0xc0, 0x00, 0x00, 0x04, 0x08, 0x08, 0x08, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x69, 0x70, 0x76, 0x34, 0x06, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65 +}; +static const uint8_t kIPv4Frag3[] = { + 0x45, 0x00, 0x00, 0x1d, 0xfe, 0x47, 0x00, 0x04, 0x40, 0x11, + 0xac, 0x70, 0xc0, 0x00, 0x00, 0x04, 0x08, 0x08, 0x08, 0x08, + 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01 +}; +static const uint8_t *kIPv4Fragments[] = { kIPv4Frag1, kIPv4Frag2, kIPv4Frag3 }; +static const size_t kIPv4FragLengths[] = { sizeof(kIPv4Frag1), sizeof(kIPv4Frag2), + sizeof(kIPv4Frag3) }; + +static const uint8_t kIPv6Frag1[] = { + 0x60, 0x00, 0x00, 0x00, 0x00, 0x18, 0x2c, 0x40, 0x20, 0x01, + 0x0d, 0xb8, 0x00, 0x00, 0x0b, 0x11, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x64, 0x00, 0x64, 0xff, 0x9b, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, + 0x11, 0x00, 0x00, 0x01, 0x00, 0x00, 0xfe, 0x47, 0x14, 0x5d, + 0x00, 0x35, 0x00, 0x29, 0xeb, 0x91, 0x50, 0x47, 0x01, 0x00, + 0x00, 0x01, 0x00, 0x00 +}; + +static const uint8_t kIPv6Frag2[] = { + 0x60, 0x00, 0x00, 0x00, 0x00, 0x18, 0x2c, 0x40, 0x20, 0x01, + 0x0d, 0xb8, 0x00, 0x00, 0x0b, 0x11, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x64, 0x00, 0x64, 0xff, 0x9b, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, + 0x11, 0x00, 0x00, 0x11, 0x00, 0x00, 0xfe, 0x47, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x69, 0x70, 0x76, 0x34, 0x06, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65 +}; + +static const uint8_t kIPv6Frag3[] = { + 0x60, 0x00, 0x00, 0x00, 0x00, 0x11, 0x2c, 0x40, 0x20, 0x01, + 0x0d, 0xb8, 0x00, 0x00, 0x0b, 0x11, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x64, 0x00, 0x64, 0xff, 0x9b, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, + 0x11, 0x00, 0x00, 0x20, 0x00, 0x00, 0xfe, 0x47, 0x03, 0x63, + 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01 +}; +static const uint8_t *kIPv6Fragments[] = { kIPv6Frag1, kIPv6Frag2, kIPv6Frag3 }; +static const size_t kIPv6FragLengths[] = { sizeof(kIPv6Frag1), sizeof(kIPv6Frag2), + sizeof(kIPv6Frag3) }; + +static const uint8_t kReassembledIPv4[] = { + 0x45, 0x00, 0x00, 0x3d, 0xfe, 0x47, 0x00, 0x00, 0x40, 0x11, + 0xac, 0x54, 0xc0, 0x00, 0x00, 0x04, 0x08, 0x08, 0x08, 0x08, + 0x14, 0x5d, 0x00, 0x35, 0x00, 0x29, 0x68, 0xbb, 0x50, 0x47, + 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x69, 0x70, 0x76, 0x34, 0x06, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, + 0x01 +}; + +// Expected checksums. +static const uint32_t kUdpPartialChecksum = 0xd5c8; +static const uint32_t kPayloadPartialChecksum = 0x31e9c; +static const uint16_t kUdpV4Checksum = 0xd0c7; +static const uint16_t kUdpV6Checksum = 0xa74a; + +uint8_t ip_version(const uint8_t *packet) { + uint8_t version = packet[0] >> 4; + return version; +} + +int is_ipv4_fragment(struct iphdr *ip) { + // A packet is a fragment if its fragment offset is nonzero or if the MF flag is set. + return ntohs(ip->frag_off) & (IP_OFFMASK | IP_MF); +} + +int is_ipv6_fragment(struct ip6_hdr *ip6, size_t len) { + if (ip6->ip6_nxt != IPPROTO_FRAGMENT) { + return 0; + } + struct ip6_frag *frag = (struct ip6_frag *) (ip6 + 1); + return len >= sizeof(*ip6) + sizeof(*frag) && + (frag->ip6f_offlg & (IP6F_OFF_MASK | IP6F_MORE_FRAG)); +} + +int ipv4_fragment_offset(struct iphdr *ip) { + return ntohs(ip->frag_off) & IP_OFFMASK; +} + +int ipv6_fragment_offset(struct ip6_frag *frag) { + return ntohs((frag->ip6f_offlg & IP6F_OFF_MASK) >> 3); +} + +void check_packet(const uint8_t *packet, size_t len, const char *msg) { + void *payload; + size_t payload_length = 0; + uint32_t pseudo_checksum = 0; + uint8_t protocol = 0; + int version = ip_version(packet); + switch (version) { + case 4: { + struct iphdr *ip = (struct iphdr *) packet; + ASSERT_GE(len, sizeof(*ip)) << msg << ": IPv4 packet shorter than IPv4 header\n"; + EXPECT_EQ(5, ip->ihl) << msg << ": Unsupported IP header length\n"; + EXPECT_EQ(len, ntohs(ip->tot_len)) << msg << ": Incorrect IPv4 length\n"; + EXPECT_EQ(0, ip_checksum(ip, sizeof(*ip))) << msg << ": Incorrect IP checksum\n"; + protocol = ip->protocol; + payload = ip + 1; + if (!is_ipv4_fragment(ip)) { + payload_length = len - sizeof(*ip); + pseudo_checksum = ipv4_pseudo_header_checksum(ip, payload_length); + } + ASSERT_TRUE(protocol == IPPROTO_TCP || protocol == IPPROTO_UDP || protocol == IPPROTO_ICMP) + << msg << ": Unsupported IPv4 protocol " << protocol << "\n"; + break; + } + case 6: { + struct ip6_hdr *ip6 = (struct ip6_hdr *) packet; + ASSERT_GE(len, sizeof(*ip6)) << msg << ": IPv6 packet shorter than IPv6 header\n"; + EXPECT_EQ(len - sizeof(*ip6), htons(ip6->ip6_plen)) << msg << ": Incorrect IPv6 length\n"; + + if (ip6->ip6_nxt == IPPROTO_FRAGMENT) { + struct ip6_frag *frag = (struct ip6_frag *) (ip6 + 1); + ASSERT_GE(len, sizeof(*ip6) + sizeof(*frag)) + << msg << ": IPv6 fragment: short fragment header\n"; + protocol = frag->ip6f_nxt; + payload = frag + 1; + // Even though the packet has a Fragment header, it might not be a fragment. + if (!is_ipv6_fragment(ip6, len)) { + payload_length = len - sizeof(*ip6) - sizeof(*frag); + } + } else { + // Since there are no extension headers except Fragment, this must be the payload. + protocol = ip6->ip6_nxt; + payload = ip6 + 1; + payload_length = len - sizeof(*ip6); + } + ASSERT_TRUE(protocol == IPPROTO_TCP || protocol == IPPROTO_UDP || protocol == IPPROTO_ICMPV6) + << msg << ": Unsupported IPv6 next header " << protocol; + if (payload_length) { + pseudo_checksum = ipv6_pseudo_header_checksum(ip6, payload_length, protocol); + } + break; + } + default: + FAIL() << msg << ": Unsupported IP version " << version << "\n"; + return; + } + + // If we understand the payload, verify the checksum. + if (payload_length) { + uint16_t checksum; + switch(protocol) { + case IPPROTO_UDP: + case IPPROTO_TCP: + case IPPROTO_ICMPV6: + checksum = ip_checksum_finish(ip_checksum_add(pseudo_checksum, payload, payload_length)); + break; + case IPPROTO_ICMP: + checksum = ip_checksum(payload, payload_length); + break; + default: + checksum = 0; // Don't check. + break; + } + EXPECT_EQ(0, checksum) << msg << ": Incorrect transport checksum\n"; + } + + if (protocol == IPPROTO_UDP) { + struct udphdr *udp = (struct udphdr *) payload; + EXPECT_NE(0, udp->check) << msg << ": UDP checksum 0 should be 0xffff"; + // If this is not a fragment, check the UDP length field. + if (payload_length) { + EXPECT_EQ(payload_length, ntohs(udp->len)) << msg << ": Incorrect UDP length\n"; + } + } +} + +void reassemble_packet(const uint8_t **fragments, const size_t lengths[], int numpackets, + uint8_t *reassembled, size_t *reassembled_len, const char *msg) { + struct iphdr *ip = NULL; + struct ip6_hdr *ip6 = NULL; + size_t total_length, pos = 0; + uint8_t protocol = 0; + uint8_t version = ip_version(fragments[0]); + + for (int i = 0; i < numpackets; i++) { + const uint8_t *packet = fragments[i]; + int len = lengths[i]; + int headersize, payload_offset; + + ASSERT_EQ(ip_version(packet), version) << msg << ": Inconsistent fragment versions\n"; + check_packet(packet, len, "Fragment sanity check"); + + switch (version) { + case 4: { + struct iphdr *ip_orig = (struct iphdr *) packet; + headersize = sizeof(*ip_orig); + ASSERT_TRUE(is_ipv4_fragment(ip_orig)) + << msg << ": IPv4 fragment #" << i + 1 << " not a fragment\n"; + ASSERT_EQ(pos, ipv4_fragment_offset(ip_orig) * 8 + ((i != 0) ? sizeof(*ip): 0)) + << msg << ": IPv4 fragment #" << i + 1 << ": inconsistent offset\n"; + + headersize = sizeof(*ip_orig); + payload_offset = headersize; + if (pos == 0) { + ip = (struct iphdr *) reassembled; + } + break; + } + case 6: { + struct ip6_hdr *ip6_orig = (struct ip6_hdr *) packet; + struct ip6_frag *frag = (struct ip6_frag *) (ip6_orig + 1); + ASSERT_TRUE(is_ipv6_fragment(ip6_orig, len)) + << msg << ": IPv6 fragment #" << i + 1 << " not a fragment\n"; + ASSERT_EQ(pos, ipv6_fragment_offset(frag) * 8 + ((i != 0) ? sizeof(*ip6): 0)) + << msg << ": IPv6 fragment #" << i + 1 << ": inconsistent offset\n"; + + headersize = sizeof(*ip6_orig); + payload_offset = sizeof(*ip6_orig) + sizeof(*frag); + if (pos == 0) { + ip6 = (struct ip6_hdr *) reassembled; + protocol = frag->ip6f_nxt; + } + break; + } + default: + FAIL() << msg << ": Invalid IP version << " << version; + } + + // If this is the first fragment, copy the header. + if (pos == 0) { + ASSERT_LT(headersize, (int) *reassembled_len) << msg << ": Reassembly buffer too small\n"; + memcpy(reassembled, packet, headersize); + total_length = headersize; + pos += headersize; + } + + // Copy the payload. + int payload_length = len - payload_offset; + total_length += payload_length; + ASSERT_LT(total_length, *reassembled_len) << msg << ": Reassembly buffer too small\n"; + memcpy(reassembled + pos, packet + payload_offset, payload_length); + pos += payload_length; + } + + + // Fix up the reassembled headers to reflect fragmentation and length (and IPv4 checksum). + ASSERT_EQ(total_length, pos) << msg << ": Reassembled packet length incorrect\n"; + if (ip) { + ip->frag_off &= ~htons(IP_MF); + ip->tot_len = htons(total_length); + ip->check = 0; + ip->check = ip_checksum(ip, sizeof(*ip)); + ASSERT_FALSE(is_ipv4_fragment(ip)) << msg << ": reassembled IPv4 packet is a fragment!\n"; + } + if (ip6) { + ip6->ip6_nxt = protocol; + ip6->ip6_plen = htons(total_length - sizeof(*ip6)); + ASSERT_FALSE(is_ipv6_fragment(ip6, ip6->ip6_plen)) + << msg << ": reassembled IPv6 packet is a fragment!\n"; + } + + *reassembled_len = total_length; +} + +void check_data_matches(const void *expected, const void *actual, size_t len, const char *msg) { + if (memcmp(expected, actual, len)) { + // Hex dump, 20 bytes per line, one space between bytes (1 byte = 3 chars), indented by 4. + int hexdump_len = len * 3 + (len / 20 + 1) * 5; + char expected_hexdump[hexdump_len], actual_hexdump[hexdump_len]; + unsigned pos = 0; + for (unsigned i = 0; i < len; i++) { + if (i % 20 == 0) { + sprintf(expected_hexdump + pos, "\n "); + sprintf(actual_hexdump + pos, "\n "); + pos += 4; + } + sprintf(expected_hexdump + pos, " %02x", ((uint8_t *) expected)[i]); + sprintf(actual_hexdump + pos, " %02x", ((uint8_t *) actual)[i]); + pos += 3; + } + FAIL() << msg << ": Data doesn't match" + << "\n Expected:" << (char *) expected_hexdump + << "\n Actual:" << (char *) actual_hexdump << "\n"; + } +} + +void fix_udp_checksum(uint8_t* packet) { + uint32_t pseudo_checksum; + uint8_t version = ip_version(packet); + struct udphdr *udp; + switch (version) { + case 4: { + struct iphdr *ip = (struct iphdr *) packet; + udp = (struct udphdr *) (ip + 1); + pseudo_checksum = ipv4_pseudo_header_checksum(ip, ntohs(udp->len)); + break; + } + case 6: { + struct ip6_hdr *ip6 = (struct ip6_hdr *) packet; + udp = (struct udphdr *) (ip6 + 1); + pseudo_checksum = ipv6_pseudo_header_checksum(ip6, ntohs(udp->len), IPPROTO_UDP); + break; + } + default: + FAIL() << "unsupported IP version" << version << "\n"; + return; + } + + udp->check = 0; + udp->check = ip_checksum_finish(ip_checksum_add(pseudo_checksum, udp, ntohs(udp->len))); +} + +// Testing stub for send_rawv6. The real version uses sendmsg() with a +// destination IPv6 address, and attempting to call that on our test socketpair +// fd results in EINVAL. +extern "C" void send_rawv6(int fd, clat_packet out, int iov_len) { + writev(fd, out, iov_len); +} + +void do_translate_packet(const uint8_t *original, size_t original_len, uint8_t *out, size_t *outlen, + const char *msg) { + int fds[2]; + if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0, fds)) { + abort(); + } + + char foo[512]; + snprintf(foo, sizeof(foo), "%s: Invalid original packet", msg); + check_packet(original, original_len, foo); + + int read_fd, write_fd; + uint16_t expected_proto; + int version = ip_version(original); + switch (version) { + case 4: + expected_proto = htons(ETH_P_IPV6); + read_fd = fds[1]; + write_fd = fds[0]; + break; + case 6: + expected_proto = htons(ETH_P_IP); + read_fd = fds[0]; + write_fd = fds[1]; + break; + default: + FAIL() << msg << ": Unsupported IP version " << version << "\n"; + break; + } + + translate_packet(write_fd, (version == 4), original, original_len); + + snprintf(foo, sizeof(foo), "%s: Invalid translated packet", msg); + if (version == 6) { + // Translating to IPv4. Expect a tun header. + struct tun_pi new_tun_header; + struct iovec iov[] = { + { &new_tun_header, sizeof(new_tun_header) }, + { out, *outlen } + }; + int len = readv(read_fd, iov, 2); + if (len > (int) sizeof(new_tun_header)) { + ASSERT_LT((size_t) len, *outlen) << msg << ": Translated packet buffer too small\n"; + EXPECT_EQ(expected_proto, new_tun_header.proto) << msg << "Unexpected tun proto\n"; + *outlen = len - sizeof(new_tun_header); + check_packet(out, *outlen, msg); + } else { + FAIL() << msg << ": Packet was not translated: len=" << len; + *outlen = 0; + } + } else { + // Translating to IPv6. Expect raw packet. + *outlen = read(read_fd, out, *outlen); + check_packet(out, *outlen, msg); + } +} + +void check_translated_packet(const uint8_t *original, size_t original_len, + const uint8_t *expected, size_t expected_len, const char *msg) { + uint8_t translated[MAXMTU]; + size_t translated_len = sizeof(translated); + do_translate_packet(original, original_len, translated, &translated_len, msg); + EXPECT_EQ(expected_len, translated_len) << msg << ": Translated packet length incorrect\n"; + check_data_matches(expected, translated, translated_len, msg); +} + +void check_fragment_translation(const uint8_t *original[], const size_t original_lengths[], + const uint8_t *expected[], const size_t expected_lengths[], + int numfragments, const char *msg) { + for (int i = 0; i < numfragments; i++) { + // Check that each of the fragments translates as expected. + char frag_msg[512]; + snprintf(frag_msg, sizeof(frag_msg), "%s: fragment #%d", msg, i + 1); + check_translated_packet(original[i], original_lengths[i], + expected[i], expected_lengths[i], frag_msg); + } + + // Sanity check that reassembling the original and translated fragments produces valid packets. + uint8_t reassembled[MAXMTU]; + size_t reassembled_len = sizeof(reassembled); + reassemble_packet(original, original_lengths, numfragments, reassembled, &reassembled_len, msg); + check_packet(reassembled, reassembled_len, msg); + + uint8_t translated[MAXMTU]; + size_t translated_len = sizeof(translated); + do_translate_packet(reassembled, reassembled_len, translated, &translated_len, msg); + check_packet(translated, translated_len, msg); +} + +int get_transport_checksum(const uint8_t *packet) { + struct iphdr *ip; + struct ip6_hdr *ip6; + uint8_t protocol; + const void *payload; + + int version = ip_version(packet); + switch (version) { + case 4: + ip = (struct iphdr *) packet; + if (is_ipv4_fragment(ip)) { + return -1; + } + protocol = ip->protocol; + payload = ip + 1; + break; + case 6: + ip6 = (struct ip6_hdr *) packet; + protocol = ip6->ip6_nxt; + payload = ip6 + 1; + break; + default: + return -1; + } + + switch (protocol) { + case IPPROTO_UDP: + return ((struct udphdr *) payload)->check; + + case IPPROTO_TCP: + return ((struct tcphdr *) payload)->check; + + case IPPROTO_FRAGMENT: + default: + return -1; + } +} + +struct clat_config Global_Clatd_Config; + +class ClatdTest : public ::testing::Test { + protected: + virtual void SetUp() { + inet_pton(AF_INET, kIPv4LocalAddr, &Global_Clatd_Config.ipv4_local_subnet); + inet_pton(AF_INET6, kIPv6PlatSubnet, &Global_Clatd_Config.plat_subnet); + inet_pton(AF_INET6, kIPv6LocalAddr, &Global_Clatd_Config.ipv6_local_subnet); + Global_Clatd_Config.ipv6_host_id = in6addr_any; + Global_Clatd_Config.use_dynamic_iid = 1; + } +}; + +void expect_ipv6_addr_equal(struct in6_addr *expected, struct in6_addr *actual) { + if (!IN6_ARE_ADDR_EQUAL(expected, actual)) { + char expected_str[INET6_ADDRSTRLEN], actual_str[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, expected, expected_str, sizeof(expected_str)); + inet_ntop(AF_INET6, actual, actual_str, sizeof(actual_str)); + FAIL() + << "Unexpected IPv6 address:: " + << "\n Expected: " << expected_str + << "\n Actual: " << actual_str + << "\n"; + } +} + +TEST_F(ClatdTest, TestIPv6PrefixEqual) { + EXPECT_TRUE(ipv6_prefix_equal(&Global_Clatd_Config.plat_subnet, + &Global_Clatd_Config.plat_subnet)); + EXPECT_FALSE(ipv6_prefix_equal(&Global_Clatd_Config.plat_subnet, + &Global_Clatd_Config.ipv6_local_subnet)); + + struct in6_addr subnet2 = Global_Clatd_Config.ipv6_local_subnet; + EXPECT_TRUE(ipv6_prefix_equal(&Global_Clatd_Config.ipv6_local_subnet, &subnet2)); + EXPECT_TRUE(ipv6_prefix_equal(&subnet2, &Global_Clatd_Config.ipv6_local_subnet)); + + subnet2.s6_addr[6] = 0xff; + EXPECT_FALSE(ipv6_prefix_equal(&Global_Clatd_Config.ipv6_local_subnet, &subnet2)); + EXPECT_FALSE(ipv6_prefix_equal(&subnet2, &Global_Clatd_Config.ipv6_local_subnet)); +} + +int count_onebits(const void *data, size_t size) { + int onebits = 0; + for (size_t pos = 0; pos < size; pos++) { + uint8_t *byte = ((uint8_t*) data) + pos; + for (int shift = 0; shift < 8; shift++) { + onebits += (*byte >> shift) & 1; + } + } + return onebits; +} + +TEST_F(ClatdTest, TestCountOnebits) { + uint64_t i; + i = 1; + ASSERT_EQ(1, count_onebits(&i, sizeof(i))); + i <<= 61; + ASSERT_EQ(1, count_onebits(&i, sizeof(i))); + i |= ((uint64_t) 1 << 33); + ASSERT_EQ(2, count_onebits(&i, sizeof(i))); + i = 0xf1000202020000f0; + ASSERT_EQ(5 + 1 + 1 + 1 + 4, count_onebits(&i, sizeof(i))); +} + +TEST_F(ClatdTest, TestGenIIDConfigured) { + struct in6_addr myaddr, expected; + Global_Clatd_Config.use_dynamic_iid = 0; + ASSERT_TRUE(inet_pton(AF_INET6, "::bad:ace:d00d", &Global_Clatd_Config.ipv6_host_id)); + ASSERT_TRUE(inet_pton(AF_INET6, "2001:db8:1:2:0:bad:ace:d00d", &expected)); + ASSERT_TRUE(inet_pton(AF_INET6, "2001:db8:1:2:f076:ae99:124e:aa54", &myaddr)); + config_generate_local_ipv6_subnet(&myaddr); + expect_ipv6_addr_equal(&expected, &myaddr); + + Global_Clatd_Config.use_dynamic_iid = 1; + config_generate_local_ipv6_subnet(&myaddr); + EXPECT_FALSE(IN6_ARE_ADDR_EQUAL(&expected, &myaddr)); +} + +TEST_F(ClatdTest, TestGenIIDRandom) { + struct in6_addr interface_ipv6; + ASSERT_TRUE(inet_pton(AF_INET6, "2001:db8:1:2:f076:ae99:124e:aa54", &interface_ipv6)); + Global_Clatd_Config.ipv6_host_id = in6addr_any; + + // Generate a boatload of random IIDs. + int onebits = 0; + uint64_t prev_iid = 0; + for (int i = 0; i < 100000; i++) { + struct in6_addr myaddr = interface_ipv6; + + config_generate_local_ipv6_subnet(&myaddr); + + // Check the generated IP address is in the same prefix as the interface IPv6 address. + EXPECT_TRUE(ipv6_prefix_equal(&interface_ipv6, &myaddr)); + + // Check that consecutive IIDs are not the same. + uint64_t iid = * (uint64_t*) (&myaddr.s6_addr[8]); + ASSERT_TRUE(iid != prev_iid) + << "Two consecutive random IIDs are the same: " + << std::showbase << std::hex + << iid << "\n"; + prev_iid = iid; + + // Check that the IID is checksum-neutral with the NAT64 prefix and the + // local prefix. + struct in_addr *ipv4addr = &Global_Clatd_Config.ipv4_local_subnet; + struct in6_addr *plat_subnet = &Global_Clatd_Config.plat_subnet; + + uint16_t c1 = ip_checksum_finish(ip_checksum_add(0, ipv4addr, sizeof(*ipv4addr))); + uint16_t c2 = ip_checksum_finish(ip_checksum_add(0, plat_subnet, sizeof(*plat_subnet)) + + ip_checksum_add(0, &myaddr, sizeof(myaddr))); + + if (c1 != c2) { + char myaddr_str[INET6_ADDRSTRLEN], plat_str[INET6_ADDRSTRLEN], ipv4_str[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &myaddr, myaddr_str, sizeof(myaddr_str)); + inet_ntop(AF_INET6, plat_subnet, plat_str, sizeof(plat_str)); + inet_ntop(AF_INET, ipv4addr, ipv4_str, sizeof(ipv4_str)); + FAIL() + << "Bad IID: " << myaddr_str + << " not checksum-neutral with " << ipv4_str << " and " << plat_str + << std::showbase << std::hex + << "\n IPv4 checksum: " << c1 + << "\n IPv6 checksum: " << c2 + << "\n"; + } + + // Check that IIDs are roughly random and use all the bits by counting the + // total number of bits set to 1 in a random sample of 100000 generated IIDs. + onebits += count_onebits(&iid, sizeof(iid)); + } + EXPECT_LE(3190000, onebits); + EXPECT_GE(3210000, onebits); +} + +extern "C" addr_free_func config_is_ipv4_address_free; +int never_free(in_addr_t /* addr */) { return 0; } +int always_free(in_addr_t /* addr */) { return 1; } +int only2_free(in_addr_t addr) { return (ntohl(addr) & 0xff) == 2; } +int over6_free(in_addr_t addr) { return (ntohl(addr) & 0xff) >= 6; } +int only10_free(in_addr_t addr) { return (ntohl(addr) & 0xff) == 10; } + +TEST_F(ClatdTest, SelectIPv4Address) { + struct in_addr addr; + + inet_pton(AF_INET, kIPv4LocalAddr, &addr); + + addr_free_func orig_config_is_ipv4_address_free = config_is_ipv4_address_free; + + // If no addresses are free, return INADDR_NONE. + config_is_ipv4_address_free = never_free; + EXPECT_EQ(INADDR_NONE, config_select_ipv4_address(&addr, 29)); + EXPECT_EQ(INADDR_NONE, config_select_ipv4_address(&addr, 16)); + + // If the configured address is free, pick that. But a prefix that's too big is invalid. + config_is_ipv4_address_free = always_free; + EXPECT_EQ(inet_addr(kIPv4LocalAddr), config_select_ipv4_address(&addr, 29)); + EXPECT_EQ(inet_addr(kIPv4LocalAddr), config_select_ipv4_address(&addr, 20)); + EXPECT_EQ(INADDR_NONE, config_select_ipv4_address(&addr, 15)); + + // A prefix length of 32 works, but anything above it is invalid. + EXPECT_EQ(inet_addr(kIPv4LocalAddr), config_select_ipv4_address(&addr, 32)); + EXPECT_EQ(INADDR_NONE, config_select_ipv4_address(&addr, 33)); + + // If another address is free, pick it. + config_is_ipv4_address_free = over6_free; + EXPECT_EQ(inet_addr("192.0.0.6"), config_select_ipv4_address(&addr, 29)); + + // Check that we wrap around to addresses that are lower than the first address. + config_is_ipv4_address_free = only2_free; + EXPECT_EQ(inet_addr("192.0.0.2"), config_select_ipv4_address(&addr, 29)); + EXPECT_EQ(INADDR_NONE, config_select_ipv4_address(&addr, 30)); + + // If a free address exists outside the prefix, we don't pick it. + config_is_ipv4_address_free = only10_free; + EXPECT_EQ(INADDR_NONE, config_select_ipv4_address(&addr, 29)); + EXPECT_EQ(inet_addr("192.0.0.10"), config_select_ipv4_address(&addr, 24)); + + // Now try using the real function which sees if IP addresses are free using bind(). + // Assume that the machine running the test has the address 127.0.0.1, but not 8.8.8.8. + config_is_ipv4_address_free = orig_config_is_ipv4_address_free; + addr.s_addr = inet_addr("8.8.8.8"); + EXPECT_EQ(inet_addr("8.8.8.8"), config_select_ipv4_address(&addr, 29)); + + addr.s_addr = inet_addr("127.0.0.1"); + EXPECT_EQ(inet_addr("127.0.0.2"), config_select_ipv4_address(&addr, 29)); +} + +TEST_F(ClatdTest, DataSanitycheck) { + // Sanity checks the data. + uint8_t v4_header[] = { IPV4_UDP_HEADER }; + ASSERT_EQ(sizeof(struct iphdr), sizeof(v4_header)) << "Test IPv4 header: incorrect length\n"; + + uint8_t v6_header[] = { IPV6_UDP_HEADER }; + ASSERT_EQ(sizeof(struct ip6_hdr), sizeof(v6_header)) << "Test IPv6 header: incorrect length\n"; + + uint8_t udp_header[] = { UDP_HEADER }; + ASSERT_EQ(sizeof(struct udphdr), sizeof(udp_header)) << "Test UDP header: incorrect length\n"; + + // Sanity checks check_packet. + struct udphdr *udp; + uint8_t v4_udp_packet[] = { IPV4_UDP_HEADER UDP_HEADER PAYLOAD }; + udp = (struct udphdr *) (v4_udp_packet + sizeof(struct iphdr)); + fix_udp_checksum(v4_udp_packet); + ASSERT_EQ(kUdpV4Checksum, udp->check) << "UDP/IPv4 packet checksum sanity check\n"; + check_packet(v4_udp_packet, sizeof(v4_udp_packet), "UDP/IPv4 packet sanity check"); + + uint8_t v6_udp_packet[] = { IPV6_UDP_HEADER UDP_HEADER PAYLOAD }; + udp = (struct udphdr *) (v6_udp_packet + sizeof(struct ip6_hdr)); + fix_udp_checksum(v6_udp_packet); + ASSERT_EQ(kUdpV6Checksum, udp->check) << "UDP/IPv6 packet checksum sanity check\n"; + check_packet(v6_udp_packet, sizeof(v6_udp_packet), "UDP/IPv6 packet sanity check"); + + uint8_t ipv4_ping[] = { IPV4_ICMP_HEADER IPV4_PING PAYLOAD }; + check_packet(ipv4_ping, sizeof(ipv4_ping), "IPv4 ping sanity check"); + + uint8_t ipv6_ping[] = { IPV6_ICMPV6_HEADER IPV6_PING PAYLOAD }; + check_packet(ipv6_ping, sizeof(ipv6_ping), "IPv6 ping sanity check"); + + // Sanity checks reassemble_packet. + uint8_t reassembled[MAXMTU]; + size_t total_length = sizeof(reassembled); + reassemble_packet(kIPv4Fragments, kIPv4FragLengths, ARRAYSIZE(kIPv4Fragments), + reassembled, &total_length, "Reassembly sanity check"); + check_packet(reassembled, total_length, "IPv4 Reassembled packet is valid"); + ASSERT_EQ(sizeof(kReassembledIPv4), total_length) << "IPv4 reassembly sanity check: length\n"; + ASSERT_TRUE(!is_ipv4_fragment((struct iphdr *) reassembled)) + << "Sanity check: reassembled packet is a fragment!\n"; + check_data_matches(kReassembledIPv4, reassembled, total_length, "IPv4 reassembly sanity check"); + + total_length = sizeof(reassembled); + reassemble_packet(kIPv6Fragments, kIPv6FragLengths, ARRAYSIZE(kIPv6Fragments), + reassembled, &total_length, "IPv6 reassembly sanity check"); + ASSERT_TRUE(!is_ipv6_fragment((struct ip6_hdr *) reassembled, total_length)) + << "Sanity check: reassembled packet is a fragment!\n"; + check_packet(reassembled, total_length, "IPv6 Reassembled packet is valid"); +} + +TEST_F(ClatdTest, PseudoChecksum) { + uint32_t pseudo_checksum; + + uint8_t v4_header[] = { IPV4_UDP_HEADER }; + uint8_t v4_pseudo_header[] = { IPV4_PSEUDOHEADER(v4_header, UDP_LEN) }; + pseudo_checksum = ipv4_pseudo_header_checksum((struct iphdr *) v4_header, UDP_LEN); + EXPECT_EQ(ip_checksum_finish(pseudo_checksum), + ip_checksum(v4_pseudo_header, sizeof(v4_pseudo_header))) + << "ipv4_pseudo_header_checksum incorrect\n"; + + uint8_t v6_header[] = { IPV6_UDP_HEADER }; + uint8_t v6_pseudo_header[] = { IPV6_PSEUDOHEADER(v6_header, IPPROTO_UDP, UDP_LEN) }; + pseudo_checksum = ipv6_pseudo_header_checksum((struct ip6_hdr *) v6_header, UDP_LEN, IPPROTO_UDP); + EXPECT_EQ(ip_checksum_finish(pseudo_checksum), + ip_checksum(v6_pseudo_header, sizeof(v6_pseudo_header))) + << "ipv6_pseudo_header_checksum incorrect\n"; +} + +TEST_F(ClatdTest, TransportChecksum) { + uint8_t udphdr[] = { UDP_HEADER }; + uint8_t payload[] = { PAYLOAD }; + EXPECT_EQ(kUdpPartialChecksum, ip_checksum_add(0, udphdr, sizeof(udphdr))) + << "UDP partial checksum\n"; + EXPECT_EQ(kPayloadPartialChecksum, ip_checksum_add(0, payload, sizeof(payload))) + << "Payload partial checksum\n"; + + uint8_t ip[] = { IPV4_UDP_HEADER }; + uint8_t ip6[] = { IPV6_UDP_HEADER }; + uint32_t ipv4_pseudo_sum = ipv4_pseudo_header_checksum((struct iphdr *) ip, UDP_LEN); + uint32_t ipv6_pseudo_sum = ipv6_pseudo_header_checksum((struct ip6_hdr *) ip6, UDP_LEN, + IPPROTO_UDP); + + EXPECT_EQ(0x3ad0U, ipv4_pseudo_sum) << "IPv4 pseudo-checksum sanity check\n"; + EXPECT_EQ(0x2644bU, ipv6_pseudo_sum) << "IPv6 pseudo-checksum sanity check\n"; + EXPECT_EQ( + kUdpV4Checksum, + ip_checksum_finish(ipv4_pseudo_sum + kUdpPartialChecksum + kPayloadPartialChecksum)) + << "Unexpected UDP/IPv4 checksum\n"; + EXPECT_EQ( + kUdpV6Checksum, + ip_checksum_finish(ipv6_pseudo_sum + kUdpPartialChecksum + kPayloadPartialChecksum)) + << "Unexpected UDP/IPv6 checksum\n"; + + EXPECT_EQ(kUdpV6Checksum, + ip_checksum_adjust(kUdpV4Checksum, ipv4_pseudo_sum, ipv6_pseudo_sum)) + << "Adjust IPv4/UDP checksum to IPv6\n"; + EXPECT_EQ(kUdpV4Checksum, + ip_checksum_adjust(kUdpV6Checksum, ipv6_pseudo_sum, ipv4_pseudo_sum)) + << "Adjust IPv6/UDP checksum to IPv4\n"; +} + +TEST_F(ClatdTest, AdjustChecksum) { + struct checksum_data { + uint16_t checksum; + uint32_t old_hdr_sum; + uint32_t new_hdr_sum; + uint16_t result; + } DATA[] = { + { 0x1423, 0xb8ec, 0x2d757, 0xf5b5 }, + { 0xf5b5, 0x2d757, 0xb8ec, 0x1423 }, + { 0xdd2f, 0x5555, 0x3285, 0x0000 }, + { 0x1215, 0x5560, 0x15560 + 20, 0x1200 }, + { 0xd0c7, 0x3ad0, 0x2644b, 0xa74a }, + }; + unsigned i = 0; + + for (i = 0; i < ARRAYSIZE(DATA); i++) { + struct checksum_data *data = DATA + i; + uint16_t result = ip_checksum_adjust(data->checksum, data->old_hdr_sum, data->new_hdr_sum); + EXPECT_EQ(result, data->result) + << "Incorrect checksum" << std::showbase << std::hex + << "\n Expected: " << data->result + << "\n Actual: " << result + << "\n checksum=" << data->checksum + << " old_sum=" << data->old_hdr_sum << " new_sum=" << data->new_hdr_sum << "\n"; + } +} + +TEST_F(ClatdTest, Translate) { + uint8_t udp_ipv4[] = { IPV4_UDP_HEADER UDP_HEADER PAYLOAD }; + uint8_t udp_ipv6[] = { IPV6_UDP_HEADER UDP_HEADER PAYLOAD }; + fix_udp_checksum(udp_ipv4); + fix_udp_checksum(udp_ipv6); + check_translated_packet(udp_ipv4, sizeof(udp_ipv4), udp_ipv6, sizeof(udp_ipv6), + "UDP/IPv4 -> UDP/IPv6 translation"); + check_translated_packet(udp_ipv6, sizeof(udp_ipv6), udp_ipv4, sizeof(udp_ipv4), + "UDP/IPv6 -> UDP/IPv4 translation"); + + uint8_t ipv4_ping[] = { IPV4_ICMP_HEADER IPV4_PING PAYLOAD }; + uint8_t ipv6_ping[] = { IPV6_ICMPV6_HEADER IPV6_PING PAYLOAD }; + check_translated_packet(ipv4_ping, sizeof(ipv4_ping), ipv6_ping, sizeof(ipv6_ping), + "ICMP->ICMPv6 translation"); + check_translated_packet(ipv6_ping, sizeof(ipv6_ping), ipv4_ping, sizeof(ipv4_ping), + "ICMPv6->ICMP translation"); +} + +TEST_F(ClatdTest, Fragmentation) { + check_fragment_translation(kIPv4Fragments, kIPv4FragLengths, + kIPv6Fragments, kIPv6FragLengths, + ARRAYSIZE(kIPv4Fragments), "IPv4->IPv6 fragment translation"); + + check_fragment_translation(kIPv6Fragments, kIPv6FragLengths, + kIPv4Fragments, kIPv4FragLengths, + ARRAYSIZE(kIPv6Fragments), "IPv6->IPv4 fragment translation"); +} + +void check_translate_checksum_neutral(const uint8_t *original, size_t original_len, + size_t expected_len, const char *msg) { + uint8_t translated[MAXMTU]; + size_t translated_len = sizeof(translated); + do_translate_packet(original, original_len, translated, &translated_len, msg); + EXPECT_EQ(expected_len, translated_len) << msg << ": Translated packet length incorrect\n"; + // do_translate_packet already checks packets for validity and verifies the checksum. + int original_check = get_transport_checksum(original); + int translated_check = get_transport_checksum(translated); + ASSERT_NE(-1, original_check); + ASSERT_NE(-1, translated_check); + ASSERT_EQ(original_check, translated_check) + << "Not checksum neutral: original and translated checksums differ\n"; +} + +TEST_F(ClatdTest, TranslateChecksumNeutral) { + // Generate a random clat IPv6 address and check that translation is checksum-neutral. + Global_Clatd_Config.ipv6_host_id = in6addr_any; + ASSERT_TRUE(inet_pton(AF_INET6, "2001:db8:1:2:f076:ae99:124e:aa54", + &Global_Clatd_Config.ipv6_local_subnet)); + config_generate_local_ipv6_subnet(&Global_Clatd_Config.ipv6_local_subnet); + ASSERT_NE((uint32_t) 0x00000464, Global_Clatd_Config.ipv6_local_subnet.s6_addr32[3]); + ASSERT_NE((uint32_t) 0, Global_Clatd_Config.ipv6_local_subnet.s6_addr32[3]); + + // Check that translating UDP packets is checksum-neutral. First, IPv4. + uint8_t udp_ipv4[] = { IPV4_UDP_HEADER UDP_HEADER PAYLOAD }; + fix_udp_checksum(udp_ipv4); + check_translate_checksum_neutral(udp_ipv4, sizeof(udp_ipv4), sizeof(udp_ipv4) + 20, + "UDP/IPv4 -> UDP/IPv6 checksum neutral"); + + // Now try IPv6. + uint8_t udp_ipv6[] = { IPV6_UDP_HEADER UDP_HEADER PAYLOAD }; + // The test packet uses the static IID, not the random IID. Fix up the source address. + struct ip6_hdr *ip6 = (struct ip6_hdr *) udp_ipv6; + memcpy(&ip6->ip6_src, &Global_Clatd_Config.ipv6_local_subnet, sizeof(ip6->ip6_src)); + fix_udp_checksum(udp_ipv6); + check_translate_checksum_neutral(udp_ipv4, sizeof(udp_ipv4), sizeof(udp_ipv4) + 20, + "UDP/IPv4 -> UDP/IPv6 checksum neutral"); +} diff --git a/config.c b/config.c new file mode 100644 index 0000000..b147868 --- /dev/null +++ b/config.c @@ -0,0 +1,395 @@ +/* + * Copyright 2011 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * config.c - configuration settings + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "config.h" +#include "dns64.h" +#include "logging.h" +#include "getaddr.h" +#include "clatd.h" +#include "checksum.h" + +struct clat_config Global_Clatd_Config; + +/* function: config_item_str + * locates the config item and returns the pointer to a string, or NULL on failure. Caller frees pointer + * root - parsed configuration + * item_name - name of config item to locate + * defaultvar - value to use if config item isn't present + */ +char *config_item_str(cnode *root, const char *item_name, const char *defaultvar) { + const char *tmp; + + if(!(tmp = config_str(root, item_name, defaultvar))) { + logmsg(ANDROID_LOG_FATAL,"%s config item needed",item_name); + return NULL; + } + return strdup(tmp); +} + +/* function: config_item_int16_t + * locates the config item, parses the integer, and returns the pointer ret_val_ptr, or NULL on failure + * root - parsed configuration + * item_name - name of config item to locate + * defaultvar - value to use if config item isn't present + * ret_val_ptr - pointer for return value storage + */ +int16_t *config_item_int16_t(cnode *root, const char *item_name, const char *defaultvar, int16_t *ret_val_ptr) { + const char *tmp; + char *endptr; + long int conf_int; + + if(!(tmp = config_str(root, item_name, defaultvar))) { + logmsg(ANDROID_LOG_FATAL,"%s config item needed",item_name); + return NULL; + } + + errno = 0; + conf_int = strtol(tmp,&endptr,10); + if(errno > 0) { + logmsg(ANDROID_LOG_FATAL,"%s config item is not numeric: %s (error=%s)",item_name,tmp,strerror(errno)); + return NULL; + } + if(endptr == tmp || *tmp == '\0') { + logmsg(ANDROID_LOG_FATAL,"%s config item is not numeric: %s",item_name,tmp); + return NULL; + } + if(*endptr != '\0') { + logmsg(ANDROID_LOG_FATAL,"%s config item contains non-numeric characters: %s",item_name,endptr); + return NULL; + } + if(conf_int > INT16_MAX || conf_int < INT16_MIN) { + logmsg(ANDROID_LOG_FATAL,"%s config item is too big/small: %d",item_name,conf_int); + return NULL; + } + *ret_val_ptr = conf_int; + return ret_val_ptr; +} + +/* function: config_item_ip + * locates the config item, parses the ipv4 address, and returns the pointer ret_val_ptr, or NULL on failure + * root - parsed configuration + * item_name - name of config item to locate + * defaultvar - value to use if config item isn't present + * ret_val_ptr - pointer for return value storage + */ +struct in_addr *config_item_ip(cnode *root, const char *item_name, const char *defaultvar, struct in_addr *ret_val_ptr) { + const char *tmp; + int status; + + if(!(tmp = config_str(root, item_name, defaultvar))) { + logmsg(ANDROID_LOG_FATAL,"%s config item needed",item_name); + return NULL; + } + + status = inet_pton(AF_INET, tmp, ret_val_ptr); + if(status <= 0) { + logmsg(ANDROID_LOG_FATAL,"invalid IPv4 address specified for %s: %s", item_name, tmp); + return NULL; + } + + return ret_val_ptr; +} + +/* function: config_item_ip6 + * locates the config item, parses the ipv6 address, and returns the pointer ret_val_ptr, or NULL on failure + * root - parsed configuration + * item_name - name of config item to locate + * defaultvar - value to use if config item isn't present + * ret_val_ptr - pointer for return value storage + */ +struct in6_addr *config_item_ip6(cnode *root, const char *item_name, const char *defaultvar, struct in6_addr *ret_val_ptr) { + const char *tmp; + int status; + + if(!(tmp = config_str(root, item_name, defaultvar))) { + logmsg(ANDROID_LOG_FATAL,"%s config item needed",item_name); + return NULL; + } + + status = inet_pton(AF_INET6, tmp, ret_val_ptr); + if(status <= 0) { + logmsg(ANDROID_LOG_FATAL,"invalid IPv6 address specified for %s: %s", item_name, tmp); + return NULL; + } + + return ret_val_ptr; +} + +/* function: free_config + * frees the memory used by the global config variable + */ +void free_config() { + if(Global_Clatd_Config.plat_from_dns64_hostname) { + free(Global_Clatd_Config.plat_from_dns64_hostname); + Global_Clatd_Config.plat_from_dns64_hostname = NULL; + } +} + +/* function: ipv6_prefix_equal + * compares the prefixes two ipv6 addresses. assumes the prefix lengths are both /64. + * a1 - first address + * a2 - second address + * returns: 0 if the subnets are different, 1 if they are the same. + */ +int ipv6_prefix_equal(struct in6_addr *a1, struct in6_addr *a2) { + return !memcmp(a1, a2, 8); +} + +/* function: dns64_detection + * does dns lookups to set the plat subnet or exits on failure, waits forever for a dns response with a query backoff timer + * net_id - (optional) netId to use, NETID_UNSET indicates use of default network + */ +void dns64_detection(unsigned net_id) { + int backoff_sleep, status; + struct in6_addr tmp_ptr; + + backoff_sleep = 1; + + while(1) { + status = plat_prefix(Global_Clatd_Config.plat_from_dns64_hostname,net_id,&tmp_ptr); + if(status > 0) { + memcpy(&Global_Clatd_Config.plat_subnet, &tmp_ptr, sizeof(struct in6_addr)); + return; + } + logmsg(ANDROID_LOG_WARN, "dns64_detection -- error, sleeping for %d seconds", backoff_sleep); + sleep(backoff_sleep); + backoff_sleep *= 2; + if(backoff_sleep >= 120) { + backoff_sleep = 120; + } + } +} + +/* function: gen_random_iid + * picks a random interface ID that is checksum neutral with the IPv4 address and the NAT64 prefix + * myaddr - IPv6 address to write to + * ipv4_local_subnet - clat IPv4 address + * plat_subnet - NAT64 prefix + */ +void gen_random_iid(struct in6_addr *myaddr, struct in_addr *ipv4_local_subnet, + struct in6_addr *plat_subnet) { + // Fill last 8 bytes of IPv6 address with random bits. + arc4random_buf(&myaddr->s6_addr[8], 8); + + // Make the IID checksum-neutral. That is, make it so that: + // checksum(Local IPv4 | Remote IPv4) = checksum(Local IPv6 | Remote IPv6) + // in other words (because remote IPv6 = NAT64 prefix | Remote IPv4): + // checksum(Local IPv4) = checksum(Local IPv6 | NAT64 prefix) + // Do this by adjusting the two bytes in the middle of the IID. + + uint16_t middlebytes = (myaddr->s6_addr[11] << 8) + myaddr->s6_addr[12]; + + uint32_t c1 = ip_checksum_add(0, ipv4_local_subnet, sizeof(*ipv4_local_subnet)); + uint32_t c2 = ip_checksum_add(0, plat_subnet, sizeof(*plat_subnet)) + + ip_checksum_add(0, myaddr, sizeof(*myaddr)); + + uint16_t delta = ip_checksum_adjust(middlebytes, c1, c2); + myaddr->s6_addr[11] = delta >> 8; + myaddr->s6_addr[12] = delta & 0xff; +} + +// Factored out to a separate function for testability. +int connect_is_ipv4_address_free(in_addr_t addr) { + int s = socket(AF_INET, SOCK_DGRAM, 0); + if (s == -1) { + return 0; + } + + // Attempt to connect to the address. If the connection succeeds and getsockname returns the same + // the address then the address is already assigned to the system and we can't use it. + struct sockaddr_in sin = { .sin_family = AF_INET, .sin_addr = { addr }, .sin_port = 53 }; + socklen_t len = sizeof(sin); + int inuse = connect(s, (struct sockaddr *) &sin, sizeof(sin)) == 0 && + getsockname(s, (struct sockaddr *) &sin, &len) == 0 && + (size_t) len >= sizeof(sin) && + sin.sin_addr.s_addr == addr; + + close(s); + return !inuse; +} + +addr_free_func config_is_ipv4_address_free = connect_is_ipv4_address_free; + +/* function: config_select_ipv4_address + * picks a free IPv4 address, starting from ip and trying all addresses in the prefix in order + * ip - the IP address from the configuration file + * prefixlen - the length of the prefix from which addresses may be selected. + * returns: the IPv4 address, or INADDR_NONE if no addresses were available + */ +in_addr_t config_select_ipv4_address(const struct in_addr *ip, int16_t prefixlen) { + in_addr_t chosen = INADDR_NONE; + + // Don't accept prefixes that are too large because we scan addresses one by one. + if (prefixlen < 16 || prefixlen > 32) { + return chosen; + } + + // All these are in host byte order. + in_addr_t mask = 0xffffffff >> (32 - prefixlen) << (32 - prefixlen); + in_addr_t ipv4 = ntohl(ip->s_addr); + in_addr_t first_ipv4 = ipv4; + in_addr_t prefix = ipv4 & mask; + + // Pick the first IPv4 address in the pool, wrapping around if necessary. + // So, for example, 192.0.0.4 -> 192.0.0.5 -> 192.0.0.6 -> 192.0.0.7 -> 192.0.0.0. + do { + if (config_is_ipv4_address_free(htonl(ipv4))) { + chosen = htonl(ipv4); + break; + } + ipv4 = prefix | ((ipv4 + 1) & ~mask); + } while (ipv4 != first_ipv4); + + return chosen; +} + +/* function: config_generate_local_ipv6_subnet + * generates the local ipv6 subnet when given the interface ip + * requires config.ipv6_host_id + * interface_ip - in: interface ip, out: local ipv6 host address + */ +void config_generate_local_ipv6_subnet(struct in6_addr *interface_ip) { + int i; + + if (Global_Clatd_Config.use_dynamic_iid) { + /* Generate a random interface ID. */ + gen_random_iid(interface_ip, + &Global_Clatd_Config.ipv4_local_subnet, + &Global_Clatd_Config.plat_subnet); + } else { + /* Use the specified interface ID. */ + for(i = 2; i < 4; i++) { + interface_ip->s6_addr32[i] = Global_Clatd_Config.ipv6_host_id.s6_addr32[i]; + } + } +} + +/* function: read_config + * reads the config file and parses it into the global variable Global_Clatd_Config. returns 0 on failure, 1 on success + * file - filename to parse + * uplink_interface - interface to use to reach the internet and supplier of address space + * plat_prefix - (optional) plat prefix to use, otherwise follow config file + * net_id - (optional) netId to use, NETID_UNSET indicates use of default network + */ +int read_config(const char *file, const char *uplink_interface, const char *plat_prefix, + unsigned net_id) { + cnode *root = config_node("", ""); + void *tmp_ptr = NULL; + unsigned flags; + + if(!root) { + logmsg(ANDROID_LOG_FATAL,"out of memory"); + return 0; + } + + memset(&Global_Clatd_Config, '\0', sizeof(Global_Clatd_Config)); + + config_load_file(root, file); + if(root->first_child == NULL) { + logmsg(ANDROID_LOG_FATAL,"Could not read config file %s", file); + goto failed; + } + + Global_Clatd_Config.default_pdp_interface = strdup(uplink_interface); + if (!Global_Clatd_Config.default_pdp_interface) + goto failed; + + if(!config_item_int16_t(root, "mtu", "-1", &Global_Clatd_Config.mtu)) + goto failed; + + if(!config_item_int16_t(root, "ipv4mtu", "-1", &Global_Clatd_Config.ipv4mtu)) + goto failed; + + if(!config_item_ip(root, "ipv4_local_subnet", DEFAULT_IPV4_LOCAL_SUBNET, + &Global_Clatd_Config.ipv4_local_subnet)) + goto failed; + + if(!config_item_int16_t(root, "ipv4_local_prefixlen", DEFAULT_IPV4_LOCAL_PREFIXLEN, + &Global_Clatd_Config.ipv4_local_prefixlen)) + goto failed; + + if(plat_prefix) { // plat subnet is coming from the command line + if(inet_pton(AF_INET6, plat_prefix, &Global_Clatd_Config.plat_subnet) <= 0) { + logmsg(ANDROID_LOG_FATAL,"invalid IPv6 address specified for plat prefix: %s", plat_prefix); + goto failed; + } + } else { + tmp_ptr = (void *)config_item_str(root, "plat_from_dns64", "yes"); + if(!tmp_ptr || strcmp(tmp_ptr, "no") == 0) { + free(tmp_ptr); + + if(!config_item_ip6(root, "plat_subnet", NULL, &Global_Clatd_Config.plat_subnet)) { + logmsg(ANDROID_LOG_FATAL, "plat_from_dns64 disabled, but no plat_subnet specified"); + goto failed; + } + } else { + free(tmp_ptr); + + if(!(Global_Clatd_Config.plat_from_dns64_hostname = config_item_str(root, "plat_from_dns64_hostname", DEFAULT_DNS64_DETECTION_HOSTNAME))) + goto failed; + dns64_detection(net_id); + } + } + + if (!config_item_ip6(root, "ipv6_host_id", "::", &Global_Clatd_Config.ipv6_host_id)) + goto failed; + + /* In order to prevent multiple devices attempting to use the same clat address, never use a + statically-configured interface ID on a broadcast interface such as wifi. */ + if (!IN6_IS_ADDR_UNSPECIFIED(&Global_Clatd_Config.ipv6_host_id)) { + ifc_init(); + ifc_get_info(Global_Clatd_Config.default_pdp_interface, NULL, NULL, &flags); + ifc_close(); + Global_Clatd_Config.use_dynamic_iid = (flags & IFF_BROADCAST) != 0; + } else { + Global_Clatd_Config.use_dynamic_iid = 1; + } + + return 1; + +failed: + free(root); + free_config(); + return 0; +} + +/* function; dump_config + * prints the current config + */ +void dump_config() { + char charbuffer[INET6_ADDRSTRLEN]; + + logmsg(ANDROID_LOG_DEBUG,"mtu = %d",Global_Clatd_Config.mtu); + logmsg(ANDROID_LOG_DEBUG,"ipv4mtu = %d",Global_Clatd_Config.ipv4mtu); + logmsg(ANDROID_LOG_DEBUG,"ipv6_local_subnet = %s",inet_ntop(AF_INET6, &Global_Clatd_Config.ipv6_local_subnet, charbuffer, sizeof(charbuffer))); + logmsg(ANDROID_LOG_DEBUG,"ipv4_local_subnet = %s",inet_ntop(AF_INET, &Global_Clatd_Config.ipv4_local_subnet, charbuffer, sizeof(charbuffer))); + logmsg(ANDROID_LOG_DEBUG,"ipv4_local_prefixlen = %d", Global_Clatd_Config.ipv4_local_prefixlen); + logmsg(ANDROID_LOG_DEBUG,"plat_subnet = %s",inet_ntop(AF_INET6, &Global_Clatd_Config.plat_subnet, charbuffer, sizeof(charbuffer))); + logmsg(ANDROID_LOG_DEBUG,"default_pdp_interface = %s",Global_Clatd_Config.default_pdp_interface); +} diff --git a/config.h b/config.h new file mode 100644 index 0000000..e31a81d --- /dev/null +++ b/config.h @@ -0,0 +1,50 @@ +/* + * Copyright 2011 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * config.h - configuration settings + */ +#ifndef __CONFIG_H__ +#define __CONFIG_H__ + +#include +#include + +#define DEFAULT_IPV4_LOCAL_SUBNET "192.0.0.4" +#define DEFAULT_IPV4_LOCAL_PREFIXLEN "29" +#define DEFAULT_DNS64_DETECTION_HOSTNAME "ipv4only.arpa" + +struct clat_config { + int16_t mtu, ipv4mtu; + struct in6_addr ipv6_local_subnet; + struct in6_addr ipv6_host_id; + struct in_addr ipv4_local_subnet; + int16_t ipv4_local_prefixlen; + struct in6_addr plat_subnet; + char *default_pdp_interface; + char *plat_from_dns64_hostname; + int use_dynamic_iid; +}; + +extern struct clat_config Global_Clatd_Config; + +int read_config(const char *file, const char *uplink_interface, const char *plat_prefix, + unsigned net_id); +void config_generate_local_ipv6_subnet(struct in6_addr *interface_ip); +in_addr_t config_select_ipv4_address(const struct in_addr *ip, int16_t prefixlen); +int ipv6_prefix_equal(struct in6_addr *a1, struct in6_addr *a2); + +typedef int (*addr_free_func)(in_addr_t addr); + +#endif /* __CONFIG_H__ */ diff --git a/debug.h b/debug.h new file mode 100644 index 0000000..8e09672 --- /dev/null +++ b/debug.h @@ -0,0 +1,24 @@ +/* + * Copyright 2011 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * debug.h - debug settings + */ +#ifndef __DEBUG_H__ +#define __DEBUG_H__ + +// set to 1 to enable debug logging and packet dumping. +#define CLAT_DEBUG 0 + +#endif /* __DEBUG_H__ */ diff --git a/dns64.c b/dns64.c new file mode 100644 index 0000000..4e9252d --- /dev/null +++ b/dns64.c @@ -0,0 +1,73 @@ +/* + * Copyright 2011 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * dns64.c - find the nat64 prefix with a dns64 lookup + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dns64.h" +#include "logging.h" +#include "resolv_netid.h" + +/* function: plat_prefix + * looks up an ipv4-only hostname and looks for a nat64 /96 prefix, returns 1 on success, 0 on failure + * ipv4_name - name to lookup + * net_id - (optional) netId to use, NETID_UNSET indicates use of default network + * prefix - the plat /96 prefix + */ +int plat_prefix(const char *ipv4_name, unsigned net_id, struct in6_addr *prefix) { + const struct addrinfo hints = { + .ai_family = AF_INET6, + }; + int status; + struct addrinfo *result = NULL; + struct in6_addr plat_addr; + char plat_addr_str[INET6_ADDRSTRLEN]; + + logmsg(ANDROID_LOG_INFO, "Detecting NAT64 prefix from DNS..."); + + status = android_getaddrinfofornet(ipv4_name, NULL, &hints, net_id, MARK_UNSET, &result); + if (status != 0 || result == NULL) { + logmsg(ANDROID_LOG_ERROR, "plat_prefix/dns(%s) status = %d/%s", + ipv4_name, status, gai_strerror(status)); + return 0; + } + + // Use only the first result. If other records are present, possibly with + // differing DNS64 prefixes they are ignored (there is very little sensible + // that could be done with them at this time anyway). + + if (result->ai_family != AF_INET6) { + logmsg(ANDROID_LOG_WARN, "plat_prefix/unexpected address family: %d", result->ai_family); + return 0; + } + plat_addr = ((struct sockaddr_in6 *)result->ai_addr)->sin6_addr; + // Only /96 DNS64 prefixes are supported at this time. + plat_addr.s6_addr32[3] = 0; + freeaddrinfo(result); + + logmsg(ANDROID_LOG_INFO, "Detected NAT64 prefix %s/96", + inet_ntop(AF_INET6, &plat_addr, plat_addr_str, sizeof(plat_addr_str))); + *prefix = plat_addr; + return 1; +} diff --git a/dns64.h b/dns64.h new file mode 100644 index 0000000..f5eaea8 --- /dev/null +++ b/dns64.h @@ -0,0 +1,23 @@ +/* + * Copyright 2011 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * dns64.h - find the nat64 prefix with a dns64 lookup + */ +#ifndef __DNS64_H__ +#define __DNS64_H__ + +int plat_prefix(const char *ipv4_name, unsigned net_id, struct in6_addr *prefix); + +#endif diff --git a/dump.c b/dump.c new file mode 100644 index 0000000..27b75d1 --- /dev/null +++ b/dump.c @@ -0,0 +1,248 @@ +/* + * Copyright 2011 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * dump.c - print various headers for debugging + */ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "debug.h" +#include "checksum.h" +#include "clatd.h" +#include "logging.h" + +#if CLAT_DEBUG + +/* print ip header */ +void dump_ip(struct iphdr *header) { + u_int16_t frag_flags; + char addrstr[INET6_ADDRSTRLEN]; + + frag_flags = ntohs(header->frag_off); + + printf("IP packet\n"); + printf("header_len = %x\n",header->ihl); + printf("version = %x\n",header->version); + printf("tos = %x\n",header->tos); + printf("tot_len = %x\n",ntohs(header->tot_len)); + printf("id = %x\n",ntohs(header->id)); + printf("frag: "); + if(frag_flags & IP_RF) { + printf("(RF) "); + } + if(frag_flags & IP_DF) { + printf("DF "); + } + if(frag_flags & IP_MF) { + printf("MF "); + } + printf("offset = %x\n",frag_flags & IP_OFFMASK); + printf("ttl = %x\n",header->ttl); + printf("protocol = %x\n",header->protocol); + printf("checksum = %x\n",ntohs(header->check)); + inet_ntop(AF_INET, &header->saddr, addrstr, sizeof(addrstr)); + printf("saddr = %s\n",addrstr); + inet_ntop(AF_INET, &header->daddr, addrstr, sizeof(addrstr)); + printf("daddr = %s\n",addrstr); +} + +/* print ip6 header */ +void dump_ip6(struct ip6_hdr *header) { + char addrstr[INET6_ADDRSTRLEN]; + + printf("ipv6\n"); + printf("version = %x\n",header->ip6_vfc >> 4); + printf("traffic class = %x\n",header->ip6_flow >> 20); + printf("flow label = %x\n",ntohl(header->ip6_flow & 0x000fffff)); + printf("payload len = %x\n",ntohs(header->ip6_plen)); + printf("next header = %x\n",header->ip6_nxt); + printf("hop limit = %x\n",header->ip6_hlim); + + inet_ntop(AF_INET6, &header->ip6_src, addrstr, sizeof(addrstr)); + printf("source = %s\n",addrstr); + + inet_ntop(AF_INET6, &header->ip6_dst, addrstr, sizeof(addrstr)); + printf("dest = %s\n",addrstr); +} + +/* print icmp header */ +void dump_icmp(struct icmphdr *icmp) { + printf("ICMP\n"); + + printf("icmp.type = %x ",icmp->type); + if(icmp->type == ICMP_ECHOREPLY) { + printf("echo reply"); + } else if(icmp->type == ICMP_ECHO) { + printf("echo request"); + } else { + printf("other"); + } + printf("\n"); + printf("icmp.code = %x\n",icmp->code); + printf("icmp.checksum = %x\n",ntohs(icmp->checksum)); + if(icmp->type == ICMP_ECHOREPLY || icmp->type == ICMP_ECHO) { + printf("icmp.un.echo.id = %x\n",ntohs(icmp->un.echo.id)); + printf("icmp.un.echo.sequence = %x\n",ntohs(icmp->un.echo.sequence)); + } +} + +/* print icmp6 header */ +void dump_icmp6(struct icmp6_hdr *icmp6) { + printf("ICMP6\n"); + printf("type = %x",icmp6->icmp6_type); + if(icmp6->icmp6_type == ICMP6_ECHO_REQUEST) { + printf("(echo request)"); + } else if(icmp6->icmp6_type == ICMP6_ECHO_REPLY) { + printf("(echo reply)"); + } + printf("\n"); + printf("code = %x\n",icmp6->icmp6_code); + + printf("checksum = %x\n",icmp6->icmp6_cksum); + + if((icmp6->icmp6_type == ICMP6_ECHO_REQUEST) || (icmp6->icmp6_type == ICMP6_ECHO_REPLY)) { + printf("icmp6_id = %x\n",icmp6->icmp6_id); + printf("icmp6_seq = %x\n",icmp6->icmp6_seq); + } +} + +/* print udp header */ +void dump_udp_generic(const struct udphdr *udp, uint32_t temp_checksum, + const uint8_t *payload, size_t payload_size) { + uint16_t my_checksum; + + temp_checksum = ip_checksum_add(temp_checksum, udp, sizeof(struct udphdr)); + temp_checksum = ip_checksum_add(temp_checksum, payload, payload_size); + my_checksum = ip_checksum_finish(temp_checksum); + + printf("UDP\n"); + printf("source = %x\n",ntohs(udp->source)); + printf("dest = %x\n",ntohs(udp->dest)); + printf("len = %x\n",ntohs(udp->len)); + printf("check = %x (mine %x)\n",udp->check,my_checksum); +} + +/* print ipv4/udp header */ +void dump_udp(const struct udphdr *udp, const struct iphdr *ip, + const uint8_t *payload, size_t payload_size) { + uint32_t temp_checksum; + temp_checksum = ipv4_pseudo_header_checksum(ip, sizeof(*udp) + payload_size); + dump_udp_generic(udp, temp_checksum, payload, payload_size); +} + +/* print ipv6/udp header */ +void dump_udp6(const struct udphdr *udp, const struct ip6_hdr *ip6, + const uint8_t *payload, size_t payload_size) { + uint32_t temp_checksum; + temp_checksum = ipv6_pseudo_header_checksum(ip6, sizeof(*udp) + payload_size, IPPROTO_UDP); + dump_udp_generic(udp, temp_checksum, payload, payload_size); +} + +/* print tcp header */ +void dump_tcp_generic(const struct tcphdr *tcp, const uint8_t *options, size_t options_size, uint32_t temp_checksum, const uint8_t *payload, size_t payload_size) { + uint16_t my_checksum; + + temp_checksum = ip_checksum_add(temp_checksum, tcp, sizeof(struct tcphdr)); + if(options) { + temp_checksum = ip_checksum_add(temp_checksum, options, options_size); + } + temp_checksum = ip_checksum_add(temp_checksum, payload, payload_size); + my_checksum = ip_checksum_finish(temp_checksum); + + printf("TCP\n"); + printf("source = %x\n",ntohs(tcp->source)); + printf("dest = %x\n",ntohs(tcp->dest)); + printf("seq = %x\n",ntohl(tcp->seq)); + printf("ack = %x\n",ntohl(tcp->ack_seq)); + printf("d_off = %x\n",tcp->doff); + printf("res1 = %x\n",tcp->res1); +#ifdef __BIONIC__ + printf("CWR = %x\n",tcp->cwr); + printf("ECE = %x\n",tcp->ece); +#else + printf("CWR/ECE = %x\n",tcp->res2); +#endif + printf("urg = %x ack = %x psh = %x rst = %x syn = %x fin = %x\n", + tcp->urg, tcp->ack, tcp->psh, tcp->rst, tcp->syn, tcp->fin); + printf("window = %x\n",ntohs(tcp->window)); + printf("check = %x [mine %x]\n",tcp->check,my_checksum); + printf("urgent = %x\n",tcp->urg_ptr); + + if(options) { + size_t i; + + printf("options: "); + for(i=0; i +#include +#include +#include + +#include +#include +#include + +#include "getaddr.h" +#include "netlink_msg.h" +#include "logging.h" + +// shared state between getinterface_ip and getaddr_cb +struct target { + int family; + unsigned int ifindex; + union anyip ip; + int foundip; +}; + +/* function: getaddr_cb + * callback for getinterface_ip + * msg - netlink message + * data - (struct target) info for which address we're looking for + */ +static int getaddr_cb(struct nl_msg *msg, void *data) { + struct ifaddrmsg *ifa_p; + struct rtattr *rta_p; + int rta_len; + struct target *targ_p = (struct target *)data; + + ifa_p = (struct ifaddrmsg *)nlmsg_data(nlmsg_hdr(msg)); + rta_p = (struct rtattr *)IFA_RTA(ifa_p); + + if(ifa_p->ifa_index != targ_p->ifindex) + return NL_OK; + + if(ifa_p->ifa_scope != RT_SCOPE_UNIVERSE) + return NL_OK; + + rta_len = RTM_PAYLOAD(nlmsg_hdr(msg)); + for (; RTA_OK(rta_p, rta_len); rta_p = RTA_NEXT(rta_p, rta_len)) { + switch(rta_p->rta_type) { + case IFA_ADDRESS: + if((targ_p->family == AF_INET6) && !(ifa_p->ifa_flags & IFA_F_SECONDARY)) { + memcpy(&targ_p->ip.ip6, RTA_DATA(rta_p), rta_p->rta_len - sizeof(struct rtattr)); + targ_p->foundip = 1; + return NL_OK; + } + break; + case IFA_LOCAL: + if(targ_p->family == AF_INET) { + memcpy(&targ_p->ip.ip4, RTA_DATA(rta_p), rta_p->rta_len - sizeof(struct rtattr)); + targ_p->foundip = 1; + return NL_OK; + } + break; + } + } + + return NL_OK; +} + +/* function: error_handler + * error callback for getinterface_ip + * nla - source of the error message + * err - netlink message + * arg - (struct target) info for which address we're looking for + */ +static int error_handler(__attribute__((unused)) struct sockaddr_nl *nla, + __attribute__((unused)) struct nlmsgerr *err, + __attribute__((unused)) void *arg) { + return NL_OK; +} + +/* function: getinterface_ip + * finds the first global non-privacy IP of the given family for the given interface, or returns NULL. caller frees pointer + * interface - interface to look for + * family - family + */ +union anyip *getinterface_ip(const char *interface, int family) { + struct ifaddrmsg ifa; + struct nl_cb *callbacks = NULL; + struct target targ; + union anyip *retval = NULL; + + targ.family = family; + targ.foundip = 0; + targ.ifindex = if_nametoindex(interface); + if(targ.ifindex == 0) { + return NULL; // interface not found + } + + memset(&ifa, 0, sizeof(ifa)); + ifa.ifa_family = targ.family; + + callbacks = nl_cb_alloc(NL_CB_DEFAULT); + if(!callbacks) { + goto cleanup; + } + nl_cb_set(callbacks, NL_CB_VALID, NL_CB_CUSTOM, getaddr_cb, &targ); + nl_cb_err(callbacks, NL_CB_CUSTOM, error_handler, &targ); + + // sends message and waits for a response + send_ifaddrmsg(RTM_GETADDR, NLM_F_REQUEST | NLM_F_ROOT, &ifa, callbacks); + + if(targ.foundip) { + retval = malloc(sizeof(union anyip)); + if(!retval) { + logmsg(ANDROID_LOG_FATAL,"getinterface_ip/out of memory"); + goto cleanup; + } + memcpy(retval, &targ.ip, sizeof(union anyip)); + } + +cleanup: + if(callbacks) + nl_cb_put(callbacks); + + return retval; +} diff --git a/getaddr.h b/getaddr.h new file mode 100644 index 0000000..5718e62 --- /dev/null +++ b/getaddr.h @@ -0,0 +1,28 @@ +/* + * Copyright 2011 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * getaddr.h - get a locally configured address + */ +#ifndef __GETADDR_H__ +#define __GETADDR_H__ + +union anyip { + struct in6_addr ip6; + struct in_addr ip4; +}; + +union anyip *getinterface_ip(const char *interface, int family); + +#endif diff --git a/icmp.c b/icmp.c new file mode 100644 index 0000000..75a4a4d --- /dev/null +++ b/icmp.c @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * icmp.c - convenience functions for translating ICMP and ICMPv6 packets. + */ + +#include +#include +#include +#include + +#include "logging.h" +#include "icmp.h" + +/* function: icmp_guess_ttl + * Guesses the number of hops a received packet has traversed based on its TTL. + * ttl - the ttl of the received packet. + */ +uint8_t icmp_guess_ttl(uint8_t ttl) { + if (ttl > 128) { + return 255 - ttl; + } else if (ttl > 64) { + return 128 - ttl; + } else if (ttl > 32) { + return 64 - ttl; + } else { + return 32 - ttl; + } +} + +/* function: is_icmp_error + * Determines whether an ICMP type is an error message. + * type: the ICMP type + */ +int is_icmp_error(uint8_t type) { + return type == 3 || type == 11 || type == 12; +} + +/* function: is_icmp6_error + * Determines whether an ICMPv6 type is an error message. + * type: the ICMPv6 type + */ +int is_icmp6_error(uint8_t type) { + return type < 128; +} + +/* function: icmp_to_icmp6_type + * Maps ICMP types to ICMPv6 types. Partial implementation of RFC 6145, section 4.2. + * type - the ICMPv6 type + */ +uint8_t icmp_to_icmp6_type(uint8_t type, uint8_t code) { + switch (type) { + case ICMP_ECHO: + return ICMP6_ECHO_REQUEST; + + case ICMP_ECHOREPLY: + return ICMP6_ECHO_REPLY; + + case ICMP_TIME_EXCEEDED: + return ICMP6_TIME_EXCEEDED; + + case ICMP_DEST_UNREACH: + // These two types need special translation which we don't support yet. + if (code != ICMP_UNREACH_PROTOCOL && code != ICMP_UNREACH_NEEDFRAG) { + return ICMP6_DST_UNREACH; + } + } + + // We don't understand this ICMP type. Return parameter problem so the caller will bail out. + logmsg_dbg(ANDROID_LOG_DEBUG, "icmp_to_icmp6_type: unhandled ICMP type %d", type); + return ICMP6_PARAM_PROB; +} + +/* function: icmp_to_icmp6_code + * Maps ICMP codes to ICMPv6 codes. Partial implementation of RFC 6145, section 4.2. + * type - the ICMP type + * code - the ICMP code + */ +uint8_t icmp_to_icmp6_code(uint8_t type, uint8_t code) { + switch (type) { + case ICMP_ECHO: + case ICMP_ECHOREPLY: + return 0; + + case ICMP_TIME_EXCEEDED: + return code; + + case ICMP_DEST_UNREACH: + switch (code) { + case ICMP_UNREACH_NET: + case ICMP_UNREACH_HOST: + return ICMP6_DST_UNREACH_NOROUTE; + + case ICMP_UNREACH_PORT: + return ICMP6_DST_UNREACH_NOPORT; + + case ICMP_UNREACH_NET_PROHIB: + case ICMP_UNREACH_HOST_PROHIB: + case ICMP_UNREACH_FILTER_PROHIB: + case ICMP_UNREACH_PRECEDENCE_CUTOFF: + return ICMP6_DST_UNREACH_ADMIN; + + // Otherwise, we don't understand this ICMP type/code combination. Fall through. + } + } + logmsg_dbg(ANDROID_LOG_DEBUG, "icmp_to_icmp6_code: unhandled ICMP type/code %d/%d", type, code); + return 0; +} + +/* function: icmp6_to_icmp_type + * Maps ICMPv6 types to ICMP types. Partial implementation of RFC 6145, section 5.2. + * type - the ICMP type + */ +uint8_t icmp6_to_icmp_type(uint8_t type, uint8_t code) { + switch (type) { + case ICMP6_ECHO_REQUEST: + return ICMP_ECHO; + + case ICMP6_ECHO_REPLY: + return ICMP_ECHOREPLY; + + case ICMP6_DST_UNREACH: + return ICMP_DEST_UNREACH; + + case ICMP6_TIME_EXCEEDED: + return ICMP_TIME_EXCEEDED; + } + + // We don't understand this ICMP type. Return parameter problem so the caller will bail out. + logmsg_dbg(ANDROID_LOG_DEBUG, "icmp6_to_icmp_type: unhandled ICMP type/code %d/%d", type, code); + return ICMP_PARAMETERPROB; +} + +/* function: icmp6_to_icmp_code + * Maps ICMPv6 codes to ICMP codes. Partial implementation of RFC 6145, section 5.2. + * type - the ICMPv6 type + * code - the ICMPv6 code + */ +uint8_t icmp6_to_icmp_code(uint8_t type, uint8_t code) { + switch (type) { + case ICMP6_ECHO_REQUEST: + case ICMP6_ECHO_REPLY: + case ICMP6_TIME_EXCEEDED: + return code; + + case ICMP6_DST_UNREACH: + switch (code) { + case ICMP6_DST_UNREACH_NOROUTE: + return ICMP_UNREACH_HOST; + + case ICMP6_DST_UNREACH_ADMIN: + return ICMP_UNREACH_HOST_PROHIB; + + case ICMP6_DST_UNREACH_BEYONDSCOPE: + return ICMP_UNREACH_HOST; + + case ICMP6_DST_UNREACH_ADDR: + return ICMP_HOST_UNREACH; + + case ICMP6_DST_UNREACH_NOPORT: + return ICMP_UNREACH_PORT; + + // Otherwise, we don't understand this ICMPv6 type/code combination. Fall through. + } + } + + logmsg_dbg(ANDROID_LOG_DEBUG, "icmp6_to_icmp_code: unhandled ICMP type/code %d/%d", type, code); + return 0; +} diff --git a/icmp.h b/icmp.h new file mode 100644 index 0000000..632e92d --- /dev/null +++ b/icmp.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * icmp.c - convenience functions for translating ICMP and ICMPv6 packets. + */ + +#ifndef __ICMP_H__ +#define __ICMP_H__ + +#include + +// Guesses the number of hops a received packet has traversed based on its TTL. +uint8_t icmp_guess_ttl(uint8_t ttl); + +// Determines whether an ICMP type is an error message. +int is_icmp_error(uint8_t type); + +// Determines whether an ICMPv6 type is an error message. +int is_icmp6_error(uint8_t type); + +// Maps ICMP types to ICMPv6 types. Partial implementation of RFC 6145, section 4.2. +uint8_t icmp_to_icmp6_type(uint8_t type, uint8_t code); + +// Maps ICMP codes to ICMPv6 codes. Partial implementation of RFC 6145, section 4.2. +uint8_t icmp_to_icmp6_code(uint8_t type, uint8_t code); + +// Maps ICMPv6 types to ICMP types. Partial implementation of RFC 6145, section 5.2. +uint8_t icmp6_to_icmp_type(uint8_t type, uint8_t code); + +// Maps ICMPv6 codes to ICMP codes. Partial implementation of RFC 6145, section 5.2. +uint8_t icmp6_to_icmp_code(uint8_t type, uint8_t code); + +#endif /* __ICMP_H__ */ diff --git a/ipv4.c b/ipv4.c new file mode 100644 index 0000000..4b0db39 --- /dev/null +++ b/ipv4.c @@ -0,0 +1,146 @@ +/* + * Copyright 2011 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ipv4.c - takes ipv4 packets, finds their headers, and then calls translation functions on them + */ +#include + +#include "translate.h" +#include "checksum.h" +#include "logging.h" +#include "debug.h" +#include "dump.h" + +/* function: icmp_packet + * translates an icmp packet + * out - output packet + * icmp - pointer to icmp header in packet + * checksum - pseudo-header checksum + * len - size of ip payload + * returns: the highest position in the output clat_packet that's filled in + */ +int icmp_packet(clat_packet out, clat_packet_index pos, const struct icmphdr *icmp, + uint32_t checksum, size_t len) { + const uint8_t *payload; + size_t payload_size; + + if(len < sizeof(struct icmphdr)) { + logmsg_dbg(ANDROID_LOG_ERROR, "icmp_packet/(too small)"); + return 0; + } + + payload = (const uint8_t *) (icmp + 1); + payload_size = len - sizeof(struct icmphdr); + + return icmp_to_icmp6(out, pos, icmp, checksum, payload, payload_size); +} + +/* function: ipv4_packet + * translates an ipv4 packet + * out - output packet + * packet - packet data + * len - size of packet + * returns: the highest position in the output clat_packet that's filled in + */ +int ipv4_packet(clat_packet out, clat_packet_index pos, const uint8_t *packet, size_t len) { + const struct iphdr *header = (struct iphdr *) packet; + struct ip6_hdr *ip6_targ = (struct ip6_hdr *) out[pos].iov_base; + struct ip6_frag *frag_hdr; + size_t frag_hdr_len; + uint8_t nxthdr; + const uint8_t *next_header; + size_t len_left; + uint32_t old_sum, new_sum; + int iov_len; + + if(len < sizeof(struct iphdr)) { + logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/too short for an ip header"); + return 0; + } + + if(header->ihl < 5) { + logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/ip header length set to less than 5: %x", header->ihl); + return 0; + } + + if((size_t) header->ihl * 4 > len) { // ip header length larger than entire packet + logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/ip header length set too large: %x", header->ihl); + return 0; + } + + if(header->version != 4) { + logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/ip header version not 4: %x", header->version); + return 0; + } + + /* rfc6145 - If any IPv4 options are present in the IPv4 packet, they MUST be + * ignored and the packet translated normally; there is no attempt to + * translate the options. + */ + + next_header = packet + header->ihl*4; + len_left = len - header->ihl * 4; + + nxthdr = header->protocol; + if (nxthdr == IPPROTO_ICMP) { + // ICMP and ICMPv6 have different protocol numbers. + nxthdr = IPPROTO_ICMPV6; + } + + /* Fill in the IPv6 header. We need to do this before we translate the packet because TCP and + * UDP include parts of the IP header in the checksum. Set the length to zero because we don't + * know it yet. + */ + fill_ip6_header(ip6_targ, 0, nxthdr, header); + out[pos].iov_len = sizeof(struct ip6_hdr); + + /* Calculate the pseudo-header checksum. + * Technically, the length that is used in the pseudo-header checksum is the transport layer + * length, which is not the same as len_left in the case of fragmented packets. But since + * translation does not change the transport layer length, the checksum is unaffected. + */ + old_sum = ipv4_pseudo_header_checksum(header, len_left); + new_sum = ipv6_pseudo_header_checksum(ip6_targ, len_left, nxthdr); + + // If the IPv4 packet is fragmented, add a Fragment header. + frag_hdr = (struct ip6_frag *) out[pos + 1].iov_base; + frag_hdr_len = maybe_fill_frag_header(frag_hdr, ip6_targ, header); + out[pos + 1].iov_len = frag_hdr_len; + + if (frag_hdr_len && frag_hdr->ip6f_offlg & IP6F_OFF_MASK) { + // Non-first fragment. Copy the rest of the packet as is. + iov_len = generic_packet(out, pos + 2, next_header, len_left); + } else if (nxthdr == IPPROTO_ICMPV6) { + iov_len = icmp_packet(out, pos + 2, (const struct icmphdr *) next_header, new_sum, len_left); + } else if (nxthdr == IPPROTO_TCP) { + iov_len = tcp_packet(out, pos + 2, (const struct tcphdr *) next_header, old_sum, new_sum, + len_left); + } else if (nxthdr == IPPROTO_UDP) { + iov_len = udp_packet(out, pos + 2, (const struct udphdr *) next_header, old_sum, new_sum, + len_left); + } else if (nxthdr == IPPROTO_GRE) { + iov_len = generic_packet(out, pos + 2, next_header, len_left); + } else { +#if CLAT_DEBUG + logmsg_dbg(ANDROID_LOG_ERROR, "ip_packet/unknown protocol: %x",header->protocol); + logcat_hexdump("ipv4/protocol", packet, len); +#endif + return 0; + } + + // Set the length. + ip6_targ->ip6_plen = htons(packet_length(out, pos)); + return iov_len; +} diff --git a/ipv6.c b/ipv6.c new file mode 100644 index 0000000..b485313 --- /dev/null +++ b/ipv6.c @@ -0,0 +1,179 @@ +/* + * Copyright 2011 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ipv6.c - takes ipv6 packets, finds their headers, and then calls translation functions on them + */ +#include + +#include + +#include "translate.h" +#include "checksum.h" +#include "logging.h" +#include "dump.h" +#include "config.h" +#include "debug.h" + +/* function: icmp6_packet + * takes an icmp6 packet and sets it up for translation + * out - output packet + * icmp6 - pointer to icmp6 header in packet + * checksum - pseudo-header checksum (unused) + * len - size of ip payload + * returns: the highest position in the output clat_packet that's filled in + */ +int icmp6_packet(clat_packet out, clat_packet_index pos, const struct icmp6_hdr *icmp6, + size_t len) { + const uint8_t *payload; + size_t payload_size; + + if(len < sizeof(struct icmp6_hdr)) { + logmsg_dbg(ANDROID_LOG_ERROR, "icmp6_packet/(too small)"); + return 0; + } + + payload = (const uint8_t *) (icmp6 + 1); + payload_size = len - sizeof(struct icmp6_hdr); + + return icmp6_to_icmp(out, pos, icmp6, payload, payload_size); +} + +/* function: log_bad_address + * logs a bad address to android's log buffer if debugging is turned on + * fmt - printf-style format, use %s to place the address + * badaddr - the bad address in question + */ +#if CLAT_DEBUG +void log_bad_address(const char *fmt, const struct in6_addr *src, const struct in6_addr *dst) { + char srcstr[INET6_ADDRSTRLEN]; + char dststr[INET6_ADDRSTRLEN]; + + inet_ntop(AF_INET6, src, srcstr, sizeof(srcstr)); + inet_ntop(AF_INET6, dst, dststr, sizeof(dststr)); + logmsg_dbg(ANDROID_LOG_ERROR, fmt, srcstr, dststr); +} +#else +#define log_bad_address(fmt, src, dst) +#endif + +/* function: ipv6_packet + * takes an ipv6 packet and hands it off to the layer 4 protocol function + * out - output packet + * packet - packet data + * len - size of packet + * returns: the highest position in the output clat_packet that's filled in + */ +int ipv6_packet(clat_packet out, clat_packet_index pos, const uint8_t *packet, size_t len) { + const struct ip6_hdr *ip6 = (struct ip6_hdr *) packet; + struct iphdr *ip_targ = (struct iphdr *) out[pos].iov_base; + struct ip6_frag *frag_hdr = NULL; + uint8_t protocol; + const uint8_t *next_header; + size_t len_left; + uint32_t old_sum, new_sum; + int iov_len; + + if(len < sizeof(struct ip6_hdr)) { + logmsg_dbg(ANDROID_LOG_ERROR, "ipv6_packet/too short for an ip6 header: %d", len); + return 0; + } + + if(IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) { + log_bad_address("ipv6_packet/multicast %s->%s", &ip6->ip6_src, &ip6->ip6_dst); + return 0; // silently ignore + } + + // If the packet is not from the plat subnet to the local subnet, or vice versa, drop it, unless + // it's an ICMP packet (which can come from anywhere). We do not send IPv6 packets from the plat + // subnet to the local subnet, but these can appear as inner packets in ICMP errors, so we need + // to translate them. We accept third-party ICMPv6 errors, even though their source addresses + // cannot be translated, so that things like unreachables and traceroute will work. fill_ip_header + // takes care of faking a source address for them. + if (!(is_in_plat_subnet(&ip6->ip6_src) && + IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, &Global_Clatd_Config.ipv6_local_subnet)) && + !(is_in_plat_subnet(&ip6->ip6_dst) && + IN6_ARE_ADDR_EQUAL(&ip6->ip6_src, &Global_Clatd_Config.ipv6_local_subnet)) && + ip6->ip6_nxt != IPPROTO_ICMPV6) { + log_bad_address("ipv6_packet/wrong source address: %s->%s", &ip6->ip6_src, &ip6->ip6_dst); + return 0; + } + + next_header = packet + sizeof(struct ip6_hdr); + len_left = len - sizeof(struct ip6_hdr); + + protocol = ip6->ip6_nxt; + + /* Fill in the IPv4 header. We need to do this before we translate the packet because TCP and + * UDP include parts of the IP header in the checksum. Set the length to zero because we don't + * know it yet. + */ + fill_ip_header(ip_targ, 0, protocol, ip6); + out[pos].iov_len = sizeof(struct iphdr); + + // If there's a Fragment header, parse it and decide what the next header is. + // Do this before calculating the pseudo-header checksum because it updates the next header value. + if (protocol == IPPROTO_FRAGMENT) { + frag_hdr = (struct ip6_frag *) next_header; + if (len_left < sizeof(*frag_hdr)) { + logmsg_dbg(ANDROID_LOG_ERROR, "ipv6_packet/too short for fragment header: %d", len); + return 0; + } + + next_header += sizeof(*frag_hdr); + len_left -= sizeof(*frag_hdr); + + protocol = parse_frag_header(frag_hdr, ip_targ); + } + + // ICMP and ICMPv6 have different protocol numbers. + if (protocol == IPPROTO_ICMPV6) { + protocol = IPPROTO_ICMP; + ip_targ->protocol = IPPROTO_ICMP; + } + + /* Calculate the pseudo-header checksum. + * Technically, the length that is used in the pseudo-header checksum is the transport layer + * length, which is not the same as len_left in the case of fragmented packets. But since + * translation does not change the transport layer length, the checksum is unaffected. + */ + old_sum = ipv6_pseudo_header_checksum(ip6, len_left, protocol); + new_sum = ipv4_pseudo_header_checksum(ip_targ, len_left); + + // Does not support IPv6 extension headers except Fragment. + if (frag_hdr && (frag_hdr->ip6f_offlg & IP6F_OFF_MASK)) { + iov_len = generic_packet(out, pos + 2, next_header, len_left); + } else if (protocol == IPPROTO_ICMP) { + iov_len = icmp6_packet(out, pos + 2, (const struct icmp6_hdr *) next_header, len_left); + } else if (protocol == IPPROTO_TCP) { + iov_len = tcp_packet(out, pos + 2, (const struct tcphdr *) next_header, old_sum, new_sum, + len_left); + } else if (protocol == IPPROTO_UDP) { + iov_len = udp_packet(out, pos + 2, (const struct udphdr *) next_header, old_sum, new_sum, + len_left); + } else if (protocol == IPPROTO_GRE) { + iov_len = generic_packet(out, pos + 2, next_header, len_left); + } else { +#if CLAT_DEBUG + logmsg(ANDROID_LOG_ERROR, "ipv6_packet/unknown next header type: %x", ip6->ip6_nxt); + logcat_hexdump("ipv6/nxthdr", packet, len); +#endif + return 0; + } + + // Set the length and calculate the checksum. + ip_targ->tot_len = htons(ntohs(ip_targ->tot_len) + packet_length(out, pos)); + ip_targ->check = ip_checksum(ip_targ, sizeof(struct iphdr)); + return iov_len; +} diff --git a/logging.c b/logging.c new file mode 100644 index 0000000..c90b1cf --- /dev/null +++ b/logging.c @@ -0,0 +1,55 @@ +/* + * Copyright 2011 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * logging.c - print a log message + */ + +#include +#include + +#include "logging.h" +#include "debug.h" + +/* function: logmsg + * prints a log message to android's log buffer + * prio - the log message priority + * fmt - printf format specifier + * ... - printf format arguments + */ +void logmsg(int prio, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + __android_log_vprint(prio, "clatd", fmt, ap); + va_end(ap); +} + +/* function: logmsg_dbg + * prints a log message to android's log buffer if CLAT_DEBUG is set + * prio - the log message priority + * fmt - printf format specifier + * ... - printf format arguments + */ +#if CLAT_DEBUG +void logmsg_dbg(int prio, const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + __android_log_vprint(prio, "clatd", fmt, ap); + va_end(ap); +} +#else +void logmsg_dbg(__attribute__((unused)) int prio, __attribute__((unused)) const char *fmt, ...) {} +#endif diff --git a/logging.h b/logging.h new file mode 100644 index 0000000..1f4b6b6 --- /dev/null +++ b/logging.h @@ -0,0 +1,27 @@ +/* + * Copyright 2011 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * logging.h - print a log message + */ + +#ifndef __LOGGING_H__ +#define __LOGGING_H__ +// for the priorities +#include + +void logmsg(int prio, const char *fmt, ...); +void logmsg_dbg(int prio, const char *fmt, ...); + +#endif diff --git a/mtu.c b/mtu.c new file mode 100644 index 0000000..975bf0e --- /dev/null +++ b/mtu.c @@ -0,0 +1,46 @@ +/* + * Copyright 2011 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * mtu.c - get interface mtu + */ + +#include +#include +#include +#include +#include +#include + +#include "mtu.h" + +/* function: getifmtu + * returns the interface mtu or -1 on failure + * ifname - interface name + */ +int getifmtu(const char *ifname) { + int fd; + struct ifreq if_mtu; + + fd = socket(AF_INET, SOCK_STREAM, 0); + if(fd < 0) { + return -1; + } + strncpy(if_mtu.ifr_name, ifname, IFNAMSIZ); + if_mtu.ifr_name[IFNAMSIZ - 1] = '\0'; + if(ioctl(fd, SIOCGIFMTU, &if_mtu) < 0) { + return -1; + } + return if_mtu.ifr_mtu; +} diff --git a/mtu.h b/mtu.h new file mode 100644 index 0000000..c330c24 --- /dev/null +++ b/mtu.h @@ -0,0 +1,24 @@ +/* + * Copyright 2011 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * mtu.c - get interface mtu + */ + +#ifndef __MTU_H__ +#define __MTU_H__ + +int getifmtu(const char *ifname); + +#endif diff --git a/netlink_callbacks.c b/netlink_callbacks.c new file mode 100644 index 0000000..a79aa76 --- /dev/null +++ b/netlink_callbacks.c @@ -0,0 +1,67 @@ +/* + * Copyright 2012 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * netlink_callbacks.c - generic callbacks for netlink responses + */ +#include +#include + +#include +#include +#include + +/* function: ack_handler + * generic netlink callback for ack messages + * msg - netlink message + * data - pointer to an int, stores the success code + */ +static int ack_handler(__attribute__((unused)) struct nl_msg *msg, void *data) { + int *retval = data; + *retval = 0; + return NL_OK; +} + +/* function: error_handler + * generic netlink callback for error messages + * nla - error source + * err - netlink error message + * arg - pointer to an int, stores the error code + */ +static int error_handler(__attribute__((unused)) struct sockaddr_nl *nla, + struct nlmsgerr *err, void *arg) { + int *retval = arg; + if(err->error < 0) { + *retval = err->error; + } else { + *retval = 0; // NLMSG_ERROR used as reply type on no error + } + return NL_OK; +} + +/* function: alloc_ack_callbacks + * allocates a set of netlink callbacks. returns NULL on failure. callbacks will modify retval with <0 meaning failure + * retval - shared state between caller and callback functions + */ +struct nl_cb *alloc_ack_callbacks(int *retval) { + struct nl_cb *callbacks; + + callbacks = nl_cb_alloc(NL_CB_DEFAULT); + if(!callbacks) { + return NULL; + } + nl_cb_set(callbacks, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, retval); + nl_cb_err(callbacks, NL_CB_CUSTOM, error_handler, retval); + return callbacks; +} diff --git a/netlink_callbacks.h b/netlink_callbacks.h new file mode 100644 index 0000000..298ad3e --- /dev/null +++ b/netlink_callbacks.h @@ -0,0 +1,24 @@ +/* + * Copyright 2012 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * netlink_callbacks.h - callbacks for netlink responses + */ + +#ifndef __NETLINK_CALLBACKS_H__ +#define __NETLINK_CALLBACKS_H__ + +struct nl_cb *alloc_ack_callbacks(int *retval); + +#endif diff --git a/netlink_msg.c b/netlink_msg.c new file mode 100644 index 0000000..958559c --- /dev/null +++ b/netlink_msg.c @@ -0,0 +1,187 @@ +/* + * Copyright 2012 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * netlink_msg.c - send an ifaddrmsg/ifinfomsg/rtmsg via netlink + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "netlink_msg.h" +#include "netlink_callbacks.h" + +/* function: family_size + * returns the size of the address structure for the given family, or 0 on error + * family - AF_INET or AF_INET6 + */ +size_t inet_family_size(int family) { + if(family == AF_INET) { + return sizeof(struct in_addr); + } else if(family == AF_INET6) { + return sizeof(struct in6_addr); + } else { + return 0; + } +} + +/* function: nlmsg_alloc_generic + * allocates a netlink message with the given struct inside of it. returns NULL on failure + * type - netlink message type + * flags - netlink message flags + * payload_struct - pointer to a struct to add to netlink message + * payload_len - bytelength of structure + */ +struct nl_msg *nlmsg_alloc_generic(uint16_t type, uint16_t flags, void *payload_struct, size_t payload_len) { + struct nl_msg *msg; + + msg = nlmsg_alloc(); + if(!msg) { + return NULL; + } + + if ((sizeof(struct nl_msg) + payload_len) > msg->nm_size) { + nlmsg_free(msg); + return NULL; + } + + msg->nm_nlh->nlmsg_len = NLMSG_LENGTH(payload_len); + msg->nm_nlh->nlmsg_flags = flags; + msg->nm_nlh->nlmsg_type = type; + + memcpy(nlmsg_data(msg->nm_nlh), payload_struct, payload_len); + + return msg; +} + +/* function: nlmsg_alloc_ifaddr + * allocates a netlink message with a struct ifaddrmsg inside of it. returns NULL on failure + * type - netlink message type + * flags - netlink message flags + * ifa - ifaddrmsg to copy into the new netlink message + */ +struct nl_msg *nlmsg_alloc_ifaddr(uint16_t type, uint16_t flags, struct ifaddrmsg *ifa) { + return nlmsg_alloc_generic(type, flags, ifa, sizeof(*ifa)); +} + +/* function: nlmsg_alloc_ifinfo + * allocates a netlink message with a struct ifinfomsg inside of it. returns NULL on failure + * type - netlink message type + * flags - netlink message flags + * ifi - ifinfomsg to copy into the new netlink message + */ +struct nl_msg *nlmsg_alloc_ifinfo(uint16_t type, uint16_t flags, struct ifinfomsg *ifi) { + return nlmsg_alloc_generic(type, flags, ifi, sizeof(*ifi)); +} + +/* function: nlmsg_alloc_rtmsg + * allocates a netlink message with a struct rtmsg inside of it. returns NULL on failure + * type - netlink message type + * flags - netlink message flags + * rt - rtmsg to copy into the new netlink message + */ +struct nl_msg *nlmsg_alloc_rtmsg(uint16_t type, uint16_t flags, struct rtmsg *rt) { + return nlmsg_alloc_generic(type, flags, rt, sizeof(*rt)); +} + +/* function: netlink_set_kernel_only + * sets a socket to receive messages only from the kernel + * sock - socket to connect + */ +int netlink_set_kernel_only(struct nl_sock *nl_sk) { + struct sockaddr_nl addr = { AF_NETLINK, 0, 0, 0 }; + + if (!nl_sk) { + return -EFAULT; + } + + int sockfd = nl_socket_get_fd(nl_sk); + return connect(sockfd, (struct sockaddr *) &addr, sizeof(addr)); +} + +/* function: send_netlink_msg + * sends a netlink message, reads a response, and hands the response(s) to the callbacks + * msg - netlink message to send + * callbacks - callbacks to use on responses + */ +void send_netlink_msg(struct nl_msg *msg, struct nl_cb *callbacks) { + struct nl_sock *nl_sk; + + nl_sk = nl_socket_alloc(); + if(!nl_sk) + goto cleanup; + + if(nl_connect(nl_sk, NETLINK_ROUTE) != 0) + goto cleanup; + + if(nl_send_auto_complete(nl_sk, msg) < 0) + goto cleanup; + + if(netlink_set_kernel_only(nl_sk) < 0) + goto cleanup; + + nl_recvmsgs(nl_sk, callbacks); + +cleanup: + if(nl_sk) + nl_socket_free(nl_sk); +} + +/* function: send_ifaddrmsg + * sends a netlink/ifaddrmsg message and hands the responses to the callbacks + * type - netlink message type + * flags - netlink message flags + * ifa - ifaddrmsg to send + * callbacks - callbacks to use with the responses + */ +void send_ifaddrmsg(uint16_t type, uint16_t flags, struct ifaddrmsg *ifa, struct nl_cb *callbacks) { + struct nl_msg *msg = NULL; + + msg = nlmsg_alloc_ifaddr(type, flags, ifa); + if(!msg) + return; + + send_netlink_msg(msg, callbacks); + + nlmsg_free(msg); +} + +/* function: netlink_sendrecv + * send a nl_msg and return an int status - only supports OK/ERROR responses + * msg - msg to send + */ +int netlink_sendrecv(struct nl_msg *msg) { + struct nl_cb *callbacks = NULL; + int retval = -EIO; + + callbacks = alloc_ack_callbacks(&retval); + if(!callbacks) { + return -ENOMEM; + } + + send_netlink_msg(msg, callbacks); + + nl_cb_put(callbacks); + + return retval; +} diff --git a/netlink_msg.h b/netlink_msg.h new file mode 100644 index 0000000..13e1f28 --- /dev/null +++ b/netlink_msg.h @@ -0,0 +1,30 @@ +/* + * Copyright 2012 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * netlink_msg.h - send an ifaddrmsg/ifinfomsg via netlink + */ +#ifndef __NETLINK_IFMSG_H__ +#define __NETLINK_IFMSG_H__ + +size_t inet_family_size(int family); +struct nl_msg *nlmsg_alloc_ifaddr(uint16_t type, uint16_t flags, struct ifaddrmsg *ifa); +struct nl_msg *nlmsg_alloc_ifinfo(uint16_t type, uint16_t flags, struct ifinfomsg *ifi); +struct nl_msg *nlmsg_alloc_rtmsg(uint16_t type, uint16_t flags, struct rtmsg *rt); +void send_netlink_msg(struct nl_msg *msg, struct nl_cb *callbacks); +void send_ifaddrmsg(uint16_t type, uint16_t flags, struct ifaddrmsg *ifa, struct nl_cb *callbacks); +int netlink_sendrecv(struct nl_msg *msg); +int netlink_set_kernel_only(struct nl_sock *nl_sk); + +#endif diff --git a/ring.c b/ring.c new file mode 100644 index 0000000..5e99fd5 --- /dev/null +++ b/ring.c @@ -0,0 +1,126 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ring.c - packet ring buffer functions + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "logging.h" +#include "ring.h" +#include "translate.h" +#include "tun.h" + +int ring_create(struct tun_data *tunnel) { + int packetsock = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IPV6)); + if (packetsock < 0) { + logmsg(ANDROID_LOG_FATAL, "packet socket failed: %s", strerror(errno)); + return -1; + } + + int ver = TPACKET_V2; + if (setsockopt(packetsock, SOL_PACKET, PACKET_VERSION, (void *) &ver, sizeof(ver))) { + logmsg(ANDROID_LOG_FATAL, "setsockopt(PACKET_VERSION, %d) failed: %s", ver, strerror(errno)); + return -1; + } + + int on = 1; + if (setsockopt(packetsock, SOL_PACKET, PACKET_LOSS, (void *) &on, sizeof(on))) { + logmsg(ANDROID_LOG_WARN, "PACKET_LOSS failed: %s", strerror(errno)); + } + + struct packet_ring *ring = &tunnel->ring; + ring->numblocks = TP_NUM_BLOCKS; + + int total_frames = TP_FRAMES * ring->numblocks; + + struct tpacket_req req = { + .tp_frame_size = TP_FRAME_SIZE, // Frame size. + .tp_block_size = TP_BLOCK_SIZE, // Frames per block. + .tp_block_nr = ring->numblocks, // Number of blocks. + .tp_frame_nr = total_frames, // Total frames. + }; + + if (setsockopt(packetsock, SOL_PACKET, PACKET_RX_RING, &req, sizeof(req)) < 0) { + logmsg(ANDROID_LOG_FATAL, "PACKET_RX_RING failed: %s", strerror(errno)); + return -1; + } + + size_t buflen = TP_BLOCK_SIZE * ring->numblocks; + ring->base = mmap(NULL, buflen, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_LOCKED|MAP_POPULATE, + packetsock, 0); + if (ring->base == MAP_FAILED) { + logmsg(ANDROID_LOG_FATAL, "mmap %lu failed: %s", buflen, strerror(errno)); + return -1; + } + + ring->block = 0; + ring->slot = 0; + ring->numslots = TP_BLOCK_SIZE / TP_FRAME_SIZE; + ring->next = (struct tpacket2_hdr *) ring->base; + + logmsg(ANDROID_LOG_INFO, "Using ring buffer with %d frames (%d bytes) at %p", + total_frames, buflen, ring->base); + + return packetsock; +} + +/* function: ring_advance + * advances to the next position in the packet ring + * ring - packet ring buffer + */ +static struct tpacket2_hdr* ring_advance(struct packet_ring *ring) { + uint8_t *next = (uint8_t *) ring->next; + + ring->slot++; + next += TP_FRAME_SIZE; + + if (ring->slot == ring->numslots) { + ring->slot = 0; + ring->block++; + + if (ring->block < ring->numblocks) { + next += TP_FRAME_GAP; + } else { + ring->block = 0; + next = (uint8_t *) ring->base; + } + } + + ring->next = (struct tpacket2_hdr *) next; + return ring->next; +} + +/* function: ring_read + * reads a packet from the ring buffer and translates it + * read_fd - file descriptor to read original packet from + * write_fd - file descriptor to write translated packet to + * to_ipv6 - whether the packet is to be translated to ipv6 or ipv4 + */ +void ring_read(struct packet_ring *ring, int write_fd, int to_ipv6) { + struct tpacket2_hdr *tp = ring->next; + if (tp->tp_status & TP_STATUS_USER) { + uint8_t *packet = ((uint8_t *) tp) + tp->tp_net; + translate_packet(write_fd, to_ipv6, packet, tp->tp_len); + tp->tp_status = TP_STATUS_KERNEL; + tp = ring_advance(ring); + } +} diff --git a/ring.h b/ring.h new file mode 100644 index 0000000..b9b8c11 --- /dev/null +++ b/ring.h @@ -0,0 +1,55 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * ring.c - packet ring buffer functions + */ +#ifndef __RING_H__ +#define __RING_H__ + +#include +#include + +#include "clatd.h" + +struct tun_data; + +// Frame size. Must be a multiple of TPACKET_ALIGNMENT (=16) +// Why the 16? http://lxr.free-electrons.com/source/net/packet/af_packet.c?v=3.4#L1764 +#define TP_FRAME_SIZE (TPACKET_ALIGN(MAXMTU) + TPACKET_ALIGN(TPACKET2_HDRLEN) + 16) + +// Block size. Must be a multiple of the page size, and a power of two for efficient memory use. +#define TP_BLOCK_SIZE 65536 + +// In order to save memory, our frames are not an exact divider of the block size. Therefore, the +// mmaped region will have gaps corresponding to the empty space at the end of each block. +#define TP_FRAMES (TP_BLOCK_SIZE / TP_FRAME_SIZE) +#define TP_FRAME_GAP (TP_BLOCK_SIZE % TP_FRAME_SIZE) + +// TODO: Make this configurable. This requires some refactoring because the packet socket is +// opened before we drop privileges, but the configuration file is read after. A value of 16 +// results in 656 frames (1048576 bytes). +#define TP_NUM_BLOCKS 16 + +struct packet_ring { + uint8_t *base; + struct tpacket2_hdr *next; + int slot, numslots; + int block, numblocks; +}; + +int ring_create(struct tun_data *tunnel); +void ring_read(struct packet_ring *ring, int write_fd, int to_ipv6); + +#endif diff --git a/setif.c b/setif.c new file mode 100644 index 0000000..07f5bac --- /dev/null +++ b/setif.c @@ -0,0 +1,180 @@ +/* + * Copyright 2012 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * setif.c - network interface configuration + */ +#include +#include +#include + +#include +#include +#include + +#include "logging.h" +#include "netlink_msg.h" + +#define DEBUG_OPTNAME(a) case (a): { optname = #a; break; } + +/* function: add_address + * adds an IP address to/from an interface, returns 0 on success and <0 on failure + * ifname - name of interface to change + * family - address family (AF_INET, AF_INET6) + * address - pointer to a struct in_addr or in6_addr + * prefixlen - bitlength of network (example: 24 for AF_INET's 255.255.255.0) + * broadcast - broadcast address (only for AF_INET, ignored for AF_INET6) + */ +int add_address(const char *ifname, int family, const void *address, int prefixlen, const void *broadcast) { + int retval; + size_t addr_size; + struct ifaddrmsg ifa; + struct nl_msg *msg = NULL; + + addr_size = inet_family_size(family); + if(addr_size == 0) { + retval = -EAFNOSUPPORT; + goto cleanup; + } + + memset(&ifa, 0, sizeof(ifa)); + if (!(ifa.ifa_index = if_nametoindex(ifname))) { + retval = -ENODEV; + goto cleanup; + } + ifa.ifa_family = family; + ifa.ifa_prefixlen = prefixlen; + ifa.ifa_scope = RT_SCOPE_UNIVERSE; + + msg = nlmsg_alloc_ifaddr(RTM_NEWADDR, NLM_F_ACK | NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE, &ifa); + if(!msg) { + retval = -ENOMEM; + goto cleanup; + } + + if(nla_put(msg, IFA_LOCAL, addr_size, address) < 0) { + retval = -ENOMEM; + goto cleanup; + } + if(family == AF_INET6) { + // AF_INET6 gets IFA_LOCAL + IFA_ADDRESS + if(nla_put(msg, IFA_ADDRESS, addr_size, address) < 0) { + retval = -ENOMEM; + goto cleanup; + } + } else if(family == AF_INET) { + // AF_INET gets IFA_LOCAL + IFA_BROADCAST + if(nla_put(msg, IFA_BROADCAST, addr_size, broadcast) < 0) { + retval = -ENOMEM; + goto cleanup; + } + } else { + retval = -EAFNOSUPPORT; + goto cleanup; + } + + retval = netlink_sendrecv(msg); + +cleanup: + if(msg) + nlmsg_free(msg); + + return retval; +} + +/* function: if_up + * sets interface link state to up and sets mtu, returns 0 on success and <0 on failure + * ifname - interface name to change + * mtu - new mtu + */ +int if_up(const char *ifname, int mtu) { + int retval = -1; + struct ifinfomsg ifi; + struct nl_msg *msg = NULL; + + memset(&ifi, 0, sizeof(ifi)); + if (!(ifi.ifi_index = if_nametoindex(ifname))) { + retval = -ENODEV; + goto cleanup; + } + ifi.ifi_change = IFF_UP; + ifi.ifi_flags = IFF_UP; + + msg = nlmsg_alloc_ifinfo(RTM_SETLINK, NLM_F_ACK | NLM_F_REQUEST | NLM_F_ROOT, &ifi); + if(!msg) { + retval = -ENOMEM; + goto cleanup; + } + + if(nla_put(msg, IFLA_MTU, 4, &mtu) < 0) { + retval = -ENOMEM; + goto cleanup; + } + + retval = netlink_sendrecv(msg); + +cleanup: + if(msg) + nlmsg_free(msg); + + return retval; +} + +static int do_anycast_setsockopt(int sock, int what, struct in6_addr *addr, int ifindex) { + struct ipv6_mreq mreq = { *addr, ifindex }; + char *optname; + int ret; + + switch (what) { + DEBUG_OPTNAME(IPV6_JOIN_ANYCAST) + DEBUG_OPTNAME(IPV6_LEAVE_ANYCAST) + default: + optname = "???"; + break; + } + + ret = setsockopt(sock, SOL_IPV6, what, &mreq, sizeof(mreq)); + if (ret) { + logmsg(ANDROID_LOG_ERROR, "%s: setsockopt(%s): %s", __func__, optname, strerror(errno)); + } + + return ret; +} + +/* function: add_anycast_address + * adds an anycast IPv6 address to an interface, returns 0 on success and <0 on failure + * sock - the socket to add the address to + * addr - the IP address to add + * ifname - name of interface to add the address to + */ +int add_anycast_address(int sock, struct in6_addr *addr, const char *ifname) { + int ifindex; + + ifindex = if_nametoindex(ifname); + if (!ifindex) { + logmsg(ANDROID_LOG_ERROR, "%s: unknown ifindex for interface %s", __func__, ifname); + return -ENODEV; + } + + return do_anycast_setsockopt(sock, IPV6_JOIN_ANYCAST, addr, ifindex); +} + +/* function: del_anycast_address + * removes an anycast IPv6 address from the system, returns 0 on success and <0 on failure + * sock - the socket to remove from, must have had the address added via add_anycast_address + * addr - the IP address to remove + */ +int del_anycast_address(int sock, struct in6_addr *addr) { + return do_anycast_setsockopt(sock, IPV6_LEAVE_ANYCAST, addr, 0); +} diff --git a/setif.h b/setif.h new file mode 100644 index 0000000..d31eed5 --- /dev/null +++ b/setif.h @@ -0,0 +1,27 @@ +/* + * Copyright 2012 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * setif.h - network interface configuration + */ +#ifndef __SETIF_H__ +#define __SETIF_H__ + +int add_address(const char *ifname, int family, const void *address, int cidr, const void *broadcast); +int if_up(const char *ifname, int mtu); + +int add_anycast_address(int sock, const struct in6_addr *addr, const char *interface); +int del_anycast_address(int sock, const struct in6_addr *addr); + +#endif diff --git a/translate.c b/translate.c new file mode 100644 index 0000000..ddc9bac --- /dev/null +++ b/translate.c @@ -0,0 +1,532 @@ +/* + * Copyright 2011 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * translate.c - CLAT functions / partial implementation of rfc6145 + */ +#include + +#include "icmp.h" +#include "translate.h" +#include "checksum.h" +#include "clatd.h" +#include "config.h" +#include "logging.h" +#include "debug.h" +#include "tun.h" + +/* function: packet_checksum + * calculates the checksum over all the packet components starting from pos + * checksum - checksum of packet components before pos + * packet - packet to calculate the checksum of + * pos - position to start counting from + * returns - the completed 16-bit checksum, ready to write into a checksum header field + */ +uint16_t packet_checksum(uint32_t checksum, clat_packet packet, clat_packet_index pos) { + int i; + for (i = pos; i < CLAT_POS_MAX; i++) { + if (packet[i].iov_len > 0) { + checksum = ip_checksum_add(checksum, packet[i].iov_base, packet[i].iov_len); + } + } + return ip_checksum_finish(checksum); +} + +/* function: packet_length + * returns the total length of all the packet components after pos + * packet - packet to calculate the length of + * pos - position to start counting after + * returns: the total length of the packet components after pos + */ +uint16_t packet_length(clat_packet packet, clat_packet_index pos) { + size_t len = 0; + int i; + for (i = pos + 1; i < CLAT_POS_MAX; i++) { + len += packet[i].iov_len; + } + return len; +} + +/* function: is_in_plat_subnet + * returns true iff the given IPv6 address is in the plat subnet. + * addr - IPv6 address + */ +int is_in_plat_subnet(const struct in6_addr *addr6) { + // Assumes a /96 plat subnet. + return (addr6 != NULL) && (memcmp(addr6, &Global_Clatd_Config.plat_subnet, 12) == 0); +} + +/* function: ipv6_addr_to_ipv4_addr + * return the corresponding ipv4 address for the given ipv6 address + * addr6 - ipv6 address + * returns: the IPv4 address + */ +uint32_t ipv6_addr_to_ipv4_addr(const struct in6_addr *addr6) { + if (is_in_plat_subnet(addr6)) { + // Assumes a /96 plat subnet. + return addr6->s6_addr32[3]; + } else if (IN6_ARE_ADDR_EQUAL(addr6, &Global_Clatd_Config.ipv6_local_subnet)) { + // Special-case our own address. + return Global_Clatd_Config.ipv4_local_subnet.s_addr; + } else { + // Third party packet. Let the caller deal with it. + return INADDR_NONE; + } +} + +/* function: ipv4_addr_to_ipv6_addr + * return the corresponding ipv6 address for the given ipv4 address + * addr4 - ipv4 address + */ +struct in6_addr ipv4_addr_to_ipv6_addr(uint32_t addr4) { + struct in6_addr addr6; + // Both addresses are in network byte order (addr4 comes from a network packet, and the config + // file entry is read using inet_ntop). + if (addr4 == Global_Clatd_Config.ipv4_local_subnet.s_addr) { + return Global_Clatd_Config.ipv6_local_subnet; + } else { + // Assumes a /96 plat subnet. + addr6 = Global_Clatd_Config.plat_subnet; + addr6.s6_addr32[3] = addr4; + return addr6; + } +} + +/* function: fill_tun_header + * fill in the header for the tun fd + * tun_header - tunnel header, already allocated + * proto - ethernet protocol id: ETH_P_IP(ipv4) or ETH_P_IPV6(ipv6) + */ +void fill_tun_header(struct tun_pi *tun_header, uint16_t proto) { + tun_header->flags = 0; + tun_header->proto = htons(proto); +} + +/* function: fill_ip_header + * generate an ipv4 header from an ipv6 header + * ip_targ - (ipv4) target packet header, source: original ipv4 addr, dest: local subnet addr + * payload_len - length of other data inside packet + * protocol - protocol number (tcp, udp, etc) + * old_header - (ipv6) source packet header, source: nat64 prefix, dest: local subnet prefix + */ +void fill_ip_header(struct iphdr *ip, uint16_t payload_len, uint8_t protocol, + const struct ip6_hdr *old_header) { + int ttl_guess; + memset(ip, 0, sizeof(struct iphdr)); + + ip->ihl = 5; + ip->version = 4; + ip->tos = 0; + ip->tot_len = htons(sizeof(struct iphdr) + payload_len); + ip->id = 0; + ip->frag_off = htons(IP_DF); + ip->ttl = old_header->ip6_hlim; + ip->protocol = protocol; + ip->check = 0; + + ip->saddr = ipv6_addr_to_ipv4_addr(&old_header->ip6_src); + ip->daddr = ipv6_addr_to_ipv4_addr(&old_header->ip6_dst); + + // Third-party ICMPv6 message. This may have been originated by an native IPv6 address. + // In that case, the source IPv6 address can't be translated and we need to make up an IPv4 + // source address. For now, use 255.0.0., which at least looks useful in traceroute. + if ((uint32_t) ip->saddr == INADDR_NONE) { + ttl_guess = icmp_guess_ttl(old_header->ip6_hlim); + ip->saddr = htonl((0xff << 24) + ttl_guess); + } +} + +/* function: fill_ip6_header + * generate an ipv6 header from an ipv4 header + * ip6 - (ipv6) target packet header, source: local subnet prefix, dest: nat64 prefix + * payload_len - length of other data inside packet + * protocol - protocol number (tcp, udp, etc) + * old_header - (ipv4) source packet header, source: local subnet addr, dest: internet's ipv4 addr + */ +void fill_ip6_header(struct ip6_hdr *ip6, uint16_t payload_len, uint8_t protocol, + const struct iphdr *old_header) { + memset(ip6, 0, sizeof(struct ip6_hdr)); + + ip6->ip6_vfc = 6 << 4; + ip6->ip6_plen = htons(payload_len); + ip6->ip6_nxt = protocol; + ip6->ip6_hlim = old_header->ttl; + + ip6->ip6_src = ipv4_addr_to_ipv6_addr(old_header->saddr); + ip6->ip6_dst = ipv4_addr_to_ipv6_addr(old_header->daddr); +} + +/* function: maybe_fill_frag_header + * fills a fragmentation header + * generate an ipv6 fragment header from an ipv4 header + * frag_hdr - target (ipv6) fragmentation header + * ip6_targ - target (ipv6) header + * old_header - (ipv4) source packet header + * returns: the length of the fragmentation header if present, or zero if not present + */ +size_t maybe_fill_frag_header(struct ip6_frag *frag_hdr, struct ip6_hdr *ip6_targ, + const struct iphdr *old_header) { + uint16_t frag_flags = ntohs(old_header->frag_off); + uint16_t frag_off = frag_flags & IP_OFFMASK; + if (frag_off == 0 && (frag_flags & IP_MF) == 0) { + // Not a fragment. + return 0; + } + + frag_hdr->ip6f_nxt = ip6_targ->ip6_nxt; + frag_hdr->ip6f_reserved = 0; + // In IPv4, the offset is the bottom 13 bits; in IPv6 it's the top 13 bits. + frag_hdr->ip6f_offlg = htons(frag_off << 3); + if (frag_flags & IP_MF) { + frag_hdr->ip6f_offlg |= IP6F_MORE_FRAG; + } + frag_hdr->ip6f_ident = htonl(ntohs(old_header->id)); + ip6_targ->ip6_nxt = IPPROTO_FRAGMENT; + + return sizeof(*frag_hdr); +} + +/* function: parse_frag_header + * return the length of the fragmentation header if present, or zero if not present + * generate an ipv6 fragment header from an ipv4 header + * frag_hdr - (ipv6) fragmentation header + * ip_targ - target (ipv4) header + * returns: the next header value + */ +uint8_t parse_frag_header(const struct ip6_frag *frag_hdr, struct iphdr *ip_targ) { + uint16_t frag_off = (ntohs(frag_hdr->ip6f_offlg & IP6F_OFF_MASK) >> 3); + if (frag_hdr->ip6f_offlg & IP6F_MORE_FRAG) { + frag_off |= IP_MF; + } + ip_targ->frag_off = htons(frag_off); + ip_targ->id = htons(ntohl(frag_hdr->ip6f_ident) & 0xffff); + ip_targ->protocol = frag_hdr->ip6f_nxt; + return frag_hdr->ip6f_nxt; +} + +/* function: icmp_to_icmp6 + * translate ipv4 icmp to ipv6 icmp + * out - output packet + * icmp - source packet icmp header + * checksum - pseudo-header checksum + * payload - icmp payload + * payload_size - size of payload + * returns: the highest position in the output clat_packet that's filled in + */ +int icmp_to_icmp6(clat_packet out, clat_packet_index pos, const struct icmphdr *icmp, + uint32_t checksum, const uint8_t *payload, size_t payload_size) { + struct icmp6_hdr *icmp6_targ = out[pos].iov_base; + uint8_t icmp6_type; + int clat_packet_len; + + memset(icmp6_targ, 0, sizeof(struct icmp6_hdr)); + + icmp6_type = icmp_to_icmp6_type(icmp->type, icmp->code); + icmp6_targ->icmp6_type = icmp6_type; + icmp6_targ->icmp6_code = icmp_to_icmp6_code(icmp->type, icmp->code); + + out[pos].iov_len = sizeof(struct icmp6_hdr); + + if (pos == CLAT_POS_TRANSPORTHDR && + is_icmp_error(icmp->type) && + icmp6_type != ICMP6_PARAM_PROB) { + // An ICMP error we understand, one level deep. + // Translate the nested packet (the one that caused the error). + clat_packet_len = ipv4_packet(out, pos + 1, payload, payload_size); + + // The pseudo-header checksum was calculated on the transport length of the original IPv4 + // packet that we were asked to translate. This transport length is 20 bytes smaller than it + // needs to be, because the ICMP error contains an IPv4 header, which we will be translating to + // an IPv6 header, which is 20 bytes longer. Fix it up here. + // We only need to do this for ICMP->ICMPv6, not ICMPv6->ICMP, because ICMP does not use the + // pseudo-header when calculating its checksum (as the IPv4 header has its own checksum). + checksum = checksum + htons(20); + } else if (icmp6_type == ICMP6_ECHO_REQUEST || icmp6_type == ICMP6_ECHO_REPLY) { + // Ping packet. + icmp6_targ->icmp6_id = icmp->un.echo.id; + icmp6_targ->icmp6_seq = icmp->un.echo.sequence; + out[CLAT_POS_PAYLOAD].iov_base = (uint8_t *) payload; + out[CLAT_POS_PAYLOAD].iov_len = payload_size; + clat_packet_len = CLAT_POS_PAYLOAD + 1; + } else { + // Unknown type/code. The type/code conversion functions have already logged an error. + return 0; + } + + icmp6_targ->icmp6_cksum = 0; // Checksum field must be 0 when calculating checksum. + icmp6_targ->icmp6_cksum = packet_checksum(checksum, out, pos); + + return clat_packet_len; +} + +/* function: icmp6_to_icmp + * translate ipv6 icmp to ipv4 icmp + * out - output packet + * icmp6 - source packet icmp6 header + * payload - icmp6 payload + * payload_size - size of payload + * returns: the highest position in the output clat_packet that's filled in + */ +int icmp6_to_icmp(clat_packet out, clat_packet_index pos, const struct icmp6_hdr *icmp6, + const uint8_t *payload, size_t payload_size) { + struct icmphdr *icmp_targ = out[pos].iov_base; + uint8_t icmp_type; + int clat_packet_len; + + memset(icmp_targ, 0, sizeof(struct icmphdr)); + + icmp_type = icmp6_to_icmp_type(icmp6->icmp6_type, icmp6->icmp6_code); + icmp_targ->type = icmp_type; + icmp_targ->code = icmp6_to_icmp_code(icmp6->icmp6_type, icmp6->icmp6_code); + + out[pos].iov_len = sizeof(struct icmphdr); + + if (pos == CLAT_POS_TRANSPORTHDR && + is_icmp6_error(icmp6->icmp6_type) && + icmp_type != ICMP_PARAMETERPROB) { + // An ICMPv6 error we understand, one level deep. + // Translate the nested packet (the one that caused the error). + clat_packet_len = ipv6_packet(out, pos + 1, payload, payload_size); + } else if (icmp_type == ICMP_ECHO || icmp_type == ICMP_ECHOREPLY) { + // Ping packet. + icmp_targ->un.echo.id = icmp6->icmp6_id; + icmp_targ->un.echo.sequence = icmp6->icmp6_seq; + out[CLAT_POS_PAYLOAD].iov_base = (uint8_t *) payload; + out[CLAT_POS_PAYLOAD].iov_len = payload_size; + clat_packet_len = CLAT_POS_PAYLOAD + 1; + } else { + // Unknown type/code. The type/code conversion functions have already logged an error. + return 0; + } + + icmp_targ->checksum = 0; // Checksum field must be 0 when calculating checksum. + icmp_targ->checksum = packet_checksum(0, out, pos); + + return clat_packet_len; +} + +/* function: generic_packet + * takes a generic IP packet and sets it up for translation + * out - output packet + * pos - position in the output packet of the transport header + * payload - pointer to IP payload + * len - size of ip payload + * returns: the highest position in the output clat_packet that's filled in + */ +int generic_packet(clat_packet out, clat_packet_index pos, const uint8_t *payload, size_t len) { + out[pos].iov_len = 0; + out[CLAT_POS_PAYLOAD].iov_base = (uint8_t *) payload; + out[CLAT_POS_PAYLOAD].iov_len = len; + + return CLAT_POS_PAYLOAD + 1; +} + +/* function: udp_packet + * takes a udp packet and sets it up for translation + * out - output packet + * udp - pointer to udp header in packet + * old_sum - pseudo-header checksum of old header + * new_sum - pseudo-header checksum of new header + * len - size of ip payload + */ +int udp_packet(clat_packet out, clat_packet_index pos, const struct udphdr *udp, + uint32_t old_sum, uint32_t new_sum, size_t len) { + const uint8_t *payload; + size_t payload_size; + + if(len < sizeof(struct udphdr)) { + logmsg_dbg(ANDROID_LOG_ERROR,"udp_packet/(too small)"); + return 0; + } + + payload = (const uint8_t *) (udp + 1); + payload_size = len - sizeof(struct udphdr); + + return udp_translate(out, pos, udp, old_sum, new_sum, payload, payload_size); +} + +/* function: tcp_packet + * takes a tcp packet and sets it up for translation + * out - output packet + * tcp - pointer to tcp header in packet + * checksum - pseudo-header checksum + * len - size of ip payload + * returns: the highest position in the output clat_packet that's filled in + */ +int tcp_packet(clat_packet out, clat_packet_index pos, const struct tcphdr *tcp, + uint32_t old_sum, uint32_t new_sum, size_t len) { + const uint8_t *payload; + size_t payload_size, header_size; + + if(len < sizeof(struct tcphdr)) { + logmsg_dbg(ANDROID_LOG_ERROR,"tcp_packet/(too small)"); + return 0; + } + + if(tcp->doff < 5) { + logmsg_dbg(ANDROID_LOG_ERROR,"tcp_packet/tcp header length set to less than 5: %x", tcp->doff); + return 0; + } + + if((size_t) tcp->doff*4 > len) { + logmsg_dbg(ANDROID_LOG_ERROR,"tcp_packet/tcp header length set too large: %x", tcp->doff); + return 0; + } + + header_size = tcp->doff * 4; + payload = ((const uint8_t *) tcp) + header_size; + payload_size = len - header_size; + + return tcp_translate(out, pos, tcp, header_size, old_sum, new_sum, payload, payload_size); +} + +/* function: udp_translate + * common between ipv4/ipv6 - setup checksum and send udp packet + * out - output packet + * udp - udp header + * old_sum - pseudo-header checksum of old header + * new_sum - pseudo-header checksum of new header + * payload - tcp payload + * payload_size - size of payload + * returns: the highest position in the output clat_packet that's filled in + */ +int udp_translate(clat_packet out, clat_packet_index pos, const struct udphdr *udp, + uint32_t old_sum, uint32_t new_sum, const uint8_t *payload, size_t payload_size) { + struct udphdr *udp_targ = out[pos].iov_base; + + memcpy(udp_targ, udp, sizeof(struct udphdr)); + + out[pos].iov_len = sizeof(struct udphdr); + out[CLAT_POS_PAYLOAD].iov_base = (uint8_t *) payload; + out[CLAT_POS_PAYLOAD].iov_len = payload_size; + + if (udp_targ->check) { + udp_targ->check = ip_checksum_adjust(udp->check, old_sum, new_sum); + } else { + // Zero checksums are special. RFC 768 says, "An all zero transmitted checksum value means that + // the transmitter generated no checksum (for debugging or for higher level protocols that + // don't care)." However, in IPv6 zero UDP checksums were only permitted by RFC 6935 (2013). So + // for safety we recompute it. + udp_targ->check = 0; // Checksum field must be 0 when calculating checksum. + udp_targ->check = packet_checksum(new_sum, out, pos); + } + + // RFC 768: "If the computed checksum is zero, it is transmitted as all ones (the equivalent + // in one's complement arithmetic)." + if (!udp_targ->check) { + udp_targ->check = 0xffff; + } + + return CLAT_POS_PAYLOAD + 1; +} + +/* function: tcp_translate + * common between ipv4/ipv6 - setup checksum and send tcp packet + * out - output packet + * tcp - tcp header + * header_size - size of tcp header including options + * checksum - partial checksum covering ipv4/ipv6 header + * payload - tcp payload + * payload_size - size of payload + * returns: the highest position in the output clat_packet that's filled in + */ +int tcp_translate(clat_packet out, clat_packet_index pos, const struct tcphdr *tcp, + size_t header_size, uint32_t old_sum, uint32_t new_sum, + const uint8_t *payload, size_t payload_size) { + struct tcphdr *tcp_targ = out[pos].iov_base; + out[pos].iov_len = header_size; + + if (header_size > MAX_TCP_HDR) { + // A TCP header cannot be more than MAX_TCP_HDR bytes long because it's a 4-bit field that + // counts in 4-byte words. So this can never happen unless there is a bug in the caller. + logmsg(ANDROID_LOG_ERROR, "tcp_translate: header too long %d > %d, truncating", + header_size, MAX_TCP_HDR); + header_size = MAX_TCP_HDR; + } + + memcpy(tcp_targ, tcp, header_size); + + out[CLAT_POS_PAYLOAD].iov_base = (uint8_t *) payload; + out[CLAT_POS_PAYLOAD].iov_len = payload_size; + + tcp_targ->check = ip_checksum_adjust(tcp->check, old_sum, new_sum); + + return CLAT_POS_PAYLOAD + 1; +} + +// Weak symbol so we can override it in the unit test. +void send_rawv6(int fd, clat_packet out, int iov_len) __attribute__((weak)); + +void send_rawv6(int fd, clat_packet out, int iov_len) { + // A send on a raw socket requires a destination address to be specified even if the socket's + // protocol is IPPROTO_RAW. This is the address that will be used in routing lookups; the + // destination address in the packet header only affects what appears on the wire, not where the + // packet is sent to. + static struct sockaddr_in6 sin6 = { AF_INET6, 0, 0, { { { 0, 0, 0, 0 } } }, 0 }; + static struct msghdr msg = { + .msg_name = &sin6, + .msg_namelen = sizeof(sin6), + }; + + msg.msg_iov = out, + msg.msg_iovlen = iov_len, + sin6.sin6_addr = ((struct ip6_hdr *) out[CLAT_POS_IPHDR].iov_base)->ip6_dst; + sendmsg(fd, &msg, 0); +} + +/* function: translate_packet + * takes a packet, translates it, and writes it to fd + * fd - fd to write translated packet to + * to_ipv6 - true if translating to ipv6, false if translating to ipv4 + * packet - packet + * packetsize - size of packet + */ +void translate_packet(int fd, int to_ipv6, const uint8_t *packet, size_t packetsize) { + int iov_len = 0; + + // Allocate buffers for all packet headers. + struct tun_pi tun_targ; + char iphdr[sizeof(struct ip6_hdr)]; + char fraghdr[sizeof(struct ip6_frag)]; + char transporthdr[MAX_TCP_HDR]; + char icmp_iphdr[sizeof(struct ip6_hdr)]; + char icmp_fraghdr[sizeof(struct ip6_frag)]; + char icmp_transporthdr[MAX_TCP_HDR]; + + // iovec of the packets we'll send. This gets passed down to the translation functions. + clat_packet out = { + { &tun_targ, 0 }, // Tunnel header. + { iphdr, 0 }, // IP header. + { fraghdr, 0 }, // Fragment header. + { transporthdr, 0 }, // Transport layer header. + { icmp_iphdr, 0 }, // ICMP error inner IP header. + { icmp_fraghdr, 0 }, // ICMP error fragmentation header. + { icmp_transporthdr, 0 }, // ICMP error transport layer header. + { NULL, 0 }, // Payload. No buffer, it's a pointer to the original payload. + }; + + if (to_ipv6) { + iov_len = ipv4_packet(out, CLAT_POS_IPHDR, packet, packetsize); + if (iov_len > 0) { + send_rawv6(fd, out, iov_len); + } + } else { + iov_len = ipv6_packet(out, CLAT_POS_IPHDR, packet, packetsize); + if (iov_len > 0) { + fill_tun_header(&tun_targ, ETH_P_IP); + out[CLAT_POS_TUNHDR].iov_len = sizeof(tun_targ); + send_tun(fd, out, iov_len); + } + } +} diff --git a/translate.h b/translate.h new file mode 100644 index 0000000..aa8b736 --- /dev/null +++ b/translate.h @@ -0,0 +1,90 @@ +/* + * Copyright 2011 Daniel Drown + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * translate.h - translate from one version of ip to another + */ +#ifndef __TRANSLATE_H__ +#define __TRANSLATE_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clatd.h" + +#define MAX_TCP_HDR (15 * 4) // Data offset field is 4 bits and counts in 32-bit words. + +// Calculates the checksum over all the packet components starting from pos. +uint16_t packet_checksum(uint32_t checksum, clat_packet packet, clat_packet_index pos); + +// Returns the total length of the packet components after pos. +uint16_t packet_length(clat_packet packet, clat_packet_index pos); + +// Returns true iff the given IPv6 address is in the plat subnet. +int is_in_plat_subnet(const struct in6_addr *addr6); + +// Functions to create tun, IPv4, and IPv6 headers. +void fill_tun_header(struct tun_pi *tun_header, uint16_t proto); +void fill_ip_header(struct iphdr *ip_targ, uint16_t payload_len, uint8_t protocol, + const struct ip6_hdr *old_header); +void fill_ip6_header(struct ip6_hdr *ip6, uint16_t payload_len, uint8_t protocol, + const struct iphdr *old_header); + +// Translate and send packets. +void translate_packet(int fd, int to_ipv6, const uint8_t *packet, size_t packetsize); + +// Translate IPv4 and IPv6 packets. +int ipv4_packet(clat_packet out, clat_packet_index pos, const uint8_t *packet, size_t len); +int ipv6_packet(clat_packet out, clat_packet_index pos, const uint8_t *packet, size_t len); + +// Deal with fragmented packets. +size_t maybe_fill_frag_header(struct ip6_frag *frag_hdr, struct ip6_hdr *ip6_targ, + const struct iphdr *old_header); +uint8_t parse_frag_header(const struct ip6_frag *frag_hdr, struct iphdr *ip_targ); + +// Deal with fragmented packets. +size_t maybe_fill_frag_header(struct ip6_frag *frag_hdr, struct ip6_hdr *ip6_targ, + const struct iphdr *old_header); +uint8_t parse_frag_header(const struct ip6_frag *frag_hdr, struct iphdr *ip_targ); + +// Translate ICMP packets. +int icmp_to_icmp6(clat_packet out, clat_packet_index pos, const struct icmphdr *icmp, + uint32_t checksum, const uint8_t *payload, size_t payload_size); +int icmp6_to_icmp(clat_packet out, clat_packet_index pos, const struct icmp6_hdr *icmp6, + const uint8_t *payload, size_t payload_size); + +// Translate generic IP packets. +int generic_packet(clat_packet out, clat_packet_index pos, const uint8_t *payload, size_t len); + +// Translate TCP and UDP packets. +int tcp_packet(clat_packet out, clat_packet_index pos, const struct tcphdr *tcp, + uint32_t old_sum, uint32_t new_sum, size_t len); +int udp_packet(clat_packet out, clat_packet_index pos, const struct udphdr *udp, + uint32_t old_sum, uint32_t new_sum, size_t len); + +int tcp_translate(clat_packet out, clat_packet_index pos, const struct tcphdr *tcp, + size_t header_size, uint32_t old_sum, uint32_t new_sum, + const uint8_t *payload, size_t payload_size); +int udp_translate(clat_packet out, clat_packet_index pos, const struct udphdr *udp, + uint32_t old_sum, uint32_t new_sum, + const uint8_t *payload, size_t payload_size); + +#endif /* __TRANSLATE_H__ */ diff --git a/tun.c b/tun.c new file mode 100644 index 0000000..49f0ea7 --- /dev/null +++ b/tun.c @@ -0,0 +1,89 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * tun.c - tun device functions + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clatd.h" + +/* function: tun_open + * tries to open the tunnel device + */ +int tun_open() { + int fd; + + fd = open("/dev/tun", O_RDWR); + if(fd < 0) { + fd = open("/dev/net/tun", O_RDWR); + } + + return fd; +} + +/* function: tun_alloc + * creates a tun interface and names it + * dev - the name for the new tun device + */ +int tun_alloc(char *dev, int fd) { + struct ifreq ifr; + int err; + + memset(&ifr, 0, sizeof(ifr)); + + ifr.ifr_flags = IFF_TUN; + if( *dev ) { + strncpy(ifr.ifr_name, dev, IFNAMSIZ); + ifr.ifr_name[IFNAMSIZ-1] = '\0'; + } + + if( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0 ){ + close(fd); + return err; + } + strcpy(dev, ifr.ifr_name); + return 0; +} + +/* function: set_nonblocking + * sets a filedescriptor to non-blocking mode + * fd - the filedescriptor + * returns: 0 on success, -1 on failure + */ +int set_nonblocking(int fd) { + int flags = fcntl(fd, F_GETFL); + if (flags == -1) { + return flags; + } + return fcntl(fd, F_SETFL, flags | O_NONBLOCK); +} + +/* function: send_tun + * sends a clat_packet to a tun interface + * fd - the tun filedescriptor + * out - the packet to send + * iov_len - the number of entries in the clat_packet + * returns: number of bytes read on success, -1 on failure + */ +int send_tun(int fd, clat_packet out, int iov_len) { + return writev(fd, out, iov_len); +} diff --git a/tun.h b/tun.h new file mode 100644 index 0000000..bcdd10e --- /dev/null +++ b/tun.h @@ -0,0 +1,37 @@ +/* + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * tun.h - tun device functions + */ +#ifndef __TUN_H__ +#define __TUN_H__ + +#include + +#include "clatd.h" +#include "ring.h" + +struct tun_data { + char device4[IFNAMSIZ]; + int read_fd6, write_fd6, fd4; + struct packet_ring ring; +}; + +int tun_open(); +int tun_alloc(char *dev, int fd); +int send_tun(int fd, clat_packet out, int iov_len); +int set_nonblocking(int fd); + +#endif