├── .gitignore ├── .gitmodules ├── Jamfile ├── Jamroot.jam ├── LICENSE ├── README.md ├── buildconfig.mk ├── host.mk ├── libbtdht.vcxproj ├── makefile.gcc ├── parse_dht_stats.py ├── platform.mk ├── scripts └── readBuildsFromTcpDump.py ├── src ├── Buffer.h ├── DHTMessage.cpp ├── DHTMessage.h ├── DhtImpl.cpp ├── DhtImpl.h ├── ExternalIPCounter.cpp ├── ExternalIPCounter.h ├── blockallocator.cpp ├── blockallocator.h ├── crc32c.cpp ├── dht.cpp └── dht.h └── unittests ├── Jamfile ├── TestDHT.h ├── TestDHTMessageObject.cpp ├── TestDHTRoutingTable.cpp ├── TestDataStore.cpp ├── TestDhtID.cpp ├── TestDhtImpl.cpp ├── TestDhtImpl.h ├── TestDhtImplResponse.cpp ├── TestDhtImplSpeed.cpp ├── TestExternalIPCounter.cpp ├── TestRoutingTable.cpp ├── TestSecureDhtID.cpp ├── UnitTestUDPSocket.cpp ├── UnitTestUDPSocket.h └── bencoder.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | *.dylib 9 | 10 | # Compiled Static libraries 11 | *.lai 12 | *.la 13 | *.a 14 | 15 | # http://www.gnu.org/software/automake 16 | 17 | Makefile.in 18 | 19 | # http://www.gnu.org/software/autoconf 20 | 21 | /autom4te.cache 22 | /aclocal.m4 23 | /compile 24 | /configure 25 | /depcomp 26 | /install-sh 27 | /missing 28 | /m4 29 | /config.guess 30 | /config.sub 31 | 32 | /bin 33 | /unittests/bin 34 | 35 | # http://www.gnu.org/software/libtool 36 | 37 | /ar-lib 38 | /ltmain.sh 39 | 40 | # temporary editor files etc 41 | *~ 42 | *.swp 43 | tags 44 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "btutils"] 2 | path = btutils 3 | url = https://github.com/bittorrent/libbtutils.git 4 | -------------------------------------------------------------------------------- /Jamfile: -------------------------------------------------------------------------------- 1 | import feature : feature ; 2 | 3 | use-project /btutils : btutils ; 4 | 5 | SOURCES = DHTMessage DhtImpl ExternalIPCounter blockallocator dht crc32c ; 6 | 7 | feature holepunch : off on : composite propagated link-incompatible ; 8 | feature.compose on : USE_HOLEPUNCH=1 ; 9 | 10 | lib btdht : src/$(SOURCES).cpp 11 | : # requirements 12 | src 13 | /btutils//btutils/static 14 | windows:WIN32 15 | gcc:-Wno-invalid-offsetof 16 | : # default build 17 | static 18 | : # usage-requirements 19 | src 20 | ; 21 | 22 | -------------------------------------------------------------------------------- /Jamroot.jam: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bittorrent/libbtdht/200b8adf83edeb84f1c81fb82de7e5b8624a5fa4/Jamroot.jam -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | btdht 2 | ====== 3 | 4 | The DHT implementation used by BitTorrent projects 5 | -------------------------------------------------------------------------------- /buildconfig.mk: -------------------------------------------------------------------------------- 1 | # 2 | # Build Configurations 3 | # 4 | 5 | CONFIG_DEBUG=debug 6 | CONFIG_RELEASE=release 7 | CONFIG_COVERAGE=coverage 8 | CONFIG_DEFAULT=$(CONFIG_DEBUG) 9 | # Select default if no value provided 10 | ifeq ($(CONFIG),) 11 | $(warning Configuration not specified - using default $(CONFIG_DEFAULT) configuration) 12 | $(info ) 13 | CONFIG=$(CONFIG_DEFAULT) 14 | endif 15 | 16 | # This allows cleanall to work without having to specify a valid CONFIG value 17 | $(info make command line $(MAKECMDGOALS)) 18 | ifneq "$(MAKECMDGOALS)" "cleanall" 19 | ifneq ($(CONFIG),$(CONFIG_DEBUG)) 20 | ifneq ($(CONFIG),$(CONFIG_RELEASE)) 21 | ifneq ($(CONFIG),$(CONFIG_COVERAGE)) 22 | $(error Cannot support configuration '$(CONFIG)'. Valid configurations: $(CONFIG_DEBUG), $(CONFIG_RELEASE), $(CONFIG_COVERAGE) (e.g. make CONFIG=$(CONFIG_DEFAULT))) 23 | endif 24 | endif 25 | endif 26 | endif 27 | 28 | # 29 | # Character Set 30 | # 31 | 32 | CHARSET_ANSI=ansi 33 | CHARSET_UNICODE=unicode 34 | CHARSET_DEFAULT=$(CHARSET_UNICODE) 35 | # Select default if no value provided 36 | ifeq ($(CHARSET),) 37 | $(warning Character set not specified - using default of $(CHARSET_DEFAULT)) 38 | $(info ) 39 | CHARSET=$(CHARSET_DEFAULT) 40 | endif 41 | 42 | # Validate 43 | ifneq ($(CHARSET),$(CHARSET_ANSI)) 44 | ifneq ($(CHARSET),$(CHARSET_UNICODE)) 45 | $(error Cannot support character set '$(CHARSET)'. Valid configurations: $(CHARSET_ANSI), $(CHARSET_UNICODE) (e.g. make CHARSET=$(CHARSET_DEFAULT))) 46 | endif 47 | endif 48 | 49 | # 50 | # Optimization Level 51 | # 52 | 53 | OPTIMIZE_0=0 54 | OPTIMIZE_1=1 55 | OPTIMIZE_2=2 56 | OPTIMIZE_3=3 57 | OPTIMIZE_s=s 58 | OPTIMIZE_fast=fast 59 | ifeq ($(CONFIG),$(CONFIG_DEBUG)) 60 | OPTIMIZE_DEFAULT=$(OPTIMIZE_0) 61 | endif 62 | ifeq ($(CONFIG),$(CONFIG_RELEASE)) 63 | OPTIMIZE_DEFAULT=$(OPTIMIZE_s) 64 | endif 65 | ifeq ($(CONFIG),$(CONFIG_COVERAGE)) 66 | OPTIMIZE_DEFAULT=$(OPTIMIZE_0) 67 | endif 68 | # Select default if no value provided 69 | ifeq ($(OPTIMIZE),) 70 | $(warning Optimization not specified - using configuration-specific default of $(OPTIMIZE_DEFAULT) ) 71 | $(info ) 72 | OPTIMIZE=$(OPTIMIZE_DEFAULT) 73 | endif 74 | 75 | # Validate 76 | ifneq ($(OPTIMIZE),$(OPTIMIZE_0)) 77 | ifneq ($(OPTIMIZE),$(OPTIMIZE_1)) 78 | ifneq ($(OPTIMIZE),$(OPTIMIZE_2)) 79 | ifneq ($(OPTIMIZE),$(OPTIMIZE_3)) 80 | ifneq ($(OPTIMIZE),$(OPTIMIZE_s)) 81 | ifneq ($(OPTIMIZE),$(OPTIMIZE_fast)) 82 | $(error Cannot support optimization level '$(OPTIMIZE)'. Valid configurations: $(OPTIMIZE_0), $(OPTIMIZE_1), $(OPTIMIZE_2), $(OPTIMIZE_3), $(OPTIMIZE_s), $(OPTIMIZE_fast) (e.g. make OPTIMIZE=$(OPTIMIZE_DEFAULT))) 83 | endif 84 | endif 85 | endif 86 | endif 87 | endif 88 | endif 89 | 90 | 91 | -------------------------------------------------------------------------------- /host.mk: -------------------------------------------------------------------------------- 1 | # 2 | # Host detection 3 | # 4 | 5 | # Symbolic names for HOST variable 6 | HOST_LINUX := Linux 7 | HOST_MAC := Darwin 8 | HOST_CYGWIN1 := CYGWIN_NT-6.1-WOW64 9 | HOST_CYGWIN2 := CYGWIN_NT-6.0 10 | HOST_CYGWIN3 := CYGWIN_NT-5.1 11 | HOST_CYGWIN := CYGWIN 12 | HOST_FREEBSD := FreeBSD 13 | 14 | PROC_PPC := powerpc 15 | PROC_i386 := i386 16 | 17 | ARCH_x86_64 := x86_64 18 | 19 | # HOST can be: Linux, Darwin, CYGWIN_NT-6.0, CYGWIN_NT-5.1 20 | HOST := $(shell uname -s) 21 | PROC := $(shell uname -p) 22 | ARCH := $(shell uname -m) 23 | 24 | # Normalize CYGWIN names to just one 25 | ifeq ($(HOST),$(HOST_CYGWIN1)) 26 | HOST := $(HOST_CYGWIN) 27 | endif 28 | ifeq ($(HOST),$(HOST_CYGWIN2)) 29 | HOST := $(HOST_CYGWIN) 30 | endif 31 | ifeq ($(HOST),$(HOST_CYGWIN3)) 32 | HOST := $(HOST_CYGWIN) 33 | endif 34 | 35 | -------------------------------------------------------------------------------- /libbtdht.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | WinRTDebug 22 | Win32 23 | 24 | 25 | WinRTDebug 26 | x64 27 | 28 | 29 | WinRTRelease 30 | Win32 31 | 32 | 33 | WinRTRelease 34 | x64 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | {507D1356-0000-0000-0000-000000000000} 54 | Win32Proj 55 | libbtdht2012 56 | libbtdht 57 | 58 | 59 | 60 | StaticLibrary 61 | true 62 | v140_xp 63 | Unicode 64 | 65 | 66 | StaticLibrary 67 | true 68 | v120_xp 69 | Unicode 70 | 71 | 72 | StaticLibrary 73 | true 74 | v120 75 | Unicode 76 | 77 | 78 | StaticLibrary 79 | true 80 | v120 81 | Unicode 82 | 83 | 84 | StaticLibrary 85 | false 86 | v140_xp 87 | true 88 | Unicode 89 | 90 | 91 | StaticLibrary 92 | false 93 | v120_xp 94 | true 95 | Unicode 96 | 97 | 98 | StaticLibrary 99 | false 100 | v120 101 | true 102 | Unicode 103 | 104 | 105 | StaticLibrary 106 | false 107 | v120 108 | true 109 | Unicode 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | Level2 162 | Disabled 163 | WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) 164 | Sync 165 | 166 | 167 | Windows 168 | true 169 | 170 | 171 | 172 | 173 | 174 | 175 | Level2 176 | Disabled 177 | WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) 178 | 179 | 180 | Windows 181 | true 182 | 183 | 184 | 185 | 186 | 187 | 188 | Level2 189 | Disabled 190 | WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) 191 | 192 | 193 | Windows 194 | true 195 | 196 | 197 | 198 | 199 | 200 | 201 | Level2 202 | Disabled 203 | WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) 204 | 205 | 206 | Windows 207 | true 208 | 209 | 210 | 211 | 212 | Level2 213 | 214 | 215 | MaxSpeed 216 | true 217 | true 218 | WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) 219 | Sync 220 | 221 | 222 | Windows 223 | true 224 | true 225 | true 226 | 227 | 228 | 229 | 230 | Level2 231 | 232 | 233 | MaxSpeed 234 | true 235 | true 236 | WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) 237 | 238 | 239 | Windows 240 | true 241 | true 242 | true 243 | 244 | 245 | 246 | 247 | Level2 248 | 249 | 250 | MaxSpeed 251 | true 252 | true 253 | WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) 254 | 255 | 256 | Windows 257 | true 258 | true 259 | true 260 | 261 | 262 | 263 | 264 | Level2 265 | 266 | 267 | MaxSpeed 268 | true 269 | true 270 | WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) 271 | 272 | 273 | Windows 274 | true 275 | true 276 | true 277 | 278 | 279 | 280 | 281 | 282 | -------------------------------------------------------------------------------- /makefile.gcc: -------------------------------------------------------------------------------- 1 | # 2 | # Host detection 3 | # 4 | 5 | include host.mk 6 | ifeq ($(HOST),) 7 | $(error "Can't identify host") 8 | endif 9 | 10 | # 11 | # Target specification 12 | # 13 | 14 | TARGET_ANDROID = android 15 | 16 | $(info Host $(HOST)) 17 | ifeq ($(TARGET),) 18 | $(info Using default target of $(HOST) (same as host)) 19 | TARGET = $(HOST) 20 | else 21 | ifneq ($(TARGET),$(HOST_LINUX)) 22 | ifneq ($(TARGET),$(HOST_MAC)) 23 | ifneq ($(TARGET),$(HOST_CYGWIN)) 24 | ifneq ($(TARGET),$(HOST_FREEBSD)) 25 | ifneq ($(TARGET),$(TARGET_ANDROID)) 26 | $(error Invalid target: '$(TARGET)'. Valid targets: $(HOST_LINUX), $(HOST_MAC), $(HOST_CYGWIN), $(HOST_FREEBSD), $(TARGET_ANDROID) (e.g. make TARGET=$(HOST))) 27 | endif 28 | endif 29 | endif 30 | endif 31 | endif 32 | $(info Target $(TARGET)) 33 | endif 34 | 35 | # 36 | # Platform Setup 37 | # 38 | 39 | export TARGET 40 | include platform.mk 41 | ifeq ($(PLATFORM_FLAGS),) 42 | $(error "Can't initialize for target $(TARGET)") 43 | endif 44 | 45 | # 46 | # Build setup 47 | # 48 | 49 | export CONFIG 50 | export CHARSET 51 | export OPTIMIZE 52 | include buildconfig.mk 53 | ifeq ($(CONFIG),) 54 | $(error "Can't initialize configuration (CONFIG)") 55 | endif 56 | ifeq ($(CHARSET),) 57 | $(error "Can't initialize configuration (CHARSET)") 58 | endif 59 | ifeq ($(OPTIMIZE),) 60 | $(error "Can't initialize configuration (OPTIMIZE)") 61 | endif 62 | 63 | # 64 | # Compile and linkage setup 65 | # 66 | 67 | # Show system on which we build, gcc version and machine name. Will be useful when 68 | # looking at log output of failed compilation. 69 | $(info Building on $(shell uname -a)) 70 | $(info gcc version: $(shell $(CC) -dumpversion)) 71 | $(info gcc machine: $(shell $(CC) -dumpmachine)) 72 | $(info ) 73 | 74 | # Compile flags 75 | 76 | # -c - compile/assemble source files, but do not link 77 | # -MD - output dependencies to a file located with the object file 78 | # -g - provide debugging information in OS's native format (for gdb) 79 | # -pipe - use pipes instead of temporary files for comm between compilation stages 80 | # -Wall - enable (almost) all warnings 81 | # -Werror - make all warnings into errors 82 | # -O - optimization level 83 | DHT_COMMON_FLAGS = \ 84 | -c \ 85 | -MD \ 86 | -g \ 87 | -pipe \ 88 | -Wall \ 89 | -O$(OPTIMIZE) 90 | 91 | # -std - specify the language standard 92 | DHT_CXXONLY_FLAGS = \ 93 | -std=c++11 94 | 95 | ifeq ($(CHARSET),$(CHARSET_UNICODE)) 96 | DHT_COMMON_FLAGS += -D_UNICODE 97 | endif 98 | 99 | ifeq ($(CONFIG),$(CONFIG_DEBUG)) 100 | DHT_COMMON_FLAGS += -D_DEBUG 101 | endif 102 | 103 | OUTDIR_PREFIX = obj- 104 | # Keep same order of parts as in ut_ce 105 | OUTDIR = $(OUTDIR_PREFIX)$(CHARSET)-$(CONFIG)-O$(OPTIMIZE) 106 | 107 | # Link flags 108 | 109 | LD_SYSTEM_SHLIBFLAGS = -lpthread -lm 110 | 111 | ifeq ($(TARGET),$(HOST_LINUX)) 112 | # -lrt is for clock_gettime() - see its man page 113 | LD_SYSTEM_SHLIBFLAGS += -lrt 114 | endif 115 | 116 | # Include all added compile flags in CFLAGS and CXXFLAGS 117 | DHT_CFLAGS = $(DHT_COMMON_FLAGS) $(PLATFORM_FLAGS) 118 | DHT_CXXFLAGS = $(DHT_COMMON_FLAGS) $(PLATFORM_FLAGS) $(DHT_CXXONLY_FLAGS) 119 | # Android doesn't like CFLAGS/CXXFLAGS modified in this file. 120 | # It may be useful to avoid setting LOCAL_ flags here - see later. 121 | ifeq ($(TARGET),$(TARGET_ANDROID)) 122 | LOCAL_CFLAGS += $(DHT_CFLAGS) 123 | LOCAL_CXXFLAGS += $(DHT_CXXFLAGS) 124 | endif 125 | 126 | # 127 | # Library sources and products 128 | # 129 | 130 | # btutils 131 | 132 | UTILS_DIR_ROOT = btutils 133 | UTILS_DIR = $(UTILS_DIR_ROOT) 134 | UTILS_SRC_ROOT = $(UTILS_DIR)/src 135 | OUTDIR_UTILS = $(OUTDIR)/$(UTILS_DIR_ROOT) 136 | UTILS_LIBSRCS = $(sort $(addprefix $(UTILS_SRC_ROOT)/, \ 137 | DecodeEncodedString.cpp \ 138 | RefBase.cpp \ 139 | bencoding.cpp \ 140 | bencparser.cpp \ 141 | bitfield.cpp \ 142 | bloom_filter.cpp \ 143 | get_microseconds.cpp \ 144 | inet_ntop.cpp \ 145 | interlock.cpp \ 146 | snprintf.cpp \ 147 | sockaddr.cpp \ 148 | udp_utils.cpp \ 149 | )) 150 | UTILS_LIBOBJS = $(patsubst %.cpp, $(OUTDIR)/%.o, $(UTILS_LIBSRCS)) 151 | UTILS_LIBOBJDIR = $(OUTDIR)/$(UTILS_DIR) 152 | UTILS_LIBOBJSRCDIR = $(OUTDIR)/$(UTILS_SRC_ROOT) 153 | UTILS_INCS += -I $(UTILS_SRC_ROOT) 154 | 155 | # btdht 156 | 157 | SRC_DIR = src 158 | LIBSRCS = $(sort $(addprefix $(SRC_DIR)/, \ 159 | blockallocator.cpp \ 160 | crc32c.cpp \ 161 | dht.cpp \ 162 | DhtImpl.cpp \ 163 | DHTMessage.cpp \ 164 | ExternalIPCounter.cpp \ 165 | )) 166 | INCS += -I src 167 | 168 | LIBBASENAME = utdht 169 | LIBNAME = lib$(LIBBASENAME).so 170 | LIBOBJDIR = $(OUTDIR)/$(SRC_DIR) 171 | LIBOBJS = $(patsubst $(SRC_DIR)/%.cpp, $(LIBOBJDIR)/%.o, $(LIBSRCS)) 172 | LIBDESTDIR = $(OUTDIR) 173 | OBJDESTLIB = $(LIBDESTDIR)/$(LIBNAME) 174 | UNSTRIPPEDLIBDESTDIR = $(LIBOBJDIR) 175 | OBJDESTUNSTRIPPEDLIB = $(UNSTRIPPEDLIBDESTDIR)/$(LIBNAME) 176 | 177 | LIBRARY_CXXFLAGS = -fPIC 178 | LIBRARY_BUILDFLAGS = -shared 179 | 180 | # Shared object files 181 | SHARED_OBJS = $(sort \ 182 | $(LIBOBJS) \ 183 | $(UTILS_LIBOBJS) \ 184 | ) 185 | 186 | SHARED_INCS = $(UTILS_INCS) $(INCS) 187 | 188 | # 189 | # Unit tests sources and products 190 | # 191 | 192 | 193 | GTEST_VERSION = 1.6.0 194 | GOOGLE_TEST_HOME = $(UTILS_DIR_ROOT)/vendor/gtest-$(GTEST_VERSION) 195 | GOOGLE_MOCK_HOME = $(UTILS_DIR_ROOT)/vendor/gmock-$(GTEST_VERSION) 196 | GOOGLE_TEST_DIR = $(GOOGLE_TEST_HOME)/src 197 | GOOGLE_MOCK_DIR = $(GOOGLE_MOCK_HOME)/src 198 | SRCS_GOOGLE_TEST = $(addprefix $(GOOGLE_TEST_DIR)/, \ 199 | gtest-all.cc \ 200 | ) 201 | SRCS_GOOGLE_MOCK = $(addprefix $(GOOGLE_MOCK_DIR)/, \ 202 | gmock-all.cc \ 203 | gmock_main.cc \ 204 | ) 205 | OBJS_GOOGLE_TEST = $(patsubst %.cc, $(OUTDIR)/%.o, $(SRCS_GOOGLE_TEST)) 206 | OBJS_GOOGLE_MOCK = $(patsubst %.cc, $(OUTDIR)/%.o, $(SRCS_GOOGLE_MOCK)) 207 | OUTPUT_GOOGLE_TEST_DIR = $(OUTDIR)/$(GOOGLE_TEST_DIR) 208 | OUTPUT_GOOGLE_MOCK_DIR = $(OUTDIR)/$(GOOGLE_MOCK_DIR) 209 | 210 | UNITTESTS_DIR = unittests 211 | LIBUNITTESTOBJDIR = $(OUTDIR)/$(UNITTESTS_DIR) 212 | SRCS_TESTS = $(addprefix $(UNITTESTS_DIR)/, \ 213 | TestDataStore.cpp \ 214 | TestDhtID.cpp \ 215 | TestDhtImpl.cpp \ 216 | TestDhtImplResponse.cpp \ 217 | TestDhtImplSpeed.cpp \ 218 | TestDHTMessageObject.cpp \ 219 | TestDHTRoutingTable.cpp \ 220 | TestExternalIPCounter.cpp \ 221 | TestRoutingTable.cpp \ 222 | TestSecureDhtID.cpp \ 223 | UnitTestUDPSocket.cpp \ 224 | ) 225 | OBJS_TESTS = $(patsubst %.cpp, $(OUTDIR)/%.o, $(SRCS_TESTS)) 226 | UNITTESTS_EXE_NAME = unit_tests 227 | UT_EXE_DEST = $(LIBUNITTESTOBJDIR)/$(UNITTESTS_EXE_NAME) 228 | UNITTESTS_DIR_DEPS = $(filter-out $(wildcard $(LIBUNITTESTOBJDIR)), $(LIBUNITTESTOBJDIR)) 229 | 230 | SRCS_UNITTESTS = \ 231 | $(SRCS_GOOGLE_TEST) \ 232 | $(SRCS_TESTS) 233 | 234 | OBJS_UNITTESTS = $(OBJS_TESTS) $(OBJS_GOOGLE_TEST) $(OBJS_GOOGLE_MOCK) 235 | 236 | INCLUDE_UNITTESTS = \ 237 | $(UTILS_INCS)/ \ 238 | -I$(GOOGLE_TEST_HOME)/include \ 239 | -I$(GOOGLE_TEST_HOME) \ 240 | -I$(GOOGLE_MOCK_HOME)/include \ 241 | -I$(GOOGLE_MOCK_HOME) 242 | 243 | LD_COMPONENT_SHLIBFLAGS = -L$(UNSTRIPPEDLIBDESTDIR) -l$(LIBBASENAME) 244 | LD_SHLIB_FLAGS = $(LD_COMPONENT_SHLIBFLAGS) $(LD_SYSTEM_SHLIBFLAGS) 245 | 246 | # 247 | # Rules 248 | # 249 | 250 | # Build/test rules 251 | 252 | .phony: all product test vgtest 253 | 254 | all: $(OBJDESTLIB) $(UT_EXE_DEST) 255 | 256 | product: $(OBJDESTLIB) 257 | 258 | test: $(UT_EXE_DEST) 259 | env LD_LIBRARY_PATH=$(UNSTRIPPEDLIBDESTDIR) $< 260 | 261 | # Run unit test executable under valgrind 262 | vgtest: $(UT_EXE_DEST) 263 | env LD_LIBRARY_PATH=$(UNSTRIPPEDLIBDESTDIR) valgrind --tool=memcheck --leak-check=yes $< 264 | 265 | $(OBJDESTLIB): $(OBJDESTUNSTRIPPEDLIB) 266 | strip -S -o $@ $< 267 | 268 | $(OBJDESTUNSTRIPPEDLIB): $(SHARED_OBJS) $(filter-out $(wildcard $(UNSTRIPPEDLIBDESTDIR)), $(UNSTRIPPEDLIBDESTDIR)) 269 | $(CXX) -o $@ $(LIBRARY_BUILDFLAGS) $(SHARED_OBJS) 270 | 271 | $(UT_EXE_DEST): $(OBJDESTUNSTRIPPEDLIB) $(OBJS_UNITTESTS) $(UNITTESTS_DIR_DEPS) 272 | $(CXX) -o $@ $(OBJS_UNITTESTS) $(LD_SHLIB_FLAGS) 273 | 274 | # Output directory creation rules 275 | 276 | # $(filter-out $(wildcard $(directorymacro)), $(directorymacro)) establishes 277 | # a dependency on a directory that doesn't already exist, so that if the 278 | # directory exists, the associated mkdir command won't be executed, 279 | # which would prevent a clean no-op when nothing really needs doing. 280 | 281 | $(OUTDIR): 282 | mkdir -p $@ 283 | 284 | # btutils 285 | $(UTILS_LIBOBJDIR): $(filter-out $(wildcard $(OUTDIR)), $(OUTDIR)) 286 | mkdir -p $@ 287 | $(UTILS_LIBOBJSRCDIR): $(filter-out $(wildcard $(OUTDIR)), $(OUTDIR)) 288 | mkdir -p $@ 289 | 290 | ifneq ($(UNSTRIPPEDLIBDESTDIR),$(LIBOBJDIR)) 291 | $(UNSTRIPPEDLIBDESTDIR): $(filter-out $(wildcard $(OUTDIR)), $(OUTDIR)) 292 | mkdir -p $@ 293 | 294 | endif 295 | 296 | $(LIBOBJDIR): $(filter-out $(wildcard $(OUTDIR)), $(OUTDIR)) 297 | mkdir -p $@ 298 | 299 | $(LIBUNITTESTOBJDIR): $(filter-out $(wildcard $(OUTDIR)), $(OUTDIR)) 300 | mkdir -p $@ 301 | 302 | $(OUTPUT_GOOGLE_TEST_DIR): $(filter-out $(wildcard $(OUTDIR)), $(OUTDIR)) 303 | mkdir -p $@ 304 | 305 | $(OUTPUT_GOOGLE_MOCK_DIR): $(filter-out $(wildcard $(OUTDIR)), $(OUTDIR)) 306 | mkdir -p $@ 307 | 308 | # Implicit rules 309 | 310 | UTILS_DIR_DEPS = $(filter-out $(wildcard $(UTILS_LIBOBJDIR)), $(UTILS_LIBOBJDIR)) 311 | UTILS_DIR_DEPS += $(filter-out $(wildcard $(UTILS_LIBOBJSRCDIR)), $(UTILS_LIBOBJSRCDIR)) 312 | 313 | LIBOBJS_DIR_DEPS = $(filter-out $(wildcard $(OBJDIR)), $(OBJDIR)) 314 | LIBOBJS_DIR_DEPS += $(filter-out $(wildcard $(LIBOBJDIR)), $(LIBOBJDIR)) 315 | LIBOBJS_DIR_DEPS += $(UTILS_DIR_DEPS) 316 | 317 | $(LIBOBJS): $(LIBOBJDIR)/%.o: $(SRC_DIR)/%.cpp $(LIBOBJS_DIR_DEPS) 318 | $(CXX) $(DHT_CXXFLAGS) $(SHARED_INCS) $(LIBRARY_CXXFLAGS) -o $@ $< 319 | 320 | $(UTILS_LIBOBJS): $(UTILS_LIBOBJSRCDIR)/%.o: $(UTILS_SRC_ROOT)/%.cpp $(UTILS_DIR_DEPS) 321 | $(CXX) $(DHT_CXXFLAGS) $(UTILS_INCS) $(LIBRARY_CXXFLAGS) -o $@ $< 322 | 323 | $(OBJS_TESTS): $(LIBUNITTESTOBJDIR)/%.o: $(UNITTESTS_DIR)/%.cpp $(UNITTESTS_DIR_DEPS) 324 | $(CXX) $(DHT_CXXFLAGS) $(SHARED_INCS) $(INCLUDE_UNITTESTS) -o $@ $< -DGTEST_USE_OWN_TR1_TUPLE=1 325 | 326 | $(OBJS_GOOGLE_TEST): $(OUTPUT_GOOGLE_TEST_DIR)/%.o: $(GOOGLE_TEST_DIR)/%.cc $(filter-out $(wildcard $(OUTPUT_GOOGLE_TEST_DIR)), $(OUTPUT_GOOGLE_TEST_DIR)) 327 | $(CXX) $(DHT_CXXFLAGS) $(SHARED_INCS) $(INCLUDE_UNITTESTS) -o $@ $< -DGTEST_USE_OWN_TR1_TUPLE=1 328 | 329 | $(OBJS_GOOGLE_MOCK): $(OUTPUT_GOOGLE_MOCK_DIR)/%.o: $(GOOGLE_MOCK_DIR)/%.cc $(filter-out $(wildcard $(OUTPUT_GOOGLE_MOCK_DIR)), $(OUTPUT_GOOGLE_MOCK_DIR)) 330 | $(CXX) $(DHT_CXXFLAGS) $(SHARED_INCS) $(INCLUDE_UNITTESTS) -o $@ $< -DGTEST_USE_OWN_TR1_TUPLE=1 331 | 332 | # Clean rules 333 | 334 | .phony: clean cleanall 335 | 336 | clean: 337 | rm -rf $(OUTDIR) 338 | 339 | cleanall: 340 | rm -rf $(OUTDIR_PREFIX)* 341 | -------------------------------------------------------------------------------- /parse_dht_stats.py: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import os 18 | import sys 19 | import getopt 20 | 21 | def parse_file(file, plot_period): 22 | inf = open(file, 'r') 23 | 24 | graphs = {} 25 | time = [] 26 | data = {} 27 | current = 0 28 | 29 | packets_in = 0 30 | packets_out = 1 31 | 32 | inout = [{'total': 0}, {'total': 0}, {'total': 0}, {'total': 0}] 33 | 34 | total_data = 0 35 | 36 | tids = { } 37 | events = { } 38 | 39 | with open(file, 'r') as inf: 40 | for l in inf: 41 | # skip lines that are not instrumentation 42 | loc = l.find('DHTI') 43 | if loc == -1: continue 44 | l = l[loc+4:] 45 | #print l 46 | 47 | fields = l.split('\t') 48 | if l[0] == '<': direction = packets_in; 49 | elif l[0] == '>': direction = packets_out; 50 | elif l[0] == '!': 51 | event = l[1:].strip() 52 | if event in events: 53 | events[event] += 1 54 | else: 55 | events[event] = 1 56 | continue 57 | else: raise 'invalid direction' 58 | 59 | t = int(fields[1]) 60 | method = fields[2] 61 | query = fields[3] 62 | size = int(fields[4]) 63 | tid = fields[5] 64 | 65 | if direction == packets_out: 66 | tids[tid] = method 67 | 68 | if direction == packets_in: 69 | if method == 'unknown': 70 | if tid in tids: 71 | method = tids[tid] 72 | del tids[tid] 73 | 74 | total_data += size 75 | inout[direction]['total'] += size 76 | inout[direction+2]['total'] += 1 77 | 78 | if not method in inout[direction]: 79 | inout[direction][method] = size 80 | inout[direction+2][method] = 1 81 | else: 82 | inout[direction][method] += size 83 | inout[direction+2][method] += 1 84 | 85 | if plot_period > 0: 86 | if not method in graphs: 87 | graphs[method] = [ [0] * len(time), [0] * len(time) ] 88 | data[method] = [0, 0] 89 | if int(t/float(plot_period) + 0.5) > current: 90 | for m in graphs: 91 | graphs[m][0].append(data[m][0]) 92 | graphs[m][1].append(data[m][1]) 93 | data[m][0] = 0 94 | data[m][1] = 0 95 | time.append(plot_period*current) 96 | current = int(t/float(plot_period) + 0.5) 97 | data[method][direction] += size 98 | 99 | if plot_period == 0: 100 | print 'total data: ', total_data 101 | 102 | print '\ntotal events ', len(events) 103 | for e in events.keys(): 104 | print '%d\t%s' % ( events[e], e) 105 | 106 | title = ['data in', 'data out', 'packets in', 'packets out'] 107 | 108 | for i in range(len(title)): 109 | print '\n === %s ===\n' % title[i] 110 | 111 | data = inout[i] 112 | for d in data: 113 | print '%s: %d %.2f %%' % (d, data[d], data[d] * 100 / float(data['total'])) 114 | else: 115 | import matplotlib.pyplot as plt 116 | plt.figure(1) 117 | for m in graphs: 118 | plt.plot(time, graphs[m][0], label=m) 119 | plt.title('incoming') 120 | plt.legend() 121 | plt.xlabel('ms') 122 | plt.ylabel('bytes') 123 | plt.figure(2) 124 | for m in graphs: 125 | plt.plot(time, graphs[m][1], label=m) 126 | plt.title('outgoing') 127 | plt.legend() 128 | plt.xlabel('ms') 129 | plt.ylabel('bytes') 130 | plt.show() 131 | 132 | def usage(): 133 | print 'usage', sys.argv[0], '-i input_file [-p plot_sampling_period]' 134 | print 'print data stats or plot graphs if plot_sampling_period is specified' 135 | sys.exit(-1) 136 | 137 | if __name__ == '__main__': 138 | infile = '' 139 | plot_period = 0 140 | try: 141 | opts, args = getopt.getopt(sys.argv[1:], "i:p:") 142 | except getopt.GetoptError as err: 143 | print str(err) 144 | usage() 145 | 146 | for o, a in opts: 147 | if o == '-i': 148 | infile = a 149 | elif o == '-p': 150 | plot_period = int(a) 151 | 152 | if len(infile) == 0: 153 | print 'no infile specified' 154 | usage(); 155 | 156 | parse_file(infile, plot_period) 157 | 158 | -------------------------------------------------------------------------------- /platform.mk: -------------------------------------------------------------------------------- 1 | # 2 | # Platform Setup 3 | # 4 | 5 | PLATFORM_FLAGS = -DPOSIX 6 | 7 | ifeq ($(TARGET),$(HOST_MAC)) 8 | FWKS=/System/Library/Frameworks 9 | FWKCFLAGS=-I/Developer/Headers/FlatCarbon \ 10 | -I$(FWKS)/IOKit.framework/Versions/Current/Headers 11 | 12 | PLATFORM_FLAGS += $(FWKCFLAGS) 13 | PLATFORM_LDFLAGS += -framework Carbon -framework IOKit 14 | ifeq ($(STRIP_DEAD),yes) 15 | PLATFORM_LDFLAGS += -dead_strip 16 | endif 17 | # Building for 32-bit 18 | 19 | ifeq ($(PROC),$(PROC_PPC)) 20 | PLATFORM_FLAGS += -arch ppc 21 | PLATFORM_LDFLAGS += -arch ppc 22 | else 23 | ifeq ($(ARCH), $(ARCH_x86_64)) 24 | PLATFORM_FLAGS += -arch x86_64 25 | PLATFORM_LDFLAGS += -arch x86_64 26 | else 27 | PLATFORM_FLAGS += -arch i386 28 | PLATFORM_LDFLAGS += -arch i386 29 | endif 30 | endif 31 | 32 | ifeq ($(CC), "") 33 | CC=/usr/bin/gcc 34 | endif 35 | 36 | ifeq ($(CXX), "") 37 | CXX=/usr/bin/g++ 38 | endif 39 | 40 | STRIP=/usr/bin/strip -x 41 | endif 42 | 43 | ifeq ($(TARGET),$(HOST_LINUX)) 44 | # -lrt is for clock_gettime() - see its man page 45 | PLATFORM_FLAGS += -DLINUX 46 | endif 47 | 48 | ifeq ($(TARGET),$(HOST_CYGWIN)) 49 | PLATFORM_FLAGS += -DCYGWIN 50 | endif 51 | 52 | ifeq ($(TARGET),$(HOST_FREEBSD)) 53 | PLATFORM_FLAGS += -DFREEBSD 54 | endif 55 | 56 | ifeq ($(TARGET),$(TARGET_ANDROID)) 57 | PLATFORM_FLAGS += -DLINUX 58 | PLATFORM_FLAGS += -DANDROID 59 | endif 60 | 61 | 62 | -------------------------------------------------------------------------------- /scripts/readBuildsFromTcpDump.py: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import dpkt, bencode, struct, traceback, sys, argparse, socket 18 | 19 | listMax = 40 20 | 21 | bad = 0 22 | no_version = 0 23 | 24 | nonUtIps = {} 25 | versionIps = {} 26 | bandwidth = { "in":{}, "out":{}, "bad":{ "noId":0, "notEncoded":0 } } 27 | 28 | def bootstrapCount(fp): 29 | global no_version, bad, nonUtIps, versionIps 30 | 31 | pcap = dpkt.pcap.Reader(fp) 32 | 33 | i = 0 34 | for ts, buf in pcap: 35 | eth = dpkt.ethernet.Ethernet(buf) 36 | ip = eth.data 37 | tcp = ip.data 38 | 39 | #Get the remote IP address and location identifier 40 | try: 41 | src_ip_addr_str = socket.inet_ntoa(ip.src) 42 | locId = src_ip_addr_str + ":" + str(tcp.sport) 43 | except: 44 | try: bandwidth["bad"]["noId"] += len(tcp.data) 45 | except: pass 46 | continue 47 | 48 | try: 49 | decoded = bencode.bdecode(tcp.data) 50 | except: 51 | bandwidth["bad"]["notEncoded"] += len(tcp.data) 52 | 53 | bad += 1 54 | continue 55 | 56 | version = decoded.get("v") 57 | if not version: 58 | #No version, we assume it's outbound. Change the locId 59 | src_ip_addr_str = socket.inet_ntoa(ip.dst) 60 | locId = src_ip_addr_str + ":" + str(tcp.dport) 61 | 62 | #Set outbound bandwidth 63 | try: bandwidth["out"][locId] += len(tcp.data) 64 | except: bandwidth["out"][locId] = len(tcp.data) 65 | 66 | no_version += 1 67 | continue 68 | 69 | #We have a version, we assume it's inbound. 70 | try: bandwidth["in"][locId] += len(tcp.data) 71 | except: bandwidth["in"][locId] = len(tcp.data) 72 | 73 | if version[0:2] != "UT": 74 | try: nonUtIps[version][locId] += 1 75 | except: 76 | try: nonUtIps[version][locId] = 1 77 | except: nonUtIps[version] = { locId: 1 } 78 | 79 | continue 80 | 81 | #Read the version 82 | version = version[2:] 83 | unpackedVersion = struct.unpack('>H', version) 84 | unpackedVersion = unpackedVersion[0] 85 | 86 | #Add it to the structured map. 87 | try: versionIps[unpackedVersion][locId] += 1 88 | except: 89 | try: versionIps[unpackedVersion][locId] = 1 90 | except: versionIps[unpackedVersion] = { locId: 1 } 91 | 92 | i += 1 93 | if (i % 100) == 0: 94 | sys.stdout.write(".") 95 | sys.stdout.flush() 96 | 97 | """ 98 | print '============================' 99 | print tcp.sport 100 | print '============================' 101 | print decoded 102 | print '============================' 103 | print version 104 | print '============================' 105 | print unpackedVersion 106 | print '============================' 107 | print 108 | print 109 | """ 110 | 111 | fp.close() 112 | print 113 | 114 | 115 | ###################################################### 116 | if __name__ == '__main__': 117 | #Parse the args 118 | parser = argparse.ArgumentParser() 119 | parser.add_argument(action="store", nargs='?', dest="pcapPath", help="The tcpdump PCAP file", metavar="[pcap file path]") 120 | args = parser.parse_args() 121 | 122 | #Have enough args? 123 | if not args.pcapPath: 124 | print "Usage: readBuildsFromTcpDump.py [pcap file path]\n" 125 | exit(1) 126 | 127 | try: fp = open(args.pcapPath) 128 | except: 129 | print "Cannot open '" + args.pcapPath + "'" 130 | exit(1) 131 | 132 | try: bootstrapCount(fp) 133 | except: 134 | traceback.print_exc() 135 | 136 | versionPairs = [] 137 | for build, ipMap in versionIps.iteritems(): 138 | bandwidthOut = 0 139 | for locId in ipMap.keys(): 140 | bandwidthOut += bandwidth["out"].get(locId, 0) 141 | 142 | versionPairs.append([build, sum(ipMap.values()), len(ipMap), bandwidthOut]) 143 | 144 | print 145 | print "======================================================" 146 | print "UT Builds (top " + str(listMax) + ")" 147 | print "======================================================" 148 | vpSorted = sorted(versionPairs, key=lambda pair: pair[1], reverse=True) 149 | for idx, pair in enumerate(vpSorted): 150 | if idx > listMax: break 151 | 152 | ver = pair[0] 153 | out = pair[3] 154 | outPer = out / pair[1] 155 | ratio = round(float(pair[1])/pair[2], 2) 156 | 157 | print "Build " + str(ver) + ":\t\t" +\ 158 | str(pair[1]) + " // " +\ 159 | str(pair[2]) + " unique // " +\ 160 | str(ratio) + " ratio // " +\ 161 | str(out) + " out // " +\ 162 | str(outPer) + " per request" 163 | 164 | nonUtPairs = [] 165 | for build, ipMap in nonUtIps.iteritems(): 166 | bandwidthOut = 0 167 | for locId in ipMap.keys(): 168 | bandwidthOut += bandwidth["out"].get(locId, 0) 169 | 170 | nonUtPairs.append([build, sum(ipMap.values()), len(ipMap), bandwidthOut]) 171 | 172 | print 173 | print "======================================================" 174 | print "Other Clients (top " + str(listMax) + ")" 175 | print "======================================================" 176 | nutSorted = sorted(nonUtPairs, key=lambda pair: pair[1], reverse=True) 177 | for idx, pair in enumerate(nutSorted): 178 | if idx > listMax: break 179 | 180 | ver = pair[0] 181 | out = pair[3] 182 | outPer = out / pair[1] 183 | ratio = round(pair[1]/pair[2], 2) 184 | 185 | try: 186 | unpackedVersion = struct.unpack('>H', ver[2:]) 187 | ver = ver[0:2] + str(unpackedVersion[0]) 188 | except: 189 | ver = "??? " + ver.strip() 190 | 191 | print "Build " + str(ver) + ":\t\t" +\ 192 | str(pair[1]) + " // " +\ 193 | str(pair[2]) + " unique // " +\ 194 | str(ratio) + " ratio // " +\ 195 | str(out) + " out // " +\ 196 | str(outPer) + " per request" 197 | 198 | print 199 | print "======================================================" 200 | print "Miscellaneous" 201 | print "======================================================" 202 | print "Bad: \t" + str(bad) 203 | print "No Version:\t" + str(no_version) 204 | 205 | print 206 | print 207 | 208 | print bandwidth["bad"] 209 | -------------------------------------------------------------------------------- /src/Buffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #ifndef __BUFFER__ 18 | #define __BUFFER__ 19 | 20 | #include "utypes.h" 21 | 22 | struct Buffer 23 | { 24 | byte *b; 25 | size_t len; 26 | 27 | Buffer() : b(NULL), len(0) {} 28 | Buffer(byte* buf, int l) : b(buf), len(l) {} 29 | byte operator [] (size_t i) const { return b[i]; } 30 | }; 31 | 32 | #endif // __BUFFER__ 33 | -------------------------------------------------------------------------------- /src/DHTMessage.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include // for strlen 18 | 19 | #include // for std::pair 20 | 21 | #include "DHTMessage.h" 22 | #include "bencoding.h" 23 | 24 | /** 25 | This version of DecodeMessageData() will extract a 'v' region. The region 26 | values will be assigned to vBuf once it has been determined that this message 27 | is a 'put' query. 28 | */ 29 | void DHTMessage::DecodeMessageData(byte* bencMessageBytes, int numBytes) 30 | { 31 | std::vector keys; 32 | keys.push_back("a\0v\0"); 33 | keys.push_back("r\0v\0"); 34 | if(!BencEntity::ParseInPlace(bencMessageBytes, *_bDict, bencMessageBytes + numBytes, keys, ®ion)){ 35 | _parseSuccessful = false; 36 | dhtMessageType = DHT_UNDEFINED_MESSAGE; 37 | return; 38 | } 39 | _parseSuccessful = true; 40 | DecodeMessageData(*_bDict); 41 | } 42 | 43 | DHTMessage::DHTMessage(byte* bencMessageBytes, int numBytes) 44 | { 45 | Init(); 46 | _bDict = new BencodedDict; 47 | DecodeMessageData(bencMessageBytes, numBytes); 48 | } 49 | 50 | DHTMessage::~DHTMessage() 51 | { 52 | delete _bDict; 53 | } 54 | 55 | void DHTMessage::Init() 56 | { 57 | replyDict = _bDict = NULL; 58 | _argumentsAreValid = _parseSuccessful = false; 59 | dhtMessageType = DHT_UNDEFINED_MESSAGE; 60 | dhtCommand = DHT_QUERY_UNDEFINED; 61 | type = command = NULL; 62 | id = NULL; 63 | args = NULL; 64 | read_only = false; 65 | region.first = region.second = NULL; 66 | portNum = vote = seed = scrape = noseed = sequenceNum = 0; 67 | error_code = 0; 68 | error_message = NULL; 69 | impliedPort = 0; 70 | _bDictForUser = NULL; 71 | } 72 | 73 | /** This version of DecodeMessageData() can NOT extract a 'v' region 74 | since the original string has already been parsed outside the scope of this object. */ 75 | void DHTMessage::DecodeMessageData(BencodedDict &bDict) 76 | { 77 | _bDictForUser = &bDict; 78 | 79 | // if not a dictionary, it's not a valid DHT RPC 80 | if(bDict.GetType() != BENC_DICT) 81 | { 82 | _parseSuccessful = false; 83 | dhtMessageType = DHT_UNDEFINED_MESSAGE; 84 | return; 85 | } 86 | _parseSuccessful = true; 87 | 88 | // extract the components common to all DHT messages 89 | transactionID.b = (byte*)bDict.GetString("t", &transactionID.len); 90 | version.b = (byte*)bDict.GetString("v", &version.len); 91 | external_ip.b = (byte*)bDict.GetString("ip", &external_ip.len); 92 | read_only = bDict.GetInt("ro", 0) != 0; 93 | 94 | type = bDict.GetString("y", 1); 95 | if (!type) 96 | return; 97 | 98 | switch(*type) 99 | { 100 | case 'q': 101 | { 102 | dhtMessageType = DHT_QUERY; 103 | DecodeQuery(bDict); 104 | break; 105 | } 106 | case 'r': 107 | { 108 | // Just extract the reply dictionary. Specific elements can be 109 | // extracted further down the call chain once it is known what 110 | // specific query this is a reply to. 111 | replyDict = bDict.GetDict("r"); 112 | if(replyDict){ 113 | id = (byte*)replyDict->GetString("id", DHT_ID_SIZE); 114 | dhtMessageType = DHT_RESPONSE; 115 | sequenceNum = replyDict->GetInt("seq", 1); 116 | vBuf.len = region.second - region.first; 117 | vBuf.b = region.first; 118 | signature.b = (byte*)replyDict->GetString("sig", &signature.len); 119 | key.b = (byte*)replyDict->GetString("k", &key.len); 120 | } 121 | else { 122 | dhtMessageType = DHT_UNDEFINED_MESSAGE; 123 | } 124 | 125 | break; 126 | } 127 | case 'e': 128 | { 129 | dhtMessageType = DHT_ERROR; 130 | DecodeError(bDict); 131 | break; 132 | } 133 | default: 134 | dhtMessageType = DHT_UNDEFINED_MESSAGE; 135 | } 136 | } 137 | 138 | void DHTMessage::DecodeError(BencodedDict &bDict) { 139 | BencodedList* l = bDict.GetList("e"); 140 | if (l != NULL) { 141 | error_code = l->GetInt(0); 142 | error_message = l->GetString(1); 143 | } 144 | } 145 | 146 | void DHTMessage::DecodeQuery(BencodedDict &bDict) 147 | { 148 | // Handle a query from a peer 149 | command = bDict.GetString("q"); 150 | if (!command) { 151 | dhtCommand = DHT_QUERY_UNDEFINED; 152 | return; // bad/missing command. 153 | } 154 | 155 | // get the arguments dictionary 156 | args = bDict.GetDict("a"); 157 | if (!args) { 158 | _argumentsAreValid = false; 159 | return; // bad/missing argument. 160 | } 161 | _argumentsAreValid = true; 162 | id = (byte*)args->GetString("id", DHT_ID_SIZE); 163 | 164 | // set the command enum and extract only arguments used by the command 165 | if(strcmp(command,"find_node") == 0){ 166 | dhtCommand = DHT_QUERY_FIND_NODE; 167 | target.b = (byte*)args->GetString("target", &target.len); 168 | if (target.len != DHT_ID_SIZE) _argumentsAreValid = false; 169 | } 170 | else if(strcmp(command,"get_peers") == 0){ 171 | dhtCommand = DHT_QUERY_GET_PEERS; 172 | infoHash.b = (byte*)args->GetString("info_hash", &infoHash.len); 173 | if (infoHash.len != DHT_ID_SIZE) _argumentsAreValid = false; 174 | filename.b = (byte*)args->GetString("name", &filename.len); 175 | scrape = args->GetInt("scrape", 0); 176 | noseed = args->GetInt("noseed", 0); 177 | } 178 | else if(strcmp(command,"announce_peer") == 0){ 179 | dhtCommand = DHT_QUERY_ANNOUNCE_PEER; 180 | infoHash.b = (byte*)args->GetString("info_hash", &infoHash.len); 181 | if (infoHash.len != DHT_ID_SIZE) _argumentsAreValid = false; 182 | portNum = args->GetInt("port", -1); 183 | token.b = (byte*)args->GetString("token", &token.len); 184 | filename.b = (byte*)args->GetString("name", &filename.len); 185 | seed = args->GetInt("seed", 0); 186 | impliedPort = args->GetInt("implied_port", 0); 187 | } 188 | else if(strcmp(command,"vote") == 0){ 189 | dhtCommand = DHT_QUERY_VOTE; 190 | target.b = (byte*)args->GetString("target", &target.len); 191 | if (target.len != DHT_ID_SIZE) _argumentsAreValid = false; 192 | token.b = (byte*)args->GetString("token", &token.len); 193 | vote = args->GetInt("vote", 0); 194 | filename.b = (byte*)args->GetString("name", &filename.len); 195 | } 196 | else if (strcmp(command,"get") == 0) { 197 | dhtCommand = DHT_QUERY_GET; 198 | target.b = (byte*)args->GetString("target", &target.len); 199 | if (target.len != DHT_ID_SIZE) _argumentsAreValid = false; 200 | sequenceNum = args->GetInt64("seq", 0); 201 | } 202 | else if (strcmp(command,"put") == 0) { 203 | dhtCommand = DHT_QUERY_PUT; 204 | token.b = (byte*)args->GetString("token", &token.len); 205 | vBuf.len = region.second - region.first; 206 | vBuf.b = region.first; 207 | signature.b = (byte*)args->GetString("sig", &signature.len); // 64 bytes 208 | if (signature.b && signature.len != DHT_SIG_SIZE) _argumentsAreValid = false; 209 | key.b = (byte*)args->GetString("k", &key.len); // 32 bytes 210 | if (key.b && key.len != DHT_KEY_SIZE) _argumentsAreValid = false; 211 | salt.b = (byte*)args->GetString("salt", &salt.len); 212 | sequenceNum = args->GetInt64("seq", 0); 213 | cas = args->GetInt("cas", 0); 214 | } 215 | else if (strcmp(command,"ping") == 0) { 216 | dhtCommand = DHT_QUERY_PING; 217 | } 218 | #if USE_HOLEPUNCH 219 | else if (strcmp(command, "punch") == 0) { 220 | dhtCommand = DHT_QUERY_PUNCH; 221 | target_ip.b = (byte*)args->GetString("ip", &target_ip.len); 222 | if (target_ip.b == NULL || target_ip.len != 6) 223 | _argumentsAreValid = false; 224 | } 225 | #endif 226 | else { 227 | // unknown messages with either a 'target' 228 | // or an 'info-hash' argument are treated 229 | // as a find node to not block future extensions 230 | dhtCommand = DHT_QUERY_FIND_NODE; // assume find_node... 231 | target.b = (byte*)args->GetString("target", &target.len); 232 | // check that there is a target; if not... 233 | if (target.b) { 234 | if (target.len != DHT_ID_SIZE) _argumentsAreValid = false; 235 | } 236 | else { 237 | target.b = (byte*)args->GetString("info_hash", &target.len); 238 | if (target.len != DHT_ID_SIZE) _argumentsAreValid = false; 239 | // see if there is an info_hash to use as a target; if not... 240 | if (!target.b) { 241 | // we have an invalid query command 242 | dhtCommand = DHT_QUERY_UNDEFINED; 243 | } 244 | } 245 | } 246 | } 247 | 248 | void DHTMessage::CopyFrom(DHTMessage &src) 249 | { 250 | if (&src == this) return; 251 | 252 | delete _bDict; // free ours if necessary 253 | _bDict = NULL; 254 | 255 | // If the source _bDict object is not null, then the src object allocated 256 | // its own BencodedDict. So this object needs to do its own allocation 257 | // before copying. 258 | if (src._bDict){ 259 | _bDict = new BencodedDict; 260 | *_bDict = *src._bDict; 261 | } 262 | 263 | // if _bDict is not null, set to our _bDict, otherwise the consumer provided 264 | // a BencodedDict when creating the source object, so point to the consumers dictionary. 265 | _bDictForUser = (_bDict)?_bDict:src._bDictForUser; 266 | 267 | _argumentsAreValid = src._argumentsAreValid; 268 | _parseSuccessful = src._parseSuccessful; 269 | type = src.type; 270 | command = src.command; 271 | filename = src.filename; 272 | id = src.id; 273 | target = src.target; 274 | infoHash = src.infoHash; 275 | token = src.token; 276 | portNum = src.portNum; 277 | vote = src.vote; 278 | seed = src.seed; 279 | scrape = src.scrape; 280 | args = src.args; 281 | transactionID = src.transactionID; 282 | version = src.version; 283 | key = src.key; 284 | sequenceNum = src.sequenceNum; 285 | signature = src.signature; 286 | region = src.region; 287 | vBuf = src.vBuf; 288 | target_ip = src.target_ip; 289 | impliedPort = src.impliedPort; 290 | cas = src.cas; 291 | 292 | // Warning: If this was set, it will still point to the dictionary 293 | // created by the original _bDict object 294 | replyDict = src.replyDict; 295 | } 296 | 297 | -------------------------------------------------------------------------------- /src/DHTMessage.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #ifndef __DHT_Message__ 18 | #define __DHT_Message__ 19 | 20 | #include "Buffer.h" 21 | #include // for std::pair 22 | #include "sha1_hash.h" 23 | 24 | enum 25 | { 26 | DHT_ID_SIZE = 20, 27 | DHT_KEY_SIZE = 32, 28 | DHT_SIG_SIZE = 64, 29 | DHT_MAX_SALT_SIZE = 64, 30 | DHT_ID_WORDCOUNT = DHT_ID_SIZE / sizeof(uint32) 31 | }; 32 | 33 | enum DHTMessageTypes 34 | { 35 | DHT_UNDEFINED_MESSAGE = 0, 36 | DHT_QUERY, // 'y' = q 37 | DHT_RESPONSE, // 'y' = r 38 | DHT_ERROR // 'y' = e 39 | }; 40 | 41 | enum DHTCommands 42 | { 43 | DHT_QUERY_UNDEFINED = 0, 44 | DHT_QUERY_PING, 45 | DHT_QUERY_FIND_NODE, 46 | DHT_QUERY_GET_PEERS, 47 | DHT_QUERY_ANNOUNCE_PEER, 48 | DHT_QUERY_VOTE, 49 | DHT_QUERY_GET, 50 | DHT_QUERY_PUT, 51 | #if USE_HOLEPUNCH 52 | DHT_QUERY_PUNCH 53 | #endif 54 | }; 55 | 56 | class BencodedDict; 57 | 58 | /** 59 | DHTMessage class 60 | 61 | This class is currently concerned with extracting the information for DHT 62 | queries. 63 | 64 | NOTE: The BencodedDictionary object from which the message data is extracted 65 | must be maintained for as long as you want to use the data in the public 66 | pointer memebers of the instance of this object. If you use the char* versions 67 | of the constructor and DecodeMessageData members, the internal object will be 68 | used, otherwise, your object will be used. 69 | 70 | Only those public data members that represent the arguments of the command 71 | of the message will have valid values or references. ALL OTHER MEMBERS 72 | WILL REMAIN UNINITIALIZED. 73 | 74 | When DecodeMessageData(char *) is used, the object will attempt to obtain the 75 | region for a 'v' element using the BencEntity::ParseInPlace function that 76 | supports returning a region. If it is found, it wil be mapped to Buffer 77 | vBuf for consumption. If len in the vBuf member is 0, then there is no v data. 78 | NOTE: The parse function returns the region for the first v element it finds. 79 | DHT RPC's have two defined v elements: 1) v in the arguments dictionary for 80 | the data element of a 'put' command, and 2) v for the version in the outer 81 | dictionary. A correctly formatted benc string should always have the put v 82 | positioned before the version v. Only if the command is 'put' will the public 83 | vBuf element be set. 84 | 85 | NOTE: 86 | Currently, for backwards compatibility, the BencEntity v pointer is still 87 | supported. This will eventually be removed in favor of the vBuf described 88 | above. 89 | 90 | There is very little targeted decoding of a reply ('y' = r) message that can 91 | be done by the object since reply messages don't contain an equivalent to a 92 | command that indicates what arguments should be expected in the message. It 93 | is up to the dht to use the transaction ID to associate a response with 94 | a query it emitted and then determine what parts it should extract. 95 | */ 96 | class DHTMessage 97 | { 98 | private: 99 | // when using with the bencoded string constructor, must make our own dictionary 100 | BencodedDict* _bDict; 101 | BencodedDict* _bDictForUser; 102 | bool _argumentsAreValid; 103 | bool _parseSuccessful; 104 | std::pair region; 105 | 106 | void CopyFrom(DHTMessage &src); 107 | void DecodeError(BencodedDict &bDict); 108 | void DecodeQuery(BencodedDict &bDict); 109 | void Init(); 110 | 111 | public: 112 | DHTMessage(); 113 | DHTMessage(DHTMessage &src); 114 | DHTMessage(BencodedDict &Dictionary); 115 | DHTMessage(byte* bencMessageBytes, int numBytes); 116 | ~DHTMessage(); 117 | DHTMessage& operator=(DHTMessage &rhs); 118 | 119 | void DecodeMessageData(byte* bencMessageBytes, int numBytes); 120 | void DecodeMessageData(BencodedDict &bDict); 121 | bool ValidArguments(){return _argumentsAreValid;} 122 | bool ParseSuccessful(){return _parseSuccessful;} 123 | 124 | /** Only use this function AFTER DecodeMessageData has been invoked */ 125 | BencodedDict& GetBencodedDictionary(){return *_bDictForUser;} 126 | 127 | DHTMessageTypes dhtMessageType; 128 | DHTCommands dhtCommand; 129 | 130 | // These public parts are "as is" and may or may not be valid 131 | // depending on the DHT message received 132 | cstr type; 133 | cstr command; // this command is mirrored in the command enum 134 | byte *id; 135 | int portNum; 136 | int vote; 137 | int seed; 138 | int noseed; 139 | int scrape; 140 | bool read_only; 141 | int64 sequenceNum; // 'seq' for mutable put 142 | int impliedPort; 143 | 144 | // expected current sequence number for compare-and-swap operations 145 | // if the blob we're about to overwrite has a different sequence number than 146 | // this, the write must fail and be retried. 147 | int64 cas; 148 | 149 | Buffer filename; 150 | Buffer infoHash; 151 | Buffer token; 152 | BencodedDict *args; 153 | Buffer transactionID; // tid 154 | Buffer version; // ver 155 | Buffer signature; // 'sig' for mutable put 156 | Buffer key; // 'k' for mutable put 157 | Buffer salt; // for mutable put 158 | Buffer target; 159 | Buffer external_ip; 160 | // this is used to point to the 'v' region of a "put" that is extracted when 161 | // a bencstring is parsed. It is assigned the region's values when it is 162 | // determined that a "put" request was made. Otherwise it is unassigned. 163 | Buffer vBuf; 164 | 165 | // this is the target IP address to punch a hole to for punch requests 166 | Buffer target_ip; 167 | 168 | // reply specific components 169 | BencodedDict* replyDict; 170 | 171 | int error_code; 172 | const char* error_message; 173 | }; 174 | 175 | inline DHTMessage::DHTMessage() 176 | { 177 | Init(); 178 | } 179 | 180 | inline DHTMessage::DHTMessage(BencodedDict &Dictionary) 181 | { 182 | Init(); 183 | DecodeMessageData(Dictionary); 184 | } 185 | 186 | inline DHTMessage& DHTMessage::operator=(DHTMessage &rhs) 187 | { 188 | CopyFrom(rhs); 189 | return *this; 190 | } 191 | 192 | inline DHTMessage::DHTMessage(DHTMessage &src) 193 | { 194 | CopyFrom(src); 195 | } 196 | 197 | 198 | #endif // __DHT_Message__ 199 | -------------------------------------------------------------------------------- /src/ExternalIPCounter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include "ExternalIPCounter.h" 18 | #include "sockaddr.h" // for SockAddr, is_ip_local 19 | 20 | #include // for std::make_pair 21 | #include 22 | 23 | ExternalIPCounter::ExternalIPCounter(SHACallback* sha) 24 | : _winnerV4(_map.end()), _winnerV6(_map.end()), _HeatStarted(0) 25 | , _TotalVotes(0) 26 | , _last_votes4(0) 27 | , _last_votes6(0) 28 | , _ip_change_observer(NULL) 29 | , _sha_callback(sha) 30 | {} 31 | 32 | void ExternalIPCounter::Rotate() 33 | { 34 | if (!IsExpired()) return; 35 | 36 | if (_winnerV4 != _map.end()) { 37 | byte ip_winner[4]; 38 | byte last_winner[4]; 39 | _winnerV4->first.compact(ip_winner, false); 40 | _last_winner4.compact(last_winner, false); 41 | // don't invoke the observer if last_votes is zero, that means this is the first 42 | // IP we've seen 43 | if(_last_votes4 && memcmp(ip_winner, last_winner, 4) && _ip_change_observer){ 44 | _ip_change_observer->on_ip_change(_winnerV4->first); 45 | } 46 | _last_winner4 = _winnerV4->first; 47 | _last_votes4 = _winnerV4->second; 48 | } 49 | if (_winnerV6 != _map.end()) { 50 | byte ip_winner[16]; 51 | byte last_winner[16]; 52 | _winnerV6->first.compact(ip_winner, false); 53 | _last_winner6.compact(last_winner, false); 54 | if(_last_votes6 && memcmp(ip_winner, last_winner, 16) && _ip_change_observer){ 55 | _ip_change_observer->on_ip_change(_winnerV6->first); 56 | } 57 | _last_winner6 = _winnerV6->first; 58 | _last_votes6 = _winnerV6->second; 59 | } 60 | 61 | _map.clear(); 62 | _winnerV6 = _map.end(); 63 | _winnerV4 = _map.end(); 64 | _HeatStarted = time(NULL); 65 | _TotalVotes = 0; 66 | _voterFilter.clear(); 67 | } 68 | 69 | void ExternalIPCounter::NetworkChanged() 70 | { 71 | // Force a rotation after the next vote 72 | _TotalVotes = EXTERNAL_IP_HEAT_MAX_VOTES; 73 | // Our IP likely changed so give minimal weight to previous votes 74 | for (auto& m : _map) 75 | m.second = 1; 76 | // peers who already voted may have legitmatly changed their vote 77 | // so don't filter them 78 | _voterFilter.clear(); 79 | } 80 | 81 | void ExternalIPCounter::Reset() 82 | { 83 | _TotalVotes = 0; 84 | _last_votes4 = _last_votes6 = 0; 85 | _map.clear(); 86 | _winnerV6 = _map.end(); 87 | _winnerV4 = _map.end(); 88 | _HeatStarted = time(NULL); 89 | _voterFilter.clear(); 90 | memset(&_last_winner4, 0, sizeof(SockAddr)); 91 | memset(&_last_winner6, 0, sizeof(SockAddr)); 92 | } 93 | 94 | void ExternalIPCounter::CountIP( const SockAddr& addr, int weight ) { 95 | // ignore anyone who claims our external IP is 96 | // INADDR_ANY or on a local network 97 | if(addr.is_addr_any() || is_ip_local(addr)) 98 | return; 99 | 100 | // timestamp the first time we get a vote 101 | if(! _HeatStarted) 102 | _HeatStarted = time(NULL); 103 | 104 | // attempt to insert this vote 105 | std::pair inserted = _map.insert(std::make_pair(addr, weight)); 106 | 107 | // if the new IP wasn't inserted, it's already in there 108 | // increase the vote counter 109 | if (!inserted.second) 110 | inserted.first->second += weight; 111 | 112 | // if the IP vout count exceeds the current leader, replace it 113 | if(addr.isv4() && (_winnerV4 == _map.end() || inserted.first->second > _winnerV4->second)) 114 | _winnerV4 = inserted.first; 115 | if(addr.isv6() && (_winnerV6 == _map.end() || inserted.first->second > _winnerV6->second)) 116 | _winnerV6 = inserted.first; 117 | _TotalVotes += weight; 118 | 119 | Rotate(); 120 | } 121 | 122 | void ExternalIPCounter::CountIP( const SockAddr& addr, const SockAddr& voter, int weight ) { 123 | // Don't let local peers vote on our IP address 124 | 125 | if (is_ip_local(voter)) 126 | return; 127 | 128 | // Accept an empty voter address. 129 | if ( ! voter.is_addr_any() ) { 130 | // TODO: we should support IPv6 voters as well 131 | // If voter is in bloom filter, return 132 | uint32 vaddr = voter.get_addr4(); 133 | sha1_hash key = _sha_callback((const byte*)&vaddr, 4); 134 | 135 | if (_voterFilter.test(key)) 136 | return; 137 | _voterFilter.add(key); 138 | } 139 | CountIP(addr, weight); 140 | } 141 | 142 | bool ExternalIPCounter::GetIP(SockAddr &addr) const { 143 | 144 | if (_last_votes4 >= _last_votes6 && _last_votes4 > 0) { 145 | addr = _last_winner4; 146 | return true; 147 | } else if (_last_votes6 > _last_votes4 && _last_votes6 > 0) { 148 | addr = _last_winner6; 149 | return true; 150 | } 151 | 152 | if (_winnerV4 != _map.end()) { 153 | if(_winnerV6 != _map.end() && _winnerV6->second > _winnerV4->second) { 154 | addr = _winnerV6->first; 155 | } else { 156 | addr = _winnerV4->first; 157 | } 158 | return true; 159 | } 160 | if (_winnerV6 != _map.end()) { 161 | addr = _winnerV6->first; 162 | return true; 163 | } 164 | return false; 165 | } 166 | 167 | bool ExternalIPCounter::GetIPv4(SockAddr &addr) const { 168 | if (!_last_winner4.is_addr_any()) { 169 | addr = _last_winner4; 170 | return true; 171 | } 172 | 173 | if(_winnerV4 != _map.end()) { 174 | addr = _winnerV4->first; 175 | return true; 176 | } 177 | return false; 178 | } 179 | 180 | bool ExternalIPCounter::GetIPv6(SockAddr &addr) const { 181 | if (!_last_winner6.is_addr_any()) { 182 | addr = _last_winner6; 183 | return true; 184 | } 185 | 186 | if(_winnerV6 != _map.end()) { 187 | addr = _winnerV6->first; 188 | return true; 189 | } 190 | return false; 191 | } 192 | 193 | // both thresholds must be crossed (time and count) 194 | bool ExternalIPCounter::IsExpired() const { 195 | if(!_HeatStarted) return false; 196 | if(_TotalVotes > EXTERNAL_IP_HEAT_MAX_VOTES || 197 | (_HeatStarted + EXTERNAL_IP_HEAT_DURATION) < time(NULL)) 198 | return true; 199 | return false; 200 | 201 | } 202 | 203 | -------------------------------------------------------------------------------- /src/ExternalIPCounter.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #ifndef __EXTERNAL_IP_COUNTER_H__ 18 | #define __EXTERNAL_IP_COUNTER_H__ 19 | 20 | // A voting heat ends after the max number of votes have 21 | // been counted or the heat duration (in seconds) expires, 22 | // whichever comes last 23 | #define EXTERNAL_IP_HEAT_DURATION 600 // 10 minutes 24 | #define EXTERNAL_IP_HEAT_MAX_VOTES 50 25 | 26 | #include 27 | #include "sockaddr.h" 28 | #include "bloom_filter.h" 29 | 30 | // allows the dht client to define what SHA-1 implementation to use 31 | typedef sha1_hash SHACallback(byte const* buf, int len); 32 | 33 | struct ip_change_observer { 34 | virtual ~ip_change_observer() {} 35 | virtual void on_ip_change(SockAddr const & new_ip) = 0; 36 | }; 37 | 38 | class ExternalIPCounter 39 | { 40 | public: 41 | ExternalIPCounter(SHACallback* sha); 42 | void set_ip_change_observer(ip_change_observer * ip_observer){_ip_change_observer = ip_observer;} 43 | void CountIP( const SockAddr& addr, const SockAddr& voter, int weight = 1); 44 | void CountIP( const SockAddr& addr, int weight = 1 ); 45 | bool GetIP( SockAddr& addr ) const; 46 | bool GetIPv4( SockAddr& addr ) const; 47 | bool GetIPv6( SockAddr& addr ) const; 48 | 49 | void SetHeatStarted(time_t t) { _HeatStarted = t; } 50 | 51 | void NetworkChanged(); 52 | 53 | void Reset(); 54 | 55 | private: 56 | void Rotate(); 57 | bool IsExpired() const; 58 | 59 | typedef std::map candidate_map; 60 | 61 | candidate_map _map; 62 | candidate_map::const_iterator _winnerV4; 63 | candidate_map::const_iterator _winnerV6; 64 | bloom_filter _voterFilter; 65 | time_t _HeatStarted; 66 | int _TotalVotes; 67 | 68 | SockAddr _last_winner4; 69 | SockAddr _last_winner6; 70 | int _last_votes4; 71 | int _last_votes6; 72 | ip_change_observer * _ip_change_observer; 73 | SHACallback* _sha_callback; 74 | }; 75 | 76 | 77 | #endif //__EXTERNAL_IP_COUNTER_H__ 78 | -------------------------------------------------------------------------------- /src/blockallocator.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Only used by the DHT. Maybe could be replaced someday 18 | // with something reasonable. 19 | #include "blockallocator.h" 20 | #include // for NULL and malloc() 21 | 22 | template T static inline exch(T &a, const T b) { 23 | const T t = a; 24 | a = b; 25 | return t; 26 | } 27 | 28 | void BlockAllocator::Grow() 29 | { 30 | byte *block = new byte[int64(_size) * _grow]; 31 | for(int i=_grow; --i>=0; ) { 32 | void *a = block + i * _size; 33 | *(void**)a = exch(_free, a); 34 | } 35 | } 36 | 37 | void *BlockAllocator::GetBlock() 38 | { 39 | // return malloc(_size); 40 | if (_free == NULL) Grow(); 41 | return exch(_free, *(void**)_free); 42 | } 43 | 44 | void BlockAllocator::FreeBlock(void *a) 45 | { 46 | *(void**)a = exch(_free, a); 47 | if(a != NULL && a != _free){ 48 | free(a); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/blockallocator.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // This is only used by the DHT, and is kind of silly. 18 | #ifndef __BLOCKALLOCATOR_H__ 19 | #define __BLOCKALLOCATOR_H__ 20 | 21 | #include "utypes.h" 22 | #include // for NULL 23 | 24 | struct BlockAllocator { 25 | BlockAllocator():_size(0),_grow(0), _free(NULL){} 26 | uint16 _size, _grow; 27 | void *_free; 28 | void Grow(); 29 | void *GetBlock(); 30 | void FreeBlock(void *a); 31 | }; 32 | 33 | template struct BlockAllocatorX : public BlockAllocator { 34 | 35 | T *Alloc() { return (T*)((BlockAllocator*)this)->GetBlock(); } 36 | void Free(T *a) { ((BlockAllocator*)this)->FreeBlock(a); } 37 | }; 38 | 39 | #define MAKE_BLOCK_ALLOCATOR(name,T,grow) BlockAllocatorX name = { sizeof(T), grow, NULL } 40 | 41 | #endif // __BLOCKALLOCATOR_H__ 42 | -------------------------------------------------------------------------------- /src/crc32c.cpp: -------------------------------------------------------------------------------- 1 | /* crc32c.c -- compute CRC-32C using the Intel crc32 instruction 2 | * Copyright (C) 2013 Mark Adler 3 | * Version 1.1 1 Aug 2013 Mark Adler 4 | */ 5 | 6 | /* 7 | This software is provided 'as-is', without any express or implied 8 | warranty. In no event will the author be held liable for any damages 9 | arising from the use of this software. 10 | 11 | Permission is granted to anyone to use this software for any purpose, 12 | including commercial applications, and to alter it and redistribute it 13 | freely, subject to the following restrictions: 14 | 15 | 1. The origin of this software must not be misrepresented; you must not 16 | claim that you wrote the original software. If you use this software 17 | in a product, an acknowledgment in the product documentation would be 18 | appreciated but is not required. 19 | 2. Altered source versions must be plainly marked as such, and must not be 20 | misrepresented as being the original software. 21 | 3. This notice may not be removed or altered from any source distribution. 22 | 23 | Mark Adler 24 | madler@alumni.caltech.edu 25 | */ 26 | 27 | /* Use hardware CRC instruction on Intel SSE 4.2 processors. This computes a 28 | CRC-32C, *not* the CRC-32 used by Ethernet and zip, gzip, etc. A software 29 | version is provided as a fall-back, as well as for speed comparisons. */ 30 | 31 | /* Version history: 32 | 1.0 10 Feb 2013 First version 33 | 1.1 1 Aug 2013 Correct comments on why three crc instructions in parallel 34 | */ 35 | 36 | //#include 37 | /* CRC-32C (iSCSI) polynomial in reversed bit order. */ 38 | #include "utypes.h" 39 | #include 40 | #define POLY 0x82f63b78 41 | 42 | /* Table for a quadword-at-a-time software crc. */ 43 | //static pthread_once_t crc32c_once_sw = PTHREAD_ONCE_INIT; 44 | static uint32 crc32c_table[256]; 45 | 46 | /* Construct table for software CRC-32C calculation. */ 47 | static void crc32c_init(void) 48 | { 49 | uint32 n, crc; 50 | 51 | for (n = 0; n < 256; n++) { 52 | crc = n; 53 | crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1; 54 | crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1; 55 | crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1; 56 | crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1; 57 | crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1; 58 | crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1; 59 | crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1; 60 | crc = crc & 1 ? (crc >> 1) ^ POLY : crc >> 1; 61 | crc32c_table[n] = crc; 62 | } 63 | } 64 | 65 | static struct init_crc32c 66 | { 67 | init_crc32c() 68 | { 69 | crc32c_init(); 70 | } 71 | } initialize_crc32c_table; 72 | 73 | /* Table-driven software version as a fall-back. This is about 15 times slower 74 | than using the hardware instructions. This assumes little-endian integers, 75 | as is the case on Intel processors that the assembler code here is for. */ 76 | 77 | uint32 crc32c(const unsigned char *buf, uint32 len=4) 78 | { 79 | const unsigned char *next = buf; 80 | uint64 crc; 81 | crc = 0xffffffff; 82 | while (len && ((uintptr_t)next & 7) != 0) { 83 | crc = crc32c_table[(crc ^ *next++) & 0xff] ^ (crc >> 8); 84 | len--; 85 | } 86 | while (len) { 87 | crc = crc32c_table[(crc ^ *next++) & 0xff] ^ (crc >> 8); 88 | len--; 89 | } 90 | return (uint32)crc ^ 0xffffffff; 91 | } 92 | -------------------------------------------------------------------------------- /src/dht.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | /** 18 | * @ingroup dht 19 | */ 20 | 21 | #include "DhtImpl.h" 22 | #include "sockaddr.h" 23 | #include "utypes.h" 24 | 25 | extern uint32 crc32c(const unsigned char* buf, uint32 len=4); 26 | 27 | smart_ptr create_dht(UDPSocketInterface *udp_socket_mgr, UDPSocketInterface *udp6_socket_mgr 28 | , DhtSaveCallback* save, DhtLoadCallback* load, ExternalIPCounter* eip) 29 | { 30 | return smart_ptr(new DhtImpl(udp_socket_mgr, udp6_socket_mgr, save, load, eip)); 31 | } 32 | 33 | IDht::~IDht() {} 34 | 35 | // See http://www.rasterbar.com/products/libtorrent/dht_sec.html 36 | uint32 generate_node_id_prefix(const SockAddr& addr, int random) 37 | { 38 | uint8 octets[8]; 39 | uint32 size; 40 | if (addr.isv6()) { 41 | // our external IPv6 address (network byte order) 42 | memcpy(octets, (uint const*)&addr._sin6d, 8); 43 | // If IPV6 44 | const static uint8 mask[] = { 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff }; 45 | for (int i = 0; i < 8; ++i) octets[i] &= mask[i]; 46 | size = 8; 47 | } else { 48 | // our external IPv4 address (network byte order) 49 | memcpy(octets, (uint const*)&addr._sin4, 4); 50 | // If IPV4 51 | // 00000011 00001111 00111111 11111111 52 | const static uint8 mask[] = { 0x03, 0x0f, 0x3f, 0xff }; 53 | for (int i = 0; i < 4; ++i) octets[i] &= mask[i]; 54 | size = 4; 55 | } 56 | octets[0] |= (random<<5) & 0xff; 57 | 58 | return crc32c((const unsigned char*)octets, size); 59 | } 60 | 61 | // See http://www.rasterbar.com/products/libtorrent/dht_sec.html 62 | bool DhtVerifyHardenedID(const SockAddr& addr, byte const* node_id) 63 | { 64 | if (is_ip_local(addr)) return true; 65 | uint seed = node_id[19]; 66 | uint32 crc32_hash = generate_node_id_prefix(addr, seed); 67 | //compare the first 21 bits only, so keep bits 17 to 21 only. 68 | byte from_hash = static_cast((crc32_hash >> 8) & 0xff); 69 | byte from_node = node_id[2] ; 70 | return node_id[0] == static_cast((crc32_hash >> 24) & 0xff) && 71 | node_id[1] == static_cast((crc32_hash >> 16) & 0xff) && 72 | (from_hash & 0xf8) == (from_node & 0xf8); 73 | } 74 | 75 | // See http://www.rasterbar.com/products/libtorrent/dht_sec.html 76 | void DhtCalculateHardenedID(const SockAddr& addr, byte *node_id) 77 | { 78 | uint seed = rand() & 0xff; 79 | uint32 crc32_hash = generate_node_id_prefix(addr, seed); 80 | node_id[0] = static_cast((crc32_hash >> 24) & 0xff); 81 | node_id[1] = static_cast((crc32_hash >> 16) & 0xff); 82 | node_id[2] = static_cast((crc32_hash >> 8) & 0xff); 83 | //need to change all bits except the first 5, xor randomizes the rest of the bits 84 | node_id[2] ^= static_cast(rand() & 0x7); 85 | for (int i = 3; i < 19; i++) 86 | node_id[i] = static_cast(rand() & 0xff); 87 | node_id[19] = static_cast(seed); 88 | } 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/dht.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #ifndef __DHT_H__ 18 | #define __DHT_H__ 19 | 20 | /** 21 | * @ingroup dht 22 | */ 23 | 24 | #include // for size_t 25 | #include 26 | #include "utypes.h" 27 | #include "sha1_hash.h" 28 | #include "sockaddr.h" 29 | #include "RefBase.h" 30 | #include "smart_ptr.h" 31 | 32 | class UDPSocketInterface; 33 | class ExternalIPCounter; 34 | class BencEntity; 35 | 36 | // callback types used in the DHT 37 | typedef void DhtVoteCallback(void *ctx, const byte *target, int const* votes); 38 | typedef void DhtHashFileNameCallback(void *ctx, const byte *info_hash, const byte *file_name); 39 | typedef void DhtAddNodesCallback(void *ctx, const byte *info_hash, const byte *peers, uint num_peers); 40 | typedef void DhtAddNodeResponseCallback(void*& userdata, bool is_response, SockAddr const& addr); 41 | typedef void DhtScrapeCallback(void *ctx, const byte *target, int downloaders, int seeds); 42 | typedef int DhtPutCallback(void * ctx, std::vector& buffer, int64& seq, SockAddr src); 43 | typedef int DhtPutDataCallback(void * ctx, std::vector const& buffer, int64 seq, SockAddr src); 44 | typedef void DhtPutCompletedCallback(void * ctx); 45 | typedef void DhtGetCallback(void* ctx, std::vector const& buffer); 46 | typedef void DhtLogCallback(char const* str); 47 | 48 | // asks the client to save the DHT state 49 | typedef void DhtSaveCallback(const byte* buf, int len); 50 | 51 | // asks the client to load the DHT state into ent 52 | typedef void DhtLoadCallback(BencEntity* ent); 53 | 54 | // called for all incoming and outgoing packets 55 | typedef void DhtPacketCallback(void const* buffer, size_t len, bool incoming); 56 | 57 | // should return the listen port to use for announce_peer. Return -1 to 58 | // use the implied_port feature (where the port is the same as for the DHT) 59 | typedef int DhtPortCallback(); 60 | 61 | // allows the dht client to define what SHA-1 implementation to use 62 | typedef sha1_hash DhtSHACallback(byte const* buf, int len); 63 | 64 | // callback to ed25519 crypto_sign_open used for message verification 65 | typedef bool Ed25519VerifyCallback(const unsigned char *signature, 66 | const unsigned char *message, size_t message_len, 67 | const unsigned char *key); 68 | 69 | typedef void Ed25519SignCallback(unsigned char *signature, 70 | const unsigned char *message, size_t message_len, 71 | const unsigned char *key); 72 | 73 | /** 74 | * DHT public interface 75 | */ 76 | 77 | class IDht : public RefBase 78 | { 79 | public: 80 | // Resolve gcc warning about nonvirtual destructor with virtual methods 81 | virtual ~IDht(); 82 | 83 | enum announce_flags_t 84 | { 85 | announce_seed = 1, 86 | announce_non_aggressive = 2, 87 | announce_only_get = 4, 88 | with_cas = 8, // use cas for DHT put 89 | }; 90 | 91 | virtual bool handleReadEvent(UDPSocketInterface *socket, byte *buffer, size_t len, const SockAddr& addr) = 0; 92 | virtual bool handleICMP(UDPSocketInterface *socket, byte *buffer, size_t len, const SockAddr& addr) = 0; 93 | virtual void Tick() = 0; 94 | virtual void Vote(void *ctx, const sha1_hash* info_hash, int vote, DhtVoteCallback* callb) = 0; 95 | 96 | virtual void Put( 97 | //pkey points to a 32-byte ed25519 public. 98 | const byte * pkey, 99 | const byte * skey, 100 | 101 | // This method is called in DhtSendRPC for Put. It takes v (from get 102 | // responses) as an input and may or may not change v to place in Put 103 | // messages. if the callback function returns a non-zero value, the 104 | // DhtProcess is aborted and the value is not stored back in the DHT. 105 | DhtPutCallback* put_callback, 106 | 107 | //called in CompleteThisProcess 108 | DhtPutCompletedCallback * put_completed_callback, 109 | 110 | // called every time we receive a blob from a node. This cannot be used 111 | // to modify and write back the data, this is just a sneak-peek of what's 112 | // likely to be in the final blob that's passed to put_callback if the 113 | // callback function returns a non-zero value, the DhtProcess is aborted 114 | // and the value is not stored back in the DHT. 115 | DhtPutDataCallback* put_data_callback, 116 | void *ctx, 117 | int flags = 0, 118 | 119 | // seq is an optional provided monotonically increasing sequence number to be 120 | // used in a Put request if the requester is keeping sequence number state 121 | // this number will be used if higher than any numbers gotten from peers 122 | int64 seq = 0) = 0; 123 | 124 | virtual sha1_hash ImmutablePut( 125 | const byte * data, 126 | size_t data_len, 127 | DhtPutCompletedCallback* put_completed_callback = nullptr, 128 | void *ctx = nullptr) = 0; 129 | 130 | virtual void ImmutableGet(sha1_hash target, DhtGetCallback* cb 131 | , void* ctx = nullptr) = 0; 132 | 133 | virtual void AnnounceInfoHash( 134 | const byte *info_hash, 135 | DhtAddNodesCallback *addnodes_callback, 136 | DhtPortCallback* pcb, 137 | cstr file_name, 138 | void *ctx, 139 | int flags = 0) = 0; 140 | 141 | 142 | virtual void SetId(byte new_id_bytes[20]) = 0; 143 | virtual void Enable(bool enabled, int rate) = 0; 144 | 145 | enum { 146 | DHT_ORIGIN_UNKNOWN = 0, 147 | DHT_ORIGIN_INITIAL, 148 | DHT_ORIGIN_IS_PEER, 149 | DHT_ORIGIN_FROM_PEER, // Introduced via FindPeers 150 | DHT_ORIGIN_INCOMING, // Contacted us first 151 | DHT_ORIGIN_COUNT 152 | }; 153 | 154 | virtual void SetVersion(char const* client, int major, int minor) = 0; 155 | virtual void SetRate(int bytes_per_second) = 0; 156 | 157 | virtual void SetExternalIPCounter(ExternalIPCounter* ip) = 0; 158 | virtual void SetPacketCallback(DhtPacketCallback* cb) = 0; 159 | virtual void SetAddNodeResponseCallback(DhtAddNodeResponseCallback* cb) = 0; 160 | virtual void SetSHACallback(DhtSHACallback* cb) = 0; 161 | virtual void SetEd25519VerifyCallback(Ed25519VerifyCallback* cb) = 0; 162 | virtual void SetEd25519SignCallback(Ed25519SignCallback* cb) = 0; 163 | virtual void AddBootstrapNode(SockAddr const& addr) = 0; 164 | 165 | // userdata pointer is passed on to the AddNodeReponseCallback 166 | virtual void AddNode(const SockAddr& addr, void* userdata, uint origin) = 0; 167 | virtual bool CanAnnounce() = 0; 168 | virtual void Close() = 0; 169 | virtual void Shutdown() = 0; 170 | virtual void Initialize(UDPSocketInterface *, UDPSocketInterface *) = 0; 171 | virtual bool IsEnabled() = 0; 172 | virtual void ForceRefresh() = 0; 173 | // do not respond to queries - for mobile nodes with data constraints 174 | virtual void SetReadOnly(bool readOnly) = 0; 175 | virtual void SetPingFrequency(int seconds) = 0; 176 | virtual void SetPingBatching(int num_pings) = 0; 177 | virtual void EnableQuarantine(bool e) = 0; 178 | 179 | virtual bool ProcessIncoming(byte *buffer, size_t len, const SockAddr& addr) = 0; 180 | #ifdef _DEBUG_MEM_LEAK 181 | virtual int FreeRequests() = 0; 182 | #endif 183 | virtual void DumpTracked() = 0; 184 | virtual void DumpBuckets() = 0; 185 | 186 | #ifdef DHT_SEARCH_TEST 187 | void RunSearches() = 0; 188 | #endif 189 | 190 | // 191 | // Linker 192 | // 193 | virtual int GetProbeQuota() = 0; 194 | virtual bool CanAddNode() = 0; 195 | virtual int GetNumPeers() = 0; 196 | virtual bool IsBusy() = 0; 197 | virtual int GetBootstrapState() = 0; 198 | virtual int GetRate() = 0; 199 | virtual int GetQuota() = 0; 200 | virtual int GetProbeRate() = 0; 201 | virtual int GetNumPeersTracked() = 0; 202 | virtual void Restart() = 0; 203 | virtual void GenerateId() = 0; 204 | 205 | // So we can be pointed to by a smart pointer. 206 | // Implementation can derive from RefBase. 207 | virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0; 208 | virtual ULONG STDMETHODCALLTYPE Release(void) = 0; 209 | }; 210 | 211 | smart_ptr create_dht(UDPSocketInterface *udp_socket_mgr, UDPSocketInterface *udp6_socket_mgr 212 | , DhtSaveCallback* save, DhtLoadCallback* load, ExternalIPCounter* eip = NULL); 213 | 214 | void set_log_callback(DhtLogCallback* log); 215 | 216 | #endif //__DHT_H__ 217 | 218 | -------------------------------------------------------------------------------- /unittests/Jamfile: -------------------------------------------------------------------------------- 1 | import testing ; 2 | import modules ; 3 | 4 | BOOST_ROOT = [ modules.peek : BOOST_ROOT ] ; 5 | use-project /boost : $(BOOST_ROOT) ; 6 | use-project /btdht : .. ; 7 | 8 | GTEST_ROOT = ../btutils/vendor/gtest-1.6.0 ; 9 | GMOCK_ROOT = ../btutils/vendor/gmock-1.6.0 ; 10 | 11 | lib pthread : : pthread shared ; 12 | 13 | project 14 | : requirements 15 | UnitTestUDPSocket.cpp 16 | $(GTEST_ROOT)/src/gtest-all.cc 17 | $(GMOCK_ROOT)/src/gmock-all.cc 18 | $(GMOCK_ROOT)/src/gmock_main.cc 19 | # ENABLE_SRP=1 20 | /btdht//btdht/static 21 | /boost//system/static 22 | linux:pthread 23 | 24 | $(GTEST_ROOT)/include 25 | $(GMOCK_ROOT)/include 26 | $(GTEST_ROOT) 27 | $(GMOCK_ROOT) 28 | gcc:GTEST_USE_OWN_TR1_TUPLE=1 29 | darwin:GTEST_USE_OWN_TR1_TUPLE=1 30 | 31 | ../src 32 | /opt/local/include 33 | 34 | gcc:-pthread 35 | clang:-Wall 36 | clang:-Wno-invalid-source-encoding 37 | clang:-Wno-multichar 38 | clang:-Wno-unused-private-field 39 | # on 40 | ; 41 | 42 | unit-test unittests : TestDhtID.cpp TestSecureDhtID.cpp TestDataStore.cpp 43 | TestDhtImpl.cpp TestDhtImplSpeed.cpp TestRoutingTable.cpp 44 | TestDhtImplResponse.cpp TestDHTMessageObject.cpp TestExternalIPCounter.cpp 45 | TestDHTRoutingTable.cpp ; 46 | -------------------------------------------------------------------------------- /unittests/TestDHT.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | #undef _M_CEE_PURE 19 | #undef new 20 | 21 | // TODO: SCOPED_TRACE in all fixtured tests, for convenience 22 | #if __cplusplus < 201103L && !defined _MSC_VER 23 | #define override 24 | #endif 25 | 26 | #include 27 | 28 | #ifdef _WIN32 29 | #include 30 | #else 31 | #include 32 | #endif 33 | 34 | #include 35 | using namespace boost::uuids::detail; 36 | 37 | #include "gtest/gtest.h" 38 | #include "gmock/gmock.h" 39 | using namespace ::testing; 40 | 41 | #include "utypes.h" 42 | #include "endian_utils.h" 43 | #include "dht.h" 44 | #include "DhtImpl.h" 45 | #include "bencoding.h" 46 | #include "sha1_hash.h" 47 | #include "UnitTestUDPSocket.h" 48 | 49 | // constant values to use in the dht under test 50 | // the dht ID should be 20 bytes (characters) long. 51 | const std::string DHTID_BYTES("AAAABBBBCCCCDDDDEEEE"); 52 | 53 | // defined in DhtImpl.cpp 54 | extern bool DhtVerifyHardenedID(const SockAddr& addr, byte const* node_id); 55 | extern void DhtCalculateHardenedID(const SockAddr& addr, byte *node_id); 56 | 57 | // defined in dht.cpp 58 | extern uint32 generate_node_id_prefix(const SockAddr& addr, int random); 59 | 60 | // utility objects 61 | inline sha1_hash sha1_callback(const byte* buf, int len) { 62 | sha1 hash; 63 | unsigned int digest[5]; 64 | hash.process_bytes(buf, len); 65 | hash.get_digest(digest); 66 | for(unsigned short i = 0; i < 5; i++) { 67 | digest[i] = htonl(digest[i]); 68 | } 69 | sha1_hash ret(reinterpret_cast(digest)); 70 | return ret; 71 | } 72 | 73 | class dht_test : public Test { 74 | protected: 75 | virtual void SetUp() {} 76 | virtual void TearDown() {} 77 | }; 78 | -------------------------------------------------------------------------------- /unittests/TestDHTMessageObject.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bittorrent/libbtdht/200b8adf83edeb84f1c81fb82de7e5b8624a5fa4/unittests/TestDHTMessageObject.cpp -------------------------------------------------------------------------------- /unittests/TestDataStore.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include "TestDHT.h" 18 | 19 | class data_store_test : public dht_test { 20 | protected: 21 | sha1_hash hash; 22 | DataStore ds; 23 | DataStore::pair_iterator it; 24 | PairContainerBase* containerPtr; 25 | DhtID key1, key2, key3, key4, key5; 26 | time_t cur_time; 27 | 28 | virtual void SetUp() override { 29 | SockAddr addr; 30 | 31 | key1.id[0] = 1; 32 | key2.id[0] = 2; 33 | key3.id[0] = 3; 34 | key4.id[0] = 4; 35 | key5.id[0] = 5; 36 | cur_time = time(NULL); 37 | hash = sha1_callback(reinterpret_cast(addr.get_hash_key()), 38 | addr.get_hash_key_len()); 39 | } 40 | }; 41 | 42 | TEST_F(data_store_test, AddPairToList) { 43 | // look for a key when no keys have been added 44 | it = ds.FindInList(key1, cur_time, hash); 45 | ASSERT_TRUE(it == ds.end()) << "A end iterator should have been returned" 46 | " from an attempt to find something in an empty list."; 47 | 48 | // add keys in reverse order and check that they are inserted in ascending 49 | // order 50 | ds.AddPairToList(hash, key3, 33, &containerPtr); 51 | ds.AddPairToList(hash, key2, 22, &containerPtr); 52 | ds.AddPairToList(hash, key1, 11, &containerPtr); 53 | ASSERT_EQ(3, ds.pair_list.size()); // there should now be 3 peers total 54 | 55 | std::pair > compare[] = { 56 | std::pair >(key1, 11), 57 | std::pair >(key2, 22), 58 | std::pair >(key3, 33), 59 | }; 60 | 61 | EXPECT_TRUE(std::equal(ds.pair_list.begin(), ds.pair_list.end(), compare)); 62 | 63 | // add the same key and see that the list size doesn't change 64 | ds.AddPairToList(hash, key3, 33, &containerPtr); 65 | ds.AddPairToList(hash, key3, 33, &containerPtr); 66 | ASSERT_EQ(3, ds.pair_list.size()); // there should now be 5 peers total 67 | 68 | // test the find for a key in the list 69 | it = ds.FindInList(key3, cur_time, hash); 70 | EXPECT_TRUE(it->second.value == 33); 71 | it = ds.FindInList(key2, cur_time, hash); 72 | EXPECT_TRUE(it->second.value == 22); 73 | it = ds.FindInList(key1, cur_time, hash); 74 | EXPECT_TRUE(it->second.value == 11); 75 | 76 | // test the find for a key that is NOT in the list 77 | it = ds.FindInList(key4, cur_time, hash); 78 | EXPECT_TRUE(it == ds.end()); 79 | } 80 | 81 | TEST_F(data_store_test, AddKeyToList) { 82 | // look for a key when no keys have been added 83 | it = ds.FindInList(key1, cur_time, hash); 84 | ASSERT_TRUE(it == ds.end()); 85 | 86 | // add keys in reverse order and check that they are inserted in ascending 87 | // order 88 | ds.AddKeyToList(hash, key3, &containerPtr); 89 | containerPtr->value = 33; 90 | ds.AddKeyToList(hash, key2, &containerPtr); 91 | containerPtr->value = 22; 92 | ds.AddKeyToList(hash, key1, &containerPtr); 93 | containerPtr->value = 11; 94 | ASSERT_EQ(3, ds.pair_list.size()); // there should now be 3 peers total 95 | 96 | std::pair > compare[] = { 97 | std::pair >(key1, 11), 98 | std::pair >(key2, 22), 99 | std::pair >(key3, 33), 100 | }; 101 | 102 | EXPECT_TRUE(std::equal(ds.pair_list.begin(), ds.pair_list.end(), compare)); 103 | 104 | // add the same key and see that the list size doesn't change 105 | ds.AddKeyToList(hash, key3, &containerPtr); 106 | ds.AddKeyToList(hash, key3, &containerPtr); 107 | ASSERT_EQ(3, ds.pair_list.size()); // there should now be 5 peers total 108 | 109 | // test the find for a key in the list 110 | it = ds.FindInList(key3, cur_time, hash); 111 | EXPECT_TRUE(it->second.value == 33); 112 | it = ds.FindInList(key2, cur_time, hash); 113 | EXPECT_TRUE(it->second.value == 22); 114 | it = ds.FindInList(key1, cur_time, hash); 115 | EXPECT_TRUE(it->second.value == 11); 116 | 117 | // test the find for a key that is NOT in the list 118 | it = ds.FindInList(key4, cur_time, hash); 119 | EXPECT_TRUE(it == ds.end()); 120 | } 121 | 122 | 123 | TEST_F(data_store_test, FindInList) { 124 | // look for a key when no keys have been added 125 | it = ds.FindInList(key1, cur_time, hash); 126 | ASSERT_TRUE(it == ds.end()) << "An end iterator should have been returned" 127 | " from an attempt to find something in an empty list."; 128 | 129 | // add keys in reverse order and check that they are inserted in ascending 130 | // order 131 | ds.AddPairToList(hash, key4, 44, &containerPtr); 132 | ds.AddPairToList(hash, key2, 22, &containerPtr); 133 | ds.AddPairToList(hash, key1, 11, &containerPtr); 134 | 135 | // look for a key that is not in the list 136 | it = ds.FindInList(key3, cur_time, hash); 137 | ASSERT_TRUE(it == ds.end()) << "An end iterator should have been returned" 138 | " from an attempt to find something not in the list."; 139 | } 140 | 141 | 142 | TEST_F(data_store_test, EliminateTimeouts) { 143 | ds.SetMaximumAge(7200); 144 | int numEliminated; 145 | 146 | // test elimination when list is empty 147 | try { 148 | // use a time greater than the max time provided to the constructor 149 | numEliminated = ds.EliminateTimeouts(8000); 150 | } catch(...) { 151 | FAIL() << "An exception was thrown when eliminating from an empty list."; 152 | } 153 | EXPECT_EQ(0, numEliminated) << 154 | "The list was empty, there shouldn't be any eliminations"; 155 | EXPECT_EQ(0, ds.pair_list.size()); 156 | 157 | // add 4 items (with default time of 0) and eliminate all 4 items 158 | ds.AddPairToList(hash, key1, 11, &containerPtr, 0); 159 | ds.AddPairToList(hash, key2, 22, &containerPtr, 0); 160 | ds.AddPairToList(hash, key3, 33, &containerPtr, 0); 161 | ds.AddPairToList(hash, key4, 44, &containerPtr, 0); 162 | try { 163 | // use a time greater than the max time provided to the constructor 164 | numEliminated = ds.EliminateTimeouts(8000); 165 | } catch(...) { 166 | FAIL() << 167 | "An exception was thrown when eliminating everything in the list."; 168 | } 169 | EXPECT_EQ(4, numEliminated) << 170 | "4 items should have been eliminated from the list"; 171 | EXPECT_EQ(0, ds.pair_list.size()); 172 | 173 | // add 4 items with none old enough to eliminate 174 | ds.AddPairToList(hash, key1, 11, &containerPtr, 7000); 175 | ds.AddPairToList(hash, key2, 22, &containerPtr, 7000); 176 | ds.AddPairToList(hash, key3, 33, &containerPtr, 7000); 177 | ds.AddPairToList(hash, key4, 44, &containerPtr, 7000); 178 | try { 179 | // use a time greater than the max time provided to the constructor 180 | numEliminated = ds.EliminateTimeouts(8000); 181 | } catch(...) { 182 | FAIL() << "An exception was thrown when eliminating nothing list."; 183 | } 184 | EXPECT_EQ(0, numEliminated) << 185 | "no items should have been eliminated from the list"; 186 | EXPECT_EQ(4, ds.pair_list.size()); 187 | // add 4 items with one old enough to eliminate 188 | ds.AddPairToList(hash, key1, 11, &containerPtr, 0); 189 | ds.AddPairToList(hash, key2, 22, &containerPtr, 7000); 190 | ds.AddPairToList(hash, key3, 33, &containerPtr, 7000); 191 | ds.AddPairToList(hash, key4, 44, &containerPtr, 7000); 192 | try { 193 | // use a time greater than the max time provided to the constructor 194 | numEliminated = ds.EliminateTimeouts(8000); 195 | } catch(...) { 196 | FAIL() << "An exception was thrown when eliminating from the beginning" 197 | " of the list."; 198 | } 199 | EXPECT_EQ(1, numEliminated) << 200 | "only 1 item should have been eliminated from the list"; 201 | EXPECT_EQ(3, ds.pair_list.size()); 202 | // repeat above but from the other end 203 | ds.AddPairToList(hash, key1, 11, &containerPtr, 7000); 204 | ds.AddPairToList(hash, key2, 22, &containerPtr, 7000); 205 | ds.AddPairToList(hash, key3, 33, &containerPtr, 7000); 206 | ds.AddPairToList(hash, key4, 44, &containerPtr, 0); 207 | try { 208 | // use a time greater than the max time provided to the constructor 209 | numEliminated = ds.EliminateTimeouts(8000); 210 | } catch(...) { 211 | FAIL() << 212 | "An exception was thrown when eliminating from the end of the list."; 213 | } 214 | EXPECT_EQ(1, numEliminated) << 215 | "only 1 item should have been eliminated from the list"; 216 | EXPECT_EQ(3, ds.pair_list.size()); 217 | // set up to eliminate from the middle 218 | ds.AddPairToList(hash, key1, 11, &containerPtr, 7000); 219 | ds.AddPairToList(hash, key2, 22, &containerPtr, 0); 220 | ds.AddPairToList(hash, key3, 33, &containerPtr, 0); 221 | ds.AddPairToList(hash, key4, 44, &containerPtr, 7000); 222 | try { 223 | // use a time greater than the max time provided to the constructor 224 | numEliminated = ds.EliminateTimeouts(8000); 225 | } catch(...) { 226 | FAIL() << "An exception was thrown when eliminating from the middle of" 227 | " the list."; 228 | } 229 | EXPECT_EQ(2, numEliminated) << 230 | "only 2 items should have been eliminated from the list"; 231 | EXPECT_EQ(2, ds.pair_list.size()); 232 | } 233 | 234 | TEST_F(data_store_test, RemoveItem) { 235 | ds.SetMaximumAge(7200); 236 | int numEliminated; 237 | 238 | // test removing from an empty list 239 | try { 240 | numEliminated = ds.RemoveItem(key2); 241 | } catch(...) { 242 | FAIL() << "An exception was thrown when eliminating from an empty list."; 243 | } 244 | EXPECT_EQ(0, numEliminated) << 245 | "The list was empty, there shouldn't be any eliminations"; 246 | EXPECT_EQ(0, ds.pair_list.size()); 247 | 248 | // add 4 items try to remove something not there 249 | ds.AddPairToList(hash, key1, 11, &containerPtr, 0); 250 | ds.AddPairToList(hash, key2, 22, &containerPtr, 0); 251 | ds.AddPairToList(hash, key3, 33, &containerPtr, 0); 252 | ds.AddPairToList(hash, key5, 55, &containerPtr, 0); 253 | try { 254 | numEliminated = ds.RemoveItem(key4); 255 | } catch(...) { 256 | FAIL() << "An exception was thrown when eliminating from an empty list."; 257 | } 258 | EXPECT_EQ(0, numEliminated) << "The item to be removed was not in the list," 259 | " nothing should have been removed"; 260 | EXPECT_EQ(4, ds.pair_list.size()); 261 | 262 | // remove from the beginning of the list 263 | try { 264 | numEliminated = ds.RemoveItem(key1); 265 | } catch(...) { 266 | FAIL() << "An exception was thrown when eliminating from an empty list."; 267 | } 268 | EXPECT_EQ(1, numEliminated) << "A single item should have been removed"; 269 | EXPECT_EQ(3, ds.pair_list.size()); 270 | 271 | // remove from the end of the list 272 | try { 273 | numEliminated = ds.RemoveItem(key5); 274 | } catch(...) { 275 | FAIL() << "An exception was thrown when eliminating from an empty list."; 276 | } 277 | EXPECT_EQ(1, numEliminated) << "A single item should have been removed"; 278 | EXPECT_EQ(2, ds.pair_list.size()); 279 | } 280 | 281 | TEST_F(data_store_test, EvictLeastUsed) { 282 | ds.SetMaximumAge(500); 283 | ds.SetMaximumSize(4); 284 | int numEliminated; 285 | 286 | SockAddr addr1, addr2, addr3, addr4; 287 | addr1.set_addr4(0xff000000); 288 | addr2.set_addr4(0x00ff0000); 289 | addr3.set_addr4(0x0000ff00); 290 | addr4.set_addr4(0x000000ff); 291 | 292 | // make a hash of the address for the DataStores to use to record usage of 293 | // an item 294 | sha1_hash hash1 = sha1_callback(reinterpret_cast 295 | (addr1.get_hash_key()), addr1.get_hash_key_len()); 296 | sha1_hash hash2 = sha1_callback(reinterpret_cast 297 | (addr2.get_hash_key()), addr2.get_hash_key_len()); 298 | sha1_hash hash3 = sha1_callback(reinterpret_cast 299 | (addr3.get_hash_key()), addr3.get_hash_key_len()); 300 | sha1_hash hash4 = sha1_callback(reinterpret_cast 301 | (addr4.get_hash_key()), addr4.get_hash_key_len()); 302 | 303 | // put the initial items into the list using hash1 304 | ds.AddPairToList(hash1, key1, 11, &containerPtr, 0); 305 | ds.AddPairToList(hash1, key2, 22, &containerPtr, 0); 306 | ds.AddPairToList(hash1, key3, 33, &containerPtr, 0); 307 | ds.AddPairToList(hash1, key4, 44, &containerPtr, 0); 308 | 309 | // put activity onto items 1, 2, and 4 (no activity on item 3) 310 | ds.FindInList(key1, cur_time, hash2); 311 | ds.FindInList(key2, cur_time, hash2); 312 | ds.FindInList(key4, cur_time, hash2); 313 | ds.FindInList(key1, cur_time, hash3); 314 | ds.FindInList(key2, cur_time, hash3); 315 | 316 | try { 317 | numEliminated = ds.EvictLeastUsed(); 318 | } catch(...) { 319 | FAIL() << 320 | "An exception was thrown when Evicting an unused item from the list"; 321 | } 322 | EXPECT_EQ(1, numEliminated) << "The item to be removed was not in the list," 323 | " nothing should have been removed"; 324 | EXPECT_EQ(3, ds.pair_list.size()); 325 | 326 | // look for key 3 - it should have been evicted 327 | it = ds.FindInList(key3, cur_time, hash4); 328 | ASSERT_FALSE(it != ds.end()) << 329 | "The item that should have been removed is still in the list."; 330 | 331 | // make an update happen, then add all items back so everything is in the 332 | // current bloom filter. 333 | // Items 1, 2, and 4 should now have a history in the previous 334 | // bloom filter estimated count. 335 | // use a time greater than half of the max age (500) specified in the 336 | // constructor 337 | ds.UpdateUsage(400); 338 | ds.AddPairToList(hash1, key1, 11, &containerPtr, 450); 339 | ds.AddPairToList(hash1, key2, 22, &containerPtr, 450); 340 | ds.AddPairToList(hash1, key3, 33, &containerPtr, 450); 341 | ds.AddPairToList(hash1, key4, 44, &containerPtr, 450); 342 | // again, item 3 should be evicted 343 | try { 344 | numEliminated = ds.EvictLeastUsed(); 345 | } catch(...) { 346 | FAIL() << "An exception was thrown when Evicting an unused item from" 347 | " the list"; 348 | } 349 | EXPECT_EQ(1, numEliminated) << "The item to be removed was not in the list," 350 | " nothing should have been removed"; 351 | EXPECT_EQ(3, ds.pair_list.size()); 352 | 353 | // look for key 3 - it should have been evicted 354 | it = ds.FindInList(key3, cur_time, hash4); 355 | ASSERT_TRUE(it == ds.end()) << 356 | "The item that should have been removed is still in the list."; 357 | 358 | // add a new item to the end of the list and see that it is evicted 359 | // without error 360 | ds.AddPairToList(hash1, key5, 55, &containerPtr, 455); 361 | try { 362 | numEliminated = ds.EvictLeastUsed(); 363 | } catch(...) { 364 | FAIL() << 365 | "An exception was thrown when Evicting an unused item from the list"; 366 | } 367 | EXPECT_EQ(1, numEliminated) << "The item to be removed was not in the list," 368 | " nothing should have been removed"; 369 | EXPECT_EQ(3, ds.pair_list.size()); 370 | 371 | // look for key 5 - it should have been evicted 372 | it = ds.FindInList(key3, cur_time, hash4); 373 | ASSERT_TRUE(it == ds.end()) << 374 | "The item that should have been removed is still in the list."; 375 | 376 | // make sure items 1,2, and 4 are still in the list 377 | EXPECT_TRUE(ds.FindInList(key1, cur_time, hash4) != ds.end()) << 378 | "Item 1 should still be in the list"; 379 | EXPECT_TRUE(ds.FindInList(key2, cur_time, hash4) != ds.end()) << 380 | "Item 2 should still be in the list"; 381 | EXPECT_TRUE(ds.FindInList(key4, cur_time, hash4) != ds.end()) << 382 | "Item 4 should still be in the list"; 383 | 384 | // add items 3 and 5 385 | // see that item 3 is evicted in favor of 5 when adding 5 to a full list 386 | ds.AddPairToList(hash2, key1, 11, &containerPtr, 459); 387 | ds.AddPairToList(hash2, key2, 22, &containerPtr, 459); 388 | ds.AddPairToList(hash2, key3, 33, &containerPtr, 459); 389 | ds.AddPairToList(hash2, key4, 44, &containerPtr, 459); 390 | ds.AddPairToList(hash2, key5, 55, &containerPtr, 459); 391 | EXPECT_EQ(4, ds.pair_list.size()) << 392 | "The list should be at the maximum size specified: 4"; 393 | EXPECT_FALSE(ds.FindInList(key3, cur_time, hash4) != ds.end()) << 394 | "Item 3 should have been evicted"; 395 | EXPECT_TRUE(ds.FindInList(key5, cur_time, hash4) != ds.end()) << 396 | "Item 5 should be in the list"; 397 | } 398 | -------------------------------------------------------------------------------- /unittests/TestDhtID.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include "TestDHT.h" 18 | 19 | class dht_id_test : public dht_test { 20 | protected: 21 | DhtID aDHT; 22 | DhtID bDHT; 23 | }; 24 | 25 | TEST_F(dht_id_test, init) { 26 | uint Sum = 0; 27 | 28 | for(uint x=0; x<5; ++x) { 29 | Sum += aDHT.id[x]; 30 | } 31 | EXPECT_EQ(0, Sum); 32 | } 33 | 34 | TEST_F(dht_id_test, equal) { 35 | aDHT.id[0] = bDHT.id[0] = 0x80000000; 36 | aDHT.id[1] = bDHT.id[1] = 0x0; 37 | aDHT.id[2] = bDHT.id[2] = 0x0; 38 | aDHT.id[3] = bDHT.id[3] = 0x0; 39 | aDHT.id[4] = bDHT.id[4] = 0x00000001; 40 | 41 | EXPECT_TRUE(aDHT == bDHT); 42 | } 43 | 44 | TEST_F(dht_id_test, unequal) { 45 | aDHT.id[0] = bDHT.id[0] = 0x80000000; 46 | aDHT.id[1] = bDHT.id[1] = 0x0; 47 | aDHT.id[2] = bDHT.id[2] = 0x0; 48 | aDHT.id[3] = bDHT.id[3] = 0x0; 49 | aDHT.id[4] = 0x0; 50 | bDHT.id[4] = 0x00000001; 51 | 52 | EXPECT_FALSE(aDHT == bDHT); 53 | } 54 | 55 | TEST_F(dht_id_test, lessthan_equal) { 56 | aDHT.id[0] = bDHT.id[0] = 0x800f0040; 57 | aDHT.id[1] = bDHT.id[1] = 0x80f00040; 58 | aDHT.id[2] = bDHT.id[2] = 0x030f005a; 59 | aDHT.id[3] = bDHT.id[3] = 0xe00f0040; 60 | aDHT.id[4] = bDHT.id[4] = 0x00c00001; 61 | EXPECT_FALSE(aDHT < bDHT); 62 | } 63 | 64 | TEST_F(dht_id_test, lessthan_less) { 65 | aDHT.id[0] = bDHT.id[0] = 0x80000000; 66 | aDHT.id[1] = bDHT.id[1] = 0x0; 67 | aDHT.id[2] = bDHT.id[2] = 0x0; 68 | aDHT.id[3] = bDHT.id[3] = 0x0; 69 | aDHT.id[4] = 0x0; 70 | bDHT.id[4] = 0x00000001; 71 | EXPECT_TRUE(aDHT < bDHT); 72 | } 73 | 74 | TEST_F(dht_id_test, lessthan_greater) { 75 | aDHT.id[0] = bDHT.id[0] = 0x80000000; 76 | aDHT.id[1] = bDHT.id[1] = 0x0; 77 | aDHT.id[2] = bDHT.id[2] = 0x0; 78 | aDHT.id[3] = bDHT.id[3] = 0x0; 79 | aDHT.id[4] = 0x00000001; 80 | bDHT.id[4] = 0x0; 81 | EXPECT_FALSE(aDHT < bDHT); 82 | } 83 | 84 | TEST_F(dht_id_test, notequal_high) { 85 | aDHT.id[0] = 0x80000000; 86 | bDHT.id[0] = 0x40000000; 87 | aDHT.id[1] = bDHT.id[1] = 0x0; 88 | aDHT.id[2] = bDHT.id[2] = 0x0; 89 | aDHT.id[3] = bDHT.id[3] = 0x0; 90 | aDHT.id[4] = bDHT.id[4] = 0x00000001; 91 | EXPECT_TRUE(aDHT != bDHT); 92 | } 93 | 94 | TEST_F(dht_id_test, notequal_low) { 95 | aDHT.id[0] = bDHT.id[0] = 0x80000000; 96 | aDHT.id[1] = bDHT.id[1] = 0x0; 97 | aDHT.id[2] = bDHT.id[2] = 0x0; 98 | aDHT.id[3] = bDHT.id[3] = 0x0; 99 | aDHT.id[4] = 0x0; 100 | bDHT.id[4] = 0x00000001; 101 | EXPECT_TRUE(aDHT != bDHT); 102 | } 103 | 104 | TEST_F(dht_id_test, notequal_false) { 105 | aDHT.id[0] = bDHT.id[0] = 0x80000000; 106 | aDHT.id[1] = bDHT.id[1] = 0x0; 107 | aDHT.id[2] = bDHT.id[2] = 0x0; 108 | aDHT.id[3] = bDHT.id[3] = 0x0; 109 | aDHT.id[4] = bDHT.id[4] = 0x00000001; 110 | EXPECT_FALSE(aDHT != bDHT); 111 | } 112 | -------------------------------------------------------------------------------- /unittests/TestDhtImpl.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include 18 | #include "utypes.h" 19 | #include "TestDhtImpl.h" 20 | 21 | int put_callback(void* ctx, std::vector& buffer, int64& seq, SockAddr src) { 22 | ++seq; 23 | if (ctx != NULL) { 24 | *(reinterpret_cast(ctx)) = seq; 25 | } 26 | char b[] = { '6', ':', 's', 'a', 'm', 'p', 'l', 'e' }; 27 | buffer.assign(b, b + sizeof(b)); 28 | return 0; 29 | } 30 | 31 | unsigned int count_set_bits(Buffer &data) { 32 | unsigned int count = 0; 33 | for(unsigned int x = 0; x < data.len; ++x) { 34 | count += std::bitset<8>(data.b[x]).count(); 35 | } 36 | return count; 37 | } 38 | 39 | TEST_F(dht_impl_test, SimpleInitializationTest) { 40 | impl->Enable(true, 0); 41 | ASSERT_EQ(0, impl->GetNumPeersTracked()); 42 | } 43 | 44 | TEST_F(dht_impl_test, PeersTest) { 45 | const char* DHTTestStoreFilename = "dhtstore.test"; 46 | 47 | DhtID id; 48 | for (int i = 0; i < 5; ++i) { 49 | id.id[i] = rand(); 50 | } 51 | 52 | impl->AddPeerToStore(id, DHTTestStoreFilename, 53 | SockAddr::parse_addr("10.0.1.0"), false); 54 | impl->AddPeerToStore(id, DHTTestStoreFilename, 55 | SockAddr::parse_addr("10.0.1.1"), false); 56 | impl->AddPeerToStore(id, DHTTestStoreFilename, 57 | SockAddr::parse_addr("10.0.1.2"), false); 58 | impl->AddPeerToStore(id, DHTTestStoreFilename, 59 | SockAddr::parse_addr("10.0.1.3"), true); 60 | impl->AddPeerToStore(id, DHTTestStoreFilename, 61 | SockAddr::parse_addr("10.0.1.4"), true); 62 | impl->AddPeerToStore(id, DHTTestStoreFilename, 63 | SockAddr::parse_addr("10.0.1.0"), true); 64 | 65 | str file_name = NULL; 66 | std::vector *peers = impl->GetPeersFromStore(id 67 | , &file_name, 200); 68 | EXPECT_TRUE(peers); 69 | if (peers) { 70 | ASSERT_EQ(5, peers->size()); 71 | } 72 | } 73 | 74 | TEST_F(dht_impl_test, TestTheUnitTestUDPSocketClass) { 75 | UnitTestUDPSocket TestSocket; 76 | SockAddr DummySockAddr; 77 | std::string resultData; 78 | // be careful with test data containing '\0' in the middle of the string. 79 | std::string testData("abcdefghijklmnopqrstuvwxyz\t1234567890\xf1\x04"); 80 | std::string additionalData("More Data"); 81 | 82 | // "send" some data 83 | TestSocket.Send(DummySockAddr, "", (const unsigned char*)(testData.c_str()), 84 | testData.size()); 85 | TestSocket.Send(DummySockAddr, "", 86 | (const unsigned char*)(additionalData.c_str()), additionalData.size()); 87 | 88 | // see that the test socket faithfully represents the data. 89 | resultData = TestSocket.GetSentDataAsString(1); 90 | EXPECT_EQ(additionalData, resultData); 91 | TestSocket.popPacket(); 92 | 93 | resultData = TestSocket.GetSentDataAsString(0); 94 | EXPECT_EQ(testData, resultData); 95 | } 96 | 97 | TEST_F(dht_impl_test, TestSendTo) { 98 | // the test data must be a valid bencoded string 99 | std::string 100 | testData("d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y1:qe"); 101 | 102 | impl->Enable(true, 0); 103 | 104 | impl->SendTo(peer_id.addr, 105 | (const unsigned char*)(testData.c_str()), testData.size()); 106 | EXPECT_TRUE(socket4.GetSentDataAsString() == testData); 107 | } 108 | 109 | TEST_F(dht_impl_test, TestPingRPC_ipv4) { 110 | // prepare the object for use 111 | impl->Enable(true, 0); 112 | init_dht_id(); 113 | 114 | // specify, parse, and send the message 115 | std::string testData("d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y" 116 | "1:qe"); 117 | ASSERT_NO_FATAL_FAILURE(fetch_response_to_message(&testData)); 118 | expect_transaction_id("aa", 2); 119 | expect_reply_id(); 120 | } 121 | 122 | TEST_F(dht_impl_test, TestPingRPC_ipv4_ParseKnownPackets) { 123 | // this test is aimed at the ParseKnownPackets member function that is optimized for a specific ping message format 124 | // as quoted from the code itself: 125 | // 126 | // currently we only know one packet type, the most common uT ping: 127 | // 'd1:ad2:id20:\t9\x93\xd4\xb7G\x10,Q\x9b\xf4\xc5\xfc\t\x87\x89\xeb\x93Q,e1:q4:ping1:t4:\x95\x00\x00\x001:v4:UT#\xa31:y1:qe' 128 | 129 | impl->Enable(true, 0); 130 | init_dht_id(); 131 | 132 | std::string testData("d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t4:wxyz" 133 | "1:v4:UTUT1:y1:qe"); 134 | ASSERT_NO_FATAL_FAILURE(fetch_response_to_message(&testData)); 135 | expect_transaction_id("wxyz", 4); 136 | expect_reply_id(); 137 | } 138 | 139 | TEST_F(dht_impl_test, TestGetPeersRPC_ipv4) { 140 | // prepare the object for use 141 | impl->Enable(true, 0); 142 | init_dht_id(); 143 | 144 | add_node("abcdefghij0101010101"); 145 | 146 | // specify, parse, and send the message 147 | std::string testData("d1:ad2:id20:abcdefghij01010101019:info_hash" 148 | "20:mnopqrstuvwxyz123456e1:q9:get_peers1:t2:aa1:y1:qe"); 149 | ASSERT_NO_FATAL_FAILURE(fetch_response_to_message(&testData)); 150 | expect_transaction_id("aa", 2); 151 | expect_reply_id(); 152 | 153 | // in the test environment there is exactly one node. 154 | // expect back the id provided in the query, ip=zzzz port=xx (since the querying node and this node are the same in this test) 155 | Buffer nodes; 156 | nodes.b = (unsigned char*)reply->GetString("nodes", &nodes.len); 157 | ASSERT_EQ(26, nodes.len) << "ERROR: The length of the 26 unsigned char" 158 | " node info extracted from the response arguments is the wrong size"; 159 | EXPECT_FALSE(memcmp((const void*)nodes.b, 160 | (const void *)"abcdefghij0101010101", 20)); 161 | 162 | // check that there is a token 163 | Buffer token; 164 | token.b = (unsigned char*)reply->GetString("token", &token.len); 165 | EXPECT_TRUE(token.len) << "There should have been a token of non-zero length"; 166 | } 167 | 168 | TEST_F(dht_impl_test, TestFindNodeRPC_ipv4) { 169 | // prepare the object for use 170 | impl->Enable(true, 0); 171 | init_dht_id(); 172 | 173 | add_node("abcdefghij0123456789"); 174 | 175 | // specify, parse, and send the message 176 | std::string testData("d1:ad2:id20:abcdefghij01234567896:target" 177 | "20:mnopqrstuvwxyz123456e1:q9:find_node1:t2:aa1:y1:qe"); 178 | ASSERT_NO_FATAL_FAILURE(fetch_response_to_message(&testData)); 179 | expect_transaction_id("aa", 2); 180 | expect_reply_id(); 181 | 182 | // There should be a single node, the one added above 183 | Buffer nodes; 184 | nodes.b = (unsigned char*)reply->GetString("nodes", &nodes.len); 185 | ASSERT_EQ(26, nodes.len) << "ERROR: The length of the 26 unsigned char" 186 | " node info extracted from the response arguments is the wrong size"; 187 | EXPECT_FALSE(memcmp((const void*)nodes.b, 188 | (const void *)"abcdefghij0123456789", 20)); 189 | } 190 | 191 | TEST_F(dht_impl_test, TestGetRPC_min_seq) { 192 | impl->Enable(true, 0); 193 | init_dht_id(); 194 | add_node("abababababababababab"); 195 | 196 | // put a mutable item for us to get 197 | std::vector token; 198 | fetch_token(token); 199 | len = bencoder(message, 1024) 200 | .d() 201 | ("a").d() 202 | ("id")("abcdefghij0123456789") 203 | ("k")("12345678901234567890123456789012") 204 | ("salt")("test salt") 205 | ("seq")(int64(2)) 206 | ("sig")("1234567890123456789012345678901234567890123456789012345678901234") 207 | ("token")(token) 208 | ("v")("mutable get test").e() 209 | ("q")("put") 210 | ("t")("aa") 211 | ("y")("q") 212 | .e() (); 213 | 214 | impl->ProcessIncoming(message, len, bind_addr); 215 | ASSERT_NO_FATAL_FAILURE(fetch_dict()); 216 | ASSERT_NO_FATAL_FAILURE(expect_response_type()); 217 | expect_transaction_id("aa", 2); 218 | 219 | // issue a get but specify the same seq, the node should omit the value in the response 220 | sha1_hash target = sha1_callback( 221 | reinterpret_cast("12345678901234567890123456789012test salt"), DHT_KEY_SIZE+9); 222 | Buffer hashInfo; 223 | hashInfo.b = (unsigned char*)target.value; 224 | hashInfo.len = SHA1_DIGESTSIZE; 225 | 226 | len = bencoder(message, 1024) 227 | .d() 228 | ("a").d() 229 | ("id")("abcdefghij0123456789") 230 | ("seq")(int64(2)) 231 | ("target")(hashInfo.b, hashInfo.len).e() 232 | ("q")("get") 233 | ("t")("aa") 234 | ("y")("q") 235 | .e() (); 236 | // parse and send the message constructed above 237 | socket4.Reset(); 238 | impl->ProcessIncoming(message, len, bind_addr); 239 | 240 | do { 241 | ASSERT_NO_FATAL_FAILURE(fetch_dict()); 242 | socket4.popPacket(); 243 | } while (!test_transaction_id("aa", 2)); 244 | 245 | ASSERT_NO_FATAL_FAILURE(expect_response_type()); 246 | expect_transaction_id("aa", 2); 247 | expect_reply_id(); 248 | // check that there is a token 249 | Buffer tok; 250 | reply->GetString("token", &tok.len); 251 | EXPECT_TRUE(tok.len) << "There should have been a token of non-zero length"; 252 | 253 | // check that there is a sequence number 254 | int64 seq = reply->GetInt64("seq", -1); 255 | EXPECT_EQ(2, seq); 256 | 257 | // check that there is no v 258 | Buffer value; 259 | value.b = (unsigned char*)reply->GetString("v", &value.len); 260 | ASSERT_EQ(0, value.len) << "Got a value when none was expected"; 261 | } 262 | 263 | TEST_F(dht_impl_test, TestPutRPC_ipv4) { 264 | // prepare the object for use 265 | impl->Enable(true, 0); 266 | init_dht_id(); 267 | add_node("abababababababababab"); 268 | 269 | // put a peer into the dht for it to work with 270 | impl->Update(peer_id, 0, false); 271 | 272 | DhtID target; 273 | target.id[0] = 'FFFF'; // FFFF 274 | target.id[1] = 'GGGG'; // GGGG 275 | target.id[2] = 'HHHH'; // HHHH 276 | target.id[3] = 'IIII'; // IIII 277 | target.id[4] = 'JJJJ'; // JJJJ 278 | 279 | // ***************************************************** 280 | // Make the dht emit an announce message (the get_peers rpc) 281 | // Just tell it that the target is only 16 bytes long (instead of 20) 282 | // ***************************************************** 283 | EXPECT_FALSE(impl->IsBusy()) << "The dht should not be busy yet"; 284 | int64 seq_result = 0; 285 | impl->Put(pkey, skey, &put_callback, NULL, NULL, &seq_result, 0); 286 | ASSERT_NO_FATAL_FAILURE(fetch_dict()); 287 | ASSERT_NO_FATAL_FAILURE(expect_query_type()); 288 | ASSERT_NO_FATAL_FAILURE(expect_command("get")); 289 | 290 | // get the transaction ID to use later 291 | Buffer tid; 292 | tid.b = (unsigned char*)dict->GetString("t" , &tid.len); 293 | EXPECT_EQ(4, tid.len); 294 | 295 | // now look into the query data 296 | expect_reply_id(); 297 | expect_target(); 298 | 299 | int64 seq = 0; 300 | const char* v = "sample"; 301 | len = bencoder(message, 1024) 302 | .d() 303 | ("ip")("abcdxy") ("r").d() 304 | ("id")((unsigned char*)&peer_id.id.id[0], 20) ("nodes")("") 305 | ("token")(response_token) ("seq")(seq) ("v")(v).e() 306 | ("t")(tid.b, tid.len) ("y")("r") 307 | .e() (); 308 | 309 | // clear the socket and "send" the reply 310 | socket4.Reset(); 311 | impl->ProcessIncoming(message, len, peer_id.addr); 312 | EXPECT_TRUE(impl->IsBusy()) << "The dht should still be busy"; 313 | 314 | //Checking the put messages 315 | ASSERT_NO_FATAL_FAILURE(fetch_dict()); 316 | ASSERT_NO_FATAL_FAILURE(expect_query_type()); 317 | ASSERT_NO_FATAL_FAILURE(expect_command("put")); 318 | expect_transaction_id(NULL, 4); 319 | 320 | // now look into the query data 321 | expect_reply_id(); 322 | EXPECT_EQ(seq + 1, reply->GetInt("seq")); 323 | expect_signature(); 324 | expect_token(response_token); 325 | expect_value(v, strlen(v)); 326 | EXPECT_EQ(int64(1), seq_result); 327 | } 328 | 329 | TEST_F(dht_impl_test, TestPutRPC_ipv4_cas) { 330 | // prepare the object for use 331 | impl->Enable(true, 0); 332 | init_dht_id(); 333 | 334 | add_node("abababababababababab"); 335 | 336 | // put a peer into the dht for it to work with 337 | impl->Update(peer_id, 0, false); 338 | 339 | DhtID target; 340 | target.id[0] = 'FFFF'; // FFFF 341 | target.id[1] = 'GGGG'; // GGGG 342 | target.id[2] = 'HHHH'; // HHHH 343 | target.id[3] = 'IIII'; // IIII 344 | target.id[4] = 'JJJJ'; // JJJJ 345 | 346 | EXPECT_FALSE(impl->IsBusy()) << "The dht should not be busy yet"; 347 | int64 seq = 1337; 348 | impl->Put(pkey, skey, &put_callback, NULL, NULL, NULL, IDht::with_cas, seq); 349 | ASSERT_NO_FATAL_FAILURE(fetch_dict()); 350 | ASSERT_NO_FATAL_FAILURE(expect_query_type()); 351 | ASSERT_NO_FATAL_FAILURE(expect_command("get")); 352 | expect_transaction_id(NULL, 4); 353 | Buffer tid; 354 | tid.b = (unsigned char*)dict->GetString("t" , &tid.len); 355 | EXPECT_EQ(4, tid.len); 356 | 357 | // now look into the query data 358 | expect_reply_id(); 359 | expect_target(); 360 | 361 | const char* v = "sample"; 362 | 363 | len = bencoder(message, 1024) 364 | .d() 365 | ("ip")("abcdxy") ("r").d() 366 | ("id")((unsigned char*)&peer_id.id.id[0], 20) ("nodes")("") 367 | ("token")(response_token) ("seq")(seq) ("v")(v).e() 368 | ("t")(tid.b, tid.len) ("y")("r") 369 | .e() (); 370 | 371 | printf("ProcessIncoming: %s\n", message); 372 | socket4.Reset(); 373 | impl->ProcessIncoming(message, len, peer_id.addr); 374 | EXPECT_TRUE(impl->IsBusy()) << "The dht should still be busy"; 375 | 376 | ASSERT_NO_FATAL_FAILURE(fetch_dict()); 377 | ASSERT_NO_FATAL_FAILURE(expect_query_type()); 378 | ASSERT_NO_FATAL_FAILURE(expect_command("put")); 379 | expect_transaction_id(NULL, 4); 380 | 381 | expect_cas(seq); 382 | expect_reply_id(); 383 | EXPECT_EQ(seq + 1, reply->GetInt("seq")); 384 | expect_signature(); 385 | expect_token(response_token); 386 | expect_value(v, strlen(v)); 387 | } 388 | 389 | TEST_F(dht_impl_test, TestPutRPC_ipv4_seq_fail) { 390 | // prepare the object for use 391 | impl->Enable(true, 0); 392 | init_dht_id(); 393 | 394 | add_node("ababababababababababab"); 395 | 396 | DhtID target; 397 | target.id[0] = 'FFFF'; // FFFF 398 | target.id[1] = 'GGGG'; // GGGG 399 | target.id[2] = 'HHHH'; // HHHH 400 | target.id[3] = 'IIII'; // IIII 401 | target.id[4] = 'JJJJ'; // JJJJ 402 | 403 | EXPECT_FALSE(impl->IsBusy()) << "The dht should not be busy yet"; 404 | int64 seq = 42; 405 | impl->Put(pkey, skey, &put_callback, NULL, NULL, NULL, IDht::with_cas, seq); 406 | ASSERT_NO_FATAL_FAILURE(fetch_dict()); 407 | ASSERT_NO_FATAL_FAILURE(expect_query_type()); 408 | ASSERT_NO_FATAL_FAILURE(expect_command("get")); 409 | expect_transaction_id(NULL, 4); 410 | Buffer tid; 411 | tid.b = (unsigned char*)dict->GetString("t" , &tid.len); 412 | EXPECT_EQ(4, tid.len); 413 | 414 | expect_reply_id(); 415 | expect_target(); 416 | 417 | len = bencoder(message, 1024) 418 | .d() 419 | ("ip")("abcdxy") ("r").d() 420 | ("id")((unsigned char*)&peer_id.id.id[0], 20) ("nodes")("") 421 | ("token")(response_token) ("seq")(seq) ("v")(v).e() 422 | ("t")(tid.b, tid.len) ("y")("r") 423 | .e() (); 424 | 425 | printf("ProcessIncoming: %s\n", message); 426 | socket4.Reset(); 427 | impl->ProcessIncoming(message, len, peer_id.addr); 428 | EXPECT_TRUE(impl->IsBusy()) << "The dht should still be busy"; 429 | 430 | ASSERT_NO_FATAL_FAILURE(fetch_dict()); 431 | ASSERT_NO_FATAL_FAILURE(expect_query_type()); 432 | ASSERT_NO_FATAL_FAILURE(expect_command("put")); 433 | tid.b = (unsigned char*)dict->GetString("t" , &tid.len); 434 | EXPECT_EQ(4, tid.len) << "transaction ID is wrong size"; 435 | 436 | expect_cas(seq); 437 | expect_reply_id(); 438 | EXPECT_EQ(seq + 1, reply->GetInt("seq")); 439 | expect_signature(); 440 | expect_token(response_token); 441 | expect_value(v, strlen(v)); 442 | 443 | // oh no we have a higher sequence number now and thus we shall complain 444 | len = bencoder(message, 1024) 445 | .d() 446 | ("e").l()(static_cast(302))("error message!").e() 447 | ("ip")("abcdxy") ("r").d() 448 | ("id")((unsigned char*)&peer_id.id.id[0], 20).e() 449 | ("t")(tid.b, tid.len) ("y")("e") 450 | .e() (); 451 | 452 | socket4.Reset(); 453 | EXPECT_TRUE(impl->ProcessIncoming(message, len, peer_id.addr)); 454 | EXPECT_TRUE(impl->IsBusy()) << "The dht should still be busy"; 455 | ASSERT_NO_FATAL_FAILURE(fetch_dict()); 456 | ASSERT_NO_FATAL_FAILURE(expect_query_type()); 457 | ASSERT_NO_FATAL_FAILURE(expect_command("get")); 458 | expect_transaction_id(NULL, 4); 459 | expect_reply_id(); 460 | expect_target(); 461 | } 462 | 463 | TEST_F(dht_impl_test, TestAnnouncePeerRPC_ipv4) { 464 | // before we can announce_peer, we must use get_peers to obtain a token 465 | // use this to get a token 466 | std::string bEncodedGetPeers("d1:ad2:id20:abcdefghij01010101019:info_hash" 467 | "20:mnopqrstuvwxyz123456e1:q9:get_peers1:t2:aa1:y1:qe"); 468 | 469 | // insert the token between these two strings 470 | std::string testDataPart1("d1:ad2:id20:abcdefghij01234567899:info_hash" 471 | "20:mnopqrstuvwxyz1234564:porti6881e5:token"); 472 | std::string testDataPart2("e1:q13:announce_peer1:t2:aa1:y1:qe"); 473 | std::string testData; 474 | 475 | // prepare the object for use 476 | impl->Enable(true, 0); 477 | init_dht_id(); 478 | 479 | // first do the GetPeers to obtain a token 480 | ASSERT_NO_FATAL_FAILURE(fetch_response_to_message(&bEncodedGetPeers)); 481 | get_reply(); 482 | Buffer token; 483 | token.b = (unsigned char*)reply->GetString("token", &token.len); 484 | EXPECT_TRUE(token.len); 485 | 486 | // build the announce_peer test string with the token 487 | fill_test_data(testData, token, testDataPart1, testDataPart2); 488 | 489 | socket4.Reset(); 490 | impl->Tick(); 491 | 492 | // now we can start testing the response to announce_peer 493 | // Send the announce_peer query 494 | ASSERT_NO_FATAL_FAILURE(fetch_response_to_message(&testData)); 495 | expect_transaction_id("aa", 2); 496 | expect_reply_id(); 497 | } 498 | 499 | TEST_F(dht_impl_test, TestAnnouncePeerWithImpliedport) { 500 | set_port(0x0101); 501 | 502 | // before we can announce_peer, we must use get_peers to obtain a token 503 | // use this to get a token 504 | std::string bEncodedGetPeers("d1:ad2:id20:abcdefghij01010101019:info_hash" 505 | "20:mnopqrstuvwxyz123456e1:q9:get_peers1:t2:aa1:y1:qe"); 506 | 507 | // insert the token between these two strings 508 | std::string testDataPart1("d1:ad2:id20:abcdefghij012345678912:implied_port" 509 | "i1e9:info_hash20:mnopqrstuvwxyz1234564:porti6881e5:token"); 510 | std::string testDataPart2("e1:q13:announce_peer1:t2:aa1:y1:qe"); 511 | std::string testData; 512 | 513 | // prepare the object for use 514 | impl->Enable(true, 0); 515 | init_dht_id(); 516 | 517 | // first do the GetPeers to obtain a token 518 | ASSERT_NO_FATAL_FAILURE(fetch_response_to_message(&bEncodedGetPeers)); 519 | get_reply(); 520 | Buffer token; 521 | token.b = (unsigned char*)reply->GetString("token", &token.len); 522 | EXPECT_TRUE(token.len); 523 | 524 | // build the announce_peer test string with the token 525 | fill_test_data(testData, token, testDataPart1, testDataPart2); 526 | socket4.Reset(); 527 | impl->Tick(); 528 | impl->ProcessIncoming((unsigned char*)&testData[0], 529 | testData.size(), bind_addr); 530 | 531 | DhtID id; 532 | // grab the id typed into the string at the top 533 | CopyBytesToDhtID(id, (unsigned char*)(&(testDataPart1.c_str()[12]))); 534 | 535 | std::vector::iterator it = impl->GetStorageForID(id); 536 | ASSERT_TRUE(it != impl->_peer_store.end()) << "The item was not stored"; 537 | ASSERT_EQ(1, it->peers.size()) << 538 | "there should be exactly one item in the store"; 539 | 540 | EXPECT_EQ(0x01, it->peers[0].port[0]) << 541 | "The port low unsigned char is wrong"; 542 | EXPECT_EQ(0x01, it->peers[0].port[1]) << 543 | "The port high unsigned char is wrong"; 544 | } 545 | 546 | TEST_F(dht_impl_test, TestAnnouncePeerWithOutImpliedport) { 547 | set_port(0xF0F0); 548 | std::string bEncodedGetPeers("d1:ad2:id20:abcdefghij01010101019:info_hash" 549 | "20:mnopqrstuvwxyz123456e1:q9:get_peers1:t2:aa1:y1:qe"); 550 | std::string testDataPart1("d1:ad2:id20:abcdefghij01234567899:info_hash" 551 | "20:mnopqrstuvwxyz1234564:porti514e5:token"); 552 | std::string testDataPart2("e1:q13:announce_peer1:t2:aa1:y1:qe"); 553 | std::string testData; 554 | 555 | // prepare the object for use 556 | impl->Enable(true, 0); 557 | init_dht_id(); 558 | 559 | // first do the GetPeers to obtain a token 560 | ASSERT_NO_FATAL_FAILURE(fetch_response_to_message(&bEncodedGetPeers)); 561 | get_reply(); 562 | Buffer token; 563 | token.b = (unsigned char*)reply->GetString("token", &token.len); 564 | EXPECT_TRUE(token.len); 565 | 566 | // build the announce_peer test string with the token 567 | fill_test_data(testData, token, testDataPart1, testDataPart2); 568 | socket4.Reset(); 569 | impl->Tick(); 570 | impl->ProcessIncoming((unsigned char*)&testData[0], 571 | testData.size(), bind_addr); 572 | 573 | DhtID id; 574 | // grab the id typed into the string at the top 575 | CopyBytesToDhtID(id, (unsigned char*)(&(testDataPart1.c_str()[12]))); 576 | 577 | std::vector::iterator it = impl->GetStorageForID(id); 578 | ASSERT_TRUE(it != impl->_peer_store.end()) << "The item was not stored"; 579 | ASSERT_EQ(1, it->peers.size()) << 580 | "there should be exactly one item in the store"; 581 | 582 | EXPECT_EQ(0x02, it->peers[0].port[0]) << 583 | "The port low unsigned char is wrong"; 584 | EXPECT_EQ(0x02, it->peers[0].port[1]) << 585 | "The port High unsigned char is wrong"; 586 | } 587 | 588 | TEST_F(dht_impl_test, TestVoteRPC_ipv4) { 589 | impl->Enable(true, 0); 590 | init_dht_id(); 591 | 592 | // get a token to use 593 | std::vector token; 594 | fetch_token(token); 595 | 596 | len = bencoder(message, 1024) 597 | .d() 598 | ("a").d() 599 | ("id")("abcdefghij0123456789") 600 | ("target")(make_random_key_20()) 601 | ("token")(token) 602 | ("vote")(int64(1)).e() 603 | ("q")("vote") 604 | ("t")("aa") 605 | ("y")("q") 606 | .e() (); 607 | 608 | ASSERT_NO_FATAL_FAILURE(fetch_response_to_message()); 609 | expect_transaction_id("aa", 2); 610 | 611 | expect_reply_id(); 612 | BencodedList *voteList = reply->GetList("v"); 613 | ASSERT_TRUE(voteList); 614 | ASSERT_EQ(5, voteList->GetCount()); 615 | 616 | // expect 1, 0, 0, 0, 0 617 | EXPECT_EQ(1, voteList->GetInt(0)) << 618 | "Expected 1 0 0 0 0 but received 0 - - - -"; 619 | EXPECT_EQ(0, voteList->GetInt(1)) << 620 | "Expected 1 0 0 0 0 but received 1 1 - - -"; 621 | EXPECT_EQ(0, voteList->GetInt(2)) << 622 | "Expected 1 0 0 0 0 but received 1 0 1 - -"; 623 | EXPECT_EQ(0, voteList->GetInt(3)) << 624 | "Expected 1 0 0 0 0 but received 1 0 0 1 -"; 625 | EXPECT_EQ(0, voteList->GetInt(4)) << 626 | "Expected 1 0 0 0 0 but received 1 0 0 0 1"; 627 | } 628 | 629 | // verify that multiple votes to the same target are recorded 630 | TEST_F(dht_impl_test, TestVoteRPC_ipv4_MultipleVotes) { 631 | impl->Enable(true, 0); 632 | init_dht_id(); 633 | 634 | // get a token to use 635 | std::vector token; 636 | fetch_token(token); 637 | 638 | std::vector target = make_random_key_20(); 639 | 640 | // vote 5 641 | len = bencoder(message, 1024) 642 | .d() 643 | ("a").d() 644 | ("id")("abcdefghij0123456789") 645 | ("target")(target) 646 | ("token")(token) 647 | ("vote")(int64(5)).e() 648 | ("q")("vote") 649 | ("t")("aa") 650 | ("y")("q") 651 | .e() (); 652 | 653 | // parse and send the first vote message 654 | impl->ProcessIncoming(message, len, bind_addr); 655 | 656 | // prepare to send the second vote message 657 | impl->Tick(); 658 | socket4.Reset(); 659 | 660 | // make the second vote message with a vote of 2 661 | len = bencoder(message, 1024) 662 | .d() 663 | ("a").d() 664 | ("id")("abcdefghij0123456789") 665 | ("target")(target) 666 | ("token")(token) 667 | ("vote")(int64(2)).e() 668 | ("q")("vote") 669 | ("t")("aa") 670 | ("y")("q") 671 | .e() (); 672 | 673 | // parse and send the second vote message 674 | ASSERT_NO_FATAL_FAILURE(fetch_response_to_message()); 675 | expect_transaction_id("aa", 2); 676 | expect_reply_id(); 677 | 678 | BencodedList *voteList = reply->GetList("v"); 679 | ASSERT_TRUE(voteList); 680 | ASSERT_EQ(5, voteList->GetCount()); 681 | 682 | // expect 0, 1, 0, 0, 1 683 | EXPECT_EQ(0, voteList->GetInt(0)) << 684 | "Expected 0 1 0 0 1 but received 1 - - - -"; 685 | EXPECT_EQ(1, voteList->GetInt(1)) << 686 | "Expected 0 1 0 0 1 but received 0 0 - - -"; 687 | EXPECT_EQ(0, voteList->GetInt(2)) << 688 | "Expected 0 1 0 0 1 but received 0 1 1 - -"; 689 | EXPECT_EQ(0, voteList->GetInt(3)) << 690 | "Expected 0 1 0 0 1 but received 0 1 0 1 -"; 691 | EXPECT_EQ(1, voteList->GetInt(4)) << 692 | "Expected 0 1 0 0 1 but received 0 1 0 0 0"; 693 | } 694 | 695 | TEST_F(dht_impl_test, TestDHTScrapeSeed0_ipv4) { 696 | init_dht_id(); 697 | impl->Enable(true, 0); 698 | 699 | // get a token to use 700 | std::vector token; 701 | fetch_token(token); 702 | 703 | // make a random info_hash key to use 704 | std::vector infoHashKey = make_random_key_20(); 705 | 706 | // prepare the first anounce_peer with seed = 0 707 | len = bencoder(message, 1024) 708 | .d() 709 | ("a").d() 710 | ("id")("abcdefghij0101010101") 711 | ("info_hash")(infoHashKey) 712 | ("port")(int64(6881)) 713 | ("seed")(int64(0)) 714 | ("token")(token) 715 | ("name")("test torrent").e() 716 | ("q")("announce_peer") 717 | ("t")("aa") 718 | ("y")("q") 719 | .e() (); 720 | 721 | announce_and_verify(); 722 | 723 | len = bencoder(message, 1024) 724 | .d() 725 | ("a").d() 726 | ("id")("abcdefghij0101010101") 727 | ("info_hash")(infoHashKey) 728 | ("port")(int64(6881)) 729 | ("scrape")(int64(1)).e() 730 | ("q")("get_peers") 731 | ("t")("aa") 732 | ("y")("q") 733 | .e() (); 734 | 735 | ASSERT_NO_FATAL_FAILURE(fetch_response_to_message()); 736 | expect_reply_id(); 737 | 738 | // verify that BFsd and BFpe are present 739 | // see BEP #33 for details of BFsd & BFpe 740 | Buffer bfsd; 741 | bfsd.b = (unsigned char*)reply->GetString("BFsd", &bfsd.len); 742 | ASSERT_TRUE(bfsd.b && bfsd.len == 256); 743 | EXPECT_EQ(0, count_set_bits(bfsd)) << "ERROR: Expected exactly 0 bits to be" 744 | " set in the seeds bloom filter 'BFsd'"; 745 | Buffer bfpe; 746 | bfpe.b = (unsigned char*)reply->GetString("BFpe", &bfpe.len); 747 | ASSERT_TRUE(bfpe.b && bfpe.len == 256); 748 | EXPECT_EQ(2, count_set_bits(bfpe)) << "ERROR: Expected exactly 2 bits to be" 749 | " set in the peers bloom filter 'BFpe'"; 750 | } 751 | 752 | TEST_F(dht_impl_test, TestDHTScrapeSeed1_ipv4) { 753 | init_dht_id(); 754 | impl->Enable(true, 0); 755 | 756 | // get a token to use 757 | std::vector token; 758 | fetch_token(std::string("abcdefghij0123456789"), token); 759 | 760 | // make a random info_hash key to use 761 | std::vector infoHashKey = make_random_key_20(); 762 | 763 | // prepare the first anounce_peer with seed = 0 764 | len = bencoder(message, 1024) 765 | .d() 766 | ("a").d() 767 | ("id")("abcdefghij0123456789") 768 | ("info_hash")(infoHashKey) 769 | ("port")(int64(6881)) 770 | ("seed")(int64(1)) 771 | ("token")(token) 772 | ("name")("test torrent").e() 773 | ("q")("announce_peer") 774 | ("t")("aa") 775 | ("y")("q") 776 | .e() (); 777 | 778 | announce_and_verify(); 779 | 780 | len = bencoder(message, 1024) 781 | .d() 782 | ("a").d() 783 | ("id")("abcdefghij0123456789") 784 | ("info_hash")(infoHashKey) 785 | ("port")(int64(6881)) 786 | ("scrape")(int64(1)).e() 787 | ("q")("get_peers") 788 | ("t")("aa") 789 | ("y")("q") 790 | .e() (); 791 | 792 | ASSERT_NO_FATAL_FAILURE(fetch_response_to_message()); 793 | expect_reply_id(); 794 | 795 | // verify that BFsd and BFpe are present 796 | // see BEP #33 for details of BFsd & BFpe 797 | Buffer bfsd; 798 | bfsd.b = (unsigned char*)reply->GetString("BFsd", &bfsd.len); 799 | ASSERT_TRUE(bfsd.b && bfsd.len == 256); 800 | EXPECT_EQ(2, count_set_bits(bfsd)) << "ERROR: Expected exactly 2 bits to be" 801 | " set in the seeds bloom filter 'BFsd'"; 802 | Buffer bfpe; 803 | bfpe.b = (unsigned char*)reply->GetString("BFpe", &bfpe.len); 804 | ASSERT_TRUE(bfpe.b && bfpe.len == 256); 805 | ASSERT_EQ(0, count_set_bits(bfpe)) << "ERROR: Expected exactly 0 bits to be" 806 | " set in the peers bloom filter 'BFpe'"; 807 | } 808 | 809 | TEST_F(dht_impl_test, TestDHTForNonexistantPeers_ipv4) { 810 | impl->Enable(true, 0); 811 | std::vector token; 812 | int port = 6881; 813 | std::string id("abcdefghij0123456789"); 814 | 815 | char itoa_buf[3]; 816 | for(int i = 1; i <= 13; i++) { 817 | sprintf(itoa_buf, "%02d", i); 818 | fetch_token(token); 819 | len = bencoder(message, 1024) 820 | .d() 821 | ("a").d() 822 | ("id")(id) 823 | ("info_hash")(make_random_key_20()) 824 | ("port")(int64(port)) 825 | ("name")(std::string("name") + itoa_buf) 826 | ("token")(token).e() 827 | ("q")("announce_peer") 828 | ("t")("zz") 829 | ("y")("q") 830 | .e() (); 831 | ASSERT_NO_FATAL_FAILURE(fetch_response_to_message()); 832 | expect_transaction_id("zz", 2); 833 | get_reply(); 834 | EXPECT_TRUE(reply->GetString("id", 20)); 835 | impl->Tick(); 836 | socket4.Reset(); 837 | } 838 | // now make a get_peers message with a nonexistant hash 839 | len = bencoder(message, 1024) 840 | .d() 841 | ("a").d() 842 | ("id")("abcdefghij0123456789") 843 | ("info_hash")("__nonexistenthash___") 844 | ("port")(int64(port)).e() 845 | ("q")("get_peers") 846 | ("t")("aa") 847 | ("y")("q") 848 | .e() (); 849 | ASSERT_NO_FATAL_FAILURE(fetch_response_to_message()); 850 | get_reply(); 851 | cstr values = reply->GetString("values", 6); 852 | EXPECT_FALSE(values) << "ERROR: There is a 'values' key in the reply" 853 | " dictionary for a non-existent hash"; 854 | } 855 | 856 | TEST_F(dht_impl_test, TestFutureCmdAsFindNode01_ipv4) { 857 | // unknown messages with either a 'target' 858 | // or an 'info-hash' argument are treated 859 | // as a find node to not block future extensions 860 | 861 | impl->Enable(true, 0); 862 | init_dht_id(); 863 | 864 | add_node("abcdefghij0123456789"); 865 | 866 | // specify, parse, and send the message 867 | // Set a TARGET with a 'future_cmd' command in this test 868 | // it sould be treated as a find_node command 869 | std::string testData("d1:ad2:id20:abcdefghij01234567896:target" 870 | "20:mnopqrstuvwxyz123456e1:q10:future_cmd1:t2:aa1:y1:qe"); 871 | ASSERT_NO_FATAL_FAILURE(fetch_response_to_message(&testData)); 872 | expect_transaction_id("aa", 2); 873 | expect_reply_id(); 874 | 875 | // There should be a single node - this one 876 | // expect back the id provided in the query, ip=zzzz port=xx (since the querying node and this node are the same in this test) 877 | Buffer nodes; 878 | nodes.b = (unsigned char*)reply->GetString("nodes", &nodes.len); 879 | ASSERT_EQ(26, nodes.len) << "ERROR: The length of the 26 unsigned char" 880 | " node info extracted from the response arguments is the wrong size"; 881 | EXPECT_FALSE(memcmp((const void*)nodes.b, 882 | (const void *)"abcdefghij0123456789", 20)); 883 | } 884 | 885 | TEST_F(dht_impl_test, TestFutureCmdAsFindNode02_ipv4) { 886 | // unknown messages with either a 'target' 887 | // or an 'info-hash' argument are treated 888 | // as a find node to not block future extensions 889 | impl->Enable(true, 0); 890 | init_dht_id(); 891 | add_node("abcdefghij0123456789"); 892 | 893 | // specify, parse, and send the message 894 | // Set an INFO_HASH with a 'future_cmd' command in this test 895 | // it sould be treated as a find_node command 896 | std::string testData("d1:ad2:id20:abcdefghij01234567899:info_hash" 897 | "20:mnopqrstuvwxyz123456e1:q10:future_cmd1:t2:aa1:y1:qe"); 898 | ASSERT_NO_FATAL_FAILURE(fetch_response_to_message(&testData)); 899 | expect_transaction_id("aa", 2); 900 | expect_reply_id(); 901 | 902 | // There should be a single node - this one 903 | // expect back the id provided in the query, ip=zzzz port=xx (since the querying node and this node are the same in this test) 904 | Buffer nodes; 905 | nodes.b = (unsigned char*)reply->GetString("nodes", &nodes.len); 906 | ASSERT_EQ(26, nodes.len) << "ERROR: The length of the 26 unsigned char" 907 | " node info extracted from the response arguments is the wrong size"; 908 | EXPECT_FALSE(memcmp((const void*)nodes.b, 909 | (const void *)"abcdefghij0123456789", 20)); 910 | } 911 | 912 | TEST_F(dht_impl_test, TestUnknownCmdNotProcessed_ipv4) { 913 | // unknown messages with either a 'target' 914 | // or an 'info-hash' argument are treated 915 | // as a find node to not block future extensions 916 | 917 | impl->Enable(true, 0); 918 | init_dht_id(); 919 | 920 | // specify, parse, and send the message 921 | // DO NOT set a target or info_hash with this 'unknown_cmd' command in this test 922 | // it sould NOT be treated as anything 923 | std::string testData("d1:ad2:id20:abcdefghij012345678911:unknown_arg" 924 | "20:mnopqrstuvwxyz123456e1:q11:unknown_cmd1:t2:aa1:y1:qe"); 925 | impl->ProcessIncoming((unsigned char*)testData.c_str(), testData.size(), 926 | bind_addr); 927 | 928 | // get the bencoded string out of the socket 929 | std::string bencMessage = socket4.GetSentDataAsString(); 930 | EXPECT_STREQ(bencMessage.c_str(), ""); 931 | } 932 | 933 | TEST_F(dht_impl_test, TestImmutablePutRPC_ipv4) { 934 | impl->Enable(true, 0); 935 | init_dht_id(); 936 | 937 | std::vector token; 938 | fetch_token(token); 939 | 940 | len = bencoder(message, 1024) 941 | .d() 942 | ("a").d() 943 | ("id")("abcdefghij0123456789") 944 | ("token")(token) 945 | ("v")("Immutable put test").e() 946 | ("q")("put") 947 | ("t")("aa") 948 | ("y")("q") 949 | .e() (); 950 | 951 | impl->ProcessIncoming(message, len, bind_addr); 952 | ASSERT_NO_FATAL_FAILURE(fetch_dict()); 953 | ASSERT_NO_FATAL_FAILURE(expect_response_type()); 954 | expect_transaction_id("aa", 2); 955 | expect_reply_id(); 956 | } 957 | 958 | TEST_F(dht_impl_test, TestImmutableGetRPC_ipv4) { 959 | impl->Enable(true, 0); 960 | init_dht_id(); 961 | add_node("abababababababababab"); 962 | 963 | std::vector token; 964 | fetch_token(token); 965 | len = bencoder(message, 1024) 966 | .d() 967 | ("a").d() 968 | ("id")("abcdefghij0123456789") 969 | ("token")(token) 970 | ("v")("Immutable get test").e() 971 | ("q")("put") 972 | ("t")("aa") 973 | ("y")("q") 974 | .e() (); 975 | 976 | impl->ProcessIncoming(message, len, bind_addr); 977 | ASSERT_NO_FATAL_FAILURE(fetch_dict()); 978 | ASSERT_NO_FATAL_FAILURE(expect_response_type()); 979 | expect_transaction_id("aa", 2); 980 | 981 | // *** SECOND: get something out *** 982 | sha1_hash target = sha1_callback( 983 | reinterpret_cast("18:Immutable get test"), 21); 984 | Buffer hashInfo; 985 | hashInfo.b = (unsigned char*)target.value; 986 | hashInfo.len = 20; 987 | 988 | len = bencoder(message, 1024) 989 | .d() 990 | ("a").d() 991 | ("id")("abcdefghij0123456789") 992 | ("target")(hashInfo.b, hashInfo.len).e() 993 | ("q")("get") 994 | ("t")("aa") 995 | ("y")("q") 996 | .e() (); 997 | // parse and send the message constructed above 998 | socket4.Reset(); 999 | impl->ProcessIncoming(message, len, bind_addr); 1000 | 1001 | do { 1002 | ASSERT_NO_FATAL_FAILURE(fetch_dict()); 1003 | socket4.popPacket(); 1004 | } while (!test_transaction_id("aa", 2)); 1005 | 1006 | ASSERT_NO_FATAL_FAILURE(expect_response_type()); 1007 | expect_transaction_id("aa", 2); 1008 | expect_reply_id(); 1009 | // check that there is a token 1010 | Buffer tok; 1011 | reply->GetString("token", &tok.len); 1012 | EXPECT_TRUE(tok.len) << "There should have been a token of non-zero length"; 1013 | 1014 | // get the nodes 1015 | Buffer nodes; 1016 | nodes.b = (unsigned char*)reply->GetString("nodes", &nodes.len); 1017 | EXPECT_TRUE(nodes.len) << "There should have been a node"; 1018 | 1019 | // get the value "v" 1020 | // v should be an bencentity of "18:Immutable get test". Using the GetString will strip out the 18 and just return the text. 1021 | Buffer value; 1022 | value.b = (unsigned char*)reply->GetString("v", &value.len); 1023 | ASSERT_EQ(18, value.len) << "The value is the wrong length"; 1024 | EXPECT_FALSE(memcmp((const void*)value.b, (const void *)"Immutable get test", 1025 | 18)); 1026 | } 1027 | 1028 | TEST_F(dht_impl_test, TestMultipleImmutablePutRPC_ipv4) { 1029 | impl->Enable(true, 0); 1030 | init_dht_id(); 1031 | 1032 | // if the same thing gets put multiple times there should only be one 1033 | // copy of it stored 1034 | // 20_byte_dhtid_val_ 1035 | ASSERT_NO_FATAL_FAILURE(immutable_put(std::string("20_byte_dhtid_val_00"), 1036 | "i-1e")); 1037 | ASSERT_NO_FATAL_FAILURE(immutable_put(std::string("20_byte_dhtid_val_01"), 1038 | "i-1e")); 1039 | ASSERT_NO_FATAL_FAILURE(immutable_put(std::string("20_byte_dhtid_val_02"), 1040 | "i-1e")); 1041 | ASSERT_NO_FATAL_FAILURE(immutable_put(std::string("20_byte_dhtid_val_03"), 1042 | "i-1e")); 1043 | ASSERT_NO_FATAL_FAILURE(immutable_put(std::string("20_byte_dhtid_val_04"), 1044 | "i-1e")); 1045 | EXPECT_EQ(1, impl->GetNumPutItems()) << 1046 | "ERROR: multiple instances of the same thing stored"; 1047 | 1048 | // now add different things and see the count increase 1049 | ASSERT_NO_FATAL_FAILURE(immutable_put(std::string("20_byte_dhtid_val_00"), 1050 | "i2e")); 1051 | ASSERT_NO_FATAL_FAILURE(immutable_put(std::string("20_byte_dhtid_val_01"), 1052 | "i3e")); 1053 | ASSERT_NO_FATAL_FAILURE(immutable_put(std::string("20_byte_dhtid_val_02"), 1054 | "i4e")); 1055 | ASSERT_NO_FATAL_FAILURE(immutable_put(std::string("20_byte_dhtid_val_03"), 1056 | "i5e")); 1057 | ASSERT_NO_FATAL_FAILURE(immutable_put(std::string("20_byte_dhtid_val_04"), 1058 | "i6e")); 1059 | EXPECT_EQ(6, impl->GetNumPutItems()) << 1060 | "ERROR: several different thinigs did not get stored"; 1061 | } 1062 | 1063 | TEST_F(dht_impl_test, TestMultipleImmutablePutAndGetRPC_ipv4) { 1064 | std::vector hashes[5]; 1065 | std::string putValues[5]; 1066 | 1067 | // prepare the object for use 1068 | impl->Enable(true, 0); 1069 | init_dht_id(); 1070 | 1071 | putValues[0] = ("i5e"); // test an integer 1072 | putValues[1] = ("l4:spam4:eggse"); // list 1073 | putValues[2] = ("d4:spaml1:a1:bee"); // dictionary with list 1074 | putValues[3] = ("4:spam"); // string 1075 | putValues[4] = ("d3:cow3:moo4:spam4:eggse"); // dictionary 1076 | 1077 | for(int x = 0; x < 5; ++x) { 1078 | sha1_hash hash = sha1_callback(reinterpret_cast 1079 | (putValues[x].c_str()), putValues[x].size()); 1080 | hashes[x].insert(hashes[x].end(), hash.value, hash.value + 20); 1081 | } 1082 | 1083 | ASSERT_NO_FATAL_FAILURE(immutable_put(std::string("20_byte_dhtid_val_00"), 1084 | putValues[0].c_str())); 1085 | ASSERT_NO_FATAL_FAILURE(immutable_put(std::string("20_byte_dhtid_val_01"), 1086 | putValues[1].c_str())); 1087 | ASSERT_NO_FATAL_FAILURE(immutable_put(std::string("20_byte_dhtid_val_02"), 1088 | putValues[2].c_str())); 1089 | ASSERT_NO_FATAL_FAILURE(immutable_put(std::string("20_byte_dhtid_val_03"), 1090 | putValues[3].c_str())); 1091 | ASSERT_NO_FATAL_FAILURE(immutable_put(std::string("20_byte_dhtid_val_04"), 1092 | putValues[4].c_str())); 1093 | EXPECT_EQ(5, impl->GetNumPutItems()) << 1094 | "ERROR: several different thinigs did not get stored"; 1095 | 1096 | // get the data out and see that it matches what was put 1097 | BencEntity* entity; 1098 | for(int x = 0; x < 5; ++x) { 1099 | len = bencoder(message, 1024) 1100 | .d() 1101 | ("a").d() 1102 | ("id")("abcdefghij0123456789") 1103 | ("target")(&(hashes[x][0]), 20).e() 1104 | ("q")("get") 1105 | ("t")("aa") 1106 | ("y")("q") 1107 | .e() (); 1108 | socket4.Reset(); 1109 | impl->Tick(); 1110 | impl->ProcessIncoming(message, len, bind_addr); 1111 | ASSERT_NO_FATAL_FAILURE(fetch_dict()); 1112 | ASSERT_NO_FATAL_FAILURE(expect_response_type()); 1113 | get_reply(); 1114 | entity = reply->Get("v"); 1115 | ASSERT_TRUE(entity); 1116 | std::string serialized_entity = SerializeBencEntity(entity); 1117 | EXPECT_EQ(putValues[x], serialized_entity); 1118 | EXPECT_FALSE(reply->GetString("key")); 1119 | EXPECT_FALSE(reply->GetString("sig")); 1120 | } 1121 | } 1122 | -------------------------------------------------------------------------------- /unittests/TestDhtImpl.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "TestDHT.h" 20 | #include "bencoder.h" 21 | #include "snprintf.h" 22 | #include "sockaddr.h" 23 | #include "utypes.h" 24 | 25 | static const unsigned char * pkey = reinterpret_cast 26 | ("dhuieheuu383y8yr7yy3hd3hdh3gfhg3"); 27 | static const unsigned char * skey = reinterpret_cast 28 | ("dhuieheuu383y8yr7yy3hd3hdh3gfhg3dhuieheuu383y8yr7yy3hd3hdh3gfhg3"); 29 | 30 | inline void fill_test_data(std::string &result, const Buffer &token, 31 | const std::string &one, const std::string &two) { 32 | char itoa_string[50]; 33 | snprintf(itoa_string, 50, "%u", static_cast(token.len)); 34 | 35 | result.clear(); 36 | result += one; 37 | result.append(itoa_string, itoa_string + strlen(itoa_string)); 38 | result.push_back(':'); 39 | result.append(token.b, token.b + token.len); 40 | result += two; 41 | } 42 | 43 | inline std::vector make_random_byte_string(unsigned int count) { 44 | std::vector key; 45 | for(unsigned int x = 0; x < count; ++x) { 46 | key.push_back(rand()%74 + 48); // make something in the alphanumeric range 47 | } 48 | return key; 49 | } 50 | 51 | inline std::vector make_random_key_20() { 52 | return make_random_byte_string(20); 53 | } 54 | 55 | inline void ed25519_callback(unsigned char * sig, const unsigned char * v, 56 | size_t size, const unsigned char * key) { 57 | for(int i = 0; i < 64; i++) { 58 | sig[i] = 'a'; 59 | } 60 | } 61 | 62 | inline bool ed25519_verify(const unsigned char *signature, 63 | const unsigned char *message, size_t message_len, 64 | const unsigned char *key) 65 | { 66 | return true; 67 | } 68 | 69 | class dht_impl_test : public dht_test { 70 | protected: 71 | SockAddr bind_addr; 72 | std::string addr_string; 73 | std::string port_string; 74 | 75 | UnitTestUDPSocket socket4; 76 | UnitTestUDPSocket socket6; 77 | smart_ptr impl; 78 | DhtPeerID peer_id; 79 | 80 | // used by fetch_*, set by using bencoder 81 | unsigned char message[1024]; 82 | int64 len; 83 | BencEntity output; 84 | // retrieved by fetch_* 85 | BencodedDict* dict; 86 | // set by all methods that use it, can manually be retrieved by calling 87 | // get_reply 88 | BencodedDict* reply; 89 | 90 | const char* response_token; 91 | const char* v; 92 | 93 | virtual void SetUp() override { 94 | set_addr('zzzz'); 95 | set_port(('x' << 8) + 'x'); 96 | socket4.SetBindAddr(bind_addr); 97 | 98 | impl = new DhtImpl(&socket4, &socket6); 99 | impl->SetSHACallback(&sha1_callback); 100 | impl->SetEd25519SignCallback(&ed25519_callback); 101 | impl->SetEd25519VerifyCallback(&ed25519_verify); 102 | impl->EnableQuarantine(false); 103 | 104 | peer_id.id.id[0] = '1111'; // 1111 105 | peer_id.id.id[1] = 'BBBB'; // BBBB 106 | peer_id.id.id[2] = 'CCCC'; // CCCC 107 | peer_id.id.id[3] = 'DDDD'; // DDDD 108 | peer_id.id.id[4] = '0000'; // 0000 109 | peer_id.addr.set_port(128); 110 | peer_id.addr.set_addr4(0xf0f0f0f0); 111 | 112 | dict = NULL; 113 | reply = NULL; 114 | 115 | response_token = "20_byte_reply_token."; 116 | v = "sample"; 117 | } 118 | 119 | void add_node(char const* id) { 120 | 121 | extern void CopyBytesToDhtID(DhtID &id, const byte *b); 122 | 123 | // add one peer whose first_seen is old enough to include in a node 124 | // response 125 | DhtPeerID tmp; 126 | CopyBytesToDhtID(tmp.id, (const byte*)id); 127 | tmp.addr.set_port(128); 128 | tmp.addr.set_addr4(0xf0f0f0f0); 129 | impl->Update(tmp, IDht::DHT_ORIGIN_UNKNOWN, true, 10); 130 | } 131 | 132 | virtual void TearDown() override { 133 | } 134 | 135 | void set_addr(int32 v) { 136 | bind_addr.set_addr4(v); 137 | addr_string.clear(); 138 | #if BT_LITTLE_ENDIAN 139 | for(int i = 3; i >= 0; i--) 140 | #else 141 | for(int i = 0; i <= 3; i++) 142 | #endif 143 | { 144 | addr_string.push_back(reinterpret_cast(&v)[i]); 145 | } 146 | } 147 | 148 | void set_port(int16 v) { 149 | bind_addr.set_port(v); 150 | port_string.clear(); 151 | #if BT_LITTLE_ENDIAN 152 | port_string.push_back(reinterpret_cast(&v)[1]); 153 | port_string.push_back(reinterpret_cast(&v)[0]); 154 | #else 155 | port_string.push_back(reinterpret_cast(&v)[0]); 156 | port_string.push_back(reinterpret_cast(&v)[1]); 157 | #endif 158 | } 159 | 160 | void init_dht_id() { 161 | impl->SetId((unsigned char*)DHTID_BYTES.c_str()); 162 | } 163 | 164 | void fetch_dict() { 165 | std::string benc_message = socket4.GetSentDataAsString(socket4.numPackets()-1); 166 | // should not store expected dict in a BencodedDict because if the output 167 | // is somehow not a dict that will trigger a non-unittest assert, and we 168 | // wish to handle that case ourselves 169 | BencEntity::Parse((const unsigned char *)benc_message.c_str(), output, 170 | (const unsigned char *) 171 | (benc_message.c_str() + benc_message.length())); 172 | ASSERT_EQ(BENC_DICT, output.bencType); 173 | dict = BencEntity::AsDict(&output); 174 | ASSERT_TRUE(dict); 175 | ASSERT_EQ(BENC_DICT, dict->bencType); 176 | reply = NULL; 177 | } 178 | 179 | inline void get_reply() { 180 | if (reply == NULL) { 181 | cstr type = dict->GetString("y", 1); 182 | ASSERT_TRUE(type); 183 | if (type[0] == 'r') { 184 | reply = dict->GetDict("r"); 185 | } else if (type[0] == 'q') { 186 | reply = dict->GetDict("a"); 187 | } else { 188 | FAIL() << "message has unknown type"; 189 | } 190 | ASSERT_TRUE(reply); 191 | } 192 | } 193 | 194 | void expect_response_type() { 195 | cstr type = dict->GetString("y", 1); 196 | ASSERT_TRUE(type); 197 | ASSERT_EQ('r', *type); 198 | } 199 | 200 | void expect_query_type() { 201 | cstr type = dict->GetString("y", 1); 202 | ASSERT_TRUE(type); 203 | ASSERT_EQ('q', *type); 204 | } 205 | 206 | void expect_command(const char* command) { 207 | cstr c = dict->GetString("q", strlen(command)); 208 | ASSERT_TRUE(c); 209 | ASSERT_STREQ(command, c); 210 | } 211 | 212 | void expect_ip() { 213 | Buffer ip; 214 | ip.b = (unsigned char*)dict->GetString("ip", &ip.len); 215 | ASSERT_EQ(6, ip.len); 216 | EXPECT_FALSE(memcmp((const void*)ip.b, 217 | (const void *)addr_string.c_str(), 4)); 218 | EXPECT_FALSE(memcmp((const void*)(ip.b + 4), 219 | (const void *)port_string.c_str(), 2)); 220 | } 221 | 222 | void fetch_response_to_message(std::string* data = NULL) { 223 | if (data != NULL) { 224 | len = data->size(); 225 | assert(len <= 1024); 226 | memcpy(message, data->c_str(), len); 227 | } 228 | socket4.Reset(); 229 | impl->ProcessIncoming(message, len, bind_addr); 230 | ASSERT_NO_FATAL_FAILURE(fetch_dict()); 231 | ASSERT_NO_FATAL_FAILURE(expect_response_type()); 232 | ASSERT_NO_FATAL_FAILURE(expect_ip()); 233 | } 234 | 235 | bool test_transaction_id(const char* id, int id_len) { 236 | Buffer tid; 237 | tid.b = (unsigned char*)dict->GetString("t", &tid.len); 238 | if (tid.b == NULL) return false; 239 | if (id_len != tid.len) return false; 240 | 241 | return memcmp((const void*)tid.b, (const void *)id, id_len) == 0; 242 | } 243 | 244 | void expect_transaction_id(const char* id, int id_len) { 245 | Buffer tid; 246 | tid.b = (unsigned char*)dict->GetString("t", &tid.len); 247 | ASSERT_EQ(id_len, tid.len); 248 | if (id != NULL) { 249 | EXPECT_FALSE(memcmp((const void*)tid.b, (const void *)id, id_len)); 250 | } 251 | } 252 | 253 | void expect_reply_id(const char* expected = NULL) { 254 | ASSERT_NO_FATAL_FAILURE(get_reply()); 255 | unsigned char *id = (unsigned char*)reply->GetString("id", 20); 256 | ASSERT_TRUE(id); 257 | if (expected == NULL) { 258 | EXPECT_FALSE(memcmp((const void*)id, (const void *)DHTID_BYTES.c_str(), 259 | 20)); 260 | } else { 261 | EXPECT_FALSE(memcmp((const void*)id, (const void *)expected, 20)); 262 | } 263 | } 264 | 265 | void expect_token(const char* response_token) { 266 | ASSERT_NO_FATAL_FAILURE(get_reply()); 267 | Buffer token; 268 | token.b = (unsigned char*)reply->GetString("token" , &token.len); 269 | EXPECT_EQ(20, token.len); 270 | if (response_token != NULL) { 271 | EXPECT_FALSE(memcmp(response_token, token.b, 20)) << 272 | "ERROR: announced token is wrong"; 273 | } 274 | } 275 | 276 | void expect_signature() { 277 | ASSERT_NO_FATAL_FAILURE(get_reply()); 278 | Buffer sig; 279 | sig.b = (unsigned char*)reply->GetString("sig" , &sig.len); 280 | EXPECT_EQ(64, sig.len); 281 | } 282 | 283 | void expect_value(const char* value, int value_len) { 284 | ASSERT_NO_FATAL_FAILURE(get_reply()); 285 | Buffer v_out; 286 | v_out.b = (unsigned char*)reply->GetString("v" , &v_out.len); 287 | EXPECT_EQ(value_len, v_out.len); 288 | EXPECT_FALSE(memcmp(value, v_out.b, value_len)) << "ERROR: v is wrong"; 289 | } 290 | 291 | void expect_cas(uint64 expected_cas) { 292 | ASSERT_NO_FATAL_FAILURE(get_reply()); 293 | uint64 cas; 294 | cas = reply->GetInt("cas", 0); 295 | EXPECT_EQ(expected_cas, cas); 296 | } 297 | 298 | void expect_target() { 299 | ASSERT_NO_FATAL_FAILURE(get_reply()); 300 | Buffer pkey_buf; 301 | pkey_buf.b = (unsigned char*)reply->GetString("target" , &pkey_buf.len); 302 | EXPECT_EQ(20, pkey_buf.len); 303 | EXPECT_FALSE(memcmp(sha1_callback(pkey, 32).value, pkey_buf.b, 20)) << 304 | "ERROR: pkey is not the correct target"; 305 | } 306 | 307 | void fetch_token(std::vector &token) { 308 | return fetch_token(std::string("abcdefghij0101010101"), token); 309 | } 310 | 311 | void fetch_token(const std::string &id, std::vector &token_bytes) { 312 | assert(id.size() == 20); 313 | Buffer token; 314 | 315 | std::string get_peers = "d1:ad2:id20:" + id + 316 | "9:info_hash20:mnopqrstuvwxyz123456e1:q9:get_peers1:t2:aa1:y1:qe"; 317 | socket4.Reset(); 318 | impl->ProcessIncoming(reinterpret_cast 319 | (const_cast(get_peers.c_str())), 320 | get_peers.size(), bind_addr); 321 | ASSERT_NO_FATAL_FAILURE(fetch_dict()); 322 | ASSERT_NO_FATAL_FAILURE(get_reply()); 323 | token.b = (byte*)reply->GetString("token", &token.len); 324 | ASSERT_TRUE(token.len); 325 | token_bytes.assign(token.b, token.b + token.len); 326 | impl->Tick(); 327 | socket4.Reset(); 328 | } 329 | 330 | void announce_and_verify() { 331 | ASSERT_NO_FATAL_FAILURE(fetch_response_to_message()); 332 | expect_reply_id(); 333 | impl->Tick(); 334 | socket4.Reset(); 335 | } 336 | 337 | void immutable_put(const std::string &id, char const *v) { 338 | // get a token to use 339 | std::vector token; 340 | socket4.Reset(); 341 | fetch_token(id, token); 342 | len = bencoder(message, 1024) 343 | .d() 344 | ("a").d() 345 | ("id")(id) 346 | ("token")(token) 347 | ("v").raw(v).e() 348 | ("q")("put") 349 | ("t")("aa") 350 | ("y")("q") 351 | .e() (); 352 | socket4.Reset(); 353 | impl->ProcessIncoming(message, len, bind_addr); 354 | ASSERT_NO_FATAL_FAILURE(fetch_dict()); 355 | ASSERT_NO_FATAL_FAILURE(expect_response_type()); 356 | ASSERT_NO_FATAL_FAILURE(get_reply()); 357 | impl->Tick(); 358 | socket4.Reset(); 359 | } 360 | }; 361 | 362 | class AddNodesCallBackDataItem { 363 | public: 364 | byte infoHash[20]; 365 | unsigned int numPeers; 366 | std::vector compactPeerAddressBytes; 367 | 368 | bool operator==(AddNodesCallBackDataItem &right); 369 | }; 370 | 371 | inline bool AddNodesCallBackDataItem::operator==( 372 | AddNodesCallBackDataItem &right) { 373 | if(memcmp(infoHash, right.infoHash, 20) == 0 374 | && numPeers == right.numPeers 375 | && compactPeerAddressBytes == right.compactPeerAddressBytes) { 376 | return true; 377 | } 378 | return false; 379 | } 380 | 381 | class AddNodesCallbackDummy { 382 | public: 383 | static std::vector callbackData; 384 | 385 | AddNodesCallbackDummy() {} 386 | ~AddNodesCallbackDummy() {} 387 | static void Callback(void *ctx, const byte *info_hash, const byte *peers, 388 | uint num_peers); 389 | static void Reset(); 390 | }; 391 | 392 | inline void AddNodesCallbackDummy::Callback(void *ctx, const byte *info_hash, 393 | const byte *peers, uint num_peers) { 394 | AddNodesCallBackDataItem data; 395 | unsigned int x; 396 | 397 | for(x = 0; x < 20; ++x) { 398 | data.infoHash[x] = info_hash[x]; 399 | } 400 | 401 | data.numPeers = num_peers; 402 | // 6 bytes of compact address per peer 403 | for(x = 0; x < 6 * data.numPeers; ++x) { 404 | data.compactPeerAddressBytes.push_back(peers[x]); 405 | } 406 | 407 | callbackData.push_back(data); 408 | } 409 | 410 | inline void AddNodesCallbackDummy::Reset() { 411 | callbackData.clear(); 412 | } 413 | -------------------------------------------------------------------------------- /unittests/TestDhtImplResponse.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bittorrent/libbtdht/200b8adf83edeb84f1c81fb82de7e5b8624a5fa4/unittests/TestDhtImplResponse.cpp -------------------------------------------------------------------------------- /unittests/TestDhtImplSpeed.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include "TestDhtImpl.h" 18 | 19 | //TODO: why are most messages sent twice?!? 20 | 21 | const unsigned int speedTestFactor = 10; 22 | const unsigned long maxIterations = 25 * speedTestFactor; 23 | 24 | class dht_impl_speed_test : public dht_impl_test { 25 | protected: 26 | virtual void SetUp() override { 27 | dht_impl_test::SetUp(); 28 | init_dht(); 29 | } 30 | 31 | void init_dht() { 32 | impl->Enable(true, 2000); 33 | init_dht_id(); 34 | impl->Tick(); 35 | } 36 | 37 | void process_message(std::string* message_string = NULL) { 38 | if (message_string != NULL) { 39 | len = message_string->size(); 40 | memcpy(message, message_string->c_str(), len); 41 | } 42 | impl->ProcessIncoming(message, len, bind_addr); 43 | impl->Tick(); 44 | ASSERT_TRUE(socket4.GetSentByteVector().size()); 45 | socket4.Reset(); 46 | } 47 | }; 48 | 49 | TEST_F(dht_impl_speed_test, PingKnownPacketSpeedTest) { 50 | std::string known_ping_string("d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t" 51 | "4:wxyz1:v4:UTUT1:y1:qe"); 52 | 53 | for(unsigned long x = 0; x < maxIterations; ++x) { 54 | ASSERT_NO_FATAL_FAILURE(process_message(&known_ping_string)); 55 | ASSERT_NO_FATAL_FAILURE(process_message(&known_ping_string)); 56 | } 57 | } 58 | 59 | TEST_F(dht_impl_speed_test, PingArbitraryPacketSpeedTest) { 60 | std::string ping_string("d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa" 61 | "1:y1:qe"); 62 | 63 | for(unsigned long x = 0; x < maxIterations; ++x) { 64 | ASSERT_NO_FATAL_FAILURE(process_message(&ping_string)); 65 | ASSERT_NO_FATAL_FAILURE(process_message(&ping_string)); 66 | } 67 | } 68 | 69 | TEST_F(dht_impl_speed_test, PingQueriesSpeedTest) { 70 | std::string ping_string("d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa" 71 | "1:y1:qe"); 72 | std::string known_ping_string("d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t" 73 | "4:wxyz1:v4:UTUT1:y1:qe"); 74 | 75 | for(unsigned long x = 0; x < maxIterations; ++x) { 76 | ASSERT_NO_FATAL_FAILURE(process_message(&ping_string)); 77 | ASSERT_NO_FATAL_FAILURE(process_message(&known_ping_string)); 78 | } 79 | } 80 | 81 | TEST_F(dht_impl_speed_test, FindNodeSpeedTest) { 82 | std::string find_node_string("d1:ad2:id20:abcdefghij01234567896:target" 83 | "20:mnopqrstuvwxyz123456e1:q9:find_node1:t2:aa1:y1:qe"); 84 | 85 | for(unsigned long x = 0; x < maxIterations; ++x) { 86 | ASSERT_NO_FATAL_FAILURE(process_message(&find_node_string)); 87 | ASSERT_NO_FATAL_FAILURE(process_message(&find_node_string)); 88 | } 89 | } 90 | 91 | TEST_F(dht_impl_speed_test, GetPeersSpeedTest) { 92 | std::string get_peers_string("d1:ad2:id20:abcdefghij01010101019:info_hash" 93 | "20:mnopqrstuvwxyz123456e1:q9:get_peers1:t2:aa1:y1:qe"); 94 | 95 | for(unsigned long x = 0; x < maxIterations; ++x) { 96 | ASSERT_NO_FATAL_FAILURE(process_message(&get_peers_string)); 97 | ASSERT_NO_FATAL_FAILURE(process_message(&get_peers_string)); 98 | } 99 | } 100 | 101 | TEST_F(dht_impl_speed_test, AnnouncePeerSpeedTest) { 102 | std::vector token; 103 | 104 | std::string test_data_1("d1:ad2:id20:abcdefghij01234567899:info_hash" 105 | "20:mnopqrstuvwxyz1234564:porti6881e5:token"); 106 | std::string test_data_2("e1:q13:announce_peer1:t2:aa1:y1:qe"); 107 | std::string test_data; 108 | 109 | // use this since the string will be modified as it is processed 110 | std::string buffer; 111 | for(unsigned long x = 0; x < maxIterations; ++x) { 112 | if(!(x & 0x0000007f)) { // the token needs to be refreshed periodically 113 | // get a new token and re-generate the test data 114 | socket4.Reset(); 115 | fetch_token(token); 116 | test_data.clear(); 117 | fill_test_data(test_data, Buffer(&token[0], token.size()), test_data_1, 118 | test_data_2); 119 | } 120 | buffer = test_data; 121 | ASSERT_NO_FATAL_FAILURE(process_message(&buffer)); 122 | ASSERT_NO_FATAL_FAILURE(process_message(&buffer)); 123 | } 124 | } 125 | 126 | TEST_F(dht_impl_speed_test, VoteSpeedTest) { 127 | std::vector token; 128 | 129 | std::string buffer; 130 | for(unsigned long x = 0; x < maxIterations; ++x) { 131 | if(!(x & 0x0000007f)) { // the token needs to be refreshed periodically 132 | // get a new token and re-generate the test data 133 | socket4.Reset(); 134 | fetch_token(token); 135 | std::vector target = make_random_key_20(); 136 | 137 | len = bencoder(message, 1024) 138 | .d() 139 | ("a").d() 140 | ("id")("abcdefghij0123456789") 141 | ("target")(target) 142 | ("token")(token) 143 | ("vote")(int64(5)).e() 144 | ("q")("vote") 145 | ("t")("aa") 146 | ("y")("q") 147 | .e() (); 148 | ASSERT_NO_FATAL_FAILURE(process_message()); 149 | 150 | // make the second vote message with a vote of 2 151 | len = bencoder(message, 1024) 152 | .d() 153 | ("a").d() 154 | ("id")("abcdefghij0123456789") 155 | ("target")(target) 156 | ("token")(token) 157 | ("vote")(int64(2)).e() 158 | ("q")("vote") 159 | ("t")("aa") 160 | ("y")("q") 161 | .e() (); 162 | ASSERT_NO_FATAL_FAILURE(process_message()); 163 | } 164 | } 165 | } 166 | 167 | TEST_F(dht_impl_speed_test, Announce_ReplyWithNodes_Speed) { 168 | DhtID target; 169 | target.id[0] = 'FFFF'; // FFFF 170 | target.id[1] = 'GGGG'; // GGGG 171 | target.id[2] = 'HHHH'; // HHHH 172 | target.id[3] = 'IIII'; // IIII 173 | target.id[4] = 'JJJJ'; // JJJJ 174 | 175 | const char* compact_ip = "aaaa88"; 176 | 177 | // put a peer into the dht for it to work with 178 | peer_id.addr.set_port(('8' << 8) + '8'); // 88 179 | peer_id.addr.set_addr4('aaaa'); // aaaa 180 | impl->Update(peer_id, 0, true, 10); 181 | Buffer peer_id_buffer; 182 | peer_id_buffer.len = 20; 183 | peer_id_buffer.b = (byte*)&peer_id.id.id[0]; 184 | 185 | std::string filenameTxt("filaname.txt"); 186 | 187 | for(unsigned int x = 0; x < 20 * speedTestFactor; ++x) { 188 | // make sure the callback dummy is clear 189 | AddNodesCallbackDummy::Reset(); 190 | 191 | // ***************************************************** 192 | // make the dht emit an announce message (the get_peers rpc) 193 | // ***************************************************** 194 | impl->DoAnnounce(target, &AddNodesCallbackDummy::Callback, NULL, 195 | filenameTxt.c_str(), NULL, 0); 196 | ASSERT_NO_FATAL_FAILURE(fetch_dict()); 197 | ASSERT_NO_FATAL_FAILURE(expect_query_type()); 198 | ASSERT_NO_FATAL_FAILURE(expect_command("get_peers")); 199 | Buffer tid; 200 | tid.b = (byte*)dict->GetString("t" , &tid.len); 201 | len = bencoder(message, 1024) 202 | .d() 203 | ("r").d() 204 | ("id")(peer_id_buffer.b, peer_id_buffer.len) 205 | ("token")(response_token) 206 | ("values").l() 207 | (compact_ip).e().e() 208 | ("t")(tid.b, tid.len) 209 | ("y")("r") 210 | .e() (); 211 | socket4.Reset(); 212 | impl->ProcessIncoming(message, len, peer_id.addr); 213 | ASSERT_NO_FATAL_FAILURE(fetch_dict()); 214 | ASSERT_NO_FATAL_FAILURE(expect_query_type()); 215 | ASSERT_NO_FATAL_FAILURE(expect_command("announce_peer")); 216 | tid.b = (byte*)dict->GetString("t" , &tid.len); 217 | 218 | len = bencoder(message, 1024) 219 | .d() 220 | ("r").d() 221 | ("id")(peer_id_buffer.b, peer_id_buffer.len).e() 222 | ("t")(tid.b, tid.len) 223 | ("y")("r") 224 | .e() (); 225 | socket4.Reset(); 226 | impl->ProcessIncoming(message, len, peer_id.addr); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /unittests/TestExternalIPCounter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include "gtest/gtest.h" 18 | #include 19 | #include "ExternalIPCounter.h" 20 | #include "sockaddr.h" 21 | #include 22 | 23 | #include 24 | using namespace boost::uuids::detail; 25 | 26 | struct ip_change_observer_test : ip_change_observer{ 27 | bool flag; 28 | void on_ip_change(SockAddr const & new_ip) 29 | { 30 | flag = true; 31 | return; 32 | } 33 | }; 34 | 35 | static const std::vector src_addrs 36 | { 37 | "10.10.10.10:10000", 38 | "20.10.10.10:20000", 39 | "30.10.10.10:10000", 40 | "40.10.10.10:20000", 41 | }; 42 | 43 | static const std::vector test_addrs 44 | { 45 | "10.30.30.10:30000", 46 | "20.20.20.20:40000", 47 | "30.30.30.10:30000", 48 | "40.20.20.20:40000", 49 | }; 50 | 51 | sha1_hash sha1_fun(const byte* buf, int len) 52 | { 53 | sha1 hash; 54 | unsigned int digest[5]; 55 | hash.process_bytes(buf, len); 56 | hash.get_digest(digest); 57 | for(short i = 0; i < 5; i++) { 58 | digest[i] = htonl(digest[i]); 59 | } 60 | sha1_hash ret(reinterpret_cast(digest)); 61 | return ret; 62 | } 63 | 64 | TEST(externalipcounter, trigger) 65 | { 66 | ExternalIPCounter external_ip(&sha1_fun); 67 | ip_change_observer_test icot; 68 | external_ip.set_ip_change_observer(&icot); 69 | 70 | 71 | bool ok = false; 72 | 73 | // Create voter addresses 74 | SockAddr src_addr1 = SockAddr::parse_addr(src_addrs[0], &ok); 75 | ASSERT_TRUE(ok) << "Failed to parse: " << src_addrs[0]; 76 | 77 | SockAddr src_addr2 = SockAddr::parse_addr(src_addrs[1], &ok); 78 | ASSERT_TRUE(ok) << "Failed to parse: " << src_addrs[1]; 79 | 80 | SockAddr src_addr3 = SockAddr::parse_addr(src_addrs[2], &ok); 81 | ASSERT_TRUE(ok) << "Failed to parse: " << src_addrs[2]; 82 | 83 | SockAddr src_addr4 = SockAddr::parse_addr(src_addrs[3], &ok); 84 | ASSERT_TRUE(ok) << "Failed to parse: " << src_addrs[3]; 85 | 86 | // Create test addresses 87 | SockAddr addr1 = SockAddr::parse_addr(test_addrs[0], &ok); 88 | ASSERT_TRUE(ok) << "Failed to parse: " << test_addrs[0]; 89 | 90 | SockAddr addr2 = SockAddr::parse_addr(test_addrs[1], &ok); 91 | ASSERT_TRUE(ok) << "Failed to parse: " << test_addrs[1]; 92 | 93 | SockAddr addr3 = SockAddr::parse_addr(test_addrs[2], &ok); 94 | ASSERT_TRUE(ok) << "Failed to parse: " << test_addrs[2]; 95 | 96 | SockAddr addr4 = SockAddr::parse_addr(test_addrs[3], &ok); 97 | ASSERT_TRUE(ok) << "Failed to parse: " << test_addrs[3]; 98 | 99 | icot.flag = false; 100 | external_ip.CountIP(addr1, src_addr1, 10); 101 | external_ip.CountIP(addr2, src_addr2, 20); 102 | 103 | SockAddr sockAddr; 104 | external_ip.GetIP(sockAddr); 105 | 106 | ASSERT_EQ(sockAddr, addr2); 107 | ASSERT_FALSE(icot.flag); 108 | 109 | external_ip.CountIP(addr2, src_addr3, 60); 110 | ASSERT_FALSE(icot.flag); 111 | 112 | external_ip.CountIP(addr3, src_addr3, 60); 113 | external_ip.CountIP(addr4, src_addr4, 61); 114 | external_ip.GetIP(sockAddr); 115 | ASSERT_EQ(sockAddr, addr4); 116 | ASSERT_TRUE(icot.flag); 117 | 118 | } 119 | -------------------------------------------------------------------------------- /unittests/TestRoutingTable.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include "TestDhtImpl.h" 18 | 19 | DhtID random_id() { 20 | byte bytes[20]; 21 | for (int i = 0; i < 20; ++i) { 22 | bytes[i] = rand() & 0xff; 23 | } 24 | DhtID id; 25 | CopyBytesToDhtID(id, bytes); 26 | return id; 27 | } 28 | 29 | SockAddr random_address() { 30 | SockAddr ret; 31 | memset(ret._in._in6, 0, 16); 32 | for (int i = 12; i < 16; ++i) { 33 | ret._in._in6[i] = rand() & 0xff; 34 | } 35 | ret.set_port((rand() % 1000) + 1024); 36 | return ret; 37 | } 38 | 39 | class dht_routing_test : public dht_impl_test { 40 | protected: 41 | DhtID my_id; 42 | 43 | virtual void SetUp() override { 44 | dht_impl_test::SetUp(); 45 | my_id = random_id(); 46 | impl->SetId(my_id); 47 | impl->Enable(true, 0); 48 | } 49 | }; 50 | 51 | TEST_F(dht_routing_test, TestRoutingTable) { 52 | // insert 128 random IDs uniformly distributed 53 | // all RTTs are 500, later we'll test to make sure we can replace 54 | // them with lower RTT nodes 55 | 56 | for (int i = 0; i < 256; ++i) { 57 | DhtID id = random_id(); 58 | id.id[0] = (uint(i) << 24) | 0xffffff; 59 | 60 | DhtPeerID p; 61 | p.id = id; 62 | p.addr = random_address(); 63 | DhtPeer* k = impl->Update(p, IDht::DHT_ORIGIN_INCOMING, true, 500); 64 | EXPECT_TRUE(k) << "a DHT node failed to be inserted"; 65 | } 66 | 67 | EXPECT_EQ(256, impl->GetNumPeers()) << 68 | "the number of nodes is not the number we inserted"; 69 | EXPECT_EQ(32, impl->NumBuckets()) << 70 | "the number buckets is supposed to be 32 still"; 71 | 72 | // now, split the bucket 73 | DhtID id = random_id(); 74 | // copy just the 8 most significant bits from our ID 75 | uint mask = 0xffffffff >> 8; 76 | id.id[0] &= mask; 77 | id.id[0] |= my_id.id[0] & ~mask; 78 | DhtPeerID p; 79 | p.id = id; 80 | p.addr = random_address(); 81 | impl->Update(p, IDht::DHT_ORIGIN_INCOMING, true, 500); 82 | 83 | EXPECT_EQ(33, impl->NumBuckets()) << 84 | "the number buckets is supposed to be 33"; 85 | 86 | // TODO: somehow assert that there are 14 nodes in bucket 1 and 128 nodes 87 | // in bucket 0 88 | } 89 | 90 | TEST_F(dht_routing_test, TestDhtRestart) { 91 | // insert some nodes 92 | for (int i = 0; i < 10; ++i) { 93 | DhtPeerID p; 94 | p.id = random_id(); 95 | p.addr = random_address(); 96 | DhtPeer* k = impl->Update(p, IDht::DHT_ORIGIN_INCOMING, true, 500); 97 | EXPECT_TRUE(k) << "a DHT node failed to be inserted"; 98 | } 99 | impl->Restart(); 100 | } 101 | -------------------------------------------------------------------------------- /unittests/TestSecureDhtID.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #include "TestDHT.h" 18 | 19 | TEST(secure_dht_id_test, secure_dht_id_test) { 20 | 21 | byte Id_1[20], Id_2[20]; 22 | SockAddr addr_1 = SockAddr::parse_addr("4.3.2.1"); 23 | SockAddr addr_2 = SockAddr::parse_addr("[2001:420:80:1::5]"); 24 | 25 | for( int i = 0; i <5; i++) { 26 | DhtCalculateHardenedID(addr_1, Id_1); 27 | DhtCalculateHardenedID(addr_2, Id_2); 28 | EXPECT_TRUE(DhtVerifyHardenedID(addr_1, Id_1)); 29 | EXPECT_TRUE(DhtVerifyHardenedID(addr_2, Id_2)); 30 | EXPECT_TRUE(!DhtVerifyHardenedID(addr_2, Id_1)); 31 | EXPECT_TRUE(!DhtVerifyHardenedID(addr_1, Id_2)); 32 | addr_1._sin4++; 33 | addr_2._sin4++; 34 | } 35 | 36 | char const* ips[] = { 37 | "124.31.75.21", 38 | "21.75.31.124", 39 | "65.23.51.170", 40 | "84.124.73.14", 41 | "43.213.53.83" 42 | }; 43 | 44 | uint8 seeds[] = { 1, 86, 22, 65, 90 }; 45 | 46 | uint8 prefixes[][4] = { 47 | { 0x5f, 0xbf, 0xbf }, 48 | { 0x5a, 0x3c, 0xe9 }, 49 | { 0xa5, 0xd4, 0x32 }, 50 | { 0x1b, 0x03, 0x21 }, 51 | { 0xe5, 0x6f, 0x6c }, 52 | }; 53 | 54 | uint8 mask[3] = { 0xff, 0xff, 0xf8 }; 55 | 56 | 57 | for (int i = 0; i < 5; ++i) { 58 | SockAddr addr = SockAddr::parse_addr(ips[i]); 59 | uint32 id = generate_node_id_prefix(addr, seeds[i]); 60 | int bits_to_shift = 24; 61 | for (int j = 0; j < 3; ++j) { 62 | EXPECT_EQ(prefixes[i][j] & mask[j] , (byte)((id>>bits_to_shift) & 0xff) & mask[j]); 63 | bits_to_shift -= 8; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /unittests/UnitTestUDPSocket.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // these undef's needed for the google test framework 18 | #undef _M_CEE_PURE 19 | #undef new 20 | 21 | #include "UnitTestUDPSocket.h" 22 | 23 | // all this Send does is grab the bytes to be sent and put them into the vector 24 | void UnitTestUDPSocket::Send(const SockAddr& dummyDest, const byte *p, size_t len, uint32 flags /* = 0 */) 25 | { 26 | _packets.push_back(std::vector(p, p + len)); 27 | } 28 | 29 | // this pops the packets in reverse order of being sent 30 | std::string UnitTestUDPSocket::GetSentDataAsString(int i) 31 | { 32 | if (_packets.empty()) return std::string(); 33 | 34 | if (i < 0) { 35 | std::string ret; 36 | for (int i = 0; i < _packets.size(); ++i) 37 | ret.insert(ret.end(), _packets[i].begin(), _packets[i].end()); 38 | return ret; 39 | } 40 | assert(i < _packets.size()); 41 | std::string ret(_packets[i].begin(), _packets[i].end()); 42 | 43 | #ifdef _DEBUG_DHT 44 | printf("\x1b[33mread packet (str) [%d]: \"", i); 45 | for (int i = 0; i < ret.size(); ++i) 46 | printf("%c", ret[i]); 47 | printf("\"\x1b[0m\n"); 48 | #endif 49 | 50 | return ret; 51 | } 52 | 53 | std::vector UnitTestUDPSocket::GetSentByteVector(int i) 54 | { 55 | std::vector ret; 56 | if (_packets.empty()) return ret; 57 | 58 | if (i < 0) { 59 | std::vector ret; 60 | for (int i = 0; i < _packets.size(); ++i) 61 | ret.insert(ret.end(), _packets[i].begin(), _packets[i].end()); 62 | return ret; 63 | } 64 | assert(i < _packets.size()); 65 | ret = _packets[i]; 66 | 67 | #ifdef _DEBUG_DHT 68 | printf("\x1b[33mread packet (vec) [%d]: ", i); 69 | for (int i = 0; i < ret.size(); ++i) 70 | printf("%c", ret[i]); 71 | printf("\x1b[0m\n"); 72 | #endif 73 | 74 | return ret; 75 | } 76 | 77 | void UnitTestUDPSocket::popPacket() 78 | { 79 | assert(!_packets.empty()); 80 | _packets.erase(_packets.end()-1); 81 | } 82 | 83 | -------------------------------------------------------------------------------- /unittests/UnitTestUDPSocket.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #ifndef __UDPUNITTESTSOCKET_H__ 18 | #define __UDPUNITTESTSOCKET_H__ 19 | 20 | #include "vector" 21 | #include "string" 22 | 23 | #include "udp_utils.h" 24 | #include "utypes.h" 25 | 26 | /// The UnitTestUDPSocket class is intended to just capture bytes (particularly bencoded strings) being 27 | /// sent out for unit testing purposes. 28 | class UnitTestUDPSocket : public UDPSocketInterface 29 | { 30 | private: 31 | std::vector > _packets; 32 | SockAddr _bind_addr; 33 | 34 | public: 35 | UnitTestUDPSocket(){Reset();} 36 | virtual ~UnitTestUDPSocket(){} 37 | virtual void Send(const SockAddr& dest, const byte *p, size_t len, uint32 flags = 0); 38 | virtual void Send(const SockAddr& dest, cstr host, const byte *p, size_t len, uint32 flags = 0) { Send(dest, p, len, flags); }; 39 | virtual const SockAddr &GetBindAddr( void ) const {return _bind_addr;} 40 | std::string GetSentDataAsString(int i = -1); 41 | std::vector GetSentByteVector(int i = -1); 42 | void Reset() {_packets.clear(); } 43 | virtual void event(DWORD events) {} 44 | virtual DWORD get_event_mask() const {return 0;} 45 | void SetBindAddr(SockAddr &bindAddr){_bind_addr = bindAddr;} 46 | void popPacket(); 47 | 48 | int numPackets() const { return _packets.size(); } 49 | 50 | }; 51 | 52 | #endif // __UDPUNITTESTSOCKET_H__ 53 | -------------------------------------------------------------------------------- /unittests/bencoder.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 BitTorrent Inc 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include "snprintf.h" 23 | 24 | #include 25 | 26 | // a stupid, write-once bencoder, and for when no C++11 is available 27 | class bencoder { 28 | unsigned char* buffer; 29 | unsigned char* start; 30 | int64 len; 31 | std::stack checker; // state machine for bencoded result validation 32 | 33 | // maintains encountered dictionary state, 'k' means key 34 | void update_checker() { 35 | if (!checker.empty()) { 36 | if (checker.top() == 'd') { 37 | checker.push('k'); 38 | } else if (checker.top() == 'k') { 39 | checker.pop(); 40 | } 41 | } 42 | } 43 | 44 | public: 45 | bencoder(unsigned char* buffer, int64 len) : buffer(buffer), 46 | start(buffer), len(len) {} 47 | bencoder& operator() (int64 value) { 48 | update_checker(); 49 | long written = snprintf(reinterpret_cast(buffer), len, 50 | "i%de", int(value)); 51 | assert(written <= len); 52 | buffer += written; 53 | len -= written; 54 | return *this; 55 | } 56 | // use only with values -- would be silly to use with keys anyway 57 | inline bencoder& raw(char const *value) { 58 | assert(strlen(value) <= len); 59 | update_checker(); 60 | std::memcpy(buffer, value, strlen(value)); 61 | buffer += strlen(value); 62 | len -= strlen(value); 63 | return *this; 64 | } 65 | inline bencoder& operator() (char const *value) { 66 | update_checker(); 67 | long written = snprintf(reinterpret_cast(buffer), len, "%d:%s", 68 | int(strlen(value)), value); 69 | assert(written <= len); 70 | buffer += written; 71 | len -= written; 72 | return *this; 73 | } 74 | inline bencoder& operator() (unsigned char const *value, int64 v_len) { 75 | assert(v_len > 0); 76 | update_checker(); 77 | long written = snprintf(reinterpret_cast(buffer), len, 78 | #ifdef _MSC_VER 79 | "%I64d:", 80 | #else 81 | "%" PRId64 ":", 82 | #endif 83 | v_len); 84 | assert(written + v_len <= len); 85 | std::memcpy(buffer + written, value, v_len); 86 | buffer += written + v_len; 87 | len -= written + v_len; 88 | return *this; 89 | } 90 | inline bencoder& operator() (std::vector const &value) { 91 | update_checker(); 92 | long written = snprintf(reinterpret_cast(buffer), len, "%u:", 93 | uint(value.size())); 94 | assert(written + value.size() <= len); 95 | std::memcpy(buffer + written, &(value[0]), value.size()); 96 | buffer += written + value.size(); 97 | len -= written + value.size(); 98 | return *this; 99 | } 100 | inline bencoder& operator() (std::string const &value) { 101 | update_checker(); 102 | long written = snprintf(reinterpret_cast(buffer), len, "%u:", 103 | uint(value.size())); 104 | assert(written + value.size() <= len); 105 | std::memcpy(buffer + written, &(value[0]), value.size()); 106 | buffer += written + value.size(); 107 | len -= written + value.size(); 108 | return *this; 109 | } 110 | inline bencoder& operator() (char value) { 111 | assert(len >= 1); 112 | assert(value == 'd' || value == 'l' || value == 'e'); 113 | if (value == 'e') { 114 | assert(!checker.empty()); 115 | assert(checker.top() == 'l' || checker.top() == 'd'); 116 | checker.pop(); 117 | } else { 118 | update_checker(); 119 | checker.push(value); 120 | } 121 | *buffer = value; 122 | buffer++; 123 | len--; 124 | return *this; 125 | } 126 | 127 | inline bencoder& d() { 128 | return (*this)('d'); 129 | } 130 | inline bencoder& l() { 131 | return (*this)('l'); 132 | } 133 | inline bencoder& e() { 134 | return (*this)('e'); 135 | } 136 | 137 | inline int64 operator() () { 138 | assert(checker.empty()); 139 | return buffer - start; 140 | } 141 | }; 142 | --------------------------------------------------------------------------------