From c9fb17e1cc63313b69dde33ee40ba89cb7ea2662 Mon Sep 17 00:00:00 2001 From: Seonah Moon Date: Mon, 23 Sep 2019 14:33:26 +0900 Subject: [PATCH] Imported Upstream version 1.4 Change-Id: I230cabc6167ba04dce75ddd7a7a465088e8d1cb5 --- Android.mk | 47 +++ BUGS | 5 + LICENSE | 201 +++++++++++ MODULE_LICENSE_APACHE2 | 0 NOTICE | 189 ++++++++++ checksum.c | 142 ++++++++ checksum.h | 34 ++ clatd.c | 526 +++++++++++++++++++++++++++ clatd.conf | 22 ++ clatd.h | 46 +++ clatd_microbenchmark.c | 214 +++++++++++ clatd_test.cpp | 936 +++++++++++++++++++++++++++++++++++++++++++++++++ config.c | 395 +++++++++++++++++++++ config.h | 50 +++ debug.h | 24 ++ dns64.c | 73 ++++ dns64.h | 23 ++ dump.c | 248 +++++++++++++ dump.h | 40 +++ getaddr.c | 139 ++++++++ getaddr.h | 28 ++ icmp.c | 181 ++++++++++ icmp.h | 45 +++ ipv4.c | 146 ++++++++ ipv6.c | 179 ++++++++++ logging.c | 55 +++ logging.h | 27 ++ mtu.c | 46 +++ mtu.h | 24 ++ netlink_callbacks.c | 67 ++++ netlink_callbacks.h | 24 ++ netlink_msg.c | 187 ++++++++++ netlink_msg.h | 30 ++ ring.c | 126 +++++++ ring.h | 55 +++ setif.c | 180 ++++++++++ setif.h | 27 ++ translate.c | 532 ++++++++++++++++++++++++++++ translate.h | 90 +++++ tun.c | 89 +++++ tun.h | 37 ++ 41 files changed, 5529 insertions(+) create mode 100644 Android.mk create mode 100644 BUGS create mode 100644 LICENSE create mode 100644 MODULE_LICENSE_APACHE2 create mode 100644 NOTICE create mode 100644 checksum.c create mode 100644 checksum.h create mode 100644 clatd.c create mode 100644 clatd.conf create mode 100644 clatd.h create mode 100644 clatd_microbenchmark.c create mode 100644 clatd_test.cpp create mode 100644 config.c create mode 100644 config.h create mode 100644 debug.h create mode 100644 dns64.c create mode 100644 dns64.h create mode 100644 dump.c create mode 100644 dump.h create mode 100644 getaddr.c create mode 100644 getaddr.h create mode 100644 icmp.c create mode 100644 icmp.h create mode 100644 ipv4.c create mode 100644 ipv6.c create mode 100644 logging.c create mode 100644 logging.h create mode 100644 mtu.c create mode 100644 mtu.h create mode 100644 netlink_callbacks.c create mode 100644 netlink_callbacks.h create mode 100644 netlink_msg.c create mode 100644 netlink_msg.h create mode 100644 ring.c create mode 100644 ring.h create mode 100644 setif.c create mode 100644 setif.h create mode 100644 translate.c create mode 100644 translate.h create mode 100644 tun.c create mode 100644 tun.h 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 -- 2.7.4