From 07be7a25a32b57093931d8e3186dc36656720a9c Mon Sep 17 00:00:00 2001 From: Joseph Henry Date: Thu, 19 Jul 2018 17:19:06 -0700 Subject: [PATCH] Removed cruft from project --- BUILDING.md | 36 - CMakeLists.txt | 83 +- FAQ.md | 30 - README.md | 24 +- RELEASE-NOTES.md | 154 --- TESTING.md | 123 -- artwork/AppIcon_87x87.png | Bin 9622 -> 0 bytes artwork/ztapp.png | Bin 27433 -> 0 bytes artwork/ztapp100.png | Bin 4149 -> 0 bytes attic/Makefile | 473 ------- {include => attic}/VirtualBindingPair.h | 0 {src => attic}/VirtualSocket.cpp | 0 {include => attic}/VirtualSocket.h | 0 {src => attic}/VirtualSocketLayer.cpp | 0 {include => attic}/VirtualSocketLayer.h | 0 {src => attic}/picoTCP.cpp | 0 {include => attic}/picoTCP.h | 0 check.sh | 16 - docs/test_diagram.png | Bin 88477 -> 0 bytes include/SysUtils.h | 8 - include/VirtualTap.h | 110 +- include/libzt.h | 14 +- include/libztDebug.h | 22 +- include/libztDefs.h | 90 +- include/lwIP.h | 149 +-- include/lwipopts.h | 16 +- make-liblwip.mk | 105 -- makelib.bat | 2 - res/libztdll.rc | 36 - src/SysUtils.cpp | 27 +- src/VirtualTap.cpp | 407 +----- src/ZT1Service.cpp | 26 +- src/libzt.cpp | 332 +---- src/libztJNI.cpp | 381 ++---- src/lwIP.cpp | 1639 +---------------------- test/dummy.cpp | 15 - test/layer2.cpp | 194 --- test/vera++/libzt | 85 -- 38 files changed, 289 insertions(+), 4308 deletions(-) delete mode 100644 BUILDING.md delete mode 100644 FAQ.md delete mode 100644 RELEASE-NOTES.md delete mode 100644 TESTING.md delete mode 100644 artwork/AppIcon_87x87.png delete mode 100644 artwork/ztapp.png delete mode 100644 artwork/ztapp100.png delete mode 100644 attic/Makefile rename {include => attic}/VirtualBindingPair.h (100%) rename {src => attic}/VirtualSocket.cpp (100%) rename {include => attic}/VirtualSocket.h (100%) rename {src => attic}/VirtualSocketLayer.cpp (100%) rename {include => attic}/VirtualSocketLayer.h (100%) rename {src => attic}/picoTCP.cpp (100%) rename {include => attic}/picoTCP.h (100%) delete mode 100755 check.sh delete mode 100644 docs/test_diagram.png delete mode 100644 make-liblwip.mk delete mode 100644 makelib.bat delete mode 100644 res/libztdll.rc delete mode 100644 test/dummy.cpp delete mode 100644 test/layer2.cpp delete mode 100644 test/vera++/libzt diff --git a/BUILDING.md b/BUILDING.md deleted file mode 100644 index 1fcdd3b..0000000 --- a/BUILDING.md +++ /dev/null @@ -1,36 +0,0 @@ -## Build Instructions for various platforms - -### Linux - - - `make static_lib` - - `make tests` - -### macOS - - - `make static_lib` - - `make tests` - -### iOS - - - `make ios_framework` - -### FreeBSD - - - `make static_lib` - - `make tests` - -### Windows - - - [Prebuilt Binaries\Libraries Here]() - - Otherwise, - - - Set up [mingw-w64](https://mingw-w64.org/doku.php) - - Install a copy of [MSYS](http://www.mingw.org/wiki/MSYS) - - Run `msys.bat` (should be in the base directory of your MSYS installation) - - Tell it where your `mingw-64` installation is - - `make static_lib` - - `make tests` - - This will output binaries to `build/mingw-w64` - diff --git a/CMakeLists.txt b/CMakeLists.txt index e837c09..cda48e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,7 @@ # of your own application. # -cmake_minimum_required (VERSION 2.8) +cmake_minimum_required (VERSION 3.0) project (libzt) # --- SETUP @@ -54,9 +54,14 @@ if (WIN32) set (LWIP_PORT_DIR ${PROJ_DIR}/ext/lwip-contrib/ports/win32) set (COMMON_DEBUG_FLAGS "-DLIBZT_TRACE=1 -DLIBZT_DEBUG=1 -DNS_TRACE=1 -DNS_DEBUG=1") else() - set (CMAKE_C_FLAGS "-fstack-protector -DZT_SDK=1") + set (CMAKE_C_FLAGS "-fstack-protector -DLWIP_DEBUG=1 -DZT_SDK=1") set (LWIP_PORT_DIR ${PROJ_DIR}/ext/lwip-contrib/ports/unix/port) - set (COMMON_DEBUG_FLAGS "-g -DLIBZT_TRACE=1 -DLIBZT_DEBUG=1 -DNS_TRACE=1 -DNS_DEBUG=1") + set (COMMON_DEBUG_FLAGS "-g -DLIBZT_TRACE=1 -DLWIP_DEBUG=1 -DLIBZT_DEBUG=1 -DNS_TRACE=1 -DNS_DEBUG=1") +endif() + +if (${CMAKE_SYSTEM_NAME} MATCHES "Android") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -DSOCKLEN_T_DEFINED=1") + include_directories (/Users/joseph/Library/Android/sdk/ndk-bundle/sysroot/usr/include/arm-linux-androideabi) endif() # Flags for various build types @@ -94,10 +99,9 @@ add_library (lwip STATIC ${lwip_src_glob}) add_library (lwip_pic STATIC ${lwip_src_glob}) set_target_properties (lwip_pic PROPERTIES POSITION_INDEPENDENT_CODE ON) - # zto # ZeroTier Core Service -set (ZTO_SRC_DIR ${PROJ_DIR}/zto) +set (ZTO_SRC_DIR ${PROJ_DIR}/ext/ZeroTierOne) include_directories (${ZTO_SRC_DIR}/include) include_directories (${ZTO_SRC_DIR}/osdep) include_directories (${ZTO_SRC_DIR}/node) @@ -135,7 +139,6 @@ include_directories ("${LIBZT_SRC_DIR}") include_directories ("${PROJ_DIR}/include") file (GLOB libzt_src_glob ${LIBZT_SRC_DIR}/*.cpp) add_library (zt ${libzt_src_glob}) -target_link_libraries (zt pthread lwip zto) if (WIN32) target_link_libraries (zto ws2_32) target_link_libraries (zto ${lshlwapi_LIBRARY_PATH}) @@ -146,41 +149,47 @@ set_target_properties (zt PROPERTIES OUTPUT_NAME zt) add_library (ztshared SHARED ${libzt_src_glob}) target_link_libraries (ztshared zto_pic lwip_pic) set_target_properties (ztshared PROPERTIES OUTPUT_NAME zt) - +if (${CMAKE_SYSTEM_NAME} MATCHES "Android") + target_link_libraries (zt lwip zto android log) +else() + target_link_libraries (zt pthread lwip zto) +endif() # --- Test applications, examples, etc --- -file (GLOB APP_SOURCES - ${PROJ_DIR}/examples/bindings/cpp/ipv4simple/*.cpp - ${PROJ_DIR}/examples/bindings/cpp/ipv6simple/*.cpp - ${PROJ_DIR}/examples/bindings/cpp/ipv6adhoc/*.cpp - ${PROJ_DIR}/examples/bindings/cpp/sharedlib/*.cpp - ${PROJ_DIR}/examples/apps/ztproxy/*.cpp -) -foreach (testsourcefile ${APP_SOURCES}) - string (REPLACE ".cpp" "" testname ${testsourcefile}) - get_filename_component (testname ${testname} NAME) - add_executable (${testname} ${testsourcefile}) -if (WIN32) - target_link_libraries (${testname} lwip zto zt) -else() - target_link_libraries (${testname} lwip zto zt pthread dl) -endif() -endforeach (testsourcefile ${APP_SOURCES}) +if (NOT ${CMAKE_SYSTEM_NAME} MATCHES "Android") + file (GLOB APP_SOURCES + ${PROJ_DIR}/examples/cpp/ipv4simple/*.cpp + ${PROJ_DIR}/examples/cpp/ipv6simple/*.cpp + ${PROJ_DIR}/examples/cpp/ipv6adhoc/*.cpp + ${PROJ_DIR}/examples/cpp/sharedlib/*.cpp + ${PROJ_DIR}/examples/ztproxy/*.cpp + ) + foreach (testsourcefile ${APP_SOURCES}) + string (REPLACE ".cpp" "" testname ${testsourcefile}) + get_filename_component (testname ${testname} NAME) + add_executable (${testname} ${testsourcefile}) + if (WIN32) + target_link_libraries (${testname} lwip zto zt) + else() + target_link_libraries (${testname} lwip zto zt pthread dl) + endif() + endforeach (testsourcefile ${APP_SOURCES}) -# selftest -add_executable (selftest ${PROJ_DIR}/test/selftest.cpp) -target_compile_options (selftest PRIVATE -D__SELFTEST__) -if (WIN32) - target_link_libraries (selftest lwip zto zt ${ws2_32_LIBRARY_PATH} ${lshlwapi_LIBRARY_PATH} ${liphlpapi_LIBRARY_PATH}) -else() - target_link_libraries (selftest pthread lwip zto zt) -endif() + # selftest + add_executable (selftest ${PROJ_DIR}/test/selftest.cpp) + target_compile_options (selftest PRIVATE -D__SELFTEST__) + if (WIN32) + target_link_libraries (selftest lwip zto zt ${ws2_32_LIBRARY_PATH} ${lshlwapi_LIBRARY_PATH} ${liphlpapi_LIBRARY_PATH}) + else() + target_link_libraries (selftest pthread lwip zto zt) + endif() -# nativetest -add_executable (nativetest ${PROJ_DIR}/test/selftest.cpp) -target_compile_options (nativetest PRIVATE -D__NATIVETEST__) -target_link_libraries (nativetest lwip zto zt) + # nativetest + add_executable (nativetest ${PROJ_DIR}/test/selftest.cpp) + target_compile_options (nativetest PRIVATE -D__NATIVETEST__) + target_link_libraries (nativetest lwip zto zt) +endif() # Clean up intermediate library targets #file (REMOVE ${LIBRARY_OUTPUT_PATH}/libhttp.a) @@ -197,7 +206,7 @@ target_link_libraries (nativetest lwip zto zt) # --- CONFIGURATION -if (JNI EQUAL 1) +if (JNI EQUAL 1 OR ${CMAKE_SYSTEM_NAME} MATCHES "Android") MESSAGE (STATUS "Looking for JNI headers") find_package (JNI) if (JNI_FOUND) diff --git a/FAQ.md b/FAQ.md deleted file mode 100644 index 1240735..0000000 --- a/FAQ.md +++ /dev/null @@ -1,30 +0,0 @@ -## Frequently Asked Questions -*** - -### Can I use this in my commercial or closed-source application or service? - -Yes! - Just let us know, and we will work out a licensing scheme. You will need to build the library with the `lwIP` network stack. In the case that you're a non-profit, or are developing open source software, you can use this entirely for free and may choose either `picoTCP` or `lwIP` as your network stack. - -### My application or service won't fully come online - -In rare circumstances it can take a substantial amount of time for a libzt instance to come online and become reachable. In cases where it never seems to progress beyond this stage you should check to make sure there are no rogue processes on the machines using the same ZeroTier identity files, or no other instances on your ZeroTier network using said identity files. If this doesn't help. Contact us. - -### How do I get debug output? - -In order of relevance, enable the following: For `libzt`-specific debug output which basically includes the POSIX socket emulation layer and network stack *drivers*, enable `LIBZT_DEBUG=1`. For situations where you suspect that the problem might lie within the network stack itself, enable `NS_DEBUG=1`. You should also check out `include/libzt.h` for additional fine-grained debug options for each network stack. If you think that your problem is with the ZeroTier protocol itself, enable `ZT_DEBUG=1`. This will output what's happening in the ZeroTier core. Note, you can enable all of these at once if you're brave. Additionally, you can use the `LIBZT_SANITIZE=1` flag to build against the [AddressSanitization](https://github.com/google/sanitizers/wiki/AddressSanitizer) library. - -### Versioning - -Each version of libzt will include a specific (and unrelated) version of the ZeroTier core, and network stacks. For instance: Version 1.1.5 of libzt might include version 1.2.5 of ZeroTier, there is no connection between these versioning schemes. Additionally, an update to libzt doesn't necessarily translate to an update to the ZeroTier core. - -### Code Quality and Standardization - -Explicit coding style rules are specified in `test/vera++/libzt` and enforced periodically by running the source through `vera++`. Before each commit or merge into `master` one should run `test/selftest` and `test/nativetest` as specified in [TESTING.md](TESTING.md). Also, on the tabs vs. spaces debate, for this project we have chosen `tabs`. - -### How do I run unit tests? - -See: [TESTING.md](TESTING.md) - -### I want to use a different network stack. How do I do this? - -See [src/ExampleStackDriver.cpp](src/ExampleStackDriver.cpp) \ No newline at end of file diff --git a/README.md b/README.md index ce9c93b..4a8b257 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,7 @@ A library version of [ZeroTier](https://github.com/zerotier/ZeroTierOne), **libz [![irc](https://img.shields.io/badge/IRC-%23zerotier%20on%20freenode-orange.svg)](https://webchat.freenode.net/?channels=zerotier) - - Pre-Built binaries: [zerotier.com/download.shtml](https://zerotier.com/download.shtml?pk_campaign=github_libzt) - - Windows DLL (x64) can be found here: [libzt_win_x64_1.1.5.zip](https://download.zerotier.com/dist/libzt_win_x64_1.1.5.zip) - - Bindings for popular languages like [Scala](examples/bindings/scala), [Swift](examples/bindings/swift), [Java](examples/bindings/java), [Python](examples/bindings/python), etc. can be found [here](examples/bindings) + - Bindings for popular languages like [Scala](examples/scala), [Swift](examples/swift), [Java](examples/java), [Python](examples/python), etc. can be found [here](examples/bindings) *** @@ -56,7 +54,7 @@ int main() } ``` -For an example using only the [Virtual Layer 2](https://www.zerotier.com/manual.shtml#2_2?pk_campaign=github_libzt), see [test/layer2.cpp](test/layer2.cpp) +For an example using only the [Virtual Layer 2](https://www.zerotier.com/manual.shtml#2_2?pk_campaign=github_libzt), see [examples/layer2](examples/layer2/layer2.cpp) *** @@ -67,30 +65,20 @@ We recommend using [CMake](https://cmake.org/) for its extensive cross-platform ``` git submodule init git submodule update +./ext/patch_submodules.sh cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=DEBUG cmake --build build ``` -Use `libzt.a` or `libzt.dylib` in your application. They are placed in `bin\lib`. Change `CMAKE_BUILD_TYPE` to `RELEASE` for a smaller and optmized build. - -### Install - - - Install: `make install` - - Uninstall: `xargs rm < install_manifest.txt` +Builds are placed in `bin\lib`. Use `libzt.a` or `libzt.dylib` in your application. Change `CMAKE_BUILD_TYPE` to `RELEASE` for a smaller and optimized build. *** -### Testing and Debugging - - See [TESTING.md](TESTING.md) - - ### Contributing Please make pull requests against the `dev` branch. The `master` branch is release, and `edge` is for unstable and work in progress changes and is not likely to work. ### Commercial License - - If you want a commercial license to use libzt in your product contact us directly via `contact@zerotier.com`. -### Adding a custom network stack - - If you wish to use something other than lwIP or picoTCP, you can easily add your own API function calls in `src/libzt.cpp` or `src/VirtualSocket.cpp` depending on whether your stack's API exposes a POSIX-socket API or a raw API, respectively. - +If you want a commercial license to use libzt in your product contact us directly via `contact@zerotier.com`. + diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md deleted file mode 100644 index d952ee5..0000000 --- a/RELEASE-NOTES.md +++ /dev/null @@ -1,154 +0,0 @@ -Current version: 1.1.5 -*** - -### 2017-11-10 -- Version 1.1.6 - - [In progress] - - API simplification - - Removed protocol specific address getters - - Changed how network IDs and node IDs are passed to the library (everything is now uint64_t) - - Initial steps for C-- efforts - - Removed classes for stack drivers - - Removed namespaces - - Names of key classes changed slightly to prevent collisions - - Re-classification of Virtual Socket Layer (and associated stack drivers) as experimental - - lwIP sequential socket API is now default - - Slightly reworked lwIP interface setup code - -### 2017-10-19 -- Version 1.1.5 (Current) - - - Improvements in API consistency and coverage - - Re-introduction of lwIP driver, also upgraded lwIP to 2.0.3 - - Added lwIP sequential driver (basically a wrapper), and downgraded previous raw driver to experimental - - Various bug fixes for POSIX socket emulation layer - - Rework of VirtualSocket lock scheme - - Performance upgrades for RX and TX - - Windows Support - -### 2017-06-07 -- Version 1.1.4 - - - Library can now be run in IPV4 and IPV6 mode simultaneously - - -### 2017-06-05 -- Version 1.1.3 - - - Updated picoTCP to 1.4.0 - - Improved selftest - - -### 2017-05-30 -- Version 1.1.2 - - - Minor ZTO source update - - Improved selftest - - Improved documentation - - -### 2017-05-04 -- Version 1.1.1 - - - Updated ZTO source to 1.2.4 - - Various bug fixes - - -### 2017-05-02 -- Version 1.1.0 - - - Added new socket multiplexing logic - - Significant restructuring of picoTCP stack driver - - -### 2017-04-06 -- Version 1.0.0 - - - Refactored library structure - - Dynamic loading of stack no longer required - - lwIP Support dropped - - -### 2017-03-28 -- Version 0.9.0 - - - Updated ZTO source for tptr feature - - -### 2017-03-14 -- Version 0.8.0 - - - Updated ZTO source to 1.2.0 - - Various bug fixes - - -### 2017-03-07 -- Version 0.7.0 - - - Updated ZTO source to 1.1.17 - - Updated documentation - - Various bug fixes - - -### 2016-11-21 -- Version 0.6.2 - - - Updated ZTO source for reliability - - -### 2016-10-28 -- Version 0.6.1 - - - Stack driver support for IPv4 XOR IPv6 - - -### 2016-10-21 -- Version 0.6.0 - - - Updated ZTO source to 1.1.14 - - -### 2016-10-18 -- Version 0.5.0 - - - Implemented driver for picoTCP network stack - - -### 2016-09-29 -- Version 0.4.2 - - - IPv6-related fixes - - -### 2016-09-27 -- Version 0.4.1 - - - Added IPv6 support to lwIP stack driver - - -### 2016-09-11 -- Version 0.4.0 - - - Upgraded to lwIP 2.0.0 - - -### 2016-09-07 -- Version 0.3.5 - - - Introduced prettier and less complex Swift API - - -### 2016-09-06 -- Version 0.3.4 - - - Improved debugging traces - - -### 2016-09-01 -- Version 0.3.3 - - - Improved API parameter naming consistency - - -### 2016-08-17 -- Version 0.3.2 - - - Improved proxy server controls - - -### 2016-08-15 -- Version 0.3.1 - - - API consistency improvements - - -### 2016-07-25 -- Version 0.3.0 - - - Updated ZTO source to 1.1.4 - - -### 2016-07-20 -- Version 0.2.1 - - - Normalized zt_ API naming conventions - - -### 2016-06-14 -- Version 0.2.0 - - - Old netcon project moved into its own repo diff --git a/TESTING.md b/TESTING.md deleted file mode 100644 index 083785d..0000000 --- a/TESTING.md +++ /dev/null @@ -1,123 +0,0 @@ -## Testing via [selftest.cpp](test/selftest.cpp) - -### Step 1. Enabling debug output - - - `make static_lib SDK_DEBUG=1`: For debugging libzt - - `make static_lib ZT_DEBUG=1`: For debugging the ZeroTier core protocol (you usually won't need this) - -### Step 2. Build the test programs: - - - `make tests` - - This will output `selftest` and `echotest` to `build/$PLATFORM/` - - *Note, the following examples assume your testing environment is `linux`, you'll see this in the build output path. If this is not true, change it to `darwin`, `freebsd`, or `win` depending on what you're running.* - - -### Step 3. Define your test configuration in `test/selftest.conf`: - -This is essentially just a listing of libzt-based app identities and host machine identities. We will be conducting `library-to-remote-library`, `library-to-remote-host`, and `remote-host-to-library` tests over the network. For this reason we need to define who should be talking to who. - -A simple test configutation might look like the following. This will create an `alice` and `bob` test personality, that is, we will run one instance of the library as a server (alice), and one instance of the `echotest` on the same host machine. `echotest` is merely a program to record timings for transmitted data and also generate data for the library to receive). Additionally we will be running a library as a client `bob` on another remote host as well as another instance of `echotest` on that same machine. In this configuration the following will happen: - - - `alice` libzt will tx/rx to/from `bob` libzt - - `alice` libzt will send X bytes to `bob`'s `echotest` to test maximum TX rate - - `alice` libzt will request X bytes from `bob`'s `echotest` to test maximum RX rate - - `bob` libzt will send X bytes to `alice`'s `echotest` to test maximum TX rate - - `bob` libzt will request X bytes from `alice`'s `echotest` to test maximum RX rate - -``` -# Tests will use ports starting from 'port' to 'port+n' where 'n' is the number of tests - - -# Alice -name alice -mode server -nwid 17d7094b2c2c7319 -test comprehensive -port 7000 -path test/alice -ipv4 172.30.30.10 -ipv6 fd12:d719:4b6c:6c53:f799:13c4:07e0:abb8 -echo_ipv4 172.30.30.1 -echo_ipv6 fd11:d759:136e:2b53:6791:9328:31ce:618a -; - -# Bob -name bob -mode client -nwid 17d7094b2c2c7319 -test comprehensive -port 7000 -path test/bob -ipv4 172.30.30.20 -ipv6 fd11:d759:136e:2b53:6791:9328:31ce:618a -echo_ipv4 172.30.30.2 -echo_ipv6 fd12:d719:4b6c:6c53:f799:13c4:07e0:abb8 -``` - - -Build outputs are as follows: - -``` -build - | - |--darwin - | |-libzt.a - | |-selftest - | |-echotest - | - |--linux - | |-libzt.a - | |-selftest - | |-echotest - | - |--freebsd - | |-libzt.a - | |-selftest - | |-echotest - | - |--win - |-libzt.a - |-selftest - |-echotest -``` - -The self test will be performed over the network in the following configuration (addresses and ports are subject to change depending on what you define in your `test/*.conf` files): -![Image](docs/test_diagram.png) - -### Test sets: - - - Test set A: Tests for correctness, error handling, blocking behaviour, on-system performance, etc - - Test set B: Tests RX performance (from non-libzt app) - - Test set C: Tests TX performance (to non-libzt app) - -### Types of tests (defined in `selftest.cpp`) - -#### Simple Tests: - - - Simple tests merely test one aspect of the library. For instance, its role as an IPv4 server, or IPv6 client. - -#### Sustained Tests - - - Sustained tests will test the library's ability to support long-duration connections and data transfers. - -#### Slam Tests - - - Slam tests will test the library's ability to handle many repeated API calls or repeated common sequences of API calls that a typical application may make. For instance, it will try to create as many sockets as possible, or try to create a socket, bind to an address, listen, and accept over and over. This is useful for detecting memory leaks and architectural limitations in the stack drivers. - -#### Comprehensive Tests - - - A comprehensive test will test each aspect of the library one time. - -#### Random Tests - - - Makes random API calls with random (or plausible arguments/data) to test for proper error handling - -#### Performance Tests - - - Test's the library's performance characteristics - -#### Correctness Tests - - - Tests's the library's error handling, address treatment, and blocking/non-blocking behaviour. diff --git a/artwork/AppIcon_87x87.png b/artwork/AppIcon_87x87.png deleted file mode 100644 index 2cdc696f9bd021e1cd67d772fc570e0c080eca02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9622 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 M07*qoM6N<$f(KuFivR!s diff --git a/artwork/ztapp.png b/artwork/ztapp.png deleted file mode 100644 index 972ea959dfc09f29536a3e4c8df6c4c2fb12d7b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27433 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 F{{#C@|8W2S diff --git a/artwork/ztapp100.png b/artwork/ztapp100.png deleted file mode 100644 index 7d32378cc7b441cb950792ba47060d2b4d838b91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4149 zcmZ{nXHXMfu=eR)2_Q{^L$4$?J1XbJ{H??tLW zfFQk!fOM&s|NG^g`{B;cZ=X45&NH*KXJ%(MT31{32HkBs5)zUd>S{3kYqk3i8mjB; zxtMBrEmSrdsxXqP|Kej~Y4Y_At*4rq&$X)kKc4lv$diyT`l!Q{4Bt-w$qDeZ91rSN zXb#ws|C(fsHm8QwA&}1(J_B7JE{Wh1 zQ_m-ljw)fu_3cqK=qn7l3%Su5cjJAphZ*B<_)}xyKh8XFb}EC*`CQA{tBt=ipAHa% zx5shETRQ>4Yv+S9%|86ut=w;jN^tmH)Y6HHT`|&Q?@n0-TfRMS(URH%Y3VzBoNh0z$WujqQjED|2YkP}JPW<6wB2;(PLc=b zm?LeXPg!o5$YWk?SwbzjvL=oN9B+3L8~lVZ&Y30t?7NJLpvkZ2x8S5{F={jfJw3fd zaTvvzxD?48DRC;W;U>3Fb|i^4Q=)=|7E8MqP+?cA>s5&%1#9c`(!YEL-?k2V{`D?? z0u`KCs-I)|N*3nV1&}O;ij>u(>A4h${>%0Atrp6_dpX*VzrgHRtK5C%Xd*{A{6yRg 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 diff --git a/attic/Makefile b/attic/Makefile deleted file mode 100644 index cce050c..0000000 --- a/attic/Makefile +++ /dev/null @@ -1,473 +0,0 @@ -# -# ZeroTier SDK - Network Virtualization Everywhere -# Copyright (C) 2011-2017 ZeroTier, Inc. https://www.zerotier.com/ -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# -- -# -# You can be released from the requirements of the license by purchasing -# a commercial license. Buying such a license is mandatory as soon as you -# develop commercial closed-source software that incorporates or links -# directly against ZeroTier software without disclosing the source code -# of your own application. -# - -# Automagically pick clang or gcc, with preference for clang -# This is only done if we have not overridden these with an environment or CLI variable -ifeq ($(origin CC),default) - CC=$(shell if [ -e /usr/bin/clang ]; then echo clang; else echo gcc; fi) -endif -ifeq ($(origin CXX),default) - CXX=$(shell if [ -e /usr/bin/clang++ ]; then echo clang++; else echo g++; fi) -endif - -OSTYPE=$(shell uname -s | tr '[A-Z]' '[a-z]') -BUILDPATH=build/$(OSTYPE) -LIBPATH=lib -LWIPCONTRIBDIR=ext/lwip-contrib - -# default -CFLAGS+=-Wall - -# Windows -ifeq ($(OSTYPE),mingw32_nt-6.2) -ARTOOL=ar -ARFLAGS=rcs - -CC=gcc -CXX=g++ -CFLAGS= -CXXFLAGS+=-fpermissive -Wno-unknown-pragmas -Wno-pointer-arith -Wno-deprecated-declarations -Wno-conversion-null -WINDEFS=-lws2_32 -lshlwapi -liphlpapi -static -static-libgcc -static-libstdc++ -LWIPARCHINCLUDE=$(LWIPCONTRIBDIR)/ports/win32/include -STATIC_LIB=libzt.a -SHARED_LIB=libzt.dll -endif -# Darwin -ifeq ($(OSTYPE),darwin) -CFLAGS+=-fvisibility=hidden -fstack-protector -ARTOOL=libtool -ARFLAGS=-static -LWIPARCHINCLUDE=$(LWIPCONTRIBDIR)/ports/unix/include -STATIC_LIB=libzt.a -SHARED_LIB=libzt.dylib -endif -# Linux -ifeq ($(OSTYPE),linux) -CFLAGS+=-fvisibility=hidden -fstack-protector -ARTOOL=ar -ARFLAGS=rcs -LWIPARCHINCLUDE=$(LWIPCONTRIBDIR)/ports/unix/include -STATIC_LIB=libzt.a -SHARED_LIB=libzt.so -endif -# FreeBSD -ifeq ($(OSTYPE),freebsd) -CFLAGS+=-fvisibility=hidden -fstack-protector -ARTOOL=ar -ARFLAGS=rcs -LWIPARCHINCLUDE=$(LWIPCONTRIBDIR)/ports/unix/include -STATIC_LIB=libzt.a -SHARED_LIB=libzt.so -endif -# OpenBSD -ifeq ($(OSTYPE),openbsd) -CFLAGS+=-fvisibility=hidden -fstack-protector -ARTOOL=ar -ARFLAGS=rcs -LWIPARCHINCLUDE=$(LWIPCONTRIBDIR)/ports/unix/include -STATIC_LIB=libzt.a -SHARED_LIB=libzt.so -endif - -############################################################################## -## Objects and includes ## -############################################################################## - -include zto/objects.mk - -ZTO_OBJS=\ - zto/node/C25519.o \ - zto/node/Capability.o \ - zto/node/CertificateOfMembership.o \ - zto/node/CertificateOfOwnership.o \ - zto/node/Identity.o \ - zto/node/IncomingPacket.o \ - zto/node/InetAddress.o \ - zto/node/Membership.o \ - zto/node/Multicaster.o \ - zto/node/Network.o \ - zto/node/NetworkConfig.o \ - zto/node/Node.o \ - zto/node/OutboundMulticast.o \ - zto/node/Packet.o \ - zto/node/Path.o \ - zto/node/Peer.o \ - zto/node/Poly1305.o \ - zto/node/Revocation.o \ - zto/node/Salsa20.o \ - zto/node/SelfAwareness.o \ - zto/node/SHA512.o \ - zto/node/Switch.o \ - zto/node/Tag.o \ - zto/node/Topology.o \ - zto/node/Trace.o \ - zto/node/Utils.o \ - zto/controller/EmbeddedNetworkController.o \ - zto/controller/DB.o \ - zto/controller/FileDB.o \ - zto/osdep/ManagedRoute.o \ - zto/osdep/Http.o \ - zto/osdep/OSUtils.o \ - zto/service/SoftwareUpdater.o \ - zto/service/OneService.o \ - zto/ext/http-parser/http_parser.o - -ZT_INCLUDES+=-Iext \ - -Izto/osdep \ - -Izto/node \ - -Izto/service \ - -Izto/include \ - -LIBZT_INCLUDES+=-Iinclude \ - -I../include \ - -Isrc - -############################################################################## -## General Build Configuration ## -############################################################################## - -# ZT_ Configuration options for ZeroTier core -# LIBZT_ Configuration options for libzt -# NS_ Configuration options for userspace network stack - -STRIP=strip -# Determine if PIC is needed for the intended build target -ifeq ($(MAKECMDGOALS),static_lib) - CFLAGS?= - #-fPIE -endif -ifeq ($(MAKECMDGOALS),shared_lib) - CFLAGS?= - #-fPIC -endif -# ZeroTier core debug and tracing -ifeq ($(ZT_DEBUG),1) - CFLAGS?=-g - ZT_DEFS?=-DZT_TRACE=1 - STRIP=echo -else - CFLAGS?=-Ofast -endif -# For consistency. ZT_DEBUG=1 will also turn this on -ifeq ($(ZT_TRACE),1) - ZT_DEFS?=-DZT_TRACE -endif -# Don't optimize, add debug symbols -ifeq ($(LIBZT_DEBUG),1) - CFLAGS?=-g - STRIP=echo -else - CFLAGS?=-Ofast -endif -# Turns on file/function/line debug output logging -ifeq ($(LIBZT_TRACE),1) - LIBZT_DEFS?=-DLIBZT_TRACE -endif -# Experimental stack drivers which interface via raw API's -ifeq ($(LIBZT_RAW),1) - LIBZT_DEFS?=-DLIBZT_RAW=1 -endif -# Debug the userspace stack -ifeq ($(NS_DEBUG),1) - CFLAGS?=-g - STRIP=echo -endif - -# Build with address sanitization library for advanced debugging (clang) -# TODO: Add GCC version as well -ifeq ($(LIBZT_SANITIZE),1) - SANFLAGS+=-x c++ -g -fsanitize=address -DASAN_OPTIONS=symbolize=1 \ - -DASAN_SYMBOLIZER_PATH=$(shell which llvm-symbolizer) -endif - -# JNI (Java Native Interface) -ifeq ($(OSTYPE),darwin) -JNI_INCLUDES+=-I$(shell /usr/libexec/java_home)/include -JNI_INCLUDES+=-I$(shell /usr/libexec/java_home)/include/$(OSTYPE) -endif -ifeq ($(OSTYPE),linux) -JNI_INCLUDES+=-I$(shell dirname $(shell dirname $(shell readlink -f $(shell which javac))))/include -JNI_INCLUDES+=-I$(shell dirname $(shell dirname $(shell readlink -f $(shell which javac))))/include/$(OSTYPE) -endif - -CXXFLAGS+=$(CFLAGS) -Wno-format -fno-rtti -std=c++11 -ZT_DEFS+=-DZT_SDK -DZT_SOFTWARE_UPDATE_DEFAULT="\"disable\"" -LIBZT_FILES:=src/VirtualTap.cpp src/libzt.cpp src/Utilities.cpp - -############################################################################## -## Stack Configuration ## -############################################################################## - -#ifeq ($(NO_STACK),1) -#STACK_DRIVER_DEFS+=-DNO_STACK -#endif - -ifeq ($(NS_DEBUG),1) - STACK_DEFS+=LWIP_DEBUG=1 -endif - -# picoTCP -STACK_INCLUDES+=-Iext/picotcp/include -Iext/picotcp/build/include -STACK_DRIVER_FILES:=src/picoTCP.cpp - -# lwIP -LWIPDIR=ext/lwip/src -STACK_INCLUDES+=-I$(LWIPARCHINCLUDE) -Iext/lwip/src/include/lwip \ - -I$(LWIPDIR)/include \ - -I$(LWIPARCH)/include \ - -I$(LWIPDIR)/include/ipv4 \ - -I$(LWIPDIR) \ - -Iext -STACK_DRIVER_FILES:=src/lwIP.cpp - -############################################################################## -## Targets ## -############################################################################## - -%.o : %.cpp - @mkdir -p $(BUILDPATH) obj - $(CXX) $(CXXFLAGS) $(STACK_DRIVER_DEFS) $(ZT_DEFS) \ - $(ZT_INCLUDES) $(LIBZT_INCLUDES) -c $^ -o obj/$(@F) - -%.o : %.c - @mkdir -p $(BUILDPATH) obj - $(CC) $(CFLAGS) -c $^ -o obj/$(@F) - -core: - cd zto; make core - mv zto/libzerotiercore.a $(BUILDPATH) - -picotcp: - cd ext/picotcp; make lib ARCH=shared IPV4=1 IPV6=1 - -lwip: - echo $(STACK_DEFS) - make -f make-liblwip.mk liblwip.a LIBZT_IPV4=1 IPV4=1 - -lwip_driver: - $(CXX) $(CXXFLAGS) -c src/lwIP.cpp \ - $(ZT_DEFS) $(ZT_INCLUDES) $(STACK_INCLUDES) $(LIBZT_DEFS) $(LIBZT_INCLUDES) -DZT_DRIVER_MODULE - -picotcp_driver: - $(CXX) $(CXXFLAGS) -c src/picoTCP.cpp \ - $(ZT_DEFS) $(ZT_INCLUDES) $(STACK_INCLUDES) $(LIBZT_DEFS) $(LIBZT_INCLUDES) -DZT_DRIVER_MODULE - -libzt_socket_layer: - $(CXX) $(CXXFLAGS) -c src/VirtualSocket.cpp \ - $(ZT_DEFS) $(ZT_INCLUDES) $(LIBZT_INCLUDES) $(LIBZT_DEFS) - $(CXX) $(CXXFLAGS) -c src/VirtualSocketLayer.cpp \ - $(ZT_DEFS) $(ZT_INCLUDES) $(STACK_INCLUDES) $(LIBZT_INCLUDES) $(LIBZT_DEFS) - $(CXX) $(CXXFLAGS) -c src/VirtualTap.cpp \ - $(ZT_DEFS) $(ZT_INCLUDES) $(LIBZT_DEFS) $(LIBZT_INCLUDES) - $(CXX) $(CXXFLAGS) -c src/ZT1Service.cpp \ - $(ZT_DEFS) $(ZT_INCLUDES) $(LIBZT_INCLUDES) $(LIBZT_DEFS) - $(CXX) $(CXXFLAGS) -c src/libzt.cpp \ - $(ZT_DEFS) $(ZT_INCLUDES) $(STACK_INCLUDES) $(LIBZT_DEFS) $(LIBZT_INCLUDES) - $(CXX) $(CXXFLAGS) -c src/RingBuffer.cpp $(LIBZT_INCLUDES) - -jni_socket_wrapper: - $(CXX) $(CXXFLAGS) -DSDK_JNI -c src/libztJNI.cpp \ - $(ZT_DEFS) $(ZT_INCLUDES) $(STACK_INCLUDES) $(JNI_INCLUDES) $(LIBZT_DEFS) $(LIBZT_INCLUDES) - -utilities: - $(CXX) $(CXXFLAGS) -c src/SysUtils.cpp \ - $(LIBZT_INCLUDES) - $(CXX) $(CXXFLAGS) -c src/Utilities.cpp \ - $(ZT_DEFS) $(ZT_INCLUDES) $(LIBZT_INCLUDES) $(STACK_INCLUDES) - -remove_objs: - rm -rf *.o - -prereqs: remove_objs lwip lwip_driver libzt_socket_layer jni_socket_wrapper utilities - -static_lib: prereqs $(ZTO_OBJS) - mv *.o obj - mkdir -p lib - $(ARTOOL) $(ARFLAGS) obj/*.o -o $(LIBPATH)/$(STATIC_LIB) - -shared_lib: prereqs $(ZTO_OBJS) - mv *.o obj - mkdir -p lib - $(CXX) $(CXXFLAGS) obj/*.o $(SOLIBTYPE) -o $(LIBPATH)/$(SHARED_LIB) -lpthread - -dynamic_lib: prereqs $(ZTO_OBJS) - -ifdef JNI -ZT_DEFS+=-DSDK_JNI -SOLIBTYPE=-shared -else -SOLIBTYPE=-shared -# user dynamiclib for macOS -endif -ifdef STATIC -CFLAGS+= -#-fPIE -lib: static_lib -endif -ifdef SHARED -CFLAGS+= -#-fPIC -lib: shared_lib -endif - -# windows DLL -win_dll: lwip lwip_driver libzt_socket_layer utilities $(ZTO_OBJS) - # First we use mingw to build our DLL - @mkdir -p $(BUILDPATH) obj - mv *.o obj - windres -i res/libztdll.rc -o obj/libztdllres.o - $(CXX) $(CXXFLAGS) -shared -o $(BUILDPATH)/libzt.dll obj/*.o -Wl,--output-def,$(BUILDPATH)/libzt.def,--out-implib,$(BUILDPATH)/libzt.a $(WINDEFS) - $(STRIP) $(BUILDPATH)/libzt.dll - # Then do the following to generate the MSVC DLL from the def file (which was generated from the MinGW DLL): - # lib /machine:x64 /def:libzt.def - # or just execute: makelib - -# ordinary shared library -#shared_lib: lwip lwip_driver libzt_socket_layer utilities $(ZTO_OBJS) -# @mkdir -p $(BUILDPATH) obj -# mv *.o obj -# $(CXX) $(CXXFLAGS) -shared -o $(BUILDPATH)/libzt.so obj/*.o - -# dynamic library for use with Java JNI, scala, etc -#shared_jni_lib: lwip lwip_driver libzt_socket_layer jni_socket_wrapper utilities $(ZTO_OBJS) -# @mkdir -p $(BUILDPATH) obj -# mv *.o obj - #$(CXX) $(CXXFLAGS) -shared -o $(BUILDPATH)/libzt.so obj/*.o -# $(CXX) $(CXXFLAGS) -dynamiclib obj/*.o -o $(BUILDPATH)/libzt.dylib -lpthread - -# static library for use with Java JNI, scala, etc -#static_jni_lib: lwip lwip_driver libzt_socket_layer jni_socket_wrapper utilities $(ZTO_OBJS) -# @mkdir -p $(BUILDPATH) obj -# mv *.o obj -# $(ARTOOL) $(ARFLAGS) -o $(BUILDPATH)/$(STATIC_LIB) obj/*.o - -# static library -#static_lib: lwip lwip_driver libzt_socket_layer utilities $(ZTO_OBJS) -# @mkdir -p $(BUILDPATH) obj -# mv *.o obj -# mv ext/picotcp/build/lib/*.o obj -# mv ext/picotcp/build/modules/*.o obj -# $(ARTOOL) $(ARFLAGS) -o $(BUILDPATH)/$(STATIC_LIB) obj/*.o - -############################################################################## -## iOS/macOS App Frameworks ## -############################################################################## - -ios_app_framework: - cd examples/apple/ZeroTierSDK_Apple; xcodebuild -configuration Release \ - -scheme ZeroTierSDK_iOS build SYMROOT="../../../$(BUILDPATH)/ios_app_framework" - cd examples/apple/ZeroTierSDK_Apple; xcodebuild -configuration Debug \ - -scheme ZeroTierSDK_iOS build SYMROOT="../../../$(BUILDPATH)/ios_app_framework" - -macos_app_framework: - cd examples/apple/ZeroTierSDK_Apple; xcodebuild -configuration Release \ - -scheme ZeroTierSDK_OSX build SYMROOT="../../../$(BUILDPATH)/macos_app_framework" - cd examples/apple/ZeroTierSDK_Apple; xcodebuild -configuration Debug \ - -scheme ZeroTierSDK_OSX build SYMROOT="../../../$(BUILDPATH)/macos_app_framework" - -############################################################################## -## Python module ## -############################################################################## - -python_module: - swig -cpperraswarn -python -c++ -o examples/python/libzt.cc examples/python/swig_libzt.i - python examples/python/setup.py build_ext --inplace --swig-opts="-modern -I../../zto/include" - -############################################################################## -## Unit Tests ## -############################################################################## - -tests: selftest nativetest ztproxy ipv4simple ipv6simple ipv6adhoc -# intercept - -ZT_UTILS:=zto/node/Utils.cpp -Izto/node - -sample: - $(CXX) $(CXXFLAGS) -D__SELFTEST__ $(STACK_DRIVER_DEFS) $(LIBZT_DEFS) \ - $(SANFLAGS) $(LIBZT_INCLUDES) $(ZT_INCLUDES) $(ZT_UTILS) test/sample.cpp -o \ - $(BUILDPATH)/sample -L$(LIBPATH) -lzt -selftest: - $(CXX) $(CXXFLAGS) -D__SELFTEST__ $(STACK_DRIVER_DEFS) $(LIBZT_DEFS) \ - $(SANFLAGS) $(LIBZT_INCLUDES) $(ZT_INCLUDES) $(ZT_UTILS) test/selftest.cpp -o \ - $(BUILDPATH)/selftest -L$(LIBPATH) -lzt -lpthread -nativetest: - $(CXX) $(CXXFLAGS) -D__NATIVETEST__ $(STACK_DRIVER_DEFS) $(SANFLAGS) \ - $(LIBZT_INCLUDES) $(ZT_INCLUDES) test/selftest.cpp -o $(BUILDPATH)/nativetest -ztproxy: - $(CXX) $(CXXFLAGS) $(SANFLAGS) $(LIBZT_INCLUDES) $(LIBZT_DEFS) $(ZT_INCLUDES) \ - examples/apps/ztproxy/ztproxy.cpp -o $(BUILDPATH)/ztproxy $< -L$(LIBPATH) -lzt -lpthread $(WINDEFS) -intercept: - $(CXX) $(CXXFLAGS) $(SANFLAGS) $(STACK_DRIVER_DEFS) $(LIBZT_INCLUDES) \ - $(ZT_INCLUDES) examples/intercept/intercept.cpp -D_GNU_SOURCE \ - -shared -o $(BUILDPATH)/intercept.so $< -ldl -ipv4simple: - $(CXX) $(CXXFLAGS) $(SANFLAGS) $(LIBZT_INCLUDES) $(LIBZT_DEFS) \ - examples/bindings/cpp/ipv4simple/client.cpp -o $(BUILDPATH)/ipv4client -L$(LIBPATH) -lpthread -lzt $(WINDEFS) - $(CXX) $(CXXFLAGS) $(SANFLAGS) $(LIBZT_INCLUDES) $(LIBZT_DEFS) \ - examples/bindings/cpp/ipv4simple/server.cpp -o $(BUILDPATH)/ipv4server -L$(LIBPATH) -lpthread -lzt $(WINDEFS) -ipv6simple: - $(CXX) $(CXXFLAGS) $(SANFLAGS) $(LIBZT_INCLUDES) $(LIBZT_DEFS) \ - examples/bindings/cpp/ipv6simple/client.cpp -o $(BUILDPATH)/ipv6client -L$(LIBPATH) -lpthread -lzt $(WINDEFS) - $(CXX) $(CXXFLAGS) $(SANFLAGS) $(LIBZT_INCLUDES) $(LIBZT_DEFS) \ - examples/bindings/cpp/ipv6simple/server.cpp -o $(BUILDPATH)/ipv6server -L$(LIBPATH) -lpthread -lzt $(WINDEFS) -ipv6adhoc: - $(CXX) $(CXXFLAGS) $(SANFLAGS) $(LIBZT_INCLUDES) $(LIBZT_DEFS) \ - examples/bindings/cpp/ipv6adhoc/client.cpp -o $(BUILDPATH)/ipv6adhocclient -L$(LIBPATH) -lpthread -lzt $(WINDEFS) - $(CXX) $(CXXFLAGS) $(SANFLAGS) $(LIBZT_INCLUDES) $(LIBZT_DEFS) \ - examples/bindings/cpp/ipv6adhoc/server.cpp -o $(BUILDPATH)/ipv6adhocserver -L$(LIBPATH) -lpthread -lzt $(WINDEFS) -dlltest: - $(CXX) $(CXXFLAGS) - -############################################################################## -## Installation and Uninstallation ## -############################################################################## - -.PHONY: install -install: - mkdir -p $(DESTDIR)$(PREFIX)/lib - mkdir -p $(DESTDIR)$(PREFIX)/include - cp $(BUILDPATH)/$(STATIC_LIB) $(DESTDIR)$(PREFIX)/lib/ - cp include/libzt.h $(DESTDIR)$(PREFIX)/include/ - -.PHONY: uninstall -uninstall: - rm -f $(DESTDIR)$(PREFIX)/*.a - rm -f $(DESTDIR)$(PREFIX)/include/*.h - -############################################################################## -## Misc ## -############################################################################## - -.PHONY: clean -clean: - -rm f $(LIBPATH)/* - -rm -rf $(BUILDPATH)/* - -rm f obj/* - -rm f *.o *.s *.exp *.lib .depend* *.core core - -rm -rf .depend - -find . -type f \( -name '*.a' -o -name '*.o' -o -name '*.so' -o -name \ - '*.o.d' -o -name '*.out' -o -name '*.log' -o -name '*.dSYM' \) -delete - diff --git a/include/VirtualBindingPair.h b/attic/VirtualBindingPair.h similarity index 100% rename from include/VirtualBindingPair.h rename to attic/VirtualBindingPair.h diff --git a/src/VirtualSocket.cpp b/attic/VirtualSocket.cpp similarity index 100% rename from src/VirtualSocket.cpp rename to attic/VirtualSocket.cpp diff --git a/include/VirtualSocket.h b/attic/VirtualSocket.h similarity index 100% rename from include/VirtualSocket.h rename to attic/VirtualSocket.h diff --git a/src/VirtualSocketLayer.cpp b/attic/VirtualSocketLayer.cpp similarity index 100% rename from src/VirtualSocketLayer.cpp rename to attic/VirtualSocketLayer.cpp diff --git a/include/VirtualSocketLayer.h b/attic/VirtualSocketLayer.h similarity index 100% rename from include/VirtualSocketLayer.h rename to attic/VirtualSocketLayer.h diff --git a/src/picoTCP.cpp b/attic/picoTCP.cpp similarity index 100% rename from src/picoTCP.cpp rename to attic/picoTCP.cpp diff --git a/include/picoTCP.h b/attic/picoTCP.h similarity index 100% rename from include/picoTCP.h rename to attic/picoTCP.h diff --git a/check.sh b/check.sh deleted file mode 100755 index 2fa530b..0000000 --- a/check.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -red=`tput setaf 1` -green=`tput setaf 2` -reset=`tput sgr0` - -FILE="$1" - -if ([ -f "$FILE" ] && [ -s "$FILE" ]) || ([ -d "$FILE" ] && [ "$(ls -A "$FILE")" ]); -then - echo "${green}[OK ]${reset} $FILE" - exit 0 -else - echo "${red}[FAIL]${reset} $FILE" >&2 - exit 1 -fi diff --git a/docs/test_diagram.png b/docs/test_diagram.png deleted file mode 100644 index 4a61150b3c86c2396d09b944dcfe069a9db87508..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88477 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 F{0p?q1myq# diff --git a/include/SysUtils.h b/include/SysUtils.h index 1821cb2..6ff0855 100644 --- a/include/SysUtils.h +++ b/include/SysUtils.h @@ -41,14 +41,6 @@ #include #endif -/** - * @brief Returns the thread-id. Used in debug traces. - * - * @usage For internal use only. - * @return - */ -inline unsigned int gettid(); - /** * @brief Current time in milliseconds since epoch, platform-aware convenience function. * diff --git a/include/VirtualTap.h b/include/VirtualTap.h index 7c59c59..5e212fc 100644 --- a/include/VirtualTap.h +++ b/include/VirtualTap.h @@ -86,7 +86,7 @@ public: /** * Registers a device with the given address */ - bool registerIpWithStack(const ZeroTier::InetAddress &ip); + void registerIpWithStack(const ZeroTier::InetAddress &ip); /** * Adds an address to the userspace stack interface associated with this VirtualTap @@ -157,72 +157,14 @@ public: void (*_handler)(void *, void *, uint64_t, const ZeroTier::MAC &, const ZeroTier::MAC &, unsigned int, unsigned int, const void *, unsigned int); - /** - * Signals us to close the TcpVirtualSocket associated with this PhySocket - */ void phyOnUnixClose(ZeroTier::PhySocket *sock, void **uptr); - - /** - * Notifies us that there is data to be read from an application's socket - */ void phyOnUnixData(ZeroTier::PhySocket *sock, void **uptr, void *data, ssize_t len); - - /** - * Notifies us that we can write to an application's socket - */ void phyOnUnixWritable(ZeroTier::PhySocket *sock, void **uptr, bool stack_invoked); - /** - * Adds a route to the virtual tap - */ - bool routeAdd(const ZeroTier::InetAddress &ip, const ZeroTier::InetAddress &nm, const ZeroTier::InetAddress &gw); - - /** - * Deletes a route from the virtual tap - */ - bool routeDelete(const ZeroTier::InetAddress &ip, const ZeroTier::InetAddress &nm); - - /** - * Assign a VirtualSocket to the VirtualTap - */ - void addVirtualSocket(VirtualSocket *vs); - - /** - * Remove a VirtualSocket from the VirtualTap - */ - void removeVirtualSocket(); - - /****************************************************************************/ - /* DNS */ - /****************************************************************************/ - - /** - * Registers a DNS nameserver with the network stack - */ - int add_DNS_Nameserver(struct sockaddr *addr); - - /** - * Un-registers a DNS nameserver from the network stack - */ - int del_DNS_Nameserver(struct sockaddr *addr); - /****************************************************************************/ /* Vars */ /****************************************************************************/ -#if defined(STACK_PICO) - bool should_start_stack = false; - struct pico_device *picodev = NULL; - - /****************************************************************************/ - /* Guarded RX Frame Buffer for picoTCP */ - /****************************************************************************/ - - unsigned char pico_frame_rxbuf[MAX_PICO_FRAME_RX_BUF_SZ]; - int pico_frame_rxbuf_tot = 0; - Mutex _pico_frame_rxbuf_m; -#endif - std::vector > routes; void *zt1ServiceRef = NULL; @@ -261,56 +203,6 @@ public: */ uint64_t last_housekeeping_ts = 0; - /****************************************************************************/ - /* In these, we will call the stack's corresponding functions, this is */ - /* where one would put logic to select between different stacks */ - /****************************************************************************/ - - /** - * Connect to a remote host via the userspace stack interface associated with this VirtualTap - */ - int Connect(VirtualSocket *vs, const struct sockaddr *addr, socklen_t addrlen); - - /** - * Bind to the userspace stack interface associated with this VirtualTap - */ - int Bind(VirtualSocket *vs, const struct sockaddr *addr, socklen_t addrlen); - - /** - * Listen for a VirtualSocket - */ - int Listen(VirtualSocket *vs, int backlog); - - /** - * Accepts an incoming VirtualSocket - */ - VirtualSocket* Accept(VirtualSocket *vs); - - /** - * Move data from RX buffer to application's "socket" - */ - int Read(VirtualSocket *vs, PhySocket *sock, void **uptr, bool stack_invoked); - - /** - * Move data from application's "socket" into network stack - */ - int Write(VirtualSocket *vs, void *data, ssize_t len); - - /** - * Send data to specified host - */ - int SendTo(VirtualSocket *vs, const void *buf, size_t len, int flags, const struct sockaddr *addr, socklen_t addrlen); - - /** - * Closes a VirtualSocket - */ - int Close(VirtualSocket *vs); - - /** - * Shuts down some aspect of a VirtualSocket - */ - int Shutdown(VirtualSocket *vs, int how); - /** * Disposes of previously-closed VirtualSockets */ diff --git a/include/libzt.h b/include/libzt.h index 46566c7..6d3a7fd 100644 --- a/include/libzt.h +++ b/include/libzt.h @@ -223,7 +223,7 @@ ZT_SOCKET_API int ZTCALL zts_get_num_assigned_addresses(const uint64_t nwid); * @return The number of addresses assigned */ ZT_SOCKET_API int ZTCALL zts_get_address_at_index( - const uint64_t nwid, const int index, struct sockaddr_storage *addr); + const uint64_t nwid, const int index, struct sockaddr *addr, socklen_t *addrlen); /** * @brief Get IP address for this device on a given network @@ -231,7 +231,7 @@ ZT_SOCKET_API int ZTCALL zts_get_address_at_index( * @usage FIXME: Only returns first address found, good enough for most cases * @param nwid Network ID * @param addr Destination structure for address - * @param address_family To designate what family of addresses we want to return. AF_INET or AF_INET6 + * @param addrlen size of destination address buffer, will be changed to size of returned address * @return 0 if an address was successfully found, -1 if failure */ ZT_SOCKET_API int ZTCALL zts_get_address( @@ -269,16 +269,6 @@ ZT_SOCKET_API void ZTCALL zts_get_rfc4193_addr( */ ZT_SOCKET_API unsigned long zts_get_peer_count(); -/** - * @brief Get the virtual address of a peer given its Node ID - * - * @usage Call this after zts_start() has succeeded - * @param peer Returned peer address - * @param nodeId Provided Node ID - * @return - */ -ZT_SOCKET_API int ZTCALL zts_get_peer_address(char *peer, const uint64_t nodeId); - /****************************************************************************/ /* POSIX-like socket API */ /****************************************************************************/ diff --git a/include/libztDebug.h b/include/libztDebug.h index fb93fa6..13e82e2 100644 --- a/include/libztDebug.h +++ b/include/libztDebug.h @@ -75,13 +75,11 @@ #define ZT_FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) // short -extern unsigned int gettid(); - #ifdef __linux__ #define ZT_THREAD_ID syscall(SYS_gettid) #endif #ifdef __APPLE__ - #define ZT_THREAD_ID (long)0 //(long)gettid() + #define ZT_THREAD_ID (long)0 #endif #ifdef _WIN32 #define ZT_THREAD_ID (long)0 @@ -99,8 +97,7 @@ extern unsigned int gettid(); #if defined(__ANDROID__) #define DEBUG_STACK(fmt, args...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, ZT_LOG_TAG, \ "STACK[%ld]: %17s:%5d:%20s: " fmt "\n", ZT_THREAD_ID, ZT_FILENAME, __LINE__, __FUNCTION__, ##args)) -#endif -#if defined(_WIN32) +#elif defined(_WIN32) #define DEBUG_STACK(fmt, ...) fprintf(stderr, ZT_YEL "STACK[%ld]: %17s:%5d:%25s: " fmt \ ZT_RESET, ZT_THREAD_ID, ZT_FILENAME, __LINE__, __FUNCTION__, __VA_ARGS__) #else @@ -115,8 +112,7 @@ extern unsigned int gettid(); #if defined(__ANDROID__) #define DEBUG_TEST(fmt, args...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, ZT_LOG_TAG, \ "TEST : %17s:%5d:%25s: " fmt "\n", ZT_FILENAME, __LINE__, __FUNCTION__, ##args)) - #endif - #if defined(_WIN32) + #elif defined(_WIN32) #define DEBUG_TEST(fmt, ...) fprintf(stderr, ZT_CYN "TEST [%ld]: %17s:%5d:%25s: " fmt "\n" \ ZT_RESET, ZT_THREAD_ID, ZT_FILENAME, __LINE__, __FUNCTION__, __VA_ARGS__) #else @@ -132,8 +128,7 @@ extern unsigned int gettid(); #if defined(__ANDROID__) #define DEBUG_ERROR(fmt, args...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, ZT_LOG_TAG, \ "ERROR: %17s:%5d:%20s: " fmt "\n", ZT_FILENAME, __LINE__, __FUNCTION__, ##args)) - #endif - #if defined(_WIN32) + #elif defined(_WIN32) #define DEBUG_ERROR(fmt, ...) fprintf(stderr, ZT_RED "ERROR[%ld]: %17s:%5d:%25s: " fmt "\n" \ ZT_RESET, ZT_THREAD_ID, ZT_FILENAME, __LINE__, __FUNCTION__, __VA_ARGS__) #else @@ -149,8 +144,7 @@ extern unsigned int gettid(); #if defined(__ANDROID__) #define DEBUG_INFO(fmt, args...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, ZT_LOG_TAG, \ "INFO : %17s:%5d:%20s: " fmt "\n", ZT_FILENAME, __LINE__, __FUNCTION__, ##args)) - #endif - #if defined(_WIN32) + #elif defined(_WIN32) #define DEBUG_INFO(fmt, ...) fprintf(stderr, ZT_WHT "INFO [%ld]: %17s:%5d:%25s: " fmt "\n" \ ZT_RESET, ZT_THREAD_ID, ZT_FILENAME, __LINE__, __FUNCTION__, __VA_ARGS__) #else @@ -166,8 +160,7 @@ extern unsigned int gettid(); #if defined(__ANDROID__) #define DEBUG_TRANS(fmt, args...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, ZT_LOG_TAG, \ "TRANS: %17s:%5d:%25s: " fmt "\n", ZT_FILENAME, __LINE__, __FUNCTION__, ##args)) - #endif - #if defined(_WIN32) + #elif defined(_WIN32) #define DEBUG_TRANS(fmt, ...) fprintf(stderr, ZT_GRN "TRANS[%ld]: %17s:%5d:%25s: " fmt "\n" \ ZT_RESET, ZT_THREAD_ID, ZT_FILENAME, __LINE__, __FUNCTION__, __VA_ARGS__) #else @@ -183,8 +176,7 @@ extern unsigned int gettid(); #if defined(__ANDROID__) #define DEBUG_EXTRA(fmt, args...) ((void)__android_log_print(ANDROID_LOG_VERBOSE, ZT_LOG_TAG, \ "EXTRA: %17s:%5d:%25s: " fmt "\n", ZT_FILENAME, __LINE__, __FUNCTION__, ##args)) - #endif - #if defined(_WIN32) + #elif defined(_WIN32) #define DEBUG_EXTRA(fmt, ...) fprintf(stderr, ZT_WHT "EXTRA[%ld]: %17s:%5d:%25s: " fmt "\n" \ ZT_RESET, ZT_THREAD_ID, ZT_FILENAME, __LINE__, __FUNCTION__, __VA_ARGS__, (long)0) #else diff --git a/include/libztDefs.h b/include/libztDefs.h index 5a5e077..ff81c83 100644 --- a/include/libztDefs.h +++ b/include/libztDefs.h @@ -33,87 +33,15 @@ #ifndef LIBZT_DEFINES_H #define LIBZT_DEFINES_H +#define LIBZT_IPV4 1 +#define LIBZT_IPV6 1 + /** * Default port that libzt will use to support all virtual communication */ #define LIBZT_DEFAULT_PORT 9994 -/** - * Use ZeroTier Virtual Socket layer to abstract network stack raw API - */ -#define ZT_VIRTUAL_SOCKET 0 -/** - * Use lwIP sockets API - */ -#define ZT_LWIP_SEQ_SOCKET 1 -/** - * Use pico BSD socket API - */ -#define ZT_PICO_BSD_SOCKET 0 -#define STACK_LWIP 1 -#define STACK_PICO 0 -#define NO_STACK 0 // for layer-2 only (this will omit all userspace network stack code) - -/* sanity checks for userspace network stack and socket API layer choices - - EX. - zts_socket() - 1. ) ZT_VIRTUAL_SOCKET? -> virt_socket() --- Choose this if the default socket layer isn't doing what you need - STACK_LWIP? -> raw lwip_ API - STACK_PICO? -> raw pico_ API - otherStack? -> raw API - - 2.) ZT_LWIP_SEQ_SOCKET? (default) -> lwip_socket() --- currently provides greatest safety and performance - 3.) ZT_PICO_BSD_SOCKET? -> pico_ socket API - otherStack? -> other_stack_socket() - - Default is: STACK_LWIP=1 ZT_LWIP_SEQ_SOCKET=1 - -*/ - -#if (STACK_LWIP+STACK_PICO) > 1 -#error "Multiple network stacks specified. Pick only one." -#endif -#if STACK_LWIP==0 && STACK_PICO==0 && NO_STACK==0 -#error "No network stacks specified and NO_STACK wasn't set. Pick one." -#endif -#if ZT_VIRTUAL_SOCKET==0 && ZT_LWIP_SEQ_SOCKET==0 && ZT_PICO_BSD_SOCKET==0 -#error "No socket handling layer specified. Pick one." -#endif -#if (ZT_VIRTUAL_SOCKET + ZT_LWIP_SEQ_SOCKET + ZT_PICO_BSD_SOCKET) > 1 -#error "Multiple socket handling layers specified. Pick only one." -#endif -#if ZT_LWIP_SEQ_SOCKET==1 && STACK_LWIP==0 -#error "ZT_LWIP_SEQ_SOCKET is selected as socket handling layer, but STACK_LWIP isn't set" -#endif -#if ZT_PICO_BSD_SOCKET==1 && STACK_PICO==0 -#error "ZT_PICO_BSD_SOCKET is selected as socket handling layer, but STACK_PICO isn't set" -#endif - -#if STACK_LWIP==1 - #undef STACK_PICO - #undef NO_STACK -#endif -#if STACK_PICO==1 - #undef STACK_LWIP - #undef NO_STACK -#endif -#if NO_STACK==1 - #undef STACK_LWIP - #undef STACK_PICO -#endif -#if ZT_VIRTUAL_SOCKET==1 - #undef ZT_LWIP_SEQ_SOCKET - #undef ZT_PICO_BSD_SOCKET -#endif -#if ZT_LWIP_SEQ_SOCKET==1 - #undef ZT_VIRTUAL_SOCKET - #undef ZT_PICO_BSD_SOCKET -#endif -#if ZT_PICO_BSD_SOCKET==1 - #undef ZT_VIRTUAL_SOCKET - #undef ZT_LWIP_SEQ_SOCKET -#endif +#define NO_STACK 0 // for layer-2 only (this will omit all userspace network stack code) /** * Maximum MTU size for ZeroTier @@ -166,8 +94,6 @@ struct sockaddr_ll { // For LWIP configuration see: include/lwipopts.h -#if defined(STACK_LWIP) - /* The following three quantities are related and govern how incoming frames are fed into the network stack's core: @@ -208,7 +134,6 @@ typedef signed char err_t; #define LWIP_STATUS_TMR_INTERVAL 500 // #define LWIP_CHKSUM , See: RFC1071 for inspiration -#endif /****************************************************************************/ /* Defines */ @@ -259,11 +184,6 @@ typedef signed char err_t; */ #define ZT_API_CHECK_INTERVAL 50 -/** - * Maximum size of guarded RX buffer (for picoTCP raw driver only) - */ -#define MAX_PICO_FRAME_RX_BUF_SZ ZT_MAX_MTU * 128 - /** * Size of TCP TX buffer for VirtualSockets used in raw network stack drivers */ @@ -384,4 +304,6 @@ typedef signed char err_t; #define ZT_IOCTL_SIG int fd, unsigned long request, void *argp #define ZT_SYSCALL_SIG long number, ... +#define LIBZT_SERVICE_NOT_STARTED_STR "service not started yet, call zts_startjoin()" + #endif // _H diff --git a/include/lwIP.h b/include/lwIP.h index 38389bd..9f1f300 100644 --- a/include/lwIP.h +++ b/include/lwIP.h @@ -34,8 +34,7 @@ #define ZT_LWIP_HPP #include "libztDefs.h" - -#ifdef STACK_LWIP +#include "lwip/err.h" namespace ZeroTier { class MAC; @@ -43,8 +42,6 @@ namespace ZeroTier { struct InetAddress; } -//#include "lwip/err.h" - /** * @brief Initialize network stack semaphores, threads, and timers. * @@ -105,146 +102,4 @@ err_t lwip_eth_tx(struct netif *netif, struct pbuf *p); void lwip_eth_rx(VirtualTap *tap, const ZeroTier::MAC &from, const ZeroTier::MAC &to, unsigned int etherType, const void *data, unsigned int len); -/****************************************************************************/ -/* Raw API driver */ -/****************************************************************************/ - -#ifdef ZT_VIRTUAL_SOCKET - -class VirtualSocket; - -/** - * Returns the number of TCP PCBs currently allocated - */ -int rd_lwip_num_current_tcp_pcbs(); - -/** - * Returns the number of UDP PCBs currently allocated - */ -int rd_lwip_num_current_udp_pcbs(); - -/** - * Returns the number of RAW PCBs currently allocated - */ -int rd_lwip_num_current_raw_pcbs(); - -/** - * Returns the total number of PCBs of any time or state - */ -int rd_lwip_num_total_pcbs(); - -/** - * Registers a DNS nameserver with the network stack - */ -int rd_lwip_add_dns_nameserver(struct sockaddr *addr); - -/** - * Un-registers a DNS nameserver from the network stack - */ -int rd_lwip_del_dns_nameserver(struct sockaddr *addr); - -/** - * Main stack loop - */ -void rd_lwip_loop(VirtualTap *tap); - -/** - * Creates a stack-specific "socket" or "VirtualSocket object" - */ -int rd_lwip_socket(void **pcb, int socket_family, int socket_type, int protocol); - -/** - * Connect to remote host via userspace network stack interface - Called from VirtualTap - */ -int rd_lwip_connect(VirtualSocket *vs, const struct sockaddr *addr, socklen_t addrlen); - -/** - * Bind to a userspace network stack interface - Called from VirtualTap - */ -int rd_lwip_bind(VirtualTap *tap, VirtualSocket *vs, const struct sockaddr *addr, socklen_t addrlen); - -/** - * Listen for incoming VirtualSockets - Called from VirtualTap - */ -int rd_lwip_listen(VirtualSocket *vs, int backlog); - -/** - * Accept an incoming VirtualSocket - Called from VirtualTap - */ -VirtualSocket* rd_lwip_accept(VirtualSocket *vs); - -/** - * Read from RX buffer to application - Called from VirtualTap - */ -int rd_lwip_read(VirtualSocket *vs, bool lwip_invoked); - -/** - * Write to userspace network stack - Called from VirtualTap - */ -int rd_lwip_write(VirtualSocket *vs, void *data, ssize_t len); - -/** - * Close a VirtualSocket - Called from VirtualTap - */ -int rd_lwip_close(VirtualSocket *vs); - -/** - * Shuts down some aspect of a VirtualSocket - Called from VirtualTap - */ -int rd_lwip_shutdown(VirtualSocket *vs, int how); - -/** - * Sets a property of a socket - */ -int rd_lwip_setsockopt(VirtualSocket *vs, int level, int optname, const void *optval, socklen_t optlen); - -/** - * Gets a property of a socket - */ -int rd_lwip_getsockopt(VirtualSocket *vs, int level, int optname, void *optval, socklen_t *optlen); - -// --- Callbacks from network stack --- - -#ifdef ZT_DRIVER_MODULE // only include these symbols if we're building the full driver - -/** - * Callback for handling received UDP packets (already processed by network stack) - */ -static err_t rd_lwip_cb_tcp_recved(void *arg, struct tcp_pcb *PCB, struct pbuf *p, err_t err); - -/** - * Callback for handling accepted connection - */ -static err_t rd_lwip_cb_accept(void *arg, struct tcp_pcb *newPCB, err_t err); - -/** - * Callback for handling received TCP packets (already processed by stack) - */ -static void rd_lwip_cb_udp_recved(void * arg, struct udp_pcb * upcb, struct pbuf * p, const ip_addr_t * addr, u16_t port); - -/** - * Callback for handling errors from within the network stack - */ -static void rd_lwip_cb_err(void *arg, err_t err); - -/** - * Callback for handling periodic background tasks - */ -static err_t rd_lwip_cb_poll(void* arg, struct tcp_pcb *PCB); - -/** - * Callback for handling confirmation of sent packets - */ -static err_t rd_lwip_cb_sent(void *arg, struct tcp_pcb *PCB, u16_t len); - -/** - * Callback for handling successful connections - */ -static err_t rd_lwip_cb_connected(void *arg, struct tcp_pcb *PCB, err_t err); - -#endif // ZT_DRIVER_MODULE - -#endif - -#endif // ZT_VIRTUAL_SOCKET -#endif // STACK_LWIP +#endif // _H diff --git a/include/lwipopts.h b/include/lwipopts.h index 5c5ce67..d8823fc 100644 --- a/include/lwipopts.h +++ b/include/lwipopts.h @@ -44,7 +44,12 @@ /* * Provides its own errno */ + +#if __ANDROID__ #define LWIP_PROVIDE_ERRNO 0 +#else +#define LWIP_PROVIDE_ERRNO 0 +#endif /* * Provides core locking machinery @@ -56,6 +61,7 @@ */ #define LWIP_POSIX_SOCKETS_IO_NAMES 0 +#define LWIP_NOASSERT 1 /* * */ @@ -70,8 +76,10 @@ /* * Provides network/host byte transformation macros */ -#define LWIP_DONT_PROVIDE_BYTEORDER_FUNCTIONS 1 +#if __ANDROID__ +#define LWIP_DONT_PROVIDE_BYTEORDER_FUNCTIONS 1 +#endif /* * Include user defined options first. Anything not defined in these files @@ -80,8 +88,8 @@ #include "lwip/debug.h" // IP Protocol version -#define LWIP_IPV6 1 #define LWIP_IPV4 1 +#define LWIP_IPV6 1 // --- DEBUG --- @@ -115,7 +123,7 @@ // interfaces #define SLIP_DEBUG LWIP_DBG_OFF #define NETIF_DEBUG LWIP_DBG_OFF -// API (not used in libzt) +// API #define API_LIB_DEBUG LWIP_DBG_OFF #define API_MSG_DEBUG LWIP_DBG_OFF #define SOCKETS_DEBUG LWIP_DBG_OFF @@ -474,7 +482,7 @@ happening sooner than they should. /** * LWIP_DHCP==1: Enable DHCP module. */ -#define LWIP_DHCP 1 +#define LWIP_DHCP 0 /*------------------------------------------------------------------------------ diff --git a/make-liblwip.mk b/make-liblwip.mk deleted file mode 100644 index 615fc29..0000000 --- a/make-liblwip.mk +++ /dev/null @@ -1,105 +0,0 @@ - - -# -# Copyright (c) 2001, 2002 Swedish Institute of Computer Science. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without modification, -# are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# 3. The name of the author may not be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED -# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT -# SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT -# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING -# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -# OF SUCH DAMAGE. -# -# This file is part of the lwIP TCP/IP stack. -# -# Author: Adam Dunkels -# - -CONTRIBDIR=ext/lwip-contrib -LWIPDIR=ext/lwip/src - -# Automagically pick clang or gcc, with preference for clang -# This is only done if we have not overridden these with an environment or CLI variable -ifeq ($(origin CC),default) - CC=$(shell if [ -e /usr/bin/clang ]; then echo clang; else echo gcc; fi) -endif - -OSTYPE=$(shell uname -s | tr '[A-Z]' '[a-z]') -BUILD=build/$(OSTYPE) -CCDEP=$(CC) - -# Windows -ifeq ($(OSTYPE),mingw32_nt-6.2) -CC=gcc -WINDEFS=-Wno-c++11-compat -std=c++98 -LWIPARCH=$(CONTRIBDIR)/ports/win32 -endif -ifeq ($(OSTYPE),linux) -LWIPARCH=$(CONTRIBDIR)/ports/unix -CFLAGS+=-nostdlib -endif -ifeq ($(OSTYPE),darwin) -LWIPARCH=$(CONTRIBDIR)/ports/unix -endif -ifeq ($(OSTYPE),freebsd) -LWIPARCH=$(CONTRIBDIR)/ports/unix -endif - -LWIPINCLUDES:=-I$(LWIPDIR)/include -I$(LWIPARCH) -I$(LWIPARCH)/include -I$(LWIPDIR) -I. -Iext -Iinclude -CFLAGS+=$(WINDEFS) -Wno-format -Wno-deprecated -O3 -g -Wall -fPIC $(LWIPINCLUDES) - -ifeq ($(NS_DEBUG),1) -CFLAGS+=-DLWIP_DEBUG=1 -endif -#ifeq ($(IPV4),1) -CFLAGS+=-DLWIP_IPV4=1 -DIPv4 -#endif -#ifeq ($(IPV6),1) -CFLAGS+=-DLWIP_IPV6=1 -DIPv6 -#endif - -UNIXLIB=liblwip.a - -all: $(UNIXLIB) -.PHONY: all - -include $(LWIPDIR)/Filelists.mk - -# ARCHFILES: Architecture specific files. -ARCHFILES=$(wildcard $(LWIPARCH)/port/*.c $(LWIPARCH)/*.c $(LWIPARCH)tapif.c $(LWIPARCH)/netif/list.c $(LWIPARCH)/netif/tcpdump.c) - -LWIPNOAPPSFILES+=$(ARCHFILES) -LWIPNOAPPSFILESW=$(wildcard $(LWIPNOAPPSFILES)) -LWIPNOAPPSOBJS=$(notdir $(LWIPNOAPPSFILESW:.c=.o)) - -%.o: - $(CC) $(CFLAGS) -c $(<:.o=.c) - -clean: - rm -f *.o $(LWIPNOAPPSOBJS) *.s .depend* *.core core - -depend dep: .depend - -include .depend - -$(UNIXLIB): $(LWIPNOAPPSOBJS) - $(CC) $(CFLAGS) -g -nostartfiles -shared -o obj/$@ $^ - -.depend: $(LWIPNOAPPSFILES) - $(CC) $(CFLAGS) -MM $^ > .depend || rm -f .depend \ No newline at end of file diff --git a/makelib.bat b/makelib.bat deleted file mode 100644 index 7fe6084..0000000 --- a/makelib.bat +++ /dev/null @@ -1,2 +0,0 @@ -REM For conversion of MinGW DLL to MSVC lib -lib /machine:x64 /def:build\mingw32_nt-6.2\libzt.def /OUT:build\mingw32_nt-6.2\libzt.lib \ No newline at end of file diff --git a/res/libztdll.rc b/res/libztdll.rc deleted file mode 100644 index 186c8ba..0000000 --- a/res/libztdll.rc +++ /dev/null @@ -1,36 +0,0 @@ -#define APSTUDIO_READONLY_SYMBOLS -#include "windows.h" -#undef APSTUDIO_READONLY_SYMBOLS - -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK -#pragma code_page(1252) - -VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,1,5,0 - PRODUCTVERSION 1,1,5,0 - FILEFLAGSMASK 0x3fL - FILEFLAGS 0x0L - FILEOS 0x40004L - FILETYPE 0x1L - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "080904b0" - BEGIN - VALUE "CompanyName", "ZeroTier\0" - VALUE "FileDescription", "ZeroTier Socket API library\0" - VALUE "FileVersion", "1.1.5.0\0" - VALUE "InternalName", "my-real-file\0" - VALUE "LegalCopyright", "Copyright (C) 2017 ZeroTier, Inc.\0" - VALUE "OriginalFilename", "libzt.dll\0" - VALUE "PrivateBuild", "820\0" - VALUE "ProductName", "ZeroTier: libzt\0" - VALUE "ProductVersion", "1.1.5.0\0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x809, 1200 - END -END \ No newline at end of file diff --git a/src/SysUtils.cpp b/src/SysUtils.cpp index 0df5654..c53d98e 100644 --- a/src/SysUtils.cpp +++ b/src/SysUtils.cpp @@ -30,17 +30,6 @@ * Platform-specific implementations of common functions */ -#if defined(STACK_LWIP) -#include "lwip/sockets.h" -#include "lwip/sys.h" -#include "lwip/ip_addr.h" -#include "lwip/netdb.h" -#include "dns.h" -#endif -#if defined(NO_STACK) -#include -#endif - #if defined(__linux__) || defined(__APPLE__) #include #include @@ -52,18 +41,4 @@ #ifdef __linux__ #include #include -#endif - -inline unsigned int gettid() -{ -#ifdef _WIN32 - //return GetCurrentThreadId(); - return 0; -#elif defined(__linux__) - return static_cast(syscall(__NR_gettid)); -#elif defined(__APPLE__) - uint64_t tid64; - pthread_threadid_np(0, &tid64); - return static_cast(tid64); -#endif -} +#endif \ No newline at end of file diff --git a/src/VirtualTap.cpp b/src/VirtualTap.cpp index 11817ad..6df887c 100644 --- a/src/VirtualTap.cpp +++ b/src/VirtualTap.cpp @@ -40,18 +40,13 @@ typedef unsigned short u16_t; #include "libzt.h" #include "libztDebug.h" #include "lwIP.h" -#include "picoTCP.h" #include "ZT1Service.h" -#include "VirtualSocket.h" -#include "VirtualSocketLayer.h" #include "SysUtils.h" #include "Mutex.hpp" #include "InetAddress.hpp" #include "OneService.hpp" -//class VirtualSocket; - int VirtualTap::devno = 0; VirtualTap::VirtualTap( @@ -87,11 +82,8 @@ VirtualTap::VirtualTap( // set virtual tap interface name (abbreviated) memset(vtap_abbr_name, 0, sizeof(vtap_abbr_name)); snprintf(vtap_abbr_name, sizeof(vtap_abbr_name), "libzt%d", devno); -#if defined(STACK_LWIP) - // initialize network stacks lwip_driver_init(); -#endif - // start vtap thread and stack I/O loops + // start virtual tap thread and stack I/O loops _thread = Thread::start(this); } @@ -113,44 +105,26 @@ bool VirtualTap::enabled() const return _enabled; } -bool VirtualTap::registerIpWithStack(const InetAddress &ip) +void VirtualTap::registerIpWithStack(const InetAddress &ip) { -#if defined(STACK_LWIP) lwip_init_interface((void*)this, this->_mac, ip); -#endif -#if defined(STACK_PICO) - pico_register_address(this, ip); -#endif - return true; } bool VirtualTap::addIp(const InetAddress &ip) { -#if defined(NO_STACK) - char ipbuf[INET6_ADDRSTRLEN]; - DEBUG_INFO("addIp=%s, nwid=%llx", ip.toString(ipbuf), (unsigned long long)_nwid); - _ips.push_back(ip); - std::sort(_ips.begin(),_ips.end()); - return true; -#endif -#if defined(STACK_PICO) || defined(STACK_LWIP) char ipbuf[INET6_ADDRSTRLEN]; DEBUG_EXTRA("addr=%s, nwid=%llx", ip.toString(ipbuf), (unsigned long long)_nwid); Mutex::Lock _l(_ips_m); - if (registerIpWithStack(ip)) { - if (std::find(_ips.begin(),_ips.end(),ip) == _ips.end()) { - _ips.push_back(ip); - std::sort(_ips.begin(),_ips.end()); - } - return true; + registerIpWithStack(ip); + if (std::find(_ips.begin(),_ips.end(),ip) == _ips.end()) { + _ips.push_back(ip); + std::sort(_ips.begin(),_ips.end()); } - return false; -#endif + return true; } bool VirtualTap::removeIp(const InetAddress &ip) { - DEBUG_EXTRA(""); Mutex::Lock _l(_ips_m); std::vector::iterator i(std::find(_ips.begin(),_ips.end(),ip)); //if (i == _ips.end()) { @@ -158,10 +132,10 @@ bool VirtualTap::removeIp(const InetAddress &ip) //} _ips.erase(i); if (ip.isV4()) { - // FIXME: De-register from network stacks + // FIXME: De-register from network stack } if (ip.isV6()) { - // FIXME: De-register from network stacks + // FIXME: De-register from network stack } return true; } @@ -175,12 +149,7 @@ std::vector VirtualTap::ips() const void VirtualTap::put(const MAC &from,const MAC &to,unsigned int etherType, const void *data,unsigned int len) { -#if defined(STACK_LWIP) lwip_eth_rx(this, from, to, etherType, data, len); -#endif -#if defined(STACK_PICO) - rd_pico_eth_rx(this,from,to,etherType,data,len); -#endif } std::string VirtualTap::deviceName() const @@ -238,366 +207,10 @@ void VirtualTap::setMtu(unsigned int mtu) void VirtualTap::threadMain() throw() { -#if defined(STACK_LWIP) && !defined(LIBZT_RAW) while (true) { _phy.poll(ZT_PHY_POLL_INTERVAL); Housekeeping(); } -#endif -#if defined(STACK_LWIP) && defined(LIBZT_RAW) - rd_lwip_loop(this); // use driver loop -#endif -#if defined(STACK_PICO) - pico_init_interface(this); - if (this->should_start_stack) { - rd_pico_loop(this); // use driver loop - } -#endif -} - -void VirtualTap::phyOnUnixClose(PhySocket *sock, void **uptr) -{ - DEBUG_EXTRA(""); -} - -void VirtualTap::phyOnUnixData(PhySocket *sock, void **uptr, void *data, ssize_t len) -{ - DEBUG_EXTRA(""); -#if defined(LIBZT_RAW) - VirtualSocket *vs = (VirtualSocket*)*uptr; - if (vs == NULL) { - return; - } - if (len > 0) { - Write(vs, data, len); - } -#endif -} - -void VirtualTap::phyOnUnixWritable(PhySocket *sock, void **uptr, bool stack_invoked) -{ - DEBUG_EXTRA(""); -#if defined(LIBZT_RAW) - if (sock) { - Read(sock,uptr,stack_invoked); - } else { - DEBUG_ERROR("!sock"); - } -#endif -} - -bool VirtualTap::routeAdd(const InetAddress &ip, const InetAddress &nm, const InetAddress &gw) -{ - bool err = false; - DEBUG_EXTRA(""); -#if defined(STACK_LWIP) - // general_lwip_init_interface(this, NULL, "n1", _mac, ip, nm, gw); - // general_turn_on_interface(NULL); - return true; -#endif -#if defined(STACK_PICO) - return rd_pico_route_add(this, ip, nm, gw, 0); -#endif -#if defined(NO_STACK) - // nothing to do -#endif - return err; -} - -bool VirtualTap::routeDelete(const InetAddress &ip, const InetAddress &nm) -{ - bool err = false; - DEBUG_EXTRA(""); -#if defined(STACK_LWIP) - // general_lwip_init_interface(this, NULL, "n1", _mac, ip, nm, gw); - // general_turn_on_interface(NULL); - return true; -#endif -#if defined(STACK_PICO) - return rd_pico_route_del(this, ip, nm, 0); -#endif -#if defined(NO_STACK) - // nothing to do -#endif - return err; -} - -void VirtualTap::addVirtualSocket(VirtualSocket *vs) -{ -#if defined(LIBZT_RAW) - DEBUG_EXTRA(""); - Mutex::Lock _l(_tcpconns_m); - _VirtualSockets.push_back(vs); -#endif -} - -void VirtualTap::removeVirtualSocket() -{ -#if defined(LIBZT_RAW) - DEBUG_EXTRA(""); - Mutex::Lock _l(_tcpconns_m); - for (int i=0; i<_VirtualSockets.size(); i++) { - if (vs == _VirtualSockets[i]) { - _VirtualSockets.erase(_VirtualSockets.begin() + i); - break; - } - } -#endif -} - -/****************************************************************************/ -/* DNS */ -/****************************************************************************/ - -int VirtualTap::add_DNS_Nameserver(struct sockaddr *addr) -{ - int err = -1; -#if defined(STACK_LWIP) && defined(LIBZT_RAW) - err = rd_lwip_add_dns_nameserver(addr); -#endif -#if defined(STACK_PICO) - rd_pico_add_dns_nameserver(addr); -#endif - return err; -} - -int VirtualTap::del_DNS_Nameserver(struct sockaddr *addr) -{ - int err = -1; -#if defined(STACK_LWIP) && defined(LIBZT_RAW) - err = rd_lwip_del_dns_nameserver(addr); -#endif -#if defined(STACK_PICO) - err = rd_pico_del_dns_nameserver(addr); -#endif - return err; -} - -/****************************************************************************/ -/* SDK Socket API */ -/****************************************************************************/ - -// Connect -int VirtualTap::Connect(VirtualSocket *vs, const struct sockaddr *addr, socklen_t addrlen) -{ -#if !defined(LIBZT_RAW) - return -1; -#endif -#if defined(NO_STACK) - return -1; -#endif - int err = -1; -#if defined(STACK_LWIP) && defined(LIBZT_RAW) - err = rd_lwip_connect(vs, addr, addrlen); -#endif -#if defined(STACK_PICO) - Mutex::Lock _l(_tcpconns_m); - err = rd_pico_connect(vs, addr, addrlen); -#endif - return err; -} - -// Bind VirtualSocket to a network stack's interface -int VirtualTap::Bind(VirtualSocket *vs, const struct sockaddr *addr, socklen_t addrlen) -{ -#if !defined(LIBZT_RAW) - return -1; -#endif -#if defined(NO_STACK) - return -1; -#endif -#if defined(STACK_LWIP) && defined(LIBZT_RAW) - Mutex::Lock _l(_tcpconns_m); - return rd_lwip_bind(this, vs, addr, addrlen); -#endif -#if defined(STACK_PICO) - Mutex::Lock _l(_tcpconns_m); - return rd_pico_bind(vs, addr, addrlen); -#endif - return -1; -} - -// Listen for an incoming VirtualSocket -int VirtualTap::Listen(VirtualSocket *vs, int backlog) -{ -#if !defined(LIBZT_RAW) - return -1; -#endif -#if defined(NO_STACK) - return -1; -#endif - int err = -1; -#if defined(STACK_LWIP) && defined(LIBZT_RAW) - Mutex::Lock _l(_tcpconns_m); - err = rd_lwip_listen(vs, backlog); -#endif -#if defined(STACK_PICO) - Mutex::Lock _l(_tcpconns_m); - return rd_pico_listen(vs, backlog); -#endif - return err; -} - -// Accept a VirtualSocket -VirtualSocket *VirtualTap::Accept(VirtualSocket *vs) -{ -#if !defined(LIBZT_RAW) - return NULL; -#endif - VirtualSocket *new_vs = NULL; -#if defined(NO_STACK) - new_vs = NULL; -#endif -#if defined(STACK_LWIP) && defined(LIBZT_RAW) - Mutex::Lock _l(_tcpconns_m); - new_vs = rd_lwip_accept(vs); -#endif -#if defined(STACK_PICO) - // TODO: separation of church and state - Mutex::Lock _l(_tcpconns_m); - new_vs = rd_pico_accept(vs); -#endif - return new_vs; -} - -// Read from stack/buffers into the app's socket -int VirtualTap::Read(VirtualSocket *vs, PhySocket *sock, void **uptr, bool stack_invoked) -{ -#if !defined(LIBZT_RAW) - return -1; -#endif -#if defined(NO_STACK) - return -1; -#endif - int err = -1; -#if defined(STACK_LWIP) && defined(LIBZT_RAW) - err = rd_lwip_read((VirtualSocket*)*(_phy.getuptr(sock)), stack_invoked); -#endif -#if defined(STACK_PICO) - err = rd_pico_read(this, sock, (VirtualSocket*)uptr, stack_invoked); -#endif - return err; -} - -// Write data from app socket to the virtual wire, either raw over VL2, or via network stack -int VirtualTap::Write(VirtualSocket *vs, void *data, ssize_t len) -{ -#if !defined(LIBZT_RAW) - return -1; -#endif -#if defined(NO_STACK) - return -1; -#endif - DEBUG_EXTRA("vs=%p, fd=%d, data=%p, len=%lu", vs, vs->app_fd, data, (unsigned long)len); - int err = -1; -#if defined(LIBZT_RAW) - // VL2, SOCK_RAW, no network stack - if (vs->socket_type == SOCK_RAW) { - struct ether_header *eh = (struct ether_header *) data; - MAC src_mac; - MAC dest_mac; - src_mac.setTo(eh->ether_shost, 6); - dest_mac.setTo(eh->ether_dhost, 6); - _handler(_arg,NULL,_nwid,src_mac,dest_mac, Utils::ntoh((uint16_t)eh->ether_type),0, ((char*)data) + sizeof(struct ether_header),len - sizeof(struct ether_header)); - return len; - } -#endif -#if defined(STACK_LWIP) && defined(LIBZT_RAW) - err = rd_lwip_write(vs, data, len); -#endif -#if defined(STACK_PICO) - err = rd_pico_write(vs, data, len); -#endif - return err; -} - -// Send data to a specified host -int VirtualTap::SendTo(VirtualSocket *vs, const void *buf, size_t len, int flags, const struct sockaddr *addr, socklen_t addrlen) -{ -#if !defined(ZT_VIRTUAL_SOCKET) - return -1; -#endif - int err = -1; -#if defined(STACK_LWIP) && defined(ZT_VIRTUAL_SOCKET) - if ((err = rd_lwip_connect(vs, addr, addrlen)) < 0) { // implicit - return err; - } - if ((err = rd_lwip_write(vs, (void*)buf, len)) < 0) { - return err; - } -#endif -#if defined(STACK_PICO) && defined(ZT_VIRTUAL_SOCKET) - if ((err = rd_pico_connect(vs, addr, addrlen)) < 0) { // implicit - errno = ENOTCONN; - return err; - } - if ((err = rd_pico_write(vs, (void*)buf, len)) < 0) { - errno = ENOBUFS; // TODO: translate pico err to something more useful - return err; - } -#endif - return err; -} - -// Remove VritualSocket from VirtualTap, and instruct network stacks to dismantle their -// respective protocol control structures -int VirtualTap::Close(VirtualSocket *vs) -{ -#if !defined(LIBZT_RAW) - return -1; -#endif - int err = 0; -#if defined(LIBZT_RAW) - if (vs == NULL) { - DEBUG_ERROR("invalid VirtualSocket"); - return -1; - } - if (vs->sock) { - DEBUG_EXTRA("calling _phy.close()"); - _phy.close(vs->sock, true); - } - removeVirtualSocket(vs); -#endif -#if defined(STACK_LWIP) && defined(LIBZT_RAW) - err = rd_lwip_close(vs); -#endif -#if defined(STACK_PICO) - /* - if (vs->get_state() != VS_STATE_CLOSED && vs->get_state() != VS_STATE_LISTENING) { - DEBUG_EXTRA("vs=%p, vs->get_state()=%d, vs->picosock->state=%d", vs, vs->get_state(), vs->picosock->state); - // doesn't make sense to shut down a listening socket, just close it - if ((err = vs->tap->Shutdown(vs, SHUT_RDWR)) < 0) { - DEBUG_ERROR("error while shutting down socket"); - return - 1; - } - } - rd_pico_Close(vs); - removeVirtualSocket(vs); - if (vs->socket_type == SOCK_STREAM) { - while (!(vs->picosock->state & PICO_SOCKET_STATE_CLOSED)) { - nanosleep((const struct timespec[]) {{0, (ZT_ACCEPT_RECHECK_DELAY * 1000000)}}, NULL); - DEBUG_EXTRA("virtual lingering on socket, ps=%p, buf remaining=%d",vs->picosock, vs->TXbuf->count()); - } - } - */ -#endif - return err; -} - -// Shuts down some aspect of a connection -int VirtualTap::Shutdown(VirtualSocket *vs, int how) -{ -#if !defined(LIBZT_RAW) - return -1; -#endif - int err = 0; -#if defined(STACK_LWIP) && defined(LIBZT_RAW) - err = rd_lwip_shutdown(vs, how); -#endif -#if defined(STACK_PICO) - err = rd_pico_shutdown(vs, how); -#endif - return err; } void VirtualTap::Housekeeping() @@ -618,6 +231,7 @@ void VirtualTap::Housekeeping() char ipbuf[INET6_ADDRSTRLEN], ipbuf2[INET6_ADDRSTRLEN], ipbuf3[INET6_ADDRSTRLEN]; // TODO: Rework this when we have time // check if pushed route exists in tap (add) + /* for (int i=0; iat(i).target; @@ -657,6 +271,7 @@ void VirtualTap::Housekeeping() routeDelete(routes[i].first, routes[i].second); } } + */ } // TODO: Clean up VirtualSocket objects last_housekeeping_ts = time_now(); diff --git a/src/ZT1Service.cpp b/src/ZT1Service.cpp index dd136ef..f903717 100644 --- a/src/ZT1Service.cpp +++ b/src/ZT1Service.cpp @@ -272,7 +272,7 @@ int zts_get_num_assigned_addresses(const uint64_t nwid) } int zts_get_address_at_index( - const uint64_t nwid, const int index, struct sockaddr_storage *addr) + const uint64_t nwid, const int index, struct sockaddr *addr, socklen_t *addrlen) { if (!zt1Service) { return -1; @@ -283,8 +283,9 @@ int zts_get_address_at_index( return err; } _vtaps_lock.lock(); - if (index <= tap->_ips.size()) { - memcpy(addr, &(tap->_ips[index]), sizeof(struct sockaddr_storage)); + if (index > -1 && index <= tap->_ips.size()) { + memcpy(addr, &(tap->_ips[index]), *addrlen); + *addrlen = tap->_ips[index].isV4() ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); err = 0; } _vtaps_lock.unlock(); @@ -514,25 +515,6 @@ unsigned long zts_get_peer_count() } } -int zts_get_peer_address(char *peer, const uint64_t nodeId) -{ - /* - if (zt1Service) { - ZT_PeerList *pl = zt1Service->getNode()->peers(); - // uint64_t addr; - for (size_t i=0; ipeerCount; i++) { - // ZT_Peer *p = &(pl->peers[i]); - // DEBUG_INFO("peer[%d] = %lx", i, p->address); - } - return pl->peerCount; - } - else { - return -1; - } - */ - return -1; -} - bool _ipv6_in_subnet(ZeroTier::InetAddress *subnet, ZeroTier::InetAddress *addr) { ZeroTier::InetAddress r(addr); diff --git a/src/libzt.cpp b/src/libzt.cpp index 6272d61..38ec851 100644 --- a/src/libzt.cpp +++ b/src/libzt.cpp @@ -27,25 +27,15 @@ /** * @file * - * Application-facing, partially-POSIX-compliant socket API + * Application-facing, socket-like API */ #include "libztDefs.h" -#if defined(STACK_LWIP) #include "lwip/sockets.h" #include "lwip/ip_addr.h" #include "lwip/netdb.h" -//#include "dns.h" -#endif -#if defined(NO_STACK) -#include -#endif -#if defined(STACK_PICO) -#include -#endif -#include "VirtualSocketLayer.h" #include "libztDebug.h" #include @@ -62,104 +52,58 @@ int zts_socket(int socket_family, int socket_type, int protocol) { DEBUG_EXTRA("family=%d, type=%d, proto=%d", socket_family, socket_type, protocol); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - /* with this option, the VirtualSocket layer will abstract a stack's raw API - into something that resembles a POSIX socket API, this driver shall be implemented in - src/stack_name.cpp and include/stack_name.h */ - return virt_socket(socket_family, socket_type, protocol); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) - /* use the lwIP community's own socket API, this provides thread safety and core - locking */ int socket_family_adj = platform_adjusted_socket_family(socket_family); int err = lwip_socket(socket_family_adj, socket_type, protocol); return err; -#endif -#if defined(ZT_PICO_BSD_SOCKET) - // return pico_bsd_socket(socket_family, socket_type, protocol); - return -1; -#endif } int zts_connect(int fd, const struct sockaddr *addr, socklen_t addrlen) { DEBUG_EXTRA("fd=%d",fd); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - return virt_connect(fd, addr, addrlen); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) struct sockaddr_storage ss; memcpy(&ss, addr, addrlen); fix_addr_socket_family((struct sockaddr*)&ss); return lwip_connect(fd, (struct sockaddr*)&ss, addrlen); -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } int zts_bind(int fd, const struct sockaddr *addr, socklen_t addrlen) { DEBUG_EXTRA("fd=%d", fd); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - return virt_bind(fd, addr, addrlen); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) struct sockaddr_storage ss; memcpy(&ss, addr, addrlen); fix_addr_socket_family((struct sockaddr*)&ss); return lwip_bind(fd, (struct sockaddr*)&ss, addrlen); -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } int zts_listen(int fd, int backlog) { DEBUG_EXTRA("fd=%d", fd); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - return virt_listen(fd, backlog); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) return lwip_listen(fd, backlog); -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } int zts_accept(int fd, struct sockaddr *addr, socklen_t *addrlen) { DEBUG_EXTRA("fd=%d", fd); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - return virt_accept(fd, addr, addrlen); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) return lwip_accept(fd, addr, addrlen); -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } #if defined(__linux__) @@ -167,19 +111,11 @@ int zts_accept4(int fd, struct sockaddr *addr, socklen_t *addrlen, int flags) { DEBUG_EXTRA("fd=%d", fd); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - return virt_accept4(fd, addr, addrlen, flags); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) - // return lwip_accept4(fd, addr, addrlen, flags); + // lwip_accept4(fd, addr, addrlen, flags); return -1; -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } #endif @@ -187,120 +123,68 @@ int zts_setsockopt(int fd, int level, int optname, const void *optval, socklen_t { DEBUG_EXTRA("fd=%d, level=%d, optname=%d", fd, level, optname); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - return virt_setsockopt(fd, level, optname, optval, optlen); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) return lwip_setsockopt(fd, level, optname, optval, optlen); -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } int zts_getsockopt(int fd, int level, int optname, void *optval, socklen_t *optlen) { DEBUG_EXTRA("fd=%d, level=%d, optname=%d", fd, level, optname); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - return virt_getsockopt(fd, level, optname, optval, optlen); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) return lwip_getsockopt(fd, level, optname, optval, optlen); -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } int zts_getsockname(int fd, struct sockaddr *addr, socklen_t *addrlen) { DEBUG_EXTRA("fd=%d", fd); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - return virt_getsockname(fd, addr, addrlen); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) return lwip_getsockname(fd, addr, addrlen); -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } int zts_getpeername(int fd, struct sockaddr *addr, socklen_t *addrlen) { DEBUG_EXTRA("fd=%d", fd); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - return virt_getpeername(fd, addr, addrlen); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) return lwip_getpeername(fd, addr, addrlen); -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } int zts_gethostname(char *name, size_t len) { DEBUG_EXTRA(""); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) return -1; -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) - return -1; -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } int zts_sethostname(const char *name, size_t len) { DEBUG_EXTRA(""); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) return -1; -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) - return -1; -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } struct hostent *zts_gethostbyname(const char *name) { if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return NULL; } -#if defined(ZT_VIRTUAL_SOCKET) - //return virt_gethostbyname(name); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) // TODO: Test thread safety /* char buf[256]; @@ -320,30 +204,16 @@ struct hostent *zts_gethostbyname(const char *name) return lwip_gethostbyname(name); */ return NULL; -#endif -#if defined(ZT_PICO_BSD_SOCKET) -#endif -#if defined(STACK_LWIP) -#endif - return NULL; } int zts_close(int fd) { DEBUG_EXTRA("fd=%d", fd); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - return virt_close(fd); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) return lwip_close(fd); -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } #if defined(__linux__) @@ -355,17 +225,7 @@ int zts_poll(struct pollfd *fds, nfds_t nfds, int timeout) DEBUG_ERROR("service not started yet, call zts_startjoin()"); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - //return poll(fds, nfds, timeout); - return -1; -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) - //return poll(fds, nfds, timeout); - return -1; -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif + return poll(fds, nfds, timeout); } */ #endif @@ -373,35 +233,20 @@ int zts_poll(struct pollfd *fds, nfds_t nfds, int timeout) int zts_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) { - //DEBUG_EXTRA(""); - /* if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } - */ -#if defined(ZT_VIRTUAL_SOCKET) - return virt_select(nfds, readfds, writefds, exceptfds, timeout); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) return lwip_select(nfds, readfds, writefds, exceptfds, timeout); -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } int zts_fcntl(int fd, int cmd, int flags) { DEBUG_EXTRA("fd=%d, cmd=%d, flags=%d", fd, cmd, flags); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - return virt_fcntl(fd, cmd, flags); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) // translation from platform flag values to stack flag values int translated_flags = 0; #if defined(__linux__) @@ -415,28 +260,16 @@ int zts_fcntl(int fd, int cmd, int flags) } #endif return lwip_fcntl(fd, cmd, translated_flags); -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } int zts_ioctl(int fd, unsigned long request, void *argp) { DEBUG_EXTRA("fd=%d", fd); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - return virt_ioctl(fd, request, argp); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) return lwip_ioctl(fd, request, argp); -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } ssize_t zts_sendto(int fd, const void *buf, size_t len, int flags, @@ -444,76 +277,43 @@ ssize_t zts_sendto(int fd, const void *buf, size_t len, int flags, { DEBUG_TRANS("fd=%d, len=%zu", fd, len); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - //return virt_sendto(); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) struct sockaddr_storage ss; memcpy(&ss, addr, addrlen); fix_addr_socket_family((struct sockaddr*)&ss); return lwip_sendto(fd, buf, len, flags, (struct sockaddr*)&ss, addrlen); -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif - return -1; } ssize_t zts_send(int fd, const void *buf, size_t len, int flags) { DEBUG_TRANS("fd=%d, len=%zu", fd, len); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - return virt_send(fd, buf, len, flags); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) return lwip_send(fd, buf, len, flags); -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } ssize_t zts_sendmsg(int fd, const struct msghdr *msg, int flags) { DEBUG_TRANS("fd=%d", fd); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - return virt_sendmsg(fd, msg, flags); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) return lwip_sendmsg(fd, msg, flags); -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } ssize_t zts_recv(int fd, void *buf, size_t len, int flags) { DEBUG_TRANS("fd=%d, len=%zu", fd, len); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - return virt_recv(fd, buf, len, flags); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) return lwip_recv(fd, buf, len, flags); -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } ssize_t zts_recvfrom(int fd, void *buf, size_t len, int flags, @@ -521,133 +321,73 @@ ssize_t zts_recvfrom(int fd, void *buf, size_t len, int flags, { DEBUG_TRANS("fd=%d, len=%zu", fd, len); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - return virt_recvfrom(fd, buf, len, flags, addr, addrlen); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) return lwip_recvfrom(fd, buf, len, flags, addr, addrlen); -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } ssize_t zts_recvmsg(int fd, struct msghdr *msg, int flags) { DEBUG_TRANS("fd=%d", fd); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - return virt_recvmsg(fd, msg, flags); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) - //return lwip_recvmsg(fd, msg, flags); - return -1; -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif + return -1; // lwip_recvmsg(fd, msg, flags); + // Not currently implemented by stack } int zts_read(int fd, void *buf, size_t len) { DEBUG_TRANS("fd=%d, len=%zu", fd, len); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - return virt_read(fd, buf, len); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) return lwip_read(fd, buf, len); -#endif -#if defined(ZT_PICO_BSD_SOCKET) - // return pico_read(fd, buf, len); -#endif } int zts_write(int fd, const void *buf, size_t len) { DEBUG_TRANS("fd=%d, len=%zu", fd, len); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - return virt_write(fd, buf, len); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) return lwip_write(fd, buf, len); -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } int zts_shutdown(int fd, int how) { DEBUG_EXTRA("fd=%d, how=%d", fd, how); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) - return virt_shutdown(fd, how); -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) return lwip_shutdown(fd, how); -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } - int zts_add_dns_nameserver(struct sockaddr *addr) { DEBUG_EXTRA(""); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) + // TODO return -1; -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) - struct sockaddr_in *in4 = (struct sockaddr_in*)&addr; - static ip4_addr_t ipaddr; - ipaddr.addr = in4->sin_addr.s_addr; - // TODO: manage DNS server indices - //dns_setserver(0, (const ip_addr_t*)&ipaddr); - return 0; -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } int zts_del_dns_nameserver(struct sockaddr *addr) { DEBUG_EXTRA(""); if (zts_ready() == false) { - DEBUG_ERROR("service not started yet, call zts_startjoin()"); + DEBUG_ERROR(LIBZT_SERVICE_NOT_STARTED_STR); return -1; } -#if defined(ZT_VIRTUAL_SOCKET) + // TODO return -1; -#endif -#if defined(ZT_LWIP_SEQ_SOCKET) - return -1; -#endif -#if defined(ZT_PICO_BSD_SOCKET) - return -1; -#endif } /* The rationale for the following correctional methods is as follows: @@ -689,7 +429,6 @@ void fix_addr_socket_family(struct sockaddr *addr) #if defined(__linux__) || defined(_WIN32) /* struct sockaddr on Linux and Windows don't contain an sa_len field so we must adjust it here before feeding it into the stack. */ -#if defined(STACK_LWIP) if (addr->sa_len == 2) { if (addr->sa_family == 0) { addr->sa_family = addr->sa_len; @@ -702,7 +441,6 @@ void fix_addr_socket_family(struct sockaddr *addr) addr->sa_len = 0; } } -#endif /* once we've moved the value to its anticipated location, convert it from its platform-specific value to one that the network stack can work with */ #endif diff --git a/src/libztJNI.cpp b/src/libztJNI.cpp index 1ba63ab..9fd8230 100644 --- a/src/libztJNI.cpp +++ b/src/libztJNI.cpp @@ -48,13 +48,12 @@ extern "C" { namespace ZeroTier { - // prototype - jobject ss2inet(JNIEnv *env, struct sockaddr_storage *src_ss); - int sockinet2ss(JNIEnv *env, jobject src_inet, struct sockaddr_storage *dest_ss); + void ss2zta(JNIEnv *env, struct sockaddr_storage *ss, jobject addr); + void zta2ss(JNIEnv *env, struct sockaddr_storage *ss, jobject addr); /****************************************************************************/ - /* ZeroTier service controls */ - /****************************************************************************/ + /* ZeroTier service controls */ + /****************************************************************************/ JNIEXPORT void JNICALL Java_zerotier_ZeroTier_start( JNIEnv *env, jobject thisObj, jstring path, jboolean blocking) @@ -122,23 +121,20 @@ namespace ZeroTier { return zts_get_node_id(); } - // TODO: ZT_SOCKET_API uint64_t ZTCALL zts_get_node_id_from_file(const char *filepath); - - JNIEXPORT jint JNICALL Java_zerotier_ZeroTier_get_1num_1assigned_1addresses( + JNIEXPORT jboolean JNICALL Java_zerotier_ZeroTier_get_1num_1assigned_1addresses( JNIEnv *env, jobject thisObj, jlong nwid) { return zts_get_num_assigned_addresses(nwid); } - JNIEXPORT jobject JNICALL Java_zerotier_ZeroTier_get_1address_1at_1index( - JNIEnv *env, jobject thisObj, jlong nwid, jint index) + JNIEXPORT jint JNICALL Java_zerotier_ZeroTier_get_1address_1at_1index( + JNIEnv *env, jobject thisObj, jlong nwid, jint index, jobject addr) { struct sockaddr_storage ss; - int err; - if((err = zts_get_address_at_index(nwid, index, &ss)) < 0) { - return NULL; - } - return ss2inet(env, &ss); + socklen_t addrlen = sizeof(struct sockaddr_storage); + int err = zts_get_address_at_index(nwid, index, (struct sockaddr*)&ss, &addrlen); + ss2zta(env, &ss, addr); + return err; } JNIEXPORT jboolean JNICALL Java_zerotier_ZeroTier_has_1address( @@ -147,31 +143,29 @@ namespace ZeroTier { return zts_has_address(nwid); } - JNIEXPORT jobject JNICALL Java_zerotier_ZeroTier_get_1address( - JNIEnv *env, jobject thisObj, jlong nwid, jint address_family) + JNIEXPORT jboolean JNICALL Java_zerotier_ZeroTier_get_1address( + JNIEnv *env, jobject thisObj, jlong nwid, jint address_family, jobject addr) { struct sockaddr_storage ss; - int err; - if ((err = zts_get_address((uint64_t)nwid, &ss, address_family)) < 0) { - return NULL; - } - return ss2inet(env, &ss); + int err = zts_get_address((uint64_t)nwid, &ss, address_family); + ss2zta(env, &ss, addr); + return err; } - JNIEXPORT jobject JNICALL Java_zerotier_ZeroTier_get_6plane_addr( - JNIEnv *env, jobject thisObj, jlong nwid, jlong nodeId) + JNIEXPORT void JNICALL Java_zerotier_ZeroTier_get_6plane_addr( + JNIEnv *env, jobject thisObj, jlong nwid, jlong nodeId, jobject addr) { struct sockaddr_storage ss; zts_get_6plane_addr(&ss, nwid, nodeId); - return ss2inet(env, &ss); + ss2zta(env, &ss, addr); } - JNIEXPORT jobject JNICALL Java_zerotier_ZeroTier_get_rfc4193_addr( - JNIEnv *env, jobject thisObj, jlong nwid, jlong nodeId) + JNIEXPORT void JNICALL Java_zerotier_ZeroTier_get_rfc4193_addr( + JNIEnv *env, jobject thisObj, jlong nwid, jlong nodeId, jobject addr) { struct sockaddr_storage ss; zts_get_rfc4193_addr(&ss, nwid, nodeId); - return ss2inet(env, &ss); + ss2zta(env, &ss, addr); } JNIEXPORT jlong JNICALL Java_zerotier_ZeroTier_get_peer_count( @@ -180,11 +174,9 @@ namespace ZeroTier { return zts_get_peer_count(); } - // TODO: ZT_SOCKET_API int ZTCALL zts_get_peer_address(char *peer, const uint64_t nodeId); - /****************************************************************************/ - /* ZeroTier Socket API */ - /****************************************************************************/ + /* ZeroTier Socket API */ + /****************************************************************************/ JNIEXPORT jint JNICALL Java_zerotier_ZeroTier_socket( JNIEnv *env, jobject thisObj, jint family, jint type, jint protocol) @@ -196,10 +188,7 @@ namespace ZeroTier { JNIEnv *env, jobject thisObj, jint fd, jobject addr) { struct sockaddr_storage ss; - if(sockinet2ss(env, addr, &ss) < 0) { - return -1; // possibly invalid address format - // TODO: set errno - } + zta2ss(env, &ss, addr); socklen_t addrlen = ss.ss_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); return zts_connect(fd, (struct sockaddr *)&ss, addrlen); } @@ -209,14 +198,9 @@ namespace ZeroTier { { struct sockaddr_storage ss; int err; - if(sockinet2ss(env, addr, &ss) < 0) { - return -1; // possibly invalid address format - // TODO: set errno - } - //DEBUG_TEST("RESULT => %s : %d", inet_ntoa(in4->sin_addr), ntohs(in4->sin_port)); + zta2ss(env, &ss, addr); socklen_t addrlen = ss.ss_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); - err = zts_bind(fd, (struct sockaddr*)&ss, addrlen); - return err; + return zts_bind(fd, (struct sockaddr*)&ss, addrlen); } JNIEXPORT jint JNICALL Java_zerotier_ZeroTier_listen( @@ -229,26 +213,20 @@ namespace ZeroTier { JNIEnv *env, jobject thisObj, jint fd, jobject addr, jint port) { struct sockaddr_storage ss; - int err; socklen_t addrlen = sizeof(struct sockaddr_storage); - if ((err = zts_accept(fd, (struct sockaddr *)&ss, &addrlen)) < 0) { - return err; - } - addr = ss2inet(env, &ss); + int err = zts_accept(fd, (struct sockaddr *)&ss, &addrlen); + ss2zta(env, &ss, addr); return err; } #if defined(__linux__) JNIEXPORT jint JNICALL Java_zerotier_ZeroTier_accept4( - JNIEnv *env, jobject thisObj, jint fd, jobject addr, jint port, jint flags) + JNIEnv *env, jobject thisObj, jint fd, jobject addr, jint port, jint flags) { struct sockaddr_storage ss; - int err; socklen_t addrlen = sizeof(struct sockaddr_storage); - if ((err = zts_accept4(fd, (struct sockaddr *)&ss, &addrlen, flags)) < 0) { - return err; - } - addr = ss2inet(env, &ss); + int err = zts_accept4(fd, (struct sockaddr *)&ss, &addrlen, flags); + ss2zta(env, &ss, addr); return err; } #endif @@ -265,139 +243,99 @@ namespace ZeroTier { return zts_getsockopt(fd, level, optname, (void*)(uintptr_t)optval, (socklen_t *)optlen); } - JNIEXPORT jint JNICALL Java_zerotier_ZeroTier_getsockname(JNIEnv *env, jobject thisObj, - jint fd, jobject ztaddr) + JNIEXPORT jboolean JNICALL Java_zerotier_ZeroTier_getsockname(JNIEnv *env, jobject thisObj, + jint fd, jobject addr) { - struct sockaddr_in addr; - int err = zts_getsockname(fd, (struct sockaddr *)&addr, (socklen_t *)sizeof(struct sockaddr)); - jfieldID fid; - jclass c = (*env).GetObjectClass(ztaddr); - if (c) { - fid = (*env).GetFieldID(c, "port", "I"); - (*env).SetIntField(ztaddr, fid, addr.sin_port); - fid = (*env).GetFieldID(c,"_rawAddr", "J"); - (*env).SetLongField(ztaddr, fid,addr.sin_addr.s_addr); - } + struct sockaddr_storage ss; + socklen_t addrlen = sizeof(struct sockaddr_storage); + int err = zts_getsockname(fd, (struct sockaddr *)&ss, &addrlen); + ss2zta(env, &ss, addr); return err; } JNIEXPORT jint JNICALL Java_zerotier_ZeroTier_getpeername(JNIEnv *env, jobject thisObj, - jint fd, jobject ztaddr) + jint fd, jobject addr) { - struct sockaddr_in addr; - int err = zts_getpeername(fd, (struct sockaddr *)&addr, (socklen_t *)sizeof(struct sockaddr)); - jfieldID fid; - jclass c = (*env).GetObjectClass( ztaddr); - if (c) { - fid = (*env).GetFieldID(c, "port", "I"); - (*env).SetIntField(ztaddr, fid, addr.sin_port); - fid = (*env).GetFieldID(c,"_rawAddr", "J"); - (*env).SetLongField(ztaddr, fid,addr.sin_addr.s_addr); - } + struct sockaddr_storage ss; + int err = zts_getpeername(fd, (struct sockaddr *)&ss, (socklen_t *)sizeof(struct sockaddr_storage)); + ss2zta(env, &ss, addr); return err; } - // TODO: ZT_SOCKET_API struct hostent *zts_gethostbyname(const char *name); - JNIEXPORT jint JNICALL Java_zerotier_ZeroTier_close( JNIEnv *env, jobject thisObj, jint fd) { return zts_close(fd); } - // TODO: ZT_SOCKET_API int ZTCALL zts_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); - JNIEXPORT jint JNICALL Java_zerotier_ZeroTier_fcntl( JNIEnv *env, jobject thisObj, jint fd, jint cmd, jint flags) { return zts_fcntl(fd, cmd, flags); } - // TODO: ZT_SOCKET_API int ZTCALL zts_ioctl(int fd, unsigned long request, void *argp); + JNIEXPORT int JNICALL Java_zerotier_ZeroTier_ioctl(jint fd, jlong request, void *argp) + { + return zts_ioctl(fd, request, argp); + } JNIEXPORT jint JNICALL Java_zerotier_ZeroTier_send(JNIEnv *env, jobject thisObj, jint fd, jarray buf, jint len, int flags) { jbyte *body = (*env).GetByteArrayElements((_jbyteArray *)buf, 0); - char * bufp = (char *)malloc(sizeof(char)*len); - memcpy(bufp, body, len); + int w = zts_send(fd, body, len, flags); (*env).ReleaseByteArrayElements((_jbyteArray *)buf, body, 0); - int written_bytes = zts_write(fd, body, len); - return written_bytes; + return w; } JNIEXPORT jint JNICALL Java_zerotier_ZeroTier_sendto( - JNIEnv *env, jobject thisObj, jint fd, jarray buf, jint len, jint flags, jobject ztaddr) + JNIEnv *env, jobject thisObj, jint fd, jarray buf, jint len, jint flags, jobject addr) { - struct sockaddr_in addr; - int sent_bytes = 0; - jclass c = (*env).GetObjectClass( ztaddr); - if (c) { - jfieldID f = (*env).GetFieldID(c, "port", "I"); - addr.sin_port = htons((*env).GetIntField( ztaddr, f)); - f = (*env).GetFieldID(c, "_rawAddr", "J"); - addr.sin_addr.s_addr = (*env).GetLongField( ztaddr, f); - addr.sin_family = AF_INET; - //LOGV("zt_sendto(): fd = %d\naddr = %s\nport=%d", fd, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); - // TODO: Optimize this - jbyte *body = (*env).GetByteArrayElements((_jbyteArray *)buf, 0); - char * bufp = (char *)malloc(sizeof(char)*len); - memcpy(bufp, body, len); - (*env).ReleaseByteArrayElements((_jbyteArray *)buf, body, 0); - // "connect" and send buffer contents - sent_bytes = zts_sendto(fd, body, len, flags, (struct sockaddr *)&addr, sizeof(addr)); - } - return sent_bytes; + jbyte *body = (*env).GetByteArrayElements((_jbyteArray *)buf, 0); + struct sockaddr_storage ss; + zta2ss(env, &ss, addr); + socklen_t addrlen = ss.ss_family == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); + int w = zts_sendto(fd, body, len, flags, (struct sockaddr *)&ss, addrlen); + (*env).ReleaseByteArrayElements((_jbyteArray *)buf, body, 0); + return w; } - // TODO: ZT_SOCKET_API ssize_t ZTCALL zts_sendmsg(int fd, const struct msghdr *msg, int flags); - // TODO: ZT_SOCKET_API ssize_t ZTCALL zts_recv(int fd, void *buf, size_t len, int flags); + JNIEXPORT jint JNICALL Java_zerotier_ZeroTier_recv(JNIEnv *env, jobject thisObj, + jint fd, jarray buf, jint len, jint flags) + { + jbyte *body = (*env).GetByteArrayElements((_jbyteArray *)buf, 0); + int r = zts_recv(fd, body, len, flags); + (*env).ReleaseByteArrayElements((_jbyteArray *)buf, body, 0); + return r; + } JNIEXPORT jint JNICALL Java_zerotier_ZeroTier_recvfrom( - JNIEnv *env, jobject thisObj, jint fd, jbyteArray buf, jint len, jint flags, jobject ztaddr) + JNIEnv *env, jobject thisObj, jint fd, jbyteArray buf, jint len, jint flags, jobject addr) { - /* - struct sockaddr_in addr; - jbyte *body = (*env).GetByteArrayElements( buf, 0); - unsigned char buffer[ZT_SDK_MTU]; - int payload_offset = sizeof(int32_t) + sizeof(struct sockaddr_storage); - int rxbytes = zts_recvfrom(fd, &buffer, len, flags, (struct sockaddr *)&addr, (socklen_t *)sizeof(struct sockaddr_storage)); - if (rxbytes > 0) { - memcpy(body, (jbyte*)buffer + payload_offset, rxbytes); - } - (*env).ReleaseByteArrayElements( buf, body, 0); - // Update fields of Java ZTAddress object - jfieldID fid; - jclass c = (*env).GetObjectClass( ztaddr); - if (c) { - fid = (*env).GetFieldID(c, "port", "I"); - (*env).SetIntField(ztaddr, fid, addr.sin_port); - fid = (*env).GetFieldID(c,"_rawAddr", "J"); - (*env).SetLongField(ztaddr, fid,addr.sin_addr.s_addr); - } - */ - return 1; + socklen_t addrlen = sizeof(struct sockaddr_storage); + struct sockaddr_storage ss; + jbyte *body = (*env).GetByteArrayElements((_jbyteArray *)buf, 0); + int r = zts_recvfrom(fd, body, len, flags, (struct sockaddr *)&ss, &addrlen); + (*env).ReleaseByteArrayElements((_jbyteArray *)buf, body, 0); + ss2zta(env, &ss, addr); + return r; } - // TODO: ZT_SOCKET_API ssize_t ZTCALL zts_recvmsg(int fd, struct msghdr *msg,int flags); - JNIEXPORT jint JNICALL Java_zerotier_ZeroTier_read(JNIEnv *env, jobject thisObj, jint fd, jarray buf, jint len) { jbyte *body = (*env).GetByteArrayElements((_jbyteArray *)buf, 0); - int read_bytes = zts_read(fd, body, len); + int r = zts_read(fd, body, len); (*env).ReleaseByteArrayElements((_jbyteArray *)buf, body, 0); - return read_bytes; + return r; } JNIEXPORT jint JNICALL Java_zerotier_ZeroTier_write(JNIEnv *env, jobject thisObj, jint fd, jarray buf, jint len) { jbyte *body = (*env).GetByteArrayElements((_jbyteArray *)buf, 0); - char * bufp = (char *)malloc(sizeof(char)*len); - memcpy(bufp, body, len); + int w = zts_write(fd, body, len); (*env).ReleaseByteArrayElements((_jbyteArray *)buf, body, 0); - int written_bytes = zts_write(fd, body, len); - return written_bytes; + return w; } JNIEXPORT jint JNICALL Java_zerotier_ZeroTier_shutdown( @@ -405,113 +343,88 @@ namespace ZeroTier { { return zts_shutdown(fd, how); } - - // TODO: ZT_SOCKET_API int ZTCALL zts_add_dns_nameserver(struct sockaddr *addr); - // TODO: ZT_SOCKET_API int ZTCALL zts_del_dns_nameserver(struct sockaddr *addr); } + /****************************************************************************/ + /* Helpers (for moving data across the JNI barrier) */ + /****************************************************************************/ -// convenience function -jobject ss2inet(JNIEnv *env, struct sockaddr_storage *src_ss) +void ss2zta(JNIEnv *env, struct sockaddr_storage *ss, jobject addr) { - jobject dest_inet; - if(src_ss->ss_family == AF_INET) - { - DEBUG_ERROR("converting from INET"); - struct sockaddr_in *in4 = (struct sockaddr_in*)src_ss; - int arrlen = 4; - jbyteArray bytes = (*env).NewByteArray(arrlen); - jbyte *java_address_bytes; - java_address_bytes = (*env).GetByteArrayElements(bytes, NULL); - memcpy(java_address_bytes, &(in4->sin_addr.s_addr), arrlen); - (*env).ReleaseByteArrayElements(bytes, java_address_bytes, 0); - jclass cls = (*env).FindClass("java/net/InetAddress"); - jmethodID mid = (*env).GetStaticMethodID(cls, "getByAddress", "([B)Ljava/net/InetAddress;"); - dest_inet = (*env).CallStaticObjectMethod(cls, mid, bytes); - (*env).DeleteLocalRef(bytes); - } - if(src_ss->ss_family == AF_INET6) - { - DEBUG_ERROR("converting from INET6"); - struct sockaddr_in6 *in6 = (struct sockaddr_in6*)src_ss; - int arrlen = 16; - jbyteArray bytes = (*env).NewByteArray(arrlen); - (*env).SetByteArrayRegion(bytes, 0, 16, (const jbyte *)&(in6->sin6_addr)); - jclass cls = (*env).FindClass("java/net/InetAddress"); - jmethodID mid = (*env).GetStaticMethodID(cls, "getByAddress", "([B)Ljava/net/InetAddress;"); - dest_inet = (*env).CallStaticObjectMethod(cls, mid, bytes); - (*env).DeleteLocalRef(bytes); - } - return dest_inet; -} - - -int sockinet2ss(JNIEnv *env, jobject src_inet, struct sockaddr_storage *dest_ss) -{ - struct sockaddr_in *in4 = (struct sockaddr_in*)dest_ss; - struct sockaddr_in6 *in6 = (struct sockaddr_in6*)dest_ss; - int port = 0; - int socket_family = 0; - socklen_t addrlen; - - // --- - - jclass c = (*env).GetObjectClass(src_inet); + jclass c = (*env).GetObjectClass(addr); if (!c) { - return -1; + return; } - // get port - jmethodID getPort = (*env).GetMethodID(c, "getPort", "()I"); - if (!getPort) { - return -1; + if(ss->ss_family == AF_INET) + { + struct sockaddr_in *in4 = (struct sockaddr_in*)ss; + jfieldID fid = (*env).GetFieldID(c, "_port", "I"); + (*env).SetIntField(addr, fid, ntohs(in4->sin_port)); + fid = (*env).GetFieldID(c,"_family", "I"); + (*env).SetLongField(addr, fid, (in4->sin_family)); + fid = env->GetFieldID(c, "_ip4", "[B"); + jobject ipData = (*env).GetObjectField (addr, fid); + jbyteArray * arr = reinterpret_cast(&ipData); + char *data = (char*)(*env).GetByteArrayElements(*arr, NULL); + memcpy(data, &(in4->sin_addr.s_addr), 4); + (*env).ReleaseByteArrayElements(*arr, (jbyte*)data, 0); + + return; } - port = (*env).CallIntMethod(src_inet, getPort); - // get internal InetAddress - jobject inetaddr; - jmethodID getAddress = (*env).GetMethodID(c, "getAddress", "()Ljava/net/InetAddress;"); - if (!getAddress) { - return -1; + if(ss->ss_family == AF_INET6) + { + struct sockaddr_in6 *in6 = (struct sockaddr_in6*)ss; + jfieldID fid = (*env).GetFieldID(c, "_port", "I"); + (*env).SetIntField(addr, fid, ntohs(in6->sin6_port)); + fid = (*env).GetFieldID(c,"_family", "I"); + (*env).SetLongField(addr, fid, (in6->sin6_family)); + fid = env->GetFieldID(c, "_ip6", "[B"); + jobject ipData = (*env).GetObjectField (addr, fid); + jbyteArray * arr = reinterpret_cast(&ipData); + char *data = (char*)(*env).GetByteArrayElements(*arr, NULL); + memcpy(data, &(in6->sin6_addr.s6_addr), 16); + (*env).ReleaseByteArrayElements(*arr, (jbyte*)data, 0); + return; } - inetaddr = (*env).CallObjectMethod(src_inet, getAddress); - if (!inetaddr) { - return -1; +} + +void zta2ss(JNIEnv *env, struct sockaddr_storage *ss, jobject addr) +{ + jclass c = (*env).GetObjectClass(addr); + if (!c) { + return; } - jclass inetClass = (*env).GetObjectClass(inetaddr); - if (!inetClass) { - return -1; + jfieldID fid = (*env).GetFieldID(c, "_family", "I"); + int family = (*env).GetIntField(addr, fid); + if (family == AF_INET) + { + struct sockaddr_in *in4 = (struct sockaddr_in*)ss; + fid = (*env).GetFieldID(c, "_port", "I"); + in4->sin_port = htons((*env).GetIntField(addr, fid)); + in4->sin_family = AF_INET; + fid = env->GetFieldID(c, "_ip4", "[B"); + jobject ipData = (*env).GetObjectField (addr, fid); + jbyteArray * arr = reinterpret_cast(&ipData); + char *data = (char*)(*env).GetByteArrayElements(*arr, NULL); + memcpy(&(in4->sin_addr.s_addr), data, 4); + (*env).ReleaseByteArrayElements(*arr, (jbyte*)data, 0); + return; } - // string representation of IP address - jmethodID getHostAddress = (*env).GetMethodID(inetClass, "getHostAddress", "()Ljava/lang/String;"); - jstring addrstr = (jstring)(*env).CallObjectMethod(inetaddr, getHostAddress); - const char *addr_str = (*env).GetStringUTFChars(addrstr, NULL); - for (int i=0; isin_family = AF_INET; - in4->sin_port = htons(port); - in4->sin_addr.s_addr = inet_addr(addr_str); - /* - if (!inet_pton(AF_INET, addr_str, &(in4->sin_addr))) { - DEBUG_ERROR("error converting address %s", addr_str); - } - */ - addrlen = sizeof(struct sockaddr_in); - break; - } - if (addr_str[i]==':') { - DEBUG_INFO("ipv6"); - socket_family = AF_INET6; - if (!inet_pton(AF_INET6, addr_str, &(in6->sin6_addr))) { - DEBUG_ERROR("error converting address %s", addr_str); - } - addrlen = sizeof(struct sockaddr_in6); - break; - } + if (family == AF_INET6) + { + struct sockaddr_in6 *in6 = (struct sockaddr_in6*)ss; + jfieldID fid = (*env).GetFieldID(c, "_port", "I"); + in6->sin6_port = htons((*env).GetIntField(addr, fid)); + fid = (*env).GetFieldID(c,"_family", "I"); + in6->sin6_family = AF_INET6; + fid = env->GetFieldID(c, "_ip6", "[B"); + jobject ipData = (*env).GetObjectField (addr, fid); + jbyteArray * arr = reinterpret_cast(&ipData); + char *data = (char*)(*env).GetByteArrayElements(*arr, NULL); + memcpy(&(in6->sin6_addr.s6_addr), data, 16); + (*env).ReleaseByteArrayElements(*arr, (jbyte*)data, 0); + return; } - (*env).ReleaseStringUTFChars(addrstr, addr_str); - DEBUG_TEST("RESULT => %s : %d", inet_ntoa(in4->sin_addr), ntohs(in4->sin_port)); - return 0; } #ifdef __cplusplus diff --git a/src/lwIP.cpp b/src/lwIP.cpp index b66d83d..4b78bd1 100644 --- a/src/lwIP.cpp +++ b/src/lwIP.cpp @@ -37,14 +37,8 @@ #include "libztDefs.h" -#ifdef STACK_LWIP - -#include "VirtualSocket.h" - -// forward declarations +#include "VirtualTap.h" class VirtualTap; -class VirtualSocket; -bool virt_can_provision_new_socket(int socket_type); #include "Mutex.hpp" #include "MAC.hpp" @@ -114,7 +108,6 @@ static void tcp_timeout(void *data) // callback for when the TCPIP thread has been successfully started static void tcpip_init_done(void *arg) { - DEBUG_EXTRA("tcpip-thread"); sys_sem_t *sem; sem = (sys_sem_t *)arg; //netif_set_up(&lwipdev); @@ -126,7 +119,7 @@ static void tcpip_init_done(void *arg) void my_tcpip_callback(void *arg) { - Mutex::Lock _l(_rx_input_lock_m); + ZeroTier::Mutex::Lock _l(_rx_input_lock_m); int loop_score = LWIP_FRAMES_HANDLED_PER_CORE_CALL; // max num of packets to read per polling call // TODO: Optimize (use Ringbuffer) int pkt_num = 0; @@ -138,6 +131,7 @@ void my_tcpip_callback(void *arg) struct ip_hdr *iphdr; switch (((struct eth_hdr *)p->payload)->type) { +#ifdef LIBZT_IPV6 case PP_HTONS(ETHTYPE_IPV6): { iphdr = (struct ip_hdr *)((char *)p->payload + SIZEOF_ETH_HDR); for (int i=0; ipayload + SIZEOF_ETH_HDR); for (int i=0; idest.addr || ip4_addr_isbroadcast_u32(iphdr->dest.addr, &lwipInterfaces[i])) { if (lwipInterfaces[i].ip_addr.u_addr.ip4.addr == iphdr->dest.addr || ip4_addr_isbroadcast_u32(iphdr->dest.addr, &lwipInterfaces[i])) { if (lwipInterfaces[i].input(p, &lwipInterfaces[i]) != ERR_OK) { DEBUG_ERROR("packet input error (ipv4, p=%p, netif=%p)", p, &lwipInterfaces[i]); @@ -163,6 +160,7 @@ void my_tcpip_callback(void *arg) } } } break; +#endif case PP_HTONS(ETHTYPE_ARP): { for (int i=0; inext; - count++; - } - pcb_ptr = tcp_tw_pcbs; // PCBs in TIME-WAIT state - while (pcb_ptr) { - pcb_ptr = pcb_ptr->next; - count++; - } - /* TODO - pcb_ptr = tcp_listen_pcbs; - while (pcb_ptr) { - pcb_ptr = pcb_ptr->next; - count++; - DEBUG_ERROR("FOUND --- tcp_listen_pcbs PCB COUNT = %d", count); - }*/ - pcb_ptr = tcp_bound_pcbs; // PCBs in a bound state - while (pcb_ptr) { - pcb_ptr = pcb_ptr->next; - count++; - } - return count; -} - -int rd_lwip_num_current_udp_pcbs() -{ - // TODO: These will likely need some sort of locking protection - int count = 0; - struct udp_pcb *pcb_ptr = udp_pcbs; - while (pcb_ptr) { - pcb_ptr = pcb_ptr->next; - count++; - } - return count; -} - -int rd_lwip_num_current_raw_pcbs() -{ - // TODO: These will likely need some sort of locking protection - /* - int count = 0; - struct raw_pcb *pcb_ptr = raw_pcbs; - while (pcb_ptr) { - pcb_ptr = pcb_ptr->next; - count++; - DEBUG_ERROR("FOUND --- raw_pcbs PCB COUNT = %d", count); - } - return count; - */ - return 0; -} - -int rd_lwip_num_total_pcbs() -{ - return rd_lwip_num_current_raw_pcbs() + rd_lwip_num_current_udp_pcbs() + rd_lwip_num_current_tcp_pcbs(); -} - -int rd_lwip_add_dns_nameserver(struct sockaddr *addr) -{ - return -1; -} - -int rd_lwip_del_dns_nameserver(struct sockaddr *addr) -{ - return -1; -} - -void rd_lwip_loop(VirtualTap *tap) -{ - uint64_t prev_tcp_time = 0, prev_discovery_time = 0; - while (tap->_run) - { - uint64_t now = time_now(); - uint64_t since_tcp = now - prev_tcp_time; - uint64_t since_discovery = now - prev_discovery_time; - uint64_t tcp_remaining = LWIP_TCP_TIMER_INTERVAL; - uint64_t discovery_remaining = 5000; - - // Main TCP/ETHARP timer section - if (since_tcp >= LWIP_TCP_TIMER_INTERVAL) { - prev_tcp_time = now; - tcp_tmr(); - } - else { - tcp_remaining = LWIP_TCP_TIMER_INTERVAL - since_tcp; - } - -#if defined(LIBZT_IPV4) - if (since_discovery >= DISCOVERY_INTERVAL) { - prev_discovery_time = now; - etharp_tmr(); - } else { - discovery_remaining = DISCOVERY_INTERVAL - since_discovery; - } -#endif - -#if defined(LIBZT_IPV6) - if (since_discovery >= ND6_DISCOVERY_INTERVAL) { - prev_discovery_time = now; - nd6_tmr(); - } else { - discovery_remaining = ND6_DISCOVERY_INTERVAL - since_discovery; - } - } -#endif - - tap->_phy.poll((unsigned long)std::min(tcp_remaining,discovery_remaining)); - tap->Housekeeping(); - } -} - -int rd_lwip_socket(void **pcb, int socket_family, int socket_type, int protocol) -{ - if (virt_can_provision_new_socket(socket_type) == false) { - DEBUG_ERROR("unable to create socket due to limitation of network stack, PCBs=%d", rd_lwip_num_total_pcbs()); - errno = ENOMEM; - return -1; - } - if (socket_type == SOCK_STREAM) { - struct tcp_pcb *new_tcp_PCB = tcp_new(); - *pcb = new_tcp_PCB; - tcp_nagle_disable(new_tcp_PCB); - return ERR_OK; - } - if (socket_type == SOCK_DGRAM) { - struct udp_pcb *new_udp_PCB = udp_new(); - *pcb = new_udp_PCB; - return ERR_OK; - } - errno = ENOMEM; - return -1; -} - -int rd_lwip_connect(VirtualSocket *vs, const struct sockaddr *addr, socklen_t addrlen) -{ - ip_addr_t ba; - char addrstr[INET6_ADDRSTRLEN]; - int port = 0, err = 0; -/* -#if defined(LIBZT_IPV4) - struct sockaddr_in *in4 = (struct sockaddr_in *)addr; - if (addr->sa_family == AF_INET && vs->socket_type == SOCK_STREAM) { - inet_ntop(AF_INET, &(in4->sin_addr), addrstr, INET_ADDRSTRLEN); - DEBUG_EXTRA("connecting to %s : %d", addrstr, lwip_ntohs(in4->sin_port)); - } - convert_ip(in4, &ba); - port = lwip_ntohs(in4->sin_port); -#endif -#if defined(LIBZT_IPV6) - struct sockaddr_in6 *in6 = (struct sockaddr_in6*)&addr; - in6_to_ip6((ip6_addr *)&ba, in6); - if (addr->sa_family == AF_INET6 && vs->socket_type == SOCK_STREAM) { - inet_ntop(AF_INET6, &(in6->sin6_addr), addrstr, INET6_ADDRSTRLEN); - DEBUG_EXTRA("connecting to %s : %d", addrstr, lwip_ntohs(in6->sin6_port)); - } -#endif -*/ - if (vs->socket_type == SOCK_DGRAM) { - // generates no network traffic - if ((err = udp_connect((struct udp_pcb*)vs->pcb,(ip_addr_t *)&ba,port)) < 0) { - DEBUG_ERROR("error while connecting to with UDP"); - } - udp_recv((struct udp_pcb*)vs->pcb, rd_lwip_cb_udp_recved, vs); - return ERR_OK; - } - if (vs->socket_type == SOCK_STREAM) { - struct tcp_pcb *tpcb = (struct tcp_pcb*)vs->pcb; - tcp_sent(tpcb, rd_lwip_cb_sent); - tcp_recv(tpcb, rd_lwip_cb_tcp_recved); - tcp_err(tpcb, rd_lwip_cb_err); - tcp_poll(tpcb, rd_lwip_cb_poll, LWIP_APPLICATION_POLL_FREQ); - tcp_arg(tpcb, vs); - if ((err = tcp_connect(tpcb, &ba, port, rd_lwip_cb_connected)) < 0) { - errno = err_to_errno(err); - // We should only return a value if failure happens immediately - // Otherwise, we still need to wait for a callback from lwIP. - // - This is because an ERR_OK from tcp_connect() only verifies - // that the SYN packet was enqueued onto the stack properly, - // that's it! - DEBUG_ERROR("unable to connect"); - err = -1; - } - } - return err; -} - -int rd_lwip_bind(VirtualTap *tap, VirtualSocket *vs, const struct sockaddr *addr, socklen_t addrlen) -{ - // TODO: Check case for IP_ADDR_ANY - ip_addr_t ba; - char addrstr[INET6_ADDRSTRLEN]; - memset(addrstr, 0, INET6_ADDRSTRLEN); - int port = 0, err = 0; - /* -#if defined(LIBZT_IPV4) - struct sockaddr_in *in4 = (struct sockaddr_in *)addr; - if (addr->sa_family == AF_INET) { - inet_ntop(AF_INET, &(in4->sin_addr), addrstr, INET_ADDRSTRLEN); - DEBUG_EXTRA("binding to %s : %d", addrstr, lwip_ntohs(in4->sin_port)); - } - convert_ip(in4, &ba); - port = lwip_ntohs(in4->sin_port); -#endif -#if defined(LIBZT_IPV6) - struct sockaddr_in6 *in6 = (struct sockaddr_in6*)addr; - in6_to_ip6((ip6_addr *)&ba, in6); - if (addr->sa_family == AF_INET6) { - inet_ntop(AF_INET6, &(in6->sin6_addr), addrstr, INET6_ADDRSTRLEN); - DEBUG_EXTRA("binding to %s : %d", addrstr, lwip_ntohs(in6->sin6_port)); - } -#endif - if (vs->socket_type == SOCK_DGRAM) { - if ((err = udp_bind((struct udp_pcb*)vs->pcb, (const ip_addr_t *)&ba, port)) < 0) { - errno = err_to_errno(err); - err = -1; - } - else { - // set callback - udp_recv((struct udp_pcb*)vs->pcb, rd_lwip_cb_udp_recved, vs); - err = ERR_OK; - } - } - else if (vs->socket_type == SOCK_STREAM) { - if ((err = tcp_bind((struct tcp_pcb*)vs->pcb, (const ip_addr_t *)&ba, port)) < 0) { - errno = err_to_errno(err); - err = -1; - } - else { - err = ERR_OK; - } - } - */ - return err; -} - -int rd_lwip_listen(VirtualSocket *vs, int backlog) -{ - int err = 0; - struct tcp_pcb* listeningPCB; -#ifdef TCP_LISTEN_BACKLOG - listeningPCB = tcp_listen_with_backlog((struct tcp_pcb*)vs->pcb, backlog); -#else - listeningPCB = tcp_listen((struct tcp_pcb*)vs->pcb); -#endif - if (listeningPCB) { - vs->pcb = listeningPCB; - // set callback - tcp_accept(listeningPCB, rd_lwip_cb_accept); - tcp_arg(listeningPCB, vs); - err = ERR_OK; - } - else { - errno = ENOMEM; - err = -1; - } - return err; -} - -VirtualSocket* rd_lwip_accept(VirtualSocket *vs) -{ - if (vs == NULL) { - DEBUG_ERROR("invalid virtual socket"); - return NULL; - } - // Retreive first of queued VirtualSockets from parent VirtualSocket - // TODO: check multithreaded behaviour - VirtualSocket *new_vs = NULL; - if (vs->_AcceptedConnections.size()) { - new_vs = vs->_AcceptedConnections.front(); - vs->_AcceptedConnections.pop(); - } - return new_vs; -} - -int rd_lwip_read(VirtualSocket *vs, bool lwip_invoked) -{ - DEBUG_EXTRA("vs=%p", vs); - int err = 0; - if (vs == NULL) { - DEBUG_ERROR("no virtual socket"); - return -1; - } - if (lwip_invoked == false) { - DEBUG_INFO("!lwip_invoked"); - vs->tap->_tcpconns_m.lock(); - vs->_rx_m.lock(); - } - if (vs->socket_type == SOCK_STREAM && vs->RXbuf->count()) { - int wr = std::min((ssize_t)ZT_STACK_TCP_SOCKET_RX_SZ, (ssize_t)vs->RXbuf->count()); - int n = vs->tap->_phy.streamSend(vs->sock, vs->RXbuf->get_buf(), wr); - if (n > 0) { - vs->RXbuf->consume(n); - tcp_recved((struct tcp_pcb*)vs->pcb, n); - DEBUG_TRANS("TCP RX %d bytes", n); - } - } - if (vs->RXbuf->count() == 0) { - vs->tap->_phy.setNotifyWritable(vs->sock, false); // nothing else to send to the app - } - if (lwip_invoked == false) { - vs->tap->_tcpconns_m.unlock(); - vs->_rx_m.unlock(); - } - return err; -} - -int rd_lwip_write(VirtualSocket *vs, void *data, ssize_t len) -{ - int err = 0; - if (vs == NULL) { - DEBUG_ERROR("no virtual socket"); - return -1; - } - DEBUG_EXTRA("fd=%d, vs=%p, pcb=%p, pcb->state=%d, len=%d", - vs->app_fd, vs, (struct tcp_pcb*)(vs->pcb), ((struct tcp_pcb*)(vs->pcb))->state, len); - if (vs->socket_type == SOCK_DGRAM) { - // TODO: Packet re-assembly hasn't yet been tested with lwIP so UDP packets are limited to MTU-sized chunks - int udp_trans_len = std::min(len, (ssize_t)ZT_MAX_MTU); - struct pbuf * pb = pbuf_alloc(PBUF_TRANSPORT, udp_trans_len, PBUF_POOL); - if (pb == NULL) { - DEBUG_ERROR("unable to allocate new pbuf of len=%d", udp_trans_len); - return -1; - } - memcpy(pb->payload, data, udp_trans_len); - int err = udp_send((struct udp_pcb*)vs->pcb, pb); - - if (err == ERR_MEM) { - DEBUG_ERROR("error sending packet. out of memory"); - } else if (err == ERR_RTE) { - DEBUG_ERROR("could not find route to destinations address"); - } else if (err != ERR_OK) { - DEBUG_ERROR("error sending packet - %d", err); - } - pbuf_free(pb); - if (err == ERR_OK) { - return udp_trans_len; - } - } - if (vs->socket_type == SOCK_STREAM) { - // How much we are currently allowed to write to the VirtualSocket - ssize_t sndbuf = ((struct tcp_pcb*)vs->pcb)->snd_buf; - if (sndbuf == 0) { - // PCB send buffer is full, turn off readability notifications for the - // corresponding PhySocket until lwip_cb_sent() is called and confirms that there is - // now space on the buffer - DEBUG_ERROR("lwIP stack is full, sndbuf==0"); - //vs->tap->_phy.setNotifyReadable(vs->sock, false); - err = -1; - } - vs->_tx_m.lock(); - int buf_w = vs->TXbuf->write((const char*)data, len); - if (buf_w != len) { - DEBUG_ERROR("only wrote len=%d but expected to write len=%d to TX buffer", buf_w, len); - err = ZT_ERR_GENERAL_FAILURE; - } - if (vs->TXbuf->count() <= 0) { - err = -1; // nothing to write - } - if (err == ERR_OK) { - int r = std::min((ssize_t)vs->TXbuf->count(), sndbuf); - // Writes data pulled from the client's socket buffer to LWIP. This merely sends the - // data to LWIP to be enqueued and eventually sent to the network. - if (r > 0) { - err = tcp_write((struct tcp_pcb*)vs->pcb, vs->TXbuf->get_buf(), r, vs->copymode); - tcp_output((struct tcp_pcb*)vs->pcb); - if (err != ERR_OK) { - DEBUG_ERROR("error while writing to lwIP tcp_pcb, err=%d", err); - if (err == ERR_MEM) { - DEBUG_ERROR("lwIP out of memory"); - } - err = -1; - } else { - if (vs->copymode & TCP_WRITE_FLAG_COPY) { - // since we copied the data (allocated pbufs), we can consume the buffer - vs->TXbuf->consume(r); // success - DEBUG_TRANS("len=%5d tx_buf_len=%10d [VSTXBF --> NSLWIP]", err, vs->TXbuf->count()); - } - else { - // since we only processed the data by pointer reference we - // want to preserve it until it has been ACKed by the remote host - // (DO NOTHING) - } - err = ERR_OK; - } - } - } - vs->_tx_m.unlock(); - } - return err; -} - -int rd_lwip_close(VirtualSocket *vs) -{ - // requests to close non-LISTEN PCBs are handled lwip_cb_poll() - int err = -1; - if (vs == NULL) { - DEBUG_ERROR("invalid vs"); - return -1; - } - if (vs->socket_type == SOCK_STREAM) { - struct tcp_pcb *tpcb = (struct tcp_pcb*)(vs->pcb); - if (tpcb == NULL) { - DEBUG_ERROR("invalid pcb"); - return -1; - } - // should be safe to tcp_close() from application thread IF PCB is in LISTENING state (I think) - if (tpcb->state == LISTEN) { - DEBUG_EXTRA("PCB is in LISTEN, calling tcp_close() from application thread."); - tcp_accept(tpcb, NULL); - if ((err = tcp_close(tpcb)) < 0) { - DEBUG_ERROR("error while calling tcp_close, fd=%d, vs=%p, pcb=%p", vs->app_fd, vs, vs->pcb); - errno = err_to_errno(err); - err = -1; - } - return ERR_OK; - } - // handle junk state values - if (tpcb->state > TIME_WAIT) { - DEBUG_EXTRA("invalid TCP pcb state, already closed, report ERR_OK"); - return ERR_OK; - } - else { - // place a request for the stack to close this VirtualSocket's PCB - vs->set_state(VS_STATE_SHOULD_SHUTDOWN); - // wait for indication of success, this will block if the PCB can't close - while (true) { - sleep(1); - nanosleep((const struct timespec[]) {{0, (ZT_API_CHECK_INTERVAL * 1000000)}}, NULL); - DEBUG_EXTRA("checking closure state... pcb->state=%d", tpcb->state); - if (vs->get_state() == VS_STATE_CLOSED || tpcb->state == CLOSED) { - return ERR_OK; - } - } - } - } - if (vs->socket_type == SOCK_DGRAM) { - // place a request for the stack to close this VirtualSocket's PCB - vs->set_state(VS_STATE_SHOULD_SHUTDOWN); - } - return err; -} - -int rd_lwip_shutdown(VirtualSocket *vs, int how) -{ - int err=0, shut_rx=0, shut_tx=0; - if (how == SHUT_RD) { - shut_rx = 1; - } - if (how == SHUT_WR) { - shut_tx = 1; - } - if (how == SHUT_RDWR) { - shut_rx = 1; - shut_tx = 1; - } - if ((err = tcp_shutdown((tcp_pcb*)(vs->pcb), shut_rx, shut_tx) < 0)) { - DEBUG_ERROR("error while shutting down socket, fd=%d", vs->app_fd); - } - return err; -} - -/****************************************************************************/ -/* Callbacks from lwIP stack */ -/****************************************************************************/ - -// write data from processed packets from the stack to the client app -/* - With the raw API, tcp_recv() sets up to receive data via a callback function. Your callback - is delivered chains of pbufs as they become available. You have to manage extracting data - from the pbuf chain, and don't forget to watch out for multiple pbufs in a single callback: - the 'tot_len' field indicates the total length of data in the pbuf chain. You must call - tcp_recved() to tell LWIP when you have processed the received data. As with the netconn API, - you may receive more or less data than you want, and will have to either wait for further - callbacks, or hold onto excess data for later processing. - - http://lwip.wikia.com/wiki/Receiving_data_with_LWIP -*/ -err_t rd_lwip_cb_tcp_recved(void *arg, struct tcp_pcb *PCB, struct pbuf *p, err_t err) -{ - //DEBUG_INFO(); - VirtualSocket *vs = (VirtualSocket *)arg; - int tot = 0; - if (vs == NULL) { - DEBUG_ERROR("no virtual socket"); - return ERR_OK; - } - struct pbuf* q = p; - if (p == NULL) { - DEBUG_INFO("p=0x0 for pcb=%p, vs->pcb=%p, this indicates a closure. No need to call tcp_close()", PCB, vs->pcb); - vs->set_state(VS_STATE_SHOULD_SHUTDOWN); - return ERR_ABRT; - } - vs->tap->_tcpconns_m.lock(); - vs->_rx_m.lock(); - // cycle through pbufs and write them to the RX buffer - while (p != NULL) { - if (p->len <= 0) { - break; - } - int avail = ZT_TCP_RX_BUF_SZ - vs->RXbuf->count(); - int len = p->len; - if (avail < len) { - DEBUG_ERROR("not enough room (%d bytes) on RX buffer", avail); - } - // place new incoming data on ringbuffer before we try to send it to the app - memcpy(vs->RXbuf->get_buf(), p->payload, len); - vs->RXbuf->produce(len); - p = p->next; - tot += len; - } - if (tot) { - tcp_recved(PCB, tot); - DEBUG_TRANS("len=%5d rx_buf_len=%10d [NSLWIP --> VSRXBF]", tot, vs->RXbuf->count()); - int w, write_attempt_sz = vs->RXbuf->count() < ZT_MAX_MTU ? vs->RXbuf->count() : ZT_MAX_MTU; - if ((w = write(vs->sdk_fd, vs->RXbuf->get_buf(), write_attempt_sz)) < 0) { - DEBUG_ERROR("write(fd=%d)=%d, errno=%d", vs->sdk_fd, w, errno); - } - if (w > 0) { - vs->RXbuf->consume(w); - if (w < write_attempt_sz) { - DEBUG_TRANS("len=%5d rx_buf_len=%10d [VSRXBF --> APPFDS]", w, vs->RXbuf->count()); - DEBUG_EXTRA("intended to write len=%d, only wrote len=%d", write_attempt_sz, w); - } - else { - DEBUG_TRANS("len=%5d rx_buf_len=%10d [VSRXBF --> APPFDS]", w, vs->RXbuf->count()); - } - } - } - else { - DEBUG_EXTRA("warning, wrote 0 bytes"); - } - vs->tap->_tcpconns_m.unlock(); - vs->_rx_m.unlock(); - pbuf_free(q); - return ERR_OK; -} - -// callback from stack to notify driver of the successful acceptance of a connection -err_t rd_lwip_cb_accept(void *arg, struct tcp_pcb *newPCB, err_t err) -{ - VirtualSocket *vs = (VirtualSocket*)arg; - struct sockaddr_storage ss; -/* -#if defined(LIBZT_IPV4) - struct sockaddr_in *in4 = (struct sockaddr_in *)&ss; - in4->sin_addr.s_addr = newPCB->remote_ip.addr; - in4->sin_port = newPCB->remote_port; -#endif -#if defined(LIBZT_IPV6) - struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&ss; - // TODO: check this - memcpy(&(in6->sin6_addr.s6_addr), &(newPCB->remote_ip), sizeof(int32_t)*4); - in6->sin6_port = newPCB->remote_port; -#endif - VirtualSocket *new_vs = new VirtualSocket(); - new_vs->socket_type = SOCK_STREAM; - new_vs->pcb = newPCB; - new_vs->tap = vs->tap; - new_vs->sock = vs->tap->_phy.wrapSocket(new_vs->sdk_fd, new_vs); - memcpy(&(new_vs->peer_addr), &ss, sizeof(new_vs->peer_addr)); - // add new VirtualSocket object to parent VirtualSocket so that we can find it via lwip_Accept() - vs->_AcceptedConnections.push(new_vs); - // set callbacks - tcp_arg(newPCB, new_vs); - tcp_recv(newPCB, rd_lwip_cb_tcp_recved); - tcp_err(newPCB, rd_lwip_cb_err); - tcp_sent(newPCB, rd_lwip_cb_sent); - tcp_poll(newPCB, rd_lwip_cb_poll, 1); - // let lwIP know that it can queue additional incoming PCBs - tcp_accepted((struct tcp_pcb*)vs->pcb); -*/ - return 0; -} - -// copy processed datagram to app socket -void rd_lwip_cb_udp_recved(void * arg, struct udp_pcb * upcb, struct pbuf * p, const ip_addr_t * addr, u16_t port) -{ - //DEBUG_EXTRA("arg(vs)=%p, pcb=%p, port=%d)", arg, upcb, port); - /* - VirtualSocket *vs = (VirtualSocket *)arg; - if (vs == NULL) { - DEBUG_ERROR("invalid virtual socket"); - return; - } - if (p == NULL) { - DEBUG_ERROR("p == NULL"); - return; - } - struct pbuf* q = p; - struct sockaddr_storage ss; - -#if defined(LIBZT_IPV4) - struct sockaddr_in *in4 = (struct sockaddr_in *)&ss; - in4->sin_addr.s_addr = addr->addr; - in4->sin_port = port; -#endif -#if defined(LIBZT_IPV6) - struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&ss; - memcpy(&(in6->sin6_addr.s6_addr), &(addr->addr), sizeof(int32_t)*4); - in6->sin6_port = port; -#endif - - char udp_payload_buf[ZT_SOCKET_MSG_BUF_SZ]; - memset(udp_payload_buf, 0, sizeof(ZT_SOCKET_MSG_BUF_SZ)); - char *msg_ptr = udp_payload_buf; - int tot_len = 0; - while (p != NULL) { - if (p->len <= 0) { - break; - } - memcpy(msg_ptr, p->payload, p->len); - msg_ptr += p->len; - tot_len += p->len; - p = p->next; - } - if (tot_len > 0) { - int w = 0; - // [sz : addr : payload] - char udp_msg_buf[ZT_SOCKET_MSG_BUF_SZ]; - memset(udp_msg_buf, 0, sizeof(ZT_SOCKET_MSG_BUF_SZ)); - int32_t len = sizeof(struct sockaddr_storage) + tot_len; - int32_t msg_tot_len = sizeof(int32_t) + len; - // len: sockaddr+payload - memcpy(udp_msg_buf, &len, sizeof(int32_t)); - // sockaddr - memcpy(udp_msg_buf + sizeof(int32_t), &ss, sizeof(struct sockaddr_storage)); - // payload - memcpy(udp_msg_buf + sizeof(int32_t) + sizeof(struct sockaddr_storage), &udp_payload_buf, tot_len); - if ((w = write(vs->sdk_fd, udp_msg_buf, msg_tot_len)) < 0) { - DEBUG_ERROR("write(fd=%d)=%d, errno=%d", vs->sdk_fd, w, errno); - } - } - pbuf_free(q); - */ -} - -// callback from stack to notify driver that data was sent -err_t rd_lwip_cb_sent(void* arg, struct tcp_pcb *PCB, u16_t len) -{ - //DEBUG_EXTRA("pcb=%p", PCB); - VirtualSocket *vs = (VirtualSocket *)arg; - if (vs == NULL) { - DEBUG_ERROR("invalid vs for PCB=%p, len=%d", PCB, len); - } - if ((vs->copymode & TCP_WRITE_FLAG_COPY) == false) { - /* - From lwIP docs: - - To achieve zero-copy on transmit, the data passed to the raw API must - remain unchanged until sent. Because the send- (or write-)functions return - when the packets have been enqueued for sending, data must be kept stable - after that, too. - - This implies that PBUF_RAM/PBUF_POOL pbufs passed to raw-API send functions - must *not* be reused by the application unless their ref-count is 1. - - For no-copy pbufs (PBUF_ROM/PBUF_REF), data must be kept unchanged, too, - but the stack/driver will/must copy PBUF_REF'ed data when enqueueing, while - PBUF_ROM-pbufs are just enqueued (as ROM-data is expected to never change). - - Also, data passed to tcp_write without the copy-flag must not be changed! - - Therefore, be careful which type of PBUF you use and if you copy TCP data - or not! - */ - - // since we decided in lwip_Write() not to consume the buffere data, as it - // was not copied and was only used by pointer reference, we can now consume - // the data on the buffer since we've got an ACK back from the remote host - vs->_tx_m.lock(); - vs->TXbuf->consume(len); - vs->_tx_m.unlock(); - } - return ERR_OK; -} - -err_t rd_lwip_cb_connected(void *arg, struct tcp_pcb *PCB, err_t err) -{ - DEBUG_EXTRA("pcb=%p", PCB); - VirtualSocket *vs = (VirtualSocket *)arg; - if (vs == NULL) { - DEBUG_ERROR("invalid virtual socket"); - return -1; - } - // add to unhandled connection set for zts_connect to pick up on - vs->tap->_tcpconns_m.lock(); - vs->set_state(VS_STATE_UNHANDLED_CONNECTED); - vs->tap->_VirtualSockets.push_back(vs); - vs->tap->_tcpconns_m.unlock(); - return ERR_OK; -} - -err_t rd_lwip_cb_poll(void* arg, struct tcp_pcb *PCB) -{ - VirtualSocket *vs = (VirtualSocket *)arg; - if (vs == NULL) { - DEBUG_ERROR("invalid vs"); - return ERR_OK; // TODO: determine appropriate error value, if any - } - if (vs->socket_type == SOCK_DGRAM) { - DEBUG_INFO("fd=%d, vs=%p, pcb=%p", vs->app_fd, vs, PCB, vs->pcb); - } - - // Handle PCB closure requests (set in lwip_Close()) - if (vs->get_state() == VS_STATE_SHOULD_SHUTDOWN) { - DEBUG_EXTRA("closing pcb=%p, fd=%d, vs=%p", PCB, vs->app_fd, vs); - int err = 0; - errno = 0; - if (vs->socket_type == SOCK_DGRAM) { - udp_remove((struct udp_pcb*)vs->pcb); - } - if (vs->socket_type == SOCK_STREAM) { - if (vs->pcb) { - struct tcp_pcb* tpcb = (struct tcp_pcb*)vs->pcb; - if (tpcb->state == CLOSED) { - DEBUG_EXTRA("pcb is in CLOSED state"); - // calling tcp_close() here would be redundant - return 0; - } - //if (tpcb->state == CLOSE_WAIT) { - // DEBUG_EXTRA("pcb is in CLOSE_WAIT state"); - // // calling tcp_close() here would be redundant - //} - if (tpcb->state > TIME_WAIT) { - DEBUG_ERROR("warning, pcb=%p is in an invalid state=%d", vs->pcb, tpcb->state); - err = -1; - } - // unregister callbacks for this PCB - tcp_arg(tpcb, NULL); - if (tpcb->state == LISTEN) { - tcp_accept(tpcb, NULL); - } - else { - tcp_recv(tpcb, NULL); - tcp_sent(tpcb, NULL); - tcp_poll(tpcb, NULL, 0); - tcp_err(tpcb, NULL); - } - if ((err = tcp_close(tpcb)) < 0) { - DEBUG_ERROR("error while calling tcp_close, fd=%d, vs=%p, pcb=%p", vs->app_fd, vs, vs->pcb); - errno = err_to_errno(err); - err = -1; - } - else { - vs->set_state(VS_STATE_CLOSED); // success - } - } - } - } - - - // Handle transmission and reception of data - if (vs->socket_type == SOCK_STREAM) { - DEBUG_INFO("fd=%d, vs=%p, PCB=%p, vs->pcb=%p, vs->pcb->state=%d", vs->app_fd, vs, PCB, (struct tcp_pcb*)(vs->pcb), ((struct tcp_pcb*)(vs->pcb))->state); - if (((struct tcp_pcb*)(vs->pcb))->state == CLOSE_WAIT) { - DEBUG_EXTRA("pcb->state=CLOSE_WAIT. do nothing"); - return ERR_OK; - } - if (((struct tcp_pcb*)(vs->pcb))->state == CLOSED) { - DEBUG_EXTRA("pcb->state=CLOSED. do nothing"); - return ERR_OK; - } - // --- Check buffers to see if we need to finish reading/writing anything --- - - // TODO: Make a more generic form of each of these RX/TX blocks that can be shared - // between all polling callbacks and read write methods - - // RX - vs->_rx_m.lock(); - if (vs->RXbuf->count()) { - // this data has already been acknowledged via tcp_recved(), we merely need to - // move it off of the ringbuffer and into the client app - int w, write_attempt_sz = vs->RXbuf->count() < ZT_MAX_MTU ? vs->RXbuf->count() : ZT_MAX_MTU; - if ((w = write(vs->sdk_fd, vs->RXbuf->get_buf(), write_attempt_sz)) < 0) { - DEBUG_ERROR("write(fd=%d)=%d, errno=%d", vs->sdk_fd, w, errno); - } - if (w > 0) { - vs->RXbuf->consume(w); - if (w < write_attempt_sz) { - DEBUG_TRANS("len=%5d rx_buf_len=%10d [VSRXBF --> APPFDS]", w, vs->RXbuf->count()); - DEBUG_EXTRA("intended to write len=%d, only wrote len=%d", write_attempt_sz, w); - } - else { - DEBUG_TRANS("len=%5d rx_buf_len=%10d [VSRXBF --> APPFDS]", w, vs->RXbuf->count()); - } - } - } - vs->_rx_m.unlock(); - // No need to lock the TX buffer since lwip_Write() will lock it for us - // TX - if (vs->TXbuf->count()) { - // we previously attempted to tcp_write(), but something went wrong, this - // is where we retry - rd_lwip_write(vs, vs->TXbuf->get_buf(), vs->TXbuf->count()); - } - } - return ERR_OK; -} - -int rd_lwip_setsockopt(VirtualSocket *vs, int level, int optname, const void *optval, socklen_t optlen) -{ - int err = -1; - errno = 0; - if (vs == NULL) { - DEBUG_ERROR("invalid vs"); - return -1; - } else { - DEBUG_EXTRA("fd=%d, level=%d, optname=%d", vs->app_fd, level, optname); - } - if (level == SOL_SOCKET) - { - /* Turns on recording of debugging information. This option enables or disables debugging in the underlying - protocol modules. This option takes an int value. This is a Boolean option.*/ - if (optname == SO_DEBUG) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* Specifies that the rules used in validating addresses supplied to bind() should allow reuse of local - addresses, if this is supported by the protocol. This option takes an int value. This is a Boolean option.*/ - if (optname == SO_REUSEADDR) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* Keeps connections active by enabling the periodic transmission of messages, if this is supported by the - protocol. This option takes an int value. */ - if (optname == SO_KEEPALIVE) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* Requests that outgoing messages bypass the standard routing facilities. */ - if (optname == SO_DONTROUTE) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* Lingers on a close() if data is present. */ - if (optname == SO_LINGER) - { - // we do this at the VirtualSocket layer since lwIP's raw API doesn't currently have a way to do this - vs->optflags &= VS_OPT_SO_LINGER; - return 0; - } - /* Permits sending of broadcast messages, if this is supported by the protocol. This option takes an int - value. This is a Boolean option. */ - if (optname == SO_BROADCAST) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* Leaves received out-of-band data (data marked urgent) inline. This option takes an int value. This is a - Boolean option. */ - if (optname == SO_OOBINLINE) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* Sets send buffer size. This option takes an int value. */ - if (optname == SO_SNDBUF) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* Sets receive buffer size. This option takes an int value. */ - if (optname == SO_RCVBUF) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* */ - if (optname == SO_STYLE) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* */ - if (optname == SO_TYPE) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* Get error status and clear */ - if (optname == SO_ERROR) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - } - if (level == IPPROTO_IP) - { - if (optname == IP_ADD_MEMBERSHIP) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_ADD_SOURCE_MEMBERSHIP) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_BIND_ADDRESS_NO_PORT) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_BLOCK_SOURCE) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_DROP_MEMBERSHIP) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_DROP_SOURCE_MEMBERSHIP) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_FREEBIND) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_HDRINCL) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_MSFILTER) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_MTU) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_MTU_DISCOVER) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_MULTICAST_ALL) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_MULTICAST_IF) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_MULTICAST_LOOP) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_MULTICAST_TTL) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_NODEFRAG) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_OPTIONS) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_PKTINFO) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_RECVOPTS) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_RECVORIGDSTADDR) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_RECVTOS) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_RECVTTL) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_RETOPTS) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_ROUTER_ALERT) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_TOS) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_TRANSPARENT) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_TTL) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_UNBLOCK_SOURCE) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - // TODO - return -1; - } - if (level == IPPROTO_TCP) - { - struct tcp_pcb *pcb = (struct tcp_pcb*)(vs->pcb); - if (pcb == NULL) { - return -1; - } - /* If set, don't send out partial frames. */ - if (optname == TCP_CORK) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* Allow a listener to be awakened only when data arrives on the socket. */ - if (optname == TCP_DEFER_ACCEPT) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* Used to collect information about this socket. The kernel returns a struct tcp_info as defined in the - file /usr/include/linux/tcp.h.*/ - if (optname == TCP_INFO) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* The maximum number of keepalive probes TCP should send before dropping the connection.*/ - if (optname == TCP_KEEPCNT) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* The time (in seconds) the connection needs to remain idle before TCP starts sending keepalive probes, - if the socket option SO_KEEPALIVE has been set on this socket. */ - if (optname == TCP_KEEPIDLE) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* The time (in seconds) between individual keepalive probes.*/ - if (optname == TCP_KEEPINTVL) { - // TODO - return -1; - } - /* The lifetime of orphaned FIN_WAIT2 state sockets. */ - if (optname == TCP_LINGER2) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* The maximum segment size for outgoing TCP packets. */ - if (optname == TCP_MAXSEG) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* If set, disable the Nagle algorithm. */ - if (optname == TCP_NODELAY) { - int enable_nagle = *((const int*)optval); - if (enable_nagle == true) { - tcp_nagle_enable(pcb); - } else { - tcp_nagle_disable(pcb); - } - return 0; - } - /* Enable quickack mode if set or disable quickack mode if cleared. */ - if (optname == TCP_QUICKACK) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* Set the number of SYN retransmits that TCP should send before aborting the attempt to connect. It - cannot exceed 255. */ - if (optname == TCP_SYNCNT) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* Bound the size of the advertised window to this value. The kernel imposes a minimum size of - SOCK_MIN_RCVBUF/2. */ - if (optname == TCP_WINDOW_CLAMP) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - } - if (level == IPPROTO_UDP) - { - /*If this option is enabled, then all data output on this socket is accumulated into a single - datagram that is transmitted when the option is disabled. */ - if (optname == UDP_CORK) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - } - return err; -} - -int rd_lwip_getsockopt(VirtualSocket *vs, int level, int optname, void *optval, socklen_t *optlen) -{ - int err = -1, optval_tmp = 0; - errno = 0; - if (vs == NULL) { - DEBUG_ERROR("invalid vs"); - return -1; - } else { - DEBUG_EXTRA("fd=%d, level=%d, optname=%d", vs->app_fd, level, optname); - } - if (level == SOL_SOCKET) - { - /* Turns on recording of debugging information. This option enables or disables debugging in the underlying - protocol modules. This option takes an int value. This is a Boolean option.*/ - if (optname == SO_DEBUG) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* Specifies that the rules used in validating addresses supplied to bind() should allow reuse of local - addresses, if this is supported by the protocol. This option takes an int value. This is a Boolean option.*/ - if (optname == SO_REUSEADDR) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* Keeps connections active by enabling the periodic transmission of messages, if this is supported by the - protocol. This option takes an int value. */ - if (optname == SO_KEEPALIVE) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* Requests that outgoing messages bypass the standard routing facilities. */ - if (optname == SO_DONTROUTE) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* Get SO_LINGER flag value */ - if (optname == SO_LINGER) - { - // we do this at the VirtualSocket layer since lwIP's raw API doesn't currently have a way to do this - optval_tmp = (vs->optflags & VS_OPT_SO_LINGER); - memcpy(optval, &optval_tmp, *optlen); - return 0; - } - /* Permits sending of broadcast messages, if this is supported by the protocol. This option takes an int - value. This is a Boolean option. */ - if (optname == SO_BROADCAST) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* Leaves received out-of-band data (data marked urgent) inline. This option takes an int value. This is a - Boolean option. */ - if (optname == SO_OOBINLINE) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* Sets send buffer size. This option takes an int value. */ - if (optname == SO_SNDBUF) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* Sets receive buffer size. This option takes an int value. */ - if (optname == SO_RCVBUF) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* */ - if (optname == SO_STYLE) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* */ - if (optname == SO_TYPE) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - /* Get error status and clear */ - if (optname == SO_ERROR) - { - // TODO - errno = ENOPROTOOPT; - return -1; - } - } - if (level == IPPROTO_IP) - { - if (optname == IP_ADD_MEMBERSHIP) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_ADD_SOURCE_MEMBERSHIP) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_BIND_ADDRESS_NO_PORT) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_BLOCK_SOURCE) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_DROP_MEMBERSHIP) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_DROP_SOURCE_MEMBERSHIP) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_FREEBIND) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_HDRINCL) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_MSFILTER) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_MTU) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_MTU_DISCOVER) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_MULTICAST_ALL) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_MULTICAST_IF) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_MULTICAST_LOOP) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_MULTICAST_TTL) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_NODEFRAG) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_OPTIONS) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_PKTINFO) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_RECVOPTS) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_RECVORIGDSTADDR) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_RECVTOS) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_RECVTTL) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_RETOPTS) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_ROUTER_ALERT) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_TOS) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_TRANSPARENT) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_TTL) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - if (optname == IP_UNBLOCK_SOURCE) { - // TODO - errno = ENOPROTOOPT; - return -1; - } - // TODO - return -1; - } - if (level == IPPROTO_TCP) - { - struct tcp_pcb *pcb = (struct tcp_pcb*)(vs->pcb); - if (pcb == NULL) { - return -1; - } - /* If set, don't send out partial frames. */ - if (optname == TCP_CORK) { - // TODO - errno = ENOPROTOOPT; - err = -1; - } - /* Allow a listener to be awakened only when data arrives on the socket. */ - if (optname == TCP_DEFER_ACCEPT) { - // TODO - errno = ENOPROTOOPT; - err = -1; - } - /* Used to collect information about this socket. */ - if (optname == TCP_INFO) { - // TODO - errno = ENOPROTOOPT; - err = -1; - } - /* The maximum number of keepalive probes TCP should send before dropping the connection.*/ - if (optname == TCP_KEEPCNT) { - // TODO - errno = ENOPROTOOPT; - err = -1; - } - /* The time (in seconds) the connection needs to remain idle before TCP starts sending keepalive probes, - if the socket option SO_KEEPALIVE has been set on this socket. */ - if (optname == TCP_KEEPIDLE) { - // TODO - errno = ENOPROTOOPT; - err = -1; - } - /* The time (in seconds) between individual keepalive probes.*/ - if (optname == TCP_KEEPINTVL) { - // TODO - err = -1; - } - /* The lifetime of orphaned FIN_WAIT2 state sockets. */ - if (optname == TCP_LINGER2) { - // TODO - errno = ENOPROTOOPT; - err = -1; - } - /* The maximum segment size for outgoing TCP packets. */ - if (optname == TCP_MAXSEG) { - // TODO - errno = ENOPROTOOPT; - err = -1; - } - /* Get value of Nagle algorithm flag */ - if (optname == TCP_NODELAY) { - optval_tmp = tcp_nagle_disabled(pcb); - memcpy(optval, &optval_tmp, *optlen); - err = 0; - } - /* Enable quickack mode if set or disable quickack mode if cleared. */ - if (optname == TCP_QUICKACK) { - // TODO - errno = ENOPROTOOPT; - err = -1; - } - /* Set the number of SYN retransmits that TCP should send before aborting the attempt to connect. It - cannot exceed 255. */ - if (optname == TCP_SYNCNT) { - // TODO - errno = ENOPROTOOPT; - err = -1; - } - /* Bound the size of the advertised window to this value. The kernel imposes a minimum size of - SOCK_MIN_RCVBUF/2. */ - if (optname == TCP_WINDOW_CLAMP) { - // TODO - errno = ENOPROTOOPT; - err = -1; - } - } - if (level == IPPROTO_UDP) - { - /* If this option is enabled, then all data output on this socket is accumulated into a single - datagram that is transmitted when the option is disabled. */ - if (optname == UDP_CORK) { - // TODO - errno = ENOPROTOOPT; - err = -1; - } - } - return err; -} - -void rd_lwip_cb_err(void *arg, err_t err) -{ - VirtualSocket *vs = (VirtualSocket *)arg; - if (vs == NULL) { - DEBUG_ERROR("err=%d, invalid virtual socket", err); - errno = -1; - } - if (vs->socket_type == SOCK_STREAM) { - DEBUG_ERROR("vs=%p, pcb=%p, pcb->state=%d, fd=%d, err=%d", vs, vs->pcb, ((struct tcp_pcb*)(vs->pcb))->state, vs->app_fd, err); - } - if (vs->socket_type == SOCK_DGRAM) { - DEBUG_ERROR("vs=%p, pcb=%p, fd=%d, err=%d", vs, vs->pcb, vs->app_fd, err); - } - switch(err) - { - case ERR_MEM: // -1 - DEBUG_ERROR("ERR_MEM->ENOMEM, Out of memory error."); - break; - case ERR_BUF: // -2 - DEBUG_ERROR("ERR_BUF->ENOBUFS, Buffer error."); - break; - case ERR_TIMEOUT: // -3 - DEBUG_ERROR("ERR_TIMEOUT->ETIMEDOUT, Timeout."); - break; - case ERR_RTE: // -4 - DEBUG_ERROR("ERR_RTE->ENETUNREACH, Routing problem."); - break; - case ERR_INPROGRESS: // -5 - DEBUG_ERROR("ERR_INPROGRESS->EINPROGRESS, Operation in progress."); - break; - case ERR_VAL: // -6 - DEBUG_ERROR("ERR_VAL->EINVAL, Illegal value."); - break; - case ERR_WOULDBLOCK: // -7 - DEBUG_ERROR("ERR_WOULDBLOCK->EWOULDBLOCK, Operation would block."); - break; - case ERR_USE: // -8 - DEBUG_ERROR("ERR_USE->EADDRINUSE, Address in use."); - break; - case ERR_ALREADY: // -9 ? - DEBUG_ERROR("ERR_ALREADY->EISCONN, Already connecting."); - break; - case ERR_ISCONN: // -10 - DEBUG_ERROR("ERR_ISCONN->EISCONN, Already connected"); - break; - case ERR_CONN: // -11 ? - DEBUG_ERROR("ERR_CONN->EISCONN, Not connected"); - break; - case ERR_IF: // -12 - DEBUG_ERROR("ERR_IF, Low-level netif error."); - break; - case ERR_ABRT: // -13 - DEBUG_ERROR("ERR_ABRT, Connection aborted."); - break; - case ERR_RST: // -14 - DEBUG_ERROR("ERR_RST, Connection reset."); - break; - case ERR_CLSD: // -15 - DEBUG_ERROR("ERR_CLSD, Connection closed."); - break; - case ERR_ARG: // -16 - DEBUG_ERROR("ERR_ARG, Illegal argument."); - break; - default: - break; - } - errno = err_to_errno(err); -} - -#include "lwip/ip_addr.h" -#include - -#include "lwip/ip_addr.h" -#define ip4_addr1b(ipaddr) (((u8_t*)(ipaddr))[0]) -#define ip4_addr2b(ipaddr) (((u8_t*)(ipaddr))[1]) -#define ip4_addr3b(ipaddr) (((u8_t*)(ipaddr))[2]) -#define ip4_addr4b(ipaddr) (((u8_t*)(ipaddr))[3]) -inline void convert_ip(struct sockaddr_in * addr, struct ip4_addr *addr4); - -/** - * Convert from standard sockaddr address to ip4_addr - */ -inline void convert_ip(struct sockaddr_in *addr, struct ip4_addr *addr4) -{ - struct sockaddr_in *ipv4 = addr; - short a = ip4_addr1b(&(ipv4->sin_addr)); - short b = ip4_addr2b(&(ipv4->sin_addr)); - short c = ip4_addr3b(&(ipv4->sin_addr)); - short d = ip4_addr4b(&(ipv4->sin_addr)); - IP4_ADDR(addr4, a,b,c,d); -} - -#define IP6_ADDR2(ipaddr, a,b,c,d,e,f,g,h) do { (ipaddr)->addr[0] = ZeroTier::Utils::hton((u32_t)((a & 0xffff) << 16) | (b & 0xffff)); \ - (ipaddr)->addr[1] = ZeroTier::Utils::hton(((c & 0xffff) << 16) | (d & 0xffff)); \ - (ipaddr)->addr[2] = ZeroTier::Utils::hton(((e & 0xffff) << 16) | (f & 0xffff)); \ - (ipaddr)->addr[3] = ZeroTier::Utils::hton(((g & 0xffff) << 16) | (h & 0xffff)); } while(0) - -/** - * Convert from standard IPV6 address structure to an lwIP native structure - */ -inline void in6_to_ip6(ip6_addr *ba, struct sockaddr_in6 *in6) -{ - uint8_t *ip = &(in6->sin6_addr).s6_addr[0]; - IP6_ADDR2(ba, - (((ip[ 0] & 0xffff) << 8) | ((ip[ 1]) & 0xffff)), - (((ip[ 2] & 0xffff) << 8) | ((ip[ 3]) & 0xffff)), - (((ip[ 4] & 0xffff) << 8) | ((ip[ 5]) & 0xffff)), - (((ip[ 6] & 0xffff) << 8) | ((ip[ 7]) & 0xffff)), - (((ip[ 8] & 0xffff) << 8) | ((ip[ 9]) & 0xffff)), - (((ip[10] & 0xffff) << 8) | ((ip[11]) & 0xffff)), - (((ip[12] & 0xffff) << 8) | ((ip[13]) & 0xffff)), - (((ip[14] & 0xffff) << 8) | ((ip[15]) & 0xffff)) - ); -} - -#endif // ZT_VIRTUAL_SOCKET -#endif // STACK_LWIP diff --git a/test/dummy.cpp b/test/dummy.cpp deleted file mode 100644 index 450da11..0000000 --- a/test/dummy.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include -#include - -#include "libzt.h" - -int main() -{ - printf("Starting ZT service"); - zts_startjoin("my_config_path",0x0000000000000000); - - printf("Dummy. Going into infinite loop. Ping me or something\n"); - while(1) { - sleep(1); - } -} diff --git a/test/layer2.cpp b/test/layer2.cpp deleted file mode 100644 index 50b82ce..0000000 --- a/test/layer2.cpp +++ /dev/null @@ -1,194 +0,0 @@ -// This file is built with libzt.a via `make tests` - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#if defined(__APPLE__) -#include -#endif -#if defined(__linux__) -#include -#include -#include -#include -#endif - -#include "libzt.h" - -unsigned short csum(unsigned short *buf, int nwords) -{ - unsigned long sum; - for(sum=0; nwords>0; nwords--) - sum += *buf++; - sum = (sum >> 16) + (sum &0xffff); - sum += (sum >> 16); - return (unsigned short)(~sum); -} - -int main(int argc , char *argv[]) -{ - if (argc < 3) { - fprintf(stderr, "usage: layer2 \n"); - return 1; - } - - // initialize library - printf("Starting libzt...\n"); - zts_startjoin(argv[1], strtoull(argv[2], NULL, 16)); - uint64_t device_id = zts_get_node_id(); - fprintf(stderr, "Complete. I am %llx\n", device_id); - - // create socket - int fd; - if ((fd = zts_socket(AF_INET, SOCK_RAW, IPPROTO_UDP)) < 0) { - printf("There was a problem creating the raw socket\n"); - exit(-1); - } - fprintf(stderr, "Created raw socket (%d)\n", fd); - -#if defined(__APPLE__) - fprintf(stderr, "SOCK_RAW not supported on mac builds yet. exiting"); - exit(0); -#endif -#if defined(__linux__) // The rest of this file isn't yet supported on non-linux platforms - // get interface index to bind on - struct ifreq if_idx; - memset(&if_idx, 0, sizeof(struct ifreq)); - strncpy(if_idx.ifr_name, "libzt0", IFNAMSIZ-1); - if (zts_ioctl(fd, SIOCGIFINDEX, &if_idx) < 0) { - perror("SIOCGIFINDEX"); - exit(-1); - } - fprintf(stderr, "if_idx.ifr_ifindex=%d\n", if_idx.ifr_ifindex); - - // get MAC address of interface to send on - struct ifreq if_mac; - memset(&if_mac, 0, sizeof(struct ifreq)); - strncpy(if_mac.ifr_name, "libzt0", IFNAMSIZ-1); - if (zts_ioctl(fd, SIOCGIFHWADDR, &if_mac) < 0) { - perror("SIOCGIFHWADDR"); - exit(-1); - } - const unsigned char* mac=(unsigned char*)if_mac.ifr_hwaddr.sa_data; - fprintf(stderr, "hwaddr=%02X:%02X:%02X:%02X:%02X:%02X\n", mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]); - - // get IP address of interface to send on - struct ifreq if_ip; - memset(&if_ip, 0, sizeof(struct ifreq)); - strncpy(if_ip.ifr_name, "libzt0", IFNAMSIZ-1); - if (zts_ioctl(fd, SIOCGIFADDR, &if_ip) < 0) { - perror("SIOCGIFADDR"); - exit(-1); - } - char ipv4_str[INET_ADDRSTRLEN]; - struct sockaddr_in *in4 = (struct sockaddr_in *)&if_ip.ifr_addr; - inet_ntop(AF_INET, (const void *)&in4->sin_addr.s_addr, ipv4_str, INET_ADDRSTRLEN); - fprintf(stderr, "addr=%s", ipv4_str); - - // construct ethernet header - int tx_len = 0; - char sendbuf[1024]; - struct ether_header *eh = (struct ether_header *) sendbuf; - memset(sendbuf, 0, 1024); - - // Ethernet header - eh->ether_shost[0] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[0]; - eh->ether_shost[1] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[1]; - eh->ether_shost[2] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[2]; - eh->ether_shost[3] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[3]; - eh->ether_shost[4] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[4]; - eh->ether_shost[5] = ((uint8_t *)&if_mac.ifr_hwaddr.sa_data)[5]; - - // set destination MAC - int MY_DEST_MAC0 = 0x72; - int MY_DEST_MAC1 = 0x92; - int MY_DEST_MAC2 = 0xd4; - int MY_DEST_MAC3 = 0xfd; - int MY_DEST_MAC4 = 0x43; - int MY_DEST_MAC5 = 0x45; - - eh->ether_dhost[0] = MY_DEST_MAC0; - eh->ether_dhost[1] = MY_DEST_MAC1; - eh->ether_dhost[2] = MY_DEST_MAC2; - eh->ether_dhost[3] = MY_DEST_MAC3; - eh->ether_dhost[4] = MY_DEST_MAC4; - eh->ether_dhost[5] = MY_DEST_MAC5; - eh->ether_type = htons(ETH_P_IP); - tx_len += sizeof(struct ether_header); - - - // Construct the IP header - int ttl = 64; - struct iphdr *iph = (struct iphdr *) (sendbuf + sizeof(struct ether_header)); - iph->ihl = 5; - iph->version = 4; - iph->tos = 16; // Low delay - iph->id = htons(54321); - iph->ttl = ttl; // hops - iph->protocol = 17; // UDP - // Source IP address, can be spoofed - iph->saddr = inet_addr(inet_ntoa(((struct sockaddr_in *)&if_ip.ifr_addr)->sin_addr)); - // iph->saddr = inet_addr("192.168.0.112"); - // Destination IP address - iph->daddr = inet_addr("10.7.7.1"); - tx_len += sizeof(struct iphdr); - - // Construct UDP header - struct udphdr *udph = (struct udphdr *) (sendbuf + sizeof(struct iphdr) + sizeof(struct ether_header)); - udph->source = htons(3423); - udph->dest = htons(5342); - udph->check = 0; // skip - tx_len += sizeof(struct udphdr); - - // Fill in UDP payload - sendbuf[tx_len++] = 0xde; - sendbuf[tx_len++] = 0xad; - sendbuf[tx_len++] = 0xbe; - sendbuf[tx_len++] = 0xef; - - // Fill in remaining header info - // Length of UDP payload and header - udph->len = htons(tx_len - sizeof(struct ether_header) - sizeof(struct iphdr)); - // Length of IP payload and header - iph->tot_len = htons(tx_len - sizeof(struct ether_header)); - // Calculate IP checksum on completed header - iph->check = csum((unsigned short *)(sendbuf+sizeof(struct ether_header)), sizeof(struct iphdr)/2); - - // Send packet - // Destination address - struct sockaddr_ll socket_address; - // Index of the network device - socket_address.sll_ifindex = if_idx.ifr_ifindex; - // Address length - socket_address.sll_halen = ETH_ALEN; - // Destination MAC - socket_address.sll_addr[0] = MY_DEST_MAC0; - socket_address.sll_addr[1] = MY_DEST_MAC1; - socket_address.sll_addr[2] = MY_DEST_MAC2; - socket_address.sll_addr[3] = MY_DEST_MAC3; - socket_address.sll_addr[4] = MY_DEST_MAC4; - socket_address.sll_addr[5] = MY_DEST_MAC5; - - while(1) - { - usleep(10000); - // Send packet - if (zts_sendto(fd, sendbuf, tx_len, 0, (struct sockaddr*)&socket_address, sizeof(struct sockaddr_ll)) < 0) - fprintf(stderr, "Send failed\n"); - } - - // dismantle all zt virtual taps - zts_stop(); -#endif - return 0; -} \ No newline at end of file diff --git a/test/vera++/libzt b/test/vera++/libzt deleted file mode 100644 index dcd02fc..0000000 --- a/test/vera++/libzt +++ /dev/null @@ -1,85 +0,0 @@ -# Source files should not use the 'r' (CR) character -rule=F001 - -# File names should be well-formed -rule=F002 - -# No trailing whitespace -rule=L001 -parameter=strict-trailing-space=0 - -# Don't use tab characters -# project deliberately uses TABS, so this rule should be left commented out -# rule=L002 - -# No leading and no trailing empty lines -rule=L003 - -# Line cannot be too long -rule=L004 -parameter=max-line-length=120 - -# There should not be too many consecutive empty lines -rule=L005 -parameter=max-consecutive-empty-lines=5 - -# Source file should not be too long -# rule=L006 -# parameter=max-file-length=3000 - -# One-line comments should not have forced continuation -rule=T001 - -# Reserved names should not be used for preprocessor macros -rule=T002 - -# Some keywords should be followed by a single space -rule=T003 - -# Some keywords should be immediately followed by a colon -rule=T004 - -# Keywords break and continue should be immediately followed by a semicolon -rule=T005 - -# Keywords return and throw should be immediately followed by a semicolon or a single space -rule=T006 - -# Semicolons should not be isolated by spaces or comments from -rule=T007 - -# Keywords catch, for, if, switch and while should be followed by a single space -rule=T008 - -# Comma should not be preceded by whitespace, but should be followed by one -rule=T009 - -# Identifiers should not be composed of 'l' and 'O' characters only -rule=T010 - -# Curly brackets from the same pair should be either in the same line or in the same column -# rule=T011 - -# Negation operator should not be used in its short form -rule=T012 - -# Source files should contain the copyright notice -rule=T013 - -# -# rule=T014 - -# HTML links in comments and string literals should be correct -rule=T015 - -# Calls to min/max should be protected against accidental macro -rule=T016 - -# Unnamed namespaces are not allowed in header files -rule=T017 - -# Using namespace is not allowed in header files -rule=T018 - -# Control structures should have complete curly-braced block of code -rule=T019