├── project-root.jam ├── architecture.dia ├── TODO ├── endian.hpp ├── socket.hpp ├── stack.hpp ├── Jamfile ├── config.hpp ├── key_rotate.cpp ├── utils.hpp ├── README.rst ├── key_rotate.hpp ├── receive_thread.hpp ├── swarm.hpp ├── arp_cache.hpp ├── messages.hpp ├── announce_thread.hpp ├── stack.cpp ├── siphash24.c ├── socket_system.hpp ├── socket_system.cpp ├── swarm.cpp ├── announce_thread.cpp ├── receive_thread.cpp ├── main.cpp ├── utils.cpp ├── socket_pcap.hpp ├── test_announce.cpp └── socket_pcap.cpp /project-root.jam: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /architecture.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arvidn/utrack/HEAD/architecture.dia -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 2 | 1. move source and header files into 'src' directory 3 | 4 | 2. clean up listing interfaces API (turn it into an iterator) and factor out 5 | into its own library. 6 | 7 | 3. clean up packet_socket interface, document it and factor out into its own 8 | library. 9 | 10 | -------------------------------------------------------------------------------- /endian.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | utrack is a very small an efficient BitTorrent tracker 3 | Copyright (C) 2011 Arvid Norberg 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #ifndef _ENDIAN_HPP_ 20 | #define _ENDIAN_HPP_ 21 | 22 | #if defined(__linux__) 23 | # include 24 | #elif defined(__FreeBSD__) || defined(__NetBSD__) 25 | # include 26 | #elif defined(__OpenBSD__) 27 | # include 28 | # define be64toh(x) betoh64(x) 29 | #else 30 | #if __BYTE_ORDER == __LITTLE_ENDIAN 31 | uint64_t inline be64toh(uint64_t x) 32 | { 33 | uint64_t ret; 34 | uint8_t* d = ((uint8_t*)&ret) + 7; 35 | uint8_t* s = (uint8_t*)&x; 36 | 37 | for (int i = 0; i < sizeof(x); ++i, --d, ++s) 38 | *d = *s; 39 | return ret; 40 | } 41 | #else 42 | #define be64toh(x) x 43 | #endif 44 | #endif 45 | 46 | #endif // _ENDIAN_HPP_ 47 | 48 | -------------------------------------------------------------------------------- /socket.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | utrack is a very small an efficient BitTorrent tracker 3 | Copyright (C) 2013-2014 Arvid Norberg 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #ifndef _PACKET_SOCKET_HPP_ 20 | #define _PACKET_SOCKET_HPP_ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include // for memset 28 | 29 | #ifdef _WIN32 30 | 31 | #include 32 | 33 | // windows doesn't have iovec or socklen 34 | struct iovec { 35 | void* iov_base; 36 | int iov_len; 37 | }; 38 | 39 | typedef int socklen_t; 40 | 41 | #else 42 | 43 | #include // for sockaddr 44 | #include // for iovec 45 | 46 | #endif 47 | 48 | struct incoming_packet_t 49 | { 50 | sockaddr_storage from; 51 | char* buffer; 52 | int buflen; 53 | }; 54 | 55 | #ifdef USE_PCAP 56 | #include "socket_pcap.hpp" 57 | #else 58 | #include "socket_system.hpp" 59 | #endif 60 | 61 | #endif // _PACKET_SOCKET_HPP_ 62 | 63 | -------------------------------------------------------------------------------- /stack.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | utrack is a very small an efficient BitTorrent tracker 3 | Copyright (C) 2014 Arvid Norberg 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include "utils.hpp" // for address_eth 20 | #include 21 | 22 | struct sockaddr_in; 23 | struct arp_cache; 24 | 25 | // renders an ethernet frame into the buffer 'buf', which can hold 'len' 26 | // bytes. The number of bytes written to the buffer is returned, or -1 if 27 | // somehting failed. 28 | int render_eth_frame(std::uint8_t* buf, int len 29 | , sockaddr_in const* to 30 | , sockaddr_in const* from 31 | , sockaddr_in const* mask 32 | , address_eth const& eth_from 33 | , arp_cache const& arp); 34 | 35 | // renders an IP and UDP frame into the buffer 'buf' which can hold 'len' 36 | // bytes. The number of bytes written to the buffer is returned, or -1 if 37 | // somehting failed. 38 | int render_ip_frame(std::uint8_t* buf, int len 39 | , iovec const* v, int num 40 | , sockaddr_in const* to 41 | , sockaddr_in const* from); 42 | 43 | -------------------------------------------------------------------------------- /Jamfile: -------------------------------------------------------------------------------- 1 | import feature : feature ; 2 | 3 | install stage-test : udp_test : . ; 4 | install stage : utrack : . ; 5 | 6 | feature pcap : off on win receive-only : composite propagated link-incompatible ; 7 | feature.compose win : USE_WINPCAP ; 8 | feature.compose receive-only : USE_SYSTEM_SEND_SOCKET ; 9 | 10 | explicit stage-test ; 11 | explicit stage ; 12 | 13 | lib libpcap : : pcap : /opt/local/lib 14 | : /opt/local/include USE_PCAP ; 15 | 16 | lib libwpcap : : wpcap : "c:\\Program Files\\wpdpack\\lib" 17 | : 18 | "c:\\Program Files\\wpdpack\\include" 19 | WPCAP USE_PCAP ; 20 | 21 | rule pcap ( properties * ) 22 | { 23 | local result ; 24 | if off in $(properties) 25 | { 26 | result += socket_system.cpp ; 27 | } 28 | else 29 | { 30 | if windows in $(properties) 31 | { 32 | result += libwpcap ; 33 | } 34 | else 35 | { 36 | result += libpcap ; 37 | } 38 | 39 | result += socket_pcap.cpp ; 40 | } 41 | return $(result) ; 42 | } 43 | 44 | lib ws2 : : ws2_32 ; 45 | lib iphlp : : iphlpapi ; 46 | 47 | exe udp_test 48 | : 49 | test_announce.cpp 50 | utils.cpp 51 | stack.cpp 52 | : 53 | @pcap 54 | windows:ws2 55 | windows:iphlp 56 | multi 57 | ; 58 | 59 | explicit udp_test ; 60 | 61 | exe utrack 62 | : 63 | main.cpp 64 | swarm.cpp 65 | announce_thread.cpp 66 | siphash24.c 67 | key_rotate.cpp 68 | receive_thread.cpp 69 | utils.cpp 70 | stack.cpp 71 | : 72 | windows:ws2 73 | windows:iphlp 74 | @pcap 75 | multi 76 | ; 77 | 78 | -------------------------------------------------------------------------------- /config.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | utrack is a very small an efficient BitTorrent tracker 3 | Copyright (C) 2014 Arvid Norberg 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #ifndef _CONFIG_HPP_ 20 | #define _CONFIG_HPP_ 21 | 22 | // magic constants 23 | 24 | enum 25 | { 26 | // the max number of peers we'll keep in a peerlist for a single 27 | // torrent. 28 | max_peerlist_size = 300000, 29 | 30 | // the default number of peers to return, unless a smaller number 31 | // was specified in the request 32 | default_num_want = 200, 33 | 34 | // the number of seconds between announces (seconds) 35 | default_interval = 1800, 36 | 37 | // if this is true, we allow peers to set which IP 38 | // they will announce as. This is off by default since 39 | // it allows for spoofing 40 | #ifdef _DEBUG 41 | allow_alternate_ip = 1, 42 | #else 43 | allow_alternate_ip = 0, 44 | #endif 45 | 46 | socket_buffer_size = 5 * 1024 * 1024, 47 | 48 | // the number of times to read (without receiving any data) from the udp 49 | // socket before going to sleep 50 | receive_spin_count = 10, 51 | 52 | // the max number of announce requests to queue up per announce_thread 53 | announce_queue_size = 8192, 54 | }; 55 | 56 | #endif 57 | 58 | -------------------------------------------------------------------------------- /key_rotate.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014 Arvid Norberg 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include // for generate 20 | #include "key_rotate.hpp" 21 | 22 | using std::chrono::steady_clock; 23 | using std::chrono::hours; 24 | 25 | key_rotate::key_rotate() 26 | : m_current(ATOMIC_VAR_INIT(0)) 27 | , m_last_rotate(steady_clock::now()) 28 | { 29 | std::random_device dev; 30 | std::generate(m_secrets[0].key.begin(), m_secrets[0].key.end(), std::ref(dev)); 31 | std::generate(m_secrets[1].key.begin(), m_secrets[1].key.end(), std::ref(dev)); 32 | } 33 | 34 | void key_rotate::tick() 35 | { 36 | steady_clock::time_point now = steady_clock::now(); 37 | if (now < m_last_rotate + hours(6)) return; 38 | 39 | std::uint32_t next_cur = (m_current.load() + 1 ) % 3; 40 | 41 | std::random_device dev; 42 | std::generate(m_secrets[next_cur].key.begin() 43 | , m_secrets[next_cur].key.end() 44 | , std::ref(dev)); 45 | m_current = next_cur; 46 | } 47 | 48 | std::array const& key_rotate::cur_key() const 49 | { 50 | return m_secrets[m_current.load()].key; 51 | } 52 | 53 | std::array const& key_rotate::prev_key() const 54 | { 55 | return m_secrets[(m_current.load() - 1) % 3].key; 56 | } 57 | 58 | -------------------------------------------------------------------------------- /utils.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | utrack is a very small an efficient BitTorrent tracker 3 | Copyright (C) 2014 Arvid Norberg 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #ifndef UTILS_HPP_ 20 | #define UTILS_HPP_ 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | #ifdef _WIN32 29 | #include 30 | #else 31 | #include 32 | #include 33 | #endif 34 | 35 | struct address_eth 36 | { 37 | address_eth() { memset(addr, 0, sizeof(addr)); } 38 | address_eth(address_eth const& a) = default; 39 | explicit address_eth(uint8_t const* ptr) { memcpy(addr, ptr, sizeof(addr)); } 40 | uint8_t addr[6]; 41 | }; 42 | 43 | struct network 44 | { 45 | sockaddr ip; 46 | sockaddr mask; 47 | }; 48 | 49 | struct device_info 50 | { 51 | char name[IFNAMSIZ]; 52 | address_eth hardware_addr; 53 | std::vector addresses; 54 | }; 55 | 56 | struct arp_entry 57 | { 58 | sockaddr addr; 59 | address_eth hw_addr; 60 | }; 61 | 62 | #if USE_PCAP 63 | std::vector interfaces(std::error_code& ec); 64 | #endif 65 | 66 | std::vector arp_table(std::error_code& ec); 67 | 68 | bool sockaddr_eq(sockaddr const* lhs, sockaddr const* rhs); 69 | std::string to_string(sockaddr const* addr); 70 | std::string to_string(address_eth const& addr); 71 | 72 | #endif 73 | 74 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | uTrack 2 | ====== 3 | 4 | uTrack is a very light weight, fast, multithreaded UDP bittorrent tracker. 5 | 6 | features 7 | -------- 8 | 9 | * UDP announce and scrape 10 | * secure connection ID to prevent IP spoofing 11 | * multithreaded with minimal lock contention 12 | * incremental purging of peers to amortize CPU load 13 | * high packet throughput via libpcap 14 | 15 | requirements 16 | ------------ 17 | 18 | utrack requires: 19 | 20 | * a C++11 conformant compiler (clang 3.1+ or GCC 4.7 or so) 21 | * BSD sockets 22 | * boost and boost-build 23 | 24 | building 25 | -------- 26 | 27 | run:: 28 | 29 | b2 30 | 31 | on the command line in the utrack root directory. 32 | 33 | Optional build options: 34 | 35 | +-------------------+--------------------------------------------------+ 36 | | option | description | 37 | +===================+==================================================+ 38 | | pcap=on | Enable libpcap support. This will improve UDP | 39 | | | performance by circumventing some of the | 40 | | | syscall overhead associated with udp sockets. | 41 | +-------------------+--------------------------------------------------+ 42 | | pcap=win | Enable libpcap support and use libwinpcap | 43 | | | specific extensions. This speeds up both sending | 44 | | | and receiving of packets. | 45 | +-------------------+--------------------------------------------------+ 46 | | pcap=receive-only | Enable libpcap only for receiving packets, use | 47 | | | regular sockets for sending replies. | 48 | +-------------------+--------------------------------------------------+ 49 | | stage | copy the resulting utrack binary to the root dir | 50 | +-------------------+--------------------------------------------------+ 51 | | stage-test | copy the resulting udp_test binary to the root | 52 | | | directory. (The test requires libpcap) | 53 | +-------------------+--------------------------------------------------+ 54 | 55 | -------------------------------------------------------------------------------- /key_rotate.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2014 Arvid Norberg 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | 18 | #ifndef _KEY_ROTATE_HPP_ 19 | #define _KEY_ROTATE_HPP_ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | struct key_rotate 27 | { 28 | key_rotate(); 29 | 30 | // tick must only be called from a single thread! 31 | void tick(); 32 | 33 | // these may be called by any thread 34 | std::array const& cur_key() const; 35 | std::array const& prev_key() const; 36 | 37 | private: 38 | 39 | struct secret_key_t 40 | { 41 | std::array key; 42 | // place all keys in separate cache lines so that 43 | // writing a new one doesn't evict the ones the other 44 | // threads are reading 45 | uint8_t padding[64-16]; 46 | }; 47 | 48 | // these are the rotating secret keys. There are 3 keys so that we 49 | // can generate a new one in the 3:rd slot without being worried about 50 | // it being used by any other thread. The most recent secret is 51 | // secrets[current_secret], and the previous secret is 52 | // every few hours or so, the secret is rotated. To rotate the cache, 53 | // the unused secrets entry is initialized with random bytes then the 54 | // current_secret is incremented (and wrapped at 3). 55 | // secrets[(current_secrets - 1)%3] 56 | 57 | secret_key_t m_secrets[3]; 58 | std::atomic m_current; 59 | std::chrono::steady_clock::time_point m_last_rotate; 60 | }; 61 | 62 | #endif 63 | 64 | -------------------------------------------------------------------------------- /receive_thread.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2010-201$ Arvid Norberg 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | 18 | #ifndef _RECEIVE_THREAD_HPP_ 19 | #define _RECEIVE_THREAD_HPP_ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #ifndef _WIN32 27 | #include 28 | #include 29 | #else 30 | #include 31 | #endif 32 | 33 | #include "socket.hpp" 34 | #include "announce_thread.hpp" // for announce_msg 35 | 36 | struct announce_thread; 37 | 38 | // this is a thread that reads packets off the UDP socket and forwards it to 39 | // to appropriate announce_thread (if it's an announce) 40 | struct receive_thread 41 | { 42 | #ifdef USE_PCAP 43 | receive_thread(packet_socket& s, std::vector const& at); 44 | #else 45 | receive_thread(int listen_port, std::vector const& at); 46 | #endif 47 | ~receive_thread(); 48 | 49 | // disallow copy 50 | receive_thread(receive_thread const&) = delete; 51 | receive_thread& operator=(receive_thread const&) = delete; 52 | 53 | void close(); 54 | 55 | std::thread::native_handle_type native_handle() { return m_thread.native_handle(); } 56 | 57 | void thread_fun(); 58 | 59 | // this thread receives incoming announces, parses them and posts 60 | // the announce to the correct announce thread, that then takes over 61 | // and is responsible for responding 62 | void incoming_packet(uint8_t const* buf, int size, sockaddr_in const* from 63 | , packet_buffer& send_buffer, std::vector* announce_buf); 64 | 65 | private: 66 | 67 | #ifdef USE_PCAP 68 | packet_socket& m_sock; 69 | #else 70 | packet_socket m_sock; 71 | #endif 72 | std::vector const& m_announce_threads; 73 | 74 | std::thread m_thread; 75 | }; 76 | 77 | #endif 78 | 79 | 80 | -------------------------------------------------------------------------------- /swarm.hpp: -------------------------------------------------------------------------------- 1 | #ifndef _SWARM_HPP_ 2 | #define _SWARM_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | using std::chrono::steady_clock; 12 | using std::chrono::seconds; 13 | 14 | #include "messages.hpp" 15 | 16 | struct peer_ip4 17 | { 18 | peer_ip4(uint32_t addr, uint16_t p) 19 | { 20 | // addr is always big endian (network byte order) 21 | memcpy(&ip, &addr, sizeof(ip)); 22 | memcpy(&port, &p, sizeof(port)); 23 | } 24 | uint32_t ip4() const 25 | { 26 | uint32_t ret; 27 | memcpy(&ret, ip, sizeof(ip)); 28 | return ret; 29 | } 30 | // split up in uint16 to get 31 | // the compact layout 32 | uint16_t ip[2]; 33 | uint16_t port; 34 | }; 35 | 36 | struct peer_entry 37 | { 38 | peer_entry(): index(0), complete(false), downloading(true), key(0), last_announce((steady_clock::time_point::min)()) {} 39 | // index into the compact array of IPs 40 | uint32_t index:30; 41 | // true if we've received complete from this peer 42 | bool complete:1; 43 | // true while this peer's left > 0 44 | bool downloading:1; 45 | // the key this peer uses in its announces 46 | // this is used to distinguish between peers 47 | // on the same IP 48 | uint32_t key; 49 | // last time this peer announced 50 | steady_clock::time_point last_announce; 51 | }; 52 | 53 | struct swarm 54 | { 55 | swarm(); 56 | void announce(steady_clock::time_point now, udp_announce_message const* hdr 57 | , char** buf, int* len 58 | , uint32_t* downloaders, uint32_t* seeds 59 | , std::mt19937& mt_engine); 60 | 61 | void scrape(uint32_t* seeds, uint32_t* download_count, uint32_t* downloaders); 62 | 63 | void purge_stale(steady_clock::time_point now); 64 | 65 | private: 66 | 67 | typedef std::unordered_map hash_map4_t; 68 | 69 | void erase_peer(swarm::hash_map4_t::iterator i); 70 | 71 | uint32_t m_seeds; 72 | uint32_t m_downloaders; 73 | uint32_t m_download_count; 74 | 75 | // the last time anyone announced to this swarm 76 | // this is used to expire swarms 77 | steady_clock::time_point m_last_announce; 78 | 79 | // hash table of all peers keyed on their IP 80 | hash_map4_t m_peers4; 81 | 82 | // compact array of all peers' IPs 83 | std::vector m_ips4; 84 | 85 | // the last peer we checked for purgine stale peers 86 | // this may be m_peers4.end(). It's used to not 87 | // necessarily go through all peers in one go 88 | hash_map4_t::iterator m_last_purge; 89 | }; 90 | 91 | #endif 92 | 93 | -------------------------------------------------------------------------------- /arp_cache.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | utrack is a very small an efficient BitTorrent tracker 3 | Copyright (C) 2013-2014 Arvid Norberg 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | struct arp_cache 23 | { 24 | void add_arp_entry(sockaddr_in const* addr, address_eth const& mac) 25 | { 26 | uint8_t* ip = (uint8_t*)&addr->sin_addr.s_addr; 27 | printf("adding ARP entry: %d.%d.%d.%d -> %02x:%02x:%02x:%02x:%02x:%02x\n" 28 | , ip[0] 29 | , ip[1] 30 | , ip[2] 31 | , ip[3] 32 | , mac.addr[0] 33 | , mac.addr[1] 34 | , mac.addr[2] 35 | , mac.addr[3] 36 | , mac.addr[4] 37 | , mac.addr[5] 38 | ); 39 | 40 | m_arp_cache[addr->sin_addr.s_addr] = mac; 41 | } 42 | 43 | bool has_entry(sockaddr_in const* from 44 | , sockaddr_in const* to 45 | , sockaddr_in const* mask) const 46 | { 47 | uint32_t dst = to->sin_addr.s_addr; 48 | uint32_t src = from->sin_addr.s_addr; 49 | uint32_t m = mask->sin_addr.s_addr; 50 | 51 | // if the address is not part of the local network, set dst to 0 52 | // to indicate the default route out of our network 53 | if ((dst & m) != (src & m)) 54 | dst = 0; 55 | 56 | return m_arp_cache.count(dst) > 0; 57 | } 58 | 59 | address_eth const& lookup(sockaddr_in const* from 60 | , sockaddr_in const* to 61 | , sockaddr_in const* mask) const 62 | { 63 | 64 | uint32_t dst = to->sin_addr.s_addr; 65 | uint32_t src = from->sin_addr.s_addr; 66 | uint32_t m = mask->sin_addr.s_addr; 67 | 68 | // if the address is not part of the local network, set dst to 0 69 | // to indicate the default route out of our network 70 | if ((dst & m) != (src & m)) 71 | dst = 0; 72 | 73 | auto i = m_arp_cache.find(dst); 74 | assert(i != m_arp_cache.end()); 75 | return i->second; 76 | } 77 | 78 | private: 79 | 80 | // maps local IPs (IPs masked by the network mask) 81 | // to the corresponding ethernet address (MAC address) 82 | std::unordered_map m_arp_cache; 83 | }; 84 | 85 | -------------------------------------------------------------------------------- /messages.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | utrack is a very small an efficient BitTorrent tracker 3 | Copyright (C) 2011 Arvid Norberg 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #ifndef _MESSAGES_HPP_ 20 | #define _MESSAGES_HPP_ 21 | 22 | #include 23 | #include 24 | 25 | struct sha1_hash 26 | { 27 | uint32_t val[5]; 28 | }; 29 | 30 | inline bool operator==(sha1_hash const& lhs, sha1_hash const& rhs) 31 | { 32 | return memcmp(lhs.val, rhs.val, 20) == 0; 33 | } 34 | 35 | enum 36 | { 37 | max_scrape_responses = 71 38 | }; 39 | 40 | #pragma pack (push) 41 | struct udp_announce_message 42 | { 43 | uint64_t connection_id; 44 | uint32_t action; 45 | uint32_t transaction_id; 46 | sha1_hash hash; 47 | sha1_hash peer_id; 48 | int64_t downloaded; 49 | int64_t left; 50 | int64_t uploaded; 51 | int32_t event; 52 | uint32_t ip; 53 | uint32_t key; 54 | int32_t num_want; 55 | uint16_t port; 56 | uint16_t extensions; 57 | }; 58 | 59 | struct udp_scrape_message 60 | { 61 | uint64_t connection_id; 62 | uint32_t action; 63 | uint32_t transaction_id; 64 | sha1_hash hash[max_scrape_responses]; 65 | }; 66 | 67 | struct udp_connect_response 68 | { 69 | uint32_t action; 70 | uint32_t transaction_id; 71 | uint64_t connection_id; 72 | }; 73 | 74 | struct udp_announce_response 75 | { 76 | uint32_t action; 77 | uint32_t transaction_id; 78 | uint32_t interval; 79 | uint32_t downloaders; 80 | uint32_t seeds; 81 | }; 82 | 83 | struct udp_scrape_data 84 | { 85 | uint32_t downloaders; 86 | uint32_t download_count; 87 | uint32_t seeds; 88 | }; 89 | 90 | struct udp_scrape_response 91 | { 92 | uint32_t action; 93 | uint32_t transaction_id; 94 | udp_scrape_data data[71]; 95 | }; 96 | #pragma pack(pop) 97 | 98 | enum action_t 99 | { 100 | action_connect = 0, 101 | action_announce = 1, 102 | action_scrape = 2, 103 | action_error = 3 104 | }; 105 | 106 | enum event_t 107 | { 108 | event_none = 0, 109 | event_completed = 1, 110 | event_started = 2, 111 | event_stopped = 3 112 | }; 113 | 114 | #endif 115 | 116 | -------------------------------------------------------------------------------- /announce_thread.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | utrack is a very small an efficient BitTorrent tracker 3 | Copyright (C) 2010-2013 Arvid Norberg 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #ifndef _ANNOUNCE_THREAD_HPP_ 20 | #define _ANNOUNCE_THREAD_HPP_ 21 | 22 | #include "messages.hpp" 23 | #include "swarm.hpp" 24 | #include "socket.hpp" 25 | 26 | #ifndef _WIN32 27 | #include 28 | #include 29 | #else 30 | #include 31 | #endif 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | struct announce_msg 41 | { 42 | union 43 | { 44 | udp_announce_message announce; 45 | udp_scrape_message scrape; 46 | } bits; 47 | sockaddr_in from; 48 | }; 49 | 50 | extern "C" int siphash(unsigned char *out, const unsigned char *in 51 | , unsigned long long inlen, const unsigned char *k); 52 | 53 | std::array gen_random_key(); 54 | 55 | struct siphash_fun 56 | { 57 | size_t operator()(sha1_hash const& h) const 58 | { 59 | // this is the secret key used in siphash to prevent hashcolision 60 | // attacks. It's initialized to random bytes on startup (or first use) 61 | static std::array hash_key = gen_random_key(); 62 | 63 | std::uint64_t ret; 64 | siphash((std::uint8_t*)&ret, (std::uint8_t const*)h.val, sizeof(h.val) 65 | , hash_key.data()); 66 | return ret; 67 | } 68 | }; 69 | 70 | // this is a thread that handles the announce for a specific 71 | // set of info-hashes, and then sends a response over its own 72 | // UDP socket 73 | struct announce_thread 74 | { 75 | #ifdef USE_PCAP 76 | announce_thread(packet_socket& s); 77 | #else 78 | announce_thread(int listen_port); 79 | #endif 80 | 81 | // disallow copy 82 | announce_thread(announce_thread const&) = delete; 83 | announce_thread& operator=(announce_thread const&) = delete; 84 | 85 | void thread_fun(); 86 | void post_announces(std::vector m); 87 | ~announce_thread(); 88 | 89 | std::thread::native_handle_type native_handle() { return m_thread.native_handle(); } 90 | 91 | private: 92 | 93 | // job queue 94 | std::mutex m_mutex; 95 | std::condition_variable m_cond; 96 | // this is the queue new jobs are posted to 97 | std::vector> m_queue; 98 | 99 | // the swarm hash table. Each thread has its own hash table of swarms. 100 | // swarms are pinned to certain threads based on their info-hash 101 | typedef std::unordered_map swarm_map_t; 102 | swarm_map_t m_swarms; 103 | 104 | #ifdef USE_PCAP 105 | packet_socket& m_sock; 106 | #else 107 | // socket used to send responses to 108 | packet_socket m_sock; 109 | #endif 110 | 111 | bool m_quit; 112 | int m_queue_size; 113 | std::thread m_thread; 114 | }; 115 | 116 | #endif 117 | 118 | -------------------------------------------------------------------------------- /stack.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | utrack is a very small an efficient BitTorrent tracker 3 | Copyright (C) 2014 Arvid Norberg 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include "stack.hpp" 20 | #include "arp_cache.hpp" 21 | #include 22 | 23 | int render_eth_frame(std::uint8_t* buf, int len 24 | , sockaddr_in const* to 25 | , sockaddr_in const* from 26 | , sockaddr_in const* mask 27 | , address_eth const& eth_from 28 | , arp_cache const& arp) 29 | { 30 | int ret = 0; 31 | 32 | if (len < 14) return -1; 33 | 34 | address_eth const& mac = arp.lookup(from, to, mask); 35 | 36 | memcpy(buf, mac.addr, 6); 37 | // source MAC address 38 | memcpy(buf + 6, eth_from.addr, 6); 39 | // ethertype (upper layer protocol) 40 | // 0x0800 = IPv4 41 | // 0x86dd = IPv6 42 | buf[12] = 0x08; 43 | buf[13] = 0x00; 44 | buf += 14; 45 | ret += 14; 46 | len -= 14; 47 | return ret; 48 | } 49 | 50 | int render_ip_frame(std::uint8_t* buf, int len 51 | , iovec const* v, int num 52 | , sockaddr_in const* to 53 | , sockaddr_in const* from) 54 | { 55 | int buf_size = 0; 56 | for (int i = 0; i < num; ++i) buf_size += v[i].iov_len; 57 | if (len - 20 - 8 < buf_size) return -1; 58 | 59 | int ret = 0; 60 | 61 | // version and header length 62 | buf[0] = (4 << 4) | 5; 63 | // DSCP and ECN 64 | buf[1] = 0; 65 | 66 | // packet length 67 | buf[2] = (buf_size + 20 + 8) >> 8; 68 | buf[3] = (buf_size + 20 + 8) & 0xff; 69 | 70 | // identification 71 | buf[4] = 0; 72 | buf[5] = 0; 73 | 74 | // fragment offset and flags 75 | buf[6] = 0; 76 | buf[7] = 0; 77 | 78 | // TTL 79 | buf[8] = 0x80; 80 | 81 | // protocol 82 | buf[9] = 17; 83 | 84 | // checksum 85 | buf[10] = 0; 86 | buf[11] = 0; 87 | 88 | // from addr 89 | memcpy(buf + 12, &from->sin_addr.s_addr, 4); 90 | 91 | // to addr 92 | memcpy(buf + 16, &to->sin_addr.s_addr, 4); 93 | 94 | // calculate the IP checksum 95 | std::uint16_t chk = 0; 96 | for (int i = 0; i < 20; i += 2) 97 | { 98 | chk += (buf[i] << 8) | buf[i+1]; 99 | } 100 | chk = ~chk; 101 | 102 | buf[10] = chk >> 8; 103 | buf[11] = chk & 0xff; 104 | 105 | buf += 20; 106 | ret += 20; 107 | len -= 20; 108 | 109 | if (from->sin_port == 0) 110 | { 111 | // we need to make up a source port here if our 112 | // listen port is 0 (i.e. in "promiscuous" mode) 113 | // this essentially only happens in the load test 114 | uint16_t port = htons(6881); 115 | memcpy(&buf[0], &port, 2); 116 | } 117 | else 118 | { 119 | memcpy(&buf[0], &from->sin_port, 2); 120 | } 121 | memcpy(&buf[2], &to->sin_port, 2); 122 | buf[4] = (buf_size + 8) >> 8; 123 | buf[5] = (buf_size + 8) & 0xff; 124 | 125 | // UDP checksum 126 | buf[6] = 0; 127 | buf[7] = 0; 128 | 129 | buf += 8; 130 | ret += 8; 131 | len -= 8; 132 | 133 | for (int i = 0; i < num; ++i) 134 | { 135 | memcpy(buf, v[i].iov_base, v[i].iov_len); 136 | buf += v[i].iov_len; 137 | ret += v[i].iov_len; 138 | len -= v[i].iov_len; 139 | } 140 | 141 | return ret; 142 | } 143 | -------------------------------------------------------------------------------- /siphash24.c: -------------------------------------------------------------------------------- 1 | /* 2 | SipHash reference C implementation 3 | 4 | Copyright (c) 2012 Jean-Philippe Aumasson 5 | Copyright (c) 2012 Daniel J. Bernstein 6 | 7 | To the extent possible under law, the author(s) have dedicated all copyright 8 | and related and neighboring rights to this software to the public domain 9 | worldwide. This software is distributed without any warranty. 10 | 11 | You should have received a copy of the CC0 Public Domain Dedication along with 12 | this software. If not, see . 13 | */ 14 | #include 15 | #include 16 | #include 17 | typedef uint64_t u64; 18 | typedef uint32_t u32; 19 | typedef uint8_t u8; 20 | 21 | #define cROUNDS 2 22 | #define dROUNDS 4 23 | 24 | #define ROTL(x,b) (u64)( ((x) << (b)) | ( (x) >> (64 - (b))) ) 25 | 26 | #define U32TO8_LE(p, v) \ 27 | (p)[0] = (u8)((v) ); (p)[1] = (u8)((v) >> 8); \ 28 | (p)[2] = (u8)((v) >> 16); (p)[3] = (u8)((v) >> 24); 29 | 30 | #define U64TO8_LE(p, v) \ 31 | U32TO8_LE((p), (u32)((v) )); \ 32 | U32TO8_LE((p) + 4, (u32)((v) >> 32)); 33 | 34 | #define U8TO64_LE(p) \ 35 | (((u64)((p)[0]) ) | \ 36 | ((u64)((p)[1]) << 8) | \ 37 | ((u64)((p)[2]) << 16) | \ 38 | ((u64)((p)[3]) << 24) | \ 39 | ((u64)((p)[4]) << 32) | \ 40 | ((u64)((p)[5]) << 40) | \ 41 | ((u64)((p)[6]) << 48) | \ 42 | ((u64)((p)[7]) << 56)) 43 | 44 | #define SIPROUND \ 45 | do { \ 46 | v0 += v1; v1=ROTL(v1,13); v1 ^= v0; v0=ROTL(v0,32); \ 47 | v2 += v3; v3=ROTL(v3,16); v3 ^= v2; \ 48 | v0 += v3; v3=ROTL(v3,21); v3 ^= v0; \ 49 | v2 += v1; v1=ROTL(v1,17); v1 ^= v2; v2=ROTL(v2,32); \ 50 | } while(0) 51 | 52 | /* SipHash-2-4 */ 53 | int siphash( unsigned char *out, const unsigned char *in, unsigned long long inlen, const unsigned char *k ) 54 | { 55 | /* "somepseudorandomlygeneratedbytes" */ 56 | u64 v0 = 0x736f6d6570736575ULL; 57 | u64 v1 = 0x646f72616e646f6dULL; 58 | u64 v2 = 0x6c7967656e657261ULL; 59 | u64 v3 = 0x7465646279746573ULL; 60 | u64 b; 61 | u64 k0 = U8TO64_LE( k ); 62 | u64 k1 = U8TO64_LE( k + 8 ); 63 | u64 m; 64 | int i; 65 | const u8 *end = in + inlen - ( inlen % sizeof( u64 ) ); 66 | const int left = inlen & 7; 67 | b = ( ( u64 )inlen ) << 56; 68 | v3 ^= k1; 69 | v2 ^= k0; 70 | v1 ^= k1; 71 | v0 ^= k0; 72 | 73 | for ( ; in != end; in += 8 ) 74 | { 75 | m = U8TO64_LE( in ); 76 | #ifdef DEBUG 77 | printf( "(%3d) v0 %08x %08x\n", ( int )inlen, ( u32 )( v0 >> 32 ), ( u32 )v0 ); 78 | printf( "(%3d) v1 %08x %08x\n", ( int )inlen, ( u32 )( v1 >> 32 ), ( u32 )v1 ); 79 | printf( "(%3d) v2 %08x %08x\n", ( int )inlen, ( u32 )( v2 >> 32 ), ( u32 )v2 ); 80 | printf( "(%3d) v3 %08x %08x\n", ( int )inlen, ( u32 )( v3 >> 32 ), ( u32 )v3 ); 81 | printf( "(%3d) compress %08x %08x\n", ( int )inlen, ( u32 )( m >> 32 ), ( u32 )m ); 82 | #endif 83 | v3 ^= m; 84 | 85 | for( i=0; i> 32 ), ( u32 )v0 ); 111 | printf( "(%3d) v1 %08x %08x\n", ( int )inlen, ( u32 )( v1 >> 32 ), ( u32 )v1 ); 112 | printf( "(%3d) v2 %08x %08x\n", ( int )inlen, ( u32 )( v2 >> 32 ), ( u32 )v2 ); 113 | printf( "(%3d) v3 %08x %08x\n", ( int )inlen, ( u32 )( v3 >> 32 ), ( u32 )v3 ); 114 | printf( "(%3d) padding %08x %08x\n", ( int )inlen, ( u32 )( b >> 32 ), ( u32 )b ); 115 | #endif 116 | v3 ^= b; 117 | 118 | for( i=0; i> 32 ), ( u32 )v0 ); 123 | printf( "(%3d) v1 %08x %08x\n", ( int )inlen, ( u32 )( v1 >> 32 ), ( u32 )v1 ); 124 | printf( "(%3d) v2 %08x %08x\n", ( int )inlen, ( u32 )( v2 >> 32 ), ( u32 )v2 ); 125 | printf( "(%3d) v3 %08x %08x\n", ( int )inlen, ( u32 )( v3 >> 32 ), ( u32 )v3 ); 126 | #endif 127 | v2 ^= 0xff; 128 | 129 | for( i=0; i. 17 | */ 18 | 19 | 20 | #ifndef SOCKET_SYSTEM_HPP 21 | #define SOCKET_SYSTEM_HPP 22 | 23 | #include 24 | #include 25 | 26 | #ifndef _WIN32 27 | #include // for close 28 | #include // for poll 29 | #else 30 | #include 31 | #endif 32 | 33 | #include "utils.hpp" // for address_eth 34 | #include "config.hpp" 35 | 36 | struct packet_buffer; 37 | 38 | struct packet_socket 39 | { 40 | friend struct packet_buffer; 41 | 42 | packet_socket(int listen_port, bool receive = false); 43 | ~packet_socket(); 44 | packet_socket(packet_socket&& s); 45 | packet_socket(packet_socket const&) = delete; 46 | 47 | void close(); 48 | 49 | bool send(packet_buffer& packets); 50 | 51 | void local_endpoint(sockaddr_in* addr); 52 | 53 | void add_arp_entry(sockaddr_in const* addr, address_eth const& mac) {} 54 | 55 | 56 | // receive at least one packet on the socket. No more than num (defaults 57 | // to 1000). For each received packet, callback is called with the following 58 | // arguments: (sockaddr_in* from, uint8_t* buffer, int len) 59 | // the buffer is valid until the callback returns 60 | // returns -1 on error 61 | template 62 | int receive(F callback, int num = 1000) 63 | { 64 | if (num == 0) return 0; 65 | 66 | sockaddr_in from; 67 | socklen_t fromlen = sizeof(from); 68 | 69 | // if there's no data available, try a few times in a row right away. 70 | // if there's still no data after that, go to sleep waiting for more 71 | int spincount = receive_spin_count; 72 | 73 | std::array buf; 74 | 75 | // this loop is primarily here to be able to restart 76 | // in the event of EINTR and also in the case of no data 77 | // being available immediately (in which case we block in poll) 78 | while (true) 79 | { 80 | fromlen = sizeof(from); 81 | int size = recvfrom(m_socket, (char*)buf.data(), buf.size()*8, 0 82 | , (sockaddr*)&from, &fromlen); 83 | if (size == -1) 84 | { 85 | #ifdef _WIN32 86 | int err = WSAGetLastError(); 87 | #else 88 | int err = errno; 89 | #endif 90 | if (err == EINTR) continue; 91 | #ifdef _WIN32 92 | if (err == WSAEWOULDBLOCK) 93 | #else 94 | if (err == EAGAIN || errno == EWOULDBLOCK) 95 | #endif 96 | { 97 | --spincount; 98 | if (spincount > 0) continue; 99 | // the first read came back empty, wait for new data 100 | // on the socket and try again 101 | pollfd e; 102 | e.fd = m_socket; 103 | e.events = POLLIN; 104 | e.revents = 0; 105 | 106 | spincount = receive_spin_count; 107 | 108 | #ifdef _WIN32 109 | int r = WSAPoll(&e, 1, 2000); 110 | #else 111 | int r = poll(&e, 1, 2000); 112 | #endif 113 | if (r == -1) 114 | { 115 | if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) 116 | continue; 117 | fprintf(stderr, "poll failed (%d): %s\n", err, strerror(err)); 118 | return -1; 119 | } 120 | 121 | if (r == 0) 122 | { 123 | // no events, see if the socket was closed 124 | if (m_socket == -1) return -1; 125 | continue; 126 | } 127 | 128 | if ((e.revents & POLLHUP) || (e.revents & POLLERR)) 129 | { 130 | fprintf(stderr, "poll returned socket failure (%d): %s\n" 131 | , err, strerror(err)); 132 | return -1; 133 | } 134 | continue; 135 | } 136 | fprintf(stderr, "recvfrom failed (%d): %s\n", err, strerror(err)); 137 | return -1; 138 | } 139 | 140 | callback(&from, (uint8_t const*)buf.data(), size); 141 | break; 142 | } 143 | 144 | return 1; 145 | } 146 | 147 | private: 148 | int m_socket; 149 | bool m_receive; 150 | }; 151 | 152 | struct packet_buffer 153 | { 154 | friend struct packet_socket; 155 | 156 | explicit packet_buffer(packet_socket& s) 157 | : m_socket(s.m_socket) 158 | {} 159 | 160 | bool is_full(int buf_size) const { return false; } 161 | 162 | bool append(iovec const* v, int num, sockaddr_in const* to); 163 | 164 | private: 165 | int m_socket; 166 | }; 167 | 168 | #endif // SOCKET_SYSTEM_HPP 169 | 170 | -------------------------------------------------------------------------------- /socket_system.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | utrack is a very small an efficient BitTorrent tracker 3 | Copyright (C) 2013-2014 Arvid Norberg 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include "socket.hpp" 20 | #include "config.hpp" 21 | 22 | #include // for stderr 23 | #include // for errno 24 | #include // for strerror 25 | #include // for exit 26 | #include // for F_GETFL and F_SETFL 27 | 28 | #include 29 | #include 30 | 31 | #ifndef MSG_NOSIGNAL 32 | #define MSG_NOSIGNAL 0 33 | #endif 34 | 35 | extern std::atomic bytes_out; 36 | 37 | packet_socket::packet_socket(int listen_port, bool receive) 38 | : m_socket(-1) 39 | , m_receive(receive) 40 | { 41 | m_socket = socket(PF_INET, SOCK_DGRAM, 0); 42 | if (m_socket < 0) 43 | { 44 | fprintf(stderr, "failed to open socket (%d): %s\n" 45 | , errno, strerror(errno)); 46 | exit(1); 47 | } 48 | 49 | int opt = socket_buffer_size; 50 | int r = setsockopt(m_socket, SOL_SOCKET, m_receive ? SO_RCVBUF : SO_SNDBUF 51 | , (char const*)&opt, sizeof(opt)); 52 | if (r == -1) 53 | { 54 | fprintf(stderr, "failed to set socket %s buffer size (%d): %s\n" 55 | , m_receive ? "receive" : "send", errno, strerror(errno)); 56 | } 57 | 58 | int one = 1; 59 | if (setsockopt(m_socket, SOL_SOCKET, SO_REUSEADDR 60 | , (char const*)&one, sizeof(one)) < 0) 61 | { 62 | fprintf(stderr, "failed to set SO_REUSEADDR on socket (%d): %s\n" 63 | , errno, strerror(errno)); 64 | } 65 | 66 | #ifdef SO_REUSEPORT 67 | if (setsockopt(m_socket, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)) < 0) 68 | { 69 | fprintf(stderr, "failed to set SO_REUSEPORT on socket (%d): %s\n" 70 | , errno, strerror(errno)); 71 | } 72 | #endif 73 | 74 | // we cannot bind the sockets meant for just outgoing packets to the 75 | // IP and port, since then they will swallow incoming packets 76 | if (m_receive) 77 | { 78 | sockaddr_in bind_addr; 79 | memset(&bind_addr, 0, sizeof(bind_addr)); 80 | bind_addr.sin_family = AF_INET; 81 | bind_addr.sin_addr.s_addr = INADDR_ANY; 82 | bind_addr.sin_port = htons(listen_port); 83 | r = bind(m_socket, (sockaddr*)&bind_addr, sizeof(bind_addr)); 84 | if (r < 0) 85 | { 86 | fprintf(stderr, "failed to bind socket to port %d (%d): %s\n" 87 | , listen_port, errno, strerror(errno)); 88 | exit(1); 89 | } 90 | 91 | #ifdef _WIN32 92 | unsigned long one = 1; 93 | r = ioctlsocket(m_socket, FIONBIO, &one); 94 | if (r < 0) 95 | { 96 | fprintf(stderr, "failed to set non-blocking mode (%d): %s\n" 97 | , errno, strerror(errno)); 98 | exit(1); 99 | } 100 | #else 101 | int flags = fcntl(m_socket, F_GETFL, 0); 102 | if (flags < 0) 103 | { 104 | fprintf(stderr, "failed to get file flags (%d): %s\n" 105 | , errno, strerror(errno)); 106 | exit(1); 107 | } 108 | flags |= O_NONBLOCK; 109 | r = fcntl(m_socket, F_SETFL, flags); 110 | if (r < 0) 111 | { 112 | fprintf(stderr, "failed to set file flags (%d): %s\n" 113 | , errno, strerror(errno)); 114 | exit(1); 115 | } 116 | #endif 117 | } 118 | } 119 | 120 | packet_socket::~packet_socket() 121 | { 122 | close(); 123 | } 124 | 125 | void packet_socket::close() 126 | { 127 | if (m_socket != -1) 128 | #ifdef _WIN32 129 | ::closesocket(m_socket); 130 | #else 131 | ::close(m_socket); 132 | #endif 133 | m_socket = -1; 134 | } 135 | 136 | packet_socket::packet_socket(packet_socket&& s) 137 | : m_socket(s.m_socket) 138 | , m_receive(s.m_receive) 139 | { 140 | s.m_socket = -1; 141 | } 142 | 143 | bool packet_socket::send(packet_buffer& packets) 144 | { 145 | // This is NOP. packets are sent directly when added to packet_buffer. 146 | return true; 147 | } 148 | 149 | // send a packet and retry on EINTR 150 | bool packet_buffer::append(iovec const* v, int num, sockaddr_in const* to) 151 | { 152 | #ifdef _WIN32 153 | // windows doesn't support the msghdr 154 | char buf[1500]; 155 | char* ptr = buf; 156 | int len = 0; 157 | if (num == 1) 158 | { 159 | ptr = (char*)v->iov_base; 160 | len = v->iov_len; 161 | } 162 | else 163 | { 164 | for (int i = 0; i < num; ++i) 165 | { 166 | memcpy(ptr, v[i].iov_base, v[i].iov_len); 167 | ptr += v[i].iov_len; 168 | len += v[i].iov_len; 169 | } 170 | ptr = buf; 171 | } 172 | #else 173 | msghdr msg; 174 | msg.msg_name = (void*)to; 175 | msg.msg_namelen = sizeof(sockaddr_in); 176 | msg.msg_iov = (iovec*)v; 177 | msg.msg_iovlen = num; 178 | msg.msg_control = 0; 179 | msg.msg_controllen = 0; 180 | msg.msg_flags = MSG_NOSIGNAL; 181 | #endif 182 | // loop just to deal with the potential EINTR 183 | do 184 | { 185 | #ifdef _WIN32 186 | int r = sendto(m_socket, ptr, len, 0 187 | , (sockaddr const*)to, sizeof(sockaddr_in)); 188 | #else 189 | int r = sendmsg(m_socket, &msg, 0); 190 | #endif 191 | if (r < 0) 192 | { 193 | if (errno == EINTR) continue; 194 | fprintf(stderr, "sendmsg failed (%d): %s\n", errno, strerror(errno)); 195 | return false; 196 | } 197 | bytes_out.fetch_add(r, std::memory_order_relaxed); 198 | } while (false); 199 | return true; 200 | } 201 | 202 | void packet_socket::local_endpoint(sockaddr_in* addr) 203 | { 204 | socklen_t len; 205 | getpeername(m_socket, (sockaddr*)addr, &len); 206 | } 207 | 208 | -------------------------------------------------------------------------------- /swarm.cpp: -------------------------------------------------------------------------------- 1 | #include "swarm.hpp" 2 | #include 3 | #include 4 | #include 5 | #include // for min, max 6 | 7 | #ifdef _WIN32 8 | #include // for ntohl 9 | #else 10 | #include // for ntohl 11 | #endif 12 | 13 | #include "config.hpp" 14 | 15 | swarm::swarm() 16 | : m_seeds(0) 17 | , m_downloaders(0) 18 | , m_download_count(0) 19 | , m_last_announce((steady_clock::time_point::min)()) 20 | { 21 | m_last_purge = m_peers4.end(); 22 | } 23 | 24 | void swarm::scrape(uint32_t* seeds, uint32_t* download_count, uint32_t* downloaders) 25 | { 26 | *seeds = m_seeds; 27 | *download_count = m_download_count; 28 | *downloaders = m_downloaders; 29 | } 30 | 31 | void swarm::announce(steady_clock::time_point now, udp_announce_message const* hdr 32 | , char** buf, int* len 33 | , uint32_t* downloaders, uint32_t* seeds 34 | , std::mt19937& mt_engine) 35 | { 36 | *seeds = m_seeds; 37 | *downloaders = m_downloaders; 38 | std::uniform_int_distribution rand(0, 1); 39 | typedef std::uniform_int_distribution::param_type pair; 40 | 41 | m_last_announce = now; 42 | 43 | // the interval setting 44 | extern int interval; 45 | 46 | if (m_last_purge == m_peers4.end() && !m_peers4.empty()) 47 | m_last_purge = m_peers4.begin(); 48 | 49 | // check the next peer for timeout 50 | if (m_last_purge != m_peers4.end()) 51 | { 52 | hash_map4_t::iterator i = m_last_purge++; 53 | if (i->second.last_announce < now - seconds(interval + interval / 2)) 54 | erase_peer(i); 55 | } 56 | 57 | hash_map4_t::iterator i = m_peers4.find(hdr->ip); 58 | 59 | if (i == m_peers4.end()) 60 | { 61 | if (ntohl(hdr->event) == event_stopped) 62 | { 63 | // we don't have this peer in the list, and it 64 | // just sent stopped. Don't do anything 65 | *buf = 0; 66 | *len = 0; 67 | return; 68 | } 69 | // insert this peer 70 | 71 | // Assume a swarm doesn't need more than this many peers in the tracker's 72 | // peer list to stay well connected and healthy 73 | if (m_peers4.size() >= max_peerlist_size) 74 | { 75 | // remove one random peer 76 | hash_map4_t::iterator del = m_peers4.begin(); 77 | std::advance(del, rand(mt_engine, pair(0, m_peers4.size() - 1))); 78 | erase_peer(del); 79 | } 80 | 81 | peer_entry e; 82 | e.last_announce = now; 83 | e.index = m_ips4.size(); 84 | e.key = hdr->key; 85 | if (ntohl(hdr->event) == event_completed) 86 | { 87 | e.complete = true; 88 | ++m_download_count; 89 | } 90 | if (ntohl(hdr->left) > 0) 91 | { 92 | e.downloading = true; 93 | ++m_downloaders; 94 | } 95 | else 96 | { 97 | e.downloading = false; 98 | ++m_seeds; 99 | } 100 | 101 | m_ips4.push_back(peer_ip4(hdr->ip, hdr->port)); 102 | std::pair ret = m_peers4.insert( 103 | std::make_pair(hdr->ip, e)); 104 | i = ret.first; 105 | } 106 | else 107 | { 108 | if (ntohl(hdr->event) == event_stopped) 109 | { 110 | // remove the peer from the list and don't 111 | // return any peers 112 | erase_peer(i); 113 | *buf = 0; 114 | *len = 0; 115 | return; 116 | } 117 | 118 | peer_entry& e = i->second; 119 | e.last_announce = now; 120 | e.key = hdr->key; 121 | 122 | // this peer just completed (and hasn't sent complete before) 123 | if (ntohl(hdr->event) == event_completed && !e.complete) 124 | { 125 | e.complete = true; 126 | ++m_download_count; 127 | } 128 | 129 | if (ntohl(hdr->left) == 0 && e.downloading) 130 | { 131 | // this peer just became a seed 132 | e.downloading = false; 133 | --m_downloaders; 134 | ++m_seeds; 135 | } 136 | else if (ntohl(hdr->left) > 0 && !e.downloading) 137 | { 138 | // this peer just reverted to being a downloader (somehow) 139 | e.downloading = true; 140 | --m_seeds; 141 | ++m_downloaders; 142 | } 143 | 144 | // the port might have changed 145 | m_ips4[e.index] = peer_ip4(hdr->ip, hdr->port); 146 | } 147 | 148 | size_t num_want = (std::min)((std::min)(size_t(default_num_want) 149 | , size_t(m_ips4.size())), size_t(ntohl(hdr->num_want))); 150 | 151 | if (num_want <= 0) 152 | { 153 | *buf = 0; 154 | *len = 0; 155 | } 156 | else 157 | { 158 | if (m_ips4.size() <= num_want) 159 | { 160 | // special case when we should send every peer 161 | *buf = (char*)&m_ips4[0]; 162 | *len = m_ips4.size() * 6; 163 | } 164 | else 165 | { 166 | // TODO: this is sub-optimal since it doesn't wrap 167 | int cursor = rand(mt_engine, pair(0, m_peers4.size() - 1 - num_want)); 168 | *buf = (char*)&m_ips4[cursor]; 169 | *len = num_want * 6; 170 | } 171 | } 172 | } 173 | 174 | void swarm::erase_peer(swarm::hash_map4_t::iterator i) 175 | { 176 | if (i == m_last_purge) ++m_last_purge; 177 | 178 | peer_entry& e = i->second; 179 | 180 | // swap the last entry in the peer IPs array 181 | // with the one we're removing 182 | hash_map4_t::iterator last = m_peers4.find(m_ips4.back().ip4()); 183 | assert(i != m_peers4.end()); 184 | 185 | last->second.index = e.index; 186 | m_ips4[e.index] = m_ips4.back(); 187 | 188 | // and then remove the last entry 189 | m_ips4.pop_back(); 190 | 191 | if (e.downloading) --m_downloaders; 192 | else --m_seeds; 193 | 194 | // and finally remove the peer_entry 195 | m_peers4.erase(i); 196 | } 197 | 198 | void swarm::purge_stale(steady_clock::time_point now) 199 | { 200 | if (m_peers4.empty()) return; 201 | 202 | // the interval setting 203 | extern int interval; 204 | 205 | int num = (std::min)(200, int(m_peers4.size())); 206 | 207 | for (int i = 0; i < num; ++i) 208 | { 209 | if (m_last_purge == m_peers4.end()) 210 | m_last_purge = m_peers4.begin(); 211 | 212 | // check the next peer for timeout 213 | hash_map4_t::iterator it = m_last_purge++; 214 | if (it->second.last_announce < now - seconds(interval + interval / 2)) 215 | erase_peer(it); 216 | } 217 | } 218 | 219 | -------------------------------------------------------------------------------- /announce_thread.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | utrack is a very small an efficient BitTorrent tracker 3 | Copyright (C) 2010-2013 Arvid Norberg 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include "announce_thread.hpp" 20 | #include "socket.hpp" 21 | #include "config.hpp" 22 | 23 | #include 24 | #include 25 | #include 26 | #include // for generate 27 | 28 | #include 29 | 30 | #ifndef _WIN32 31 | #include 32 | #include 33 | #endif 34 | 35 | #ifndef MSG_NOSIGNAL 36 | #define MSG_NOSIGNAL 0 37 | #endif 38 | 39 | using std::chrono::steady_clock; 40 | using std::chrono::seconds; 41 | 42 | extern std::atomic bytes_out; 43 | extern std::atomic announces; 44 | extern std::atomic dropped_announces; 45 | extern std::atomic scrapes; 46 | 47 | std::array gen_random_key() 48 | { 49 | std::array ret; 50 | std::random_device dev; 51 | std::generate(ret.begin(), ret.end(), std::ref(dev)); 52 | return ret; 53 | } 54 | 55 | #ifdef USE_PCAP 56 | announce_thread::announce_thread(packet_socket& s) 57 | : m_sock(s) 58 | , m_quit(false) 59 | , m_queue_size(0) 60 | , m_thread( [=]() { thread_fun(); } ) 61 | { 62 | m_queue.reserve(announce_queue_size); 63 | } 64 | #else 65 | announce_thread::announce_thread(int listen_port) 66 | : m_sock(listen_port) 67 | , m_quit(false) 68 | , m_queue_size(0) 69 | , m_thread( [=]() { thread_fun(); } ) 70 | { 71 | m_queue.reserve(announce_queue_size); 72 | } 73 | #endif 74 | 75 | void announce_thread::thread_fun() 76 | { 77 | #ifndef _WIN32 78 | sigset_t sig; 79 | sigfillset(&sig); 80 | int r = pthread_sigmask(SIG_BLOCK, &sig, NULL); 81 | if (r == -1) 82 | { 83 | fprintf(stderr, "pthread_sigmask failed (%d): %s\n", errno, strerror(errno)); 84 | } 85 | #endif 86 | 87 | std::random_device dev; 88 | std::mt19937 mt_engine(dev()); 89 | std::uniform_int_distribution rand(0, 240); 90 | 91 | // this is the queue the other one is swapped into 92 | // and then drained without needing to hold the mutex 93 | std::vector> queue; 94 | 95 | steady_clock::time_point now = steady_clock::now(); 96 | steady_clock::time_point next_prune = now + seconds(10); 97 | 98 | // round-robin for timing out peers 99 | swarm_map_t::iterator next_to_purge = m_swarms.begin(); 100 | 101 | packet_buffer send_buffer(m_sock); 102 | 103 | for (;;) 104 | { 105 | std::unique_lock l(m_mutex); 106 | while (m_queue.empty() 107 | && !m_quit 108 | && (now = steady_clock::now()) < next_prune) 109 | m_cond.wait(l); 110 | 111 | if (m_quit) break; 112 | m_queue.swap(queue); 113 | m_queue_size = 0; 114 | l.unlock(); 115 | 116 | now = steady_clock::now(); 117 | // if it's been long enough, just do some relgular 118 | // maintanence on the swarms 119 | if (now > next_prune) 120 | { 121 | next_prune = now + seconds(10); 122 | 123 | if (next_to_purge == m_swarms.end() && m_swarms.size() > 0) 124 | next_to_purge = m_swarms.begin(); 125 | 126 | if (m_swarms.size() > 0) 127 | { 128 | int num_to_purge = (std::min)(int(m_swarms.size()), 20); 129 | 130 | for (int i = 0; i < num_to_purge; ++i) 131 | { 132 | swarm& s = next_to_purge->second; 133 | s.purge_stale(now); 134 | 135 | ++next_to_purge; 136 | if (next_to_purge == m_swarms.end()) next_to_purge = m_swarms.begin(); 137 | } 138 | } 139 | } 140 | 141 | for (std::vector const& v : queue) 142 | { 143 | for (announce_msg const& m : v) 144 | { 145 | switch (ntohl(m.bits.announce.action)) 146 | { 147 | case action_announce: 148 | { 149 | // find the swarm being announce to 150 | // or create it if it doesn't exist 151 | swarm& s = m_swarms[m.bits.announce.hash]; 152 | 153 | // prepare the buffer to write the response to 154 | char* buf; 155 | int len; 156 | udp_announce_response resp; 157 | 158 | resp.action = htonl(action_announce); 159 | resp.transaction_id = m.bits.announce.transaction_id; 160 | resp.interval = htonl(1680 + rand(mt_engine)); 161 | 162 | // do the actual announce with the swarm 163 | // and get a pointer to the peers back 164 | s.announce(now, &m.bits.announce, &buf, &len, &resp.downloaders 165 | , &resp.seeds, mt_engine); 166 | 167 | announces.fetch_add(1, std::memory_order_relaxed); 168 | 169 | // now turn these counters into network byte order 170 | resp.downloaders = htonl(resp.downloaders); 171 | resp.seeds = htonl(resp.seeds); 172 | 173 | // set up the iovec array for the response. The header + the 174 | // body with the peer list 175 | iovec iov[2] = { { &resp, 20}, { buf, size_t(len) } }; 176 | 177 | send_buffer.append(iov, 2, &m.from); 178 | break; 179 | } 180 | case action_scrape: 181 | { 182 | udp_scrape_response resp; 183 | resp.action = htonl(action_scrape); 184 | resp.transaction_id = m.bits.scrape.transaction_id; 185 | 186 | scrapes.fetch_add(1, std::memory_order_relaxed); 187 | 188 | swarm_map_t::iterator j = m_swarms.find(m.bits.scrape.hash[0]); 189 | if (j != m_swarms.end()) 190 | { 191 | j->second.scrape(&resp.data[0].seeds, &resp.data[0].download_count 192 | , &resp.data[0].downloaders); 193 | resp.data[0].seeds = htonl(resp.data[0].seeds); 194 | resp.data[0].download_count = htonl(resp.data[0].download_count); 195 | resp.data[0].downloaders = htonl(resp.data[0].downloaders); 196 | } 197 | 198 | iovec iov = { &resp, 8 + 12}; 199 | send_buffer.append(&iov, 1, &m.from); 200 | break; 201 | } 202 | } 203 | } 204 | } 205 | queue.clear(); 206 | m_sock.send(send_buffer); 207 | } 208 | } 209 | 210 | void announce_thread::post_announces(std::vector m) 211 | { 212 | if (m.empty()) return; 213 | 214 | std::unique_lock l(m_mutex); 215 | 216 | // have some upper limit here, to avoid 217 | // allocating memory indefinitely 218 | if (m_queue_size >= announce_queue_size) 219 | { 220 | dropped_announces.fetch_add(m.size(), std::memory_order_relaxed); 221 | return; 222 | } 223 | 224 | m_queue_size += m.size(); 225 | bool first_insert = m_queue.empty(); 226 | m_queue.emplace_back(std::move(m)); 227 | 228 | // don't send a signal if we don't need to 229 | // it's expensive 230 | if (first_insert) 231 | m_cond.notify_one(); 232 | } 233 | 234 | announce_thread::~announce_thread() 235 | { 236 | std::unique_lock l(m_mutex); 237 | m_quit = true; 238 | l.unlock(); 239 | m_cond.notify_one(); 240 | m_thread.join(); 241 | } 242 | 243 | -------------------------------------------------------------------------------- /receive_thread.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2010-2014 Arvid Norberg 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | */ 17 | 18 | #include "receive_thread.hpp" 19 | #include "key_rotate.hpp" 20 | #include "messages.hpp" 21 | #include "endian.hpp" 22 | #include "announce_thread.hpp" 23 | #include "config.hpp" 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | extern std::atomic connects; 30 | extern std::atomic errors; 31 | extern std::atomic bytes_in; 32 | extern std::atomic dropped_bytes_out; 33 | 34 | extern key_rotate keys; 35 | 36 | extern "C" int siphash( unsigned char *out, const unsigned char *in 37 | , unsigned long long inlen, const unsigned char *k); 38 | 39 | std::uint64_t gen_secret_digest(sockaddr_in const* from 40 | , std::array const& key) 41 | { 42 | std::arraysin_addr) + sizeof(from->sin_port)> ep; 43 | memcpy(ep.data(), (char*)&from->sin_addr, sizeof(from->sin_addr)); 44 | memcpy(ep.data() + sizeof(from->sin_addr), (char*)&from->sin_port 45 | , sizeof(from->sin_port)); 46 | 47 | std::uint64_t ret; 48 | siphash((std::uint8_t*)&ret, ep.data(), ep.size(), key.data()); 49 | return ret; 50 | } 51 | 52 | uint64_t generate_connection_id(sockaddr_in const* from) 53 | { 54 | return gen_secret_digest(from, keys.cur_key()); 55 | } 56 | 57 | bool verify_connection_id(uint64_t conn_id, sockaddr_in const* from) 58 | { 59 | return conn_id == gen_secret_digest(from, keys.cur_key()) 60 | || conn_id == gen_secret_digest(from, keys.prev_key()); 61 | } 62 | 63 | #ifdef USE_PCAP 64 | receive_thread::receive_thread(packet_socket& s 65 | , std::vector const& at) 66 | : m_sock(s) 67 | , m_announce_threads(at) 68 | , m_thread( [=]() { thread_fun(); } ) {} 69 | 70 | #else 71 | 72 | receive_thread::receive_thread(int listen_port 73 | , std::vector const& at) 74 | : m_sock(listen_port, true) 75 | , m_announce_threads(at) 76 | , m_thread( [=]() { thread_fun(); } ) {} 77 | 78 | #endif 79 | 80 | receive_thread::~receive_thread() 81 | { 82 | m_sock.close(); 83 | m_thread.join(); 84 | } 85 | 86 | void receive_thread::close() 87 | { 88 | m_sock.close(); 89 | } 90 | 91 | void receive_thread::thread_fun() 92 | { 93 | #ifndef _WIN32 94 | sigset_t sig; 95 | sigfillset(&sig); 96 | int r = pthread_sigmask(SIG_BLOCK, &sig, NULL); 97 | if (r == -1) 98 | { 99 | fprintf(stderr, "pthread_sigmask failed (%d): %s\n", errno, strerror(errno)); 100 | } 101 | #endif 102 | 103 | packet_buffer send_buffer(m_sock); 104 | 105 | std::vector> announce_buf(m_announce_threads.size()); 106 | 107 | for (;;) 108 | { 109 | int r = m_sock.receive([&](sockaddr_in const* from, uint8_t const* buf, int len) 110 | { 111 | incoming_packet(buf, len, from, send_buffer, announce_buf.data()); 112 | }); 113 | 114 | m_sock.send(send_buffer); 115 | 116 | for (int i = 0; i < m_announce_threads.size(); ++i) 117 | { 118 | m_announce_threads[i]->post_announces(std::move(announce_buf[i])); 119 | announce_buf[i].clear(); 120 | } 121 | if (r < 0) break; 122 | } 123 | } 124 | 125 | // this thread receives incoming announces, parses them and posts 126 | // the announce to the correct announce thread, that then takes over 127 | // and is responsible for responding. The send_buffer is where outgoing 128 | // responses go, they will be comitted and sent off at a later time. 129 | // similarly, announce_buf is where announce messages for the announce 130 | // threads go. It's an array of announce_msg buffers, one entry per 131 | // announce thread. 132 | void receive_thread::incoming_packet(uint8_t const* buf, int size 133 | , sockaddr_in const* from, packet_buffer& send_buffer 134 | , std::vector* announce_buf) 135 | { 136 | bytes_in.fetch_add(size, std::memory_order_relaxed); 137 | 138 | // printf("received message from: %x port: %d size: %d\n" 139 | // , from->sin_addr.s_addr, ntohs(from->sin_port), size); 140 | 141 | if (size < 16) 142 | { 143 | printf("packet too short (%d)\n", size); 144 | // log incorrect packet 145 | return; 146 | } 147 | 148 | udp_announce_message* hdr = (udp_announce_message*)buf; 149 | 150 | switch (ntohl(hdr->action)) 151 | { 152 | case action_connect: 153 | { 154 | // TODO: increment dropped counter? 155 | if (send_buffer.is_full(16)) 156 | { 157 | dropped_bytes_out.fetch_add(16, std::memory_order_relaxed); 158 | return; 159 | } 160 | 161 | if (be64toh(hdr->connection_id) != 0x41727101980LL) 162 | { 163 | errors.fetch_add(1, std::memory_order_relaxed); 164 | printf("invalid connection ID for connect message (%" PRIx64 ")\n" 165 | , be64toh(hdr->connection_id)); 166 | // log error 167 | return; 168 | } 169 | udp_connect_response resp; 170 | resp.action = htonl(action_connect); 171 | resp.connection_id = generate_connection_id(from); 172 | 173 | // uint8_t const* addr = (uint8_t const*)&from->sin_addr.s_addr; 174 | // printf("connection ID (%d.%d.%d.%d:%u) %" PRIx64 "\n" 175 | // , addr[0], addr[1], addr[2], addr[3], ntohs(from->sin_port) 176 | // , resp.connection_id); 177 | 178 | resp.transaction_id = hdr->transaction_id; 179 | connects.fetch_add(1, std::memory_order_relaxed); 180 | iovec iov = { &resp, 16}; 181 | if (send_buffer.append(&iov, 1, from)) 182 | return; 183 | break; 184 | } 185 | case action_announce: 186 | { 187 | if (!verify_connection_id(hdr->connection_id, from)) 188 | { 189 | uint8_t const* addr = (uint8_t const*)&from->sin_addr.s_addr; 190 | printf("invalid connection ID (%d.%d.%d.%d:%u) %" PRIx64 "\n" 191 | , addr[0], addr[1], addr[2], addr[3], ntohs(from->sin_port) 192 | , hdr->connection_id); 193 | errors.fetch_add(1, std::memory_order_relaxed); 194 | // log error 195 | return; 196 | } 197 | // technically the announce message should 198 | // be 100 bytes, but uTorrent doesn't seem to send 199 | // the extension field at the end 200 | if (size < 98) 201 | { 202 | printf("announce packet too short. Expected 100, got %d\n", size); 203 | errors.fetch_add(1, std::memory_order_relaxed); 204 | // log incorrect packet 205 | return; 206 | } 207 | 208 | if (!allow_alternate_ip || hdr->ip == 0) 209 | hdr->ip = from->sin_addr.s_addr; 210 | 211 | // post the announce to the thread that's responsible 212 | // for this info-hash 213 | announce_msg m; 214 | m.bits.announce = *hdr; 215 | m.from = *from; 216 | 217 | // use siphash here to prevent hash collision attacks causing one 218 | // thread to overload 219 | int thread_selector = siphash_fun()(hdr->hash) % m_announce_threads.size(); 220 | announce_buf[thread_selector].push_back(m); 221 | 222 | break; 223 | } 224 | case action_scrape: 225 | { 226 | if (!verify_connection_id(hdr->connection_id, from)) 227 | { 228 | printf("invalid connection ID for connect message\n"); 229 | errors.fetch_add(1, std::memory_order_relaxed); 230 | // log error 231 | return; 232 | } 233 | if (size < 16 + 20) 234 | { 235 | printf("scrape packet too short. Expected 36, got %d\n", size); 236 | errors.fetch_add(1, std::memory_order_relaxed); 237 | // log error 238 | return; 239 | } 240 | 241 | udp_scrape_message* req = (udp_scrape_message*)buf; 242 | 243 | // for now, just support scrapes for a single hash at a time 244 | // to avoid having to bounce the request around all the threads 245 | // befor accruing all the stats 246 | 247 | // post the announce to the thread that's responsible 248 | // for this info-hash 249 | announce_msg m; 250 | m.bits.scrape = *req; 251 | m.from = *from; 252 | int thread_selector = req->hash[0].val[0] % m_announce_threads.size(); 253 | announce_buf[thread_selector].push_back(m); 254 | 255 | break; 256 | } 257 | default: 258 | printf("unknown action %d\n", ntohl(hdr->action)); 259 | assert(false); 260 | errors.fetch_add(1, std::memory_order_relaxed); 261 | break; 262 | } 263 | } 264 | 265 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | utrack is a very small an efficient BitTorrent tracker 3 | Copyright (C) 2010-2014 Arvid Norberg 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | #ifdef __linux__ 23 | #include 24 | #include 25 | #endif 26 | 27 | #ifndef _WIN32 28 | #include 29 | #include 30 | #include 31 | #include // for inet_ntop 32 | #include 33 | #else 34 | #include 35 | #endif 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | #include "swarm.hpp" 50 | #include "endian.hpp" 51 | #include "announce_thread.hpp" 52 | #include "socket.hpp" 53 | #include "key_rotate.hpp" 54 | #include "receive_thread.hpp" 55 | #include "config.hpp" 56 | 57 | #ifndef MSG_NOSIGNAL 58 | #define MSG_NOSIGNAL 0 59 | #endif 60 | 61 | using std::chrono::steady_clock; 62 | using std::chrono::duration_cast; 63 | using std::chrono::seconds; 64 | 65 | int interval = default_interval; 66 | 67 | // set to true when we're shutting down 68 | volatile bool quit = false; 69 | 70 | // the secret keys used for syn-cookies 71 | key_rotate keys; 72 | 73 | // stats counters 74 | std::atomic connects = ATOMIC_VAR_INIT(0); 75 | std::atomic announces = ATOMIC_VAR_INIT(0); 76 | std::atomic scrapes = ATOMIC_VAR_INIT(0); 77 | std::atomic errors = ATOMIC_VAR_INIT(0); 78 | std::atomic bytes_out = ATOMIC_VAR_INIT(0); 79 | std::atomic bytes_in = ATOMIC_VAR_INIT(0); 80 | std::atomic dropped_bytes_out = ATOMIC_VAR_INIT(0); 81 | 82 | // the number of dropped announce requests, because we couldn't keep up 83 | std::atomic dropped_announces = ATOMIC_VAR_INIT(0); 84 | 85 | #ifdef _WIN32 86 | BOOL WINAPI sigint(DWORD s) 87 | #else 88 | void sigint(int s) 89 | #endif 90 | { 91 | printf("shutting down\n"); 92 | quit = true; 93 | 94 | #ifdef _WIN32 95 | return TRUE; 96 | #endif 97 | } 98 | 99 | void print_usage() 100 | { 101 | printf("usage:\nutrack bind-ip [port]\n\n" 102 | " bind-ip the IP address of the network device to listen on\n" 103 | " port the UDP port to listen on (defaults to 80)\n" 104 | "\n" 105 | "utrack --help\n\n" 106 | " displays this message\n" 107 | ); 108 | exit(1); 109 | } 110 | 111 | #ifdef _WIN32 112 | static struct wsa_init_t { 113 | wsa_init_t() 114 | { 115 | WSADATA wsaData; 116 | WSAStartup(MAKEWORD(2, 2), &wsaData); 117 | } 118 | } dummy_initializer; 119 | #endif 120 | 121 | int main(int argc, char* argv[]) 122 | { 123 | if (argc == 2 && strcmp(argv[1], "--help") == 0) 124 | print_usage(); 125 | 126 | if (argc > 3 || argc < 2) print_usage(); 127 | 128 | int listen_port = 80; 129 | if (argc > 2) 130 | listen_port = atoi(argv[2]); 131 | 132 | if (listen_port == 0) 133 | { 134 | fprintf(stderr, "cannot listen on port 0\n"); 135 | exit(1); 136 | } 137 | 138 | sockaddr_in bind_addr; 139 | bind_addr.sin_family = AF_INET; 140 | #if !defined _WIN32 && !defined __linux__ 141 | bind_addr.sin_len = sizeof(sockaddr_in); 142 | #endif 143 | bind_addr.sin_port = htons(listen_port); 144 | 145 | int r = inet_pton(AF_INET, argv[1], &bind_addr.sin_addr); 146 | if (r != 1) 147 | { 148 | fprintf(stderr, "invalid bind address:\"%s\"\n", argv[1]); 149 | exit(1); 150 | } 151 | 152 | std::vector announce_threads; 153 | std::vector receive_threads; 154 | 155 | int num_cores = std::thread::hardware_concurrency(); 156 | if (num_cores == 0) num_cores = 4; 157 | 158 | #ifndef _WIN32 159 | struct sigaction sa; 160 | memset(&sa, 0, sizeof(sa)); 161 | sa.sa_handler = &sigint; 162 | r = sigaction(SIGINT, &sa, 0); 163 | if (r == -1) 164 | { 165 | fprintf(stderr, "sigaction failed (%d): %s\n", errno, strerror(errno)); 166 | quit = true; 167 | } 168 | r = sigaction(SIGTERM, &sa, 0); 169 | if (r == -1) 170 | { 171 | fprintf(stderr, "sigaction failed (%d): %s\n", errno, strerror(errno)); 172 | quit = true; 173 | } 174 | if (!quit) printf("send SIGINT or SIGTERM to quit\n"); 175 | #else 176 | if (SetConsoleCtrlHandler(&sigint, TRUE) == FALSE) 177 | { 178 | std::error_code ec(GetLastError(), std::system_category()); 179 | fprintf(stderr, "failed to register Ctrl-C handler: (%d) %s\n" 180 | , ec.value(), ec.message().c_str()); 181 | } 182 | #endif 183 | 184 | #ifdef USE_PCAP 185 | packet_socket socket((sockaddr const*)&bind_addr); 186 | #endif 187 | 188 | // create threads. We should create the same number of 189 | // announce threads as we have cores on the machine 190 | printf("starting %d announce threads\n", num_cores); 191 | #if defined __linux__ 192 | cpu_set_t* cpu = CPU_ALLOC(num_cores); 193 | if (cpu == nullptr) 194 | { 195 | fprintf(stderr, "CPU_ALLOC failed!\n"); 196 | exit(1); 197 | } 198 | int cpu_size = CPU_ALLOC_SIZE(num_cores); 199 | #endif 200 | for (int i = 0; i < num_cores; ++i) 201 | { 202 | #ifdef USE_PCAP 203 | announce_threads.push_back(new announce_thread(socket)); 204 | #else 205 | announce_threads.push_back(new announce_thread(listen_port)); 206 | #endif 207 | 208 | #if defined __linux__ 209 | std::thread::native_handle_type h = announce_threads.back()->native_handle(); 210 | CPU_ZERO_S(cpu_size, cpu); 211 | CPU_SET_S(i, cpu_size, cpu); 212 | int r = pthread_setaffinity_np(h, CPU_ALLOC_SIZE(num_cores), cpu); 213 | if (r != 0) 214 | { 215 | fprintf(stderr, "pthread_setaffinity() = %d: (%d) %s\n" 216 | , r, errno, strerror(errno)); 217 | exit(1); 218 | } 219 | #endif 220 | } 221 | 222 | #ifdef USE_PCAP 223 | const int num_receive_threads = 1; 224 | #else 225 | const int num_receive_threads = num_cores; 226 | #endif 227 | printf("starting %d receive threads\n", num_receive_threads); 228 | for (int i = 0; i < num_receive_threads; ++i) 229 | { 230 | #ifdef USE_PCAP 231 | receive_threads.push_back(new receive_thread(socket, announce_threads)); 232 | #else 233 | receive_threads.push_back(new receive_thread(listen_port, announce_threads)); 234 | #endif 235 | 236 | #if defined __linux__ 237 | std::thread::native_handle_type h = receive_threads.back()->native_handle(); 238 | CPU_ZERO_S(cpu_size, cpu); 239 | CPU_SET_S(i, cpu_size, cpu); 240 | int r = pthread_setaffinity_np(h, CPU_ALLOC_SIZE(num_cores), cpu); 241 | if (r != 0) 242 | { 243 | fprintf(stderr, "pthread_setaffinity() = %d: (%d) %s\n" 244 | , r, errno, strerror(errno)); 245 | exit(1); 246 | } 247 | #endif 248 | } 249 | 250 | int counter = 0; 251 | while (!quit) 252 | { 253 | // print column headings every 20 lines 254 | if ((counter % 20) == 0) 255 | { 256 | printf("%-10s %-10s %-10s %-10s %-10s %-10s %-10s %-10s\n" 257 | , "conns/s", "announce/s", "scrape/s", "errors/s", "drop/s" 258 | , "in (kB/s)", "out (kB/s)", "drop(kB/s)"); 259 | } 260 | 261 | ++counter; 262 | 263 | steady_clock::time_point start = steady_clock::now(); 264 | std::this_thread::sleep_for(seconds(10)); 265 | steady_clock::duration d = steady_clock::now() - start; 266 | int sec = duration_cast(d).count(); 267 | 268 | uint32_t last_connects = connects.exchange(0); 269 | uint32_t last_announces = announces.exchange(0); 270 | uint32_t last_scrapes = scrapes.exchange(0); 271 | uint32_t last_errors = errors.exchange(0); 272 | uint32_t last_bytes_in = bytes_in.exchange(0); 273 | uint32_t last_bytes_out = bytes_out.exchange(0); 274 | uint32_t last_dropped_bytes_out = dropped_bytes_out.exchange(0); 275 | uint32_t last_dropped_announces = dropped_announces.exchange(0); 276 | printf("%10u %10u %10u %10u %10u %10u %10u %10u\n" 277 | , last_connects / sec 278 | , last_announces / sec 279 | , last_scrapes / sec 280 | , last_errors / sec 281 | , last_dropped_announces / sec 282 | , last_bytes_in / 1000 / sec 283 | , last_bytes_out / 1000 / sec 284 | , last_dropped_bytes_out / 1000 / sec); 285 | keys.tick(); 286 | } 287 | 288 | for (receive_thread* i : receive_threads) 289 | i->close(); 290 | 291 | for (receive_thread* i : receive_threads) 292 | delete i; 293 | 294 | for (announce_thread* i : announce_threads) 295 | delete i; 296 | 297 | #if defined __linux__ 298 | CPU_FREE(cpu); 299 | #endif 300 | return EXIT_SUCCESS; 301 | } 302 | -------------------------------------------------------------------------------- /utils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | utrack is a very small an efficient BitTorrent tracker 3 | Copyright (C) 2014 Arvid Norberg 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include // for exit 21 | #include "utils.hpp" 22 | #include // for snprintf 23 | 24 | #ifdef _WIN32 25 | 26 | #include 27 | #include 28 | 29 | #else 30 | 31 | #ifdef __linux__ 32 | #include // for SIOCGIFADDR 33 | #else 34 | #include // for SIOCGIFADDR 35 | #include // for sockaddr_dl 36 | #include 37 | 38 | #endif // __linux__ 39 | 40 | #include // for inet_ntop 41 | #include 42 | #include 43 | 44 | #endif // _WIN32 45 | 46 | #include 47 | 48 | template 49 | struct scope_guard 50 | { 51 | scope_guard(F f) : f(f) {} 52 | ~scope_guard() { f(); } 53 | F f; 54 | }; 55 | 56 | template 57 | scope_guard make_scope_guard(F f) { 58 | return scope_guard(f); 59 | }; 60 | 61 | bool sockaddr_eq(sockaddr const* lhs, sockaddr const* rhs) 62 | { 63 | if (lhs->sa_family != rhs->sa_family) return false; 64 | 65 | switch (lhs->sa_family) 66 | { 67 | case AF_INET: 68 | return memcmp(&((sockaddr_in*)lhs)->sin_addr 69 | , &((sockaddr_in*)rhs)->sin_addr, sizeof(sockaddr_in::sin_addr)) == 0; 70 | case AF_INET6: 71 | return memcmp(&((sockaddr_in6*)lhs)->sin6_addr 72 | , &((sockaddr_in6*)rhs)->sin6_addr, sizeof(sockaddr_in6::sin6_addr)) == 0; 73 | default: 74 | return false; 75 | } 76 | } 77 | 78 | std::string to_string(sockaddr const* addr) 79 | { 80 | char buf[256]; 81 | switch(addr->sa_family) 82 | { 83 | case AF_INET: 84 | inet_ntop(AF_INET, &((sockaddr_in*)addr)->sin_addr 85 | , buf, sizeof(buf)); 86 | return std::string(buf) + ":" + std::to_string(ntohs(((sockaddr_in*)addr)->sin_port)); 87 | case AF_INET6: 88 | inet_ntop(AF_INET6, &((sockaddr_in6*)addr)->sin6_addr 89 | , buf, sizeof(buf)); 90 | return "[" + std::string(buf) + "]:" + std::to_string(ntohs(((sockaddr_in6*)addr)->sin6_port)); 91 | default: return ""; 92 | } 93 | } 94 | 95 | std::string to_string(address_eth const& addr) 96 | { 97 | char ret[6*3]; 98 | for (int i = 0; i < 6; i++) 99 | std::snprintf(&ret[i*3], 4, "%02x:", uint8_t(addr.addr[i])); 100 | return std::string(ret, 6*3 - 1); 101 | } 102 | 103 | // as long ther's still a dependency of pcap_findalldevs 104 | // only have this funtion visible when using it 105 | #if USE_PCAP 106 | 107 | std::vector interfaces(std::error_code& ec) 108 | { 109 | std::vector ret; 110 | 111 | #if defined _WIN32 112 | // find the ethernet address for device 113 | PIP_ADAPTER_ADDRESSES adapter_addresses = 0; 114 | ULONG out_buf_size = 0; 115 | if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER 116 | | GAA_FLAG_SKIP_ANYCAST, NULL, adapter_addresses, &out_buf_size) != ERROR_BUFFER_OVERFLOW) 117 | { 118 | fprintf(stderr, "GetAdaptersAddresses() failed: %d\n", GetLastError()); 119 | ec.assign(GetLastError(), system_category()); 120 | return ret; 121 | } 122 | 123 | adapter_addresses = (IP_ADAPTER_ADDRESSES*)malloc(out_buf_size); 124 | if (!adapter_addresses) 125 | { 126 | fprintf(stderr, "malloc(%d) failed\n", out_buf_size); 127 | ec.assign(std:::errc::not_enough_memory); 128 | return ret; 129 | } 130 | auto scope1 = make_scope_guard([=](){free(adapter_addresses);}); 131 | 132 | if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER 133 | | GAA_FLAG_SKIP_ANYCAST, NULL, adapter_addresses, &out_buf_size) != NO_ERROR) 134 | { 135 | fprintf(stderr, "GetAdaptersAddresses failed\n", out_buf_size); 136 | ec.assign(GetLastError(), system_category()); 137 | return ret; 138 | } 139 | 140 | char const* name = strchr(device, '{'); 141 | for (PIP_ADAPTER_ADDRESSES adapter = adapter_addresses; 142 | adapter != nullptr; adapter = adapter->Next) 143 | { 144 | device_info dev; 145 | 146 | strncpy(dev.name, adapter->AdapterName, sizeof(dev.name)); 147 | memcpy(dev.hardware_addr.addr, adapter->PhysicalAddress, 6); 148 | 149 | for (PIP_ADAPTER_UNICAST_ADDRESS ip = adapter->FirstUnicastAddress; 150 | ip != nullptr; ip = ip->Next) 151 | { 152 | network n; 153 | n.ip = ip->Address; 154 | n.mask = ip->Address; 155 | if (n.mask.sa_family == AF_INET) 156 | { 157 | sockaddr_in& mask = (sockaddr_in&)n.mask; 158 | mask.sin_addr = htonl(0xffffffff >> ip->OnLinkPrefixLength); 159 | } 160 | else 161 | { 162 | // TODO: support IPv6 163 | continue; 164 | } 165 | dev.networks.push_back(n); 166 | ret.emplace_back(dev); 167 | } 168 | } 169 | 170 | return ret; 171 | #else 172 | pcap_if_t *alldevs; 173 | char error_msg[PCAP_ERRBUF_SIZE]; 174 | 175 | // TODO: it would be nice to not depend on libpcap for this 176 | int r = pcap_findalldevs(&alldevs, error_msg); 177 | if (r != 0) 178 | { 179 | printf("pcap_findalldevs() = %d \"%s\"\n", r, error_msg); 180 | exit(1); 181 | } 182 | auto scope1 = make_scope_guard([=](){pcap_freealldevs(alldevs);}); 183 | 184 | for (pcap_if_t* d = alldevs; d != nullptr; d = d->next) 185 | { 186 | device_info dev; 187 | strncpy(dev.name, d->name, IFNAMSIZ); 188 | for (pcap_addr_t* a = d->addresses; a != nullptr; a = a->next) 189 | { 190 | if (a->addr->sa_family == AF_INET) 191 | { 192 | network n; 193 | n.ip = *a->addr; 194 | n.mask = *a->netmask; 195 | dev.addresses.push_back(n); 196 | } 197 | 198 | #if !defined __linux__ 199 | // this is how to get the hardware address on BSDs 200 | if (a->addr->sa_family == AF_LINK && a->addr->sa_data != nullptr) 201 | { 202 | sockaddr_dl* link = (struct sockaddr_dl*)a->addr; 203 | memcpy(dev.hardware_addr.addr, LLADDR(link), 6); 204 | } 205 | #endif 206 | } 207 | 208 | #if defined __linux__ 209 | // this is how to get the hardware address on linux 210 | ifreq ifr; 211 | int fd = socket(AF_INET, SOCK_DGRAM, 0); 212 | if (r < 0) 213 | { 214 | ec.assign(errno, std::system_category()); 215 | return ret; 216 | } 217 | 218 | ifr.ifr_addr.sa_family = AF_INET; 219 | strcpy(ifr.ifr_name, dev.name); 220 | int r = ioctl(fd, SIOCGIFHWADDR, &ifr); 221 | ::close(fd); 222 | if (r < 0) 223 | { 224 | ec.assign(errno, std::system_category()); 225 | return ret; 226 | } 227 | 228 | memcpy(dev.hardware_addr.addr, ifr.ifr_hwaddr.sa_data, 6); 229 | #endif 230 | 231 | ret.emplace_back(dev); 232 | } 233 | 234 | return ret; 235 | #endif 236 | } 237 | 238 | #endif // USE_PCAP 239 | 240 | std::vector arp_table(std::error_code& ec) 241 | { 242 | std::vector ret; 243 | 244 | #ifdef _WIN32 245 | MIB_IPNETTABLE* table = 0; 246 | ULONG out_buf_size = 0; 247 | if (GetIpNetTable(table, &out_buf_size, FALSE) != ERROR_INSUFFICIENT_BUFFER) 248 | { 249 | ec.assign(GetLastError(), std::system_category()); 250 | return ret; 251 | } 252 | 253 | table = (MIB_IPNETTABLE*)malloc(out_buf_size); 254 | if (!table) 255 | { 256 | ec.assign(std:::errc::not_enough_memory); 257 | return ret; 258 | } 259 | auto scope1 = make_scope_guard([=](){ free(table); }); 260 | 261 | if (GetIpNetTable(table, &out_buf_size, FALSE) != NO_ERROR) 262 | { 263 | ec.assign(GetLastError(), std::system_category()); 264 | return ret; 265 | } 266 | 267 | for (int i = 0; i < table->dwNumEntries; ++i) 268 | { 269 | arp_entry e; 270 | memset(&e, 0, sizeof(e)); 271 | sockaddr_in& in = (sockaddr_in&)e.addr; 272 | in.sin_family = AF_INET; 273 | #if !defined _WIN32 && !defined __linux__ 274 | in.sin_len = sizeof(sockaddr_in); 275 | #endif 276 | in.sin_addr.s_addr = table->tabls[i].dwAddr; 277 | 278 | assert(table->table[i]->dwPhysAddrLen == 6); 279 | memcpy(e.hw_addr.addr, table->table[i].bPhysAddr, 6); 280 | ret.emplace_back(e); 281 | } 282 | 283 | #else 284 | struct sockaddr_dl *sdl; 285 | 286 | int mib[6] = { CTL_NET, PF_ROUTE, 0, AF_INET, NET_RT_FLAGS, RTF_LLINFO}; 287 | std::vector buf; 288 | 289 | size_t needed; 290 | if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) 291 | { 292 | ec.assign(errno, std::system_category()); 293 | return ret; 294 | } 295 | 296 | buf.resize(needed); 297 | 298 | if (sysctl(mib, 6, buf.data(), &needed, NULL, 0) < 0) 299 | { 300 | ec.assign(errno, std::system_category()); 301 | return ret; 302 | } 303 | 304 | #define ROUNDUP(a) \ 305 | ((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long)) 306 | 307 | char* lim = buf.data() + needed; 308 | rt_msghdr *rtm; 309 | for (char* next = buf.data(); next < lim; next += rtm->rtm_msglen) { 310 | rtm = (rt_msghdr *)next; 311 | sockaddr_inarp* sin = (sockaddr_inarp *)(rtm + 1); 312 | sdl = (sockaddr_dl*)((char*)sin + ROUNDUP(sin->sin_len)); 313 | 314 | arp_entry e; 315 | 316 | sockaddr_in& in = (sockaddr_in&)e.addr; 317 | in.sin_family = AF_INET; 318 | #if !defined _WIN32 && !defined __linux__ 319 | in.sin_len = sizeof(sockaddr_in); 320 | #endif 321 | in.sin_addr = sin->sin_addr; 322 | 323 | memcpy(e.hw_addr.addr, LLADDR(sdl), 6); 324 | ret.emplace_back(e); 325 | } 326 | buf.clear(); 327 | #endif // _WIN32 328 | 329 | return ret; 330 | } 331 | 332 | -------------------------------------------------------------------------------- /socket_pcap.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | utrack is a very small an efficient BitTorrent tracker 3 | Copyright (C) 2013-2014 Arvid Norberg 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #ifndef SOCKET_PCAP_HPP 20 | #define SOCKET_PCAP_HPP 21 | 22 | #include 23 | 24 | #ifdef USE_WINPCAP 25 | #include 26 | #endif 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "utils.hpp" 36 | #include "arp_cache.hpp" 37 | 38 | enum { 39 | // the receive buffer size for packets, specified in uint64_ts 40 | receive_buffer_size = 0x40000, 41 | 42 | // specified in bytes 43 | send_buffer_size = 0x800000, 44 | }; 45 | 46 | struct packet_buffer; 47 | 48 | struct packet_socket : arp_cache 49 | { 50 | friend struct packet_buffer; 51 | 52 | // a listen port of 0 means accept packets on any port 53 | explicit packet_socket(sockaddr const* bind_addr); 54 | explicit packet_socket(char const* device); 55 | ~packet_socket(); 56 | packet_socket(packet_socket&& s); 57 | packet_socket(packet_socket const&) = delete; 58 | 59 | void close(); 60 | 61 | bool send(packet_buffer& packets); 62 | 63 | void local_endpoint(sockaddr_in* addr); 64 | 65 | private: 66 | 67 | template 68 | struct receive_state 69 | { 70 | pcap_t* handle; 71 | 72 | // the number of bytes to skip in each buffer to get to the IP 73 | // header. 74 | int link_header_size; 75 | 76 | // the number of packets left to receive 77 | int* num; 78 | 79 | // ignore packets sent to other addresses and ports than this one. 80 | // a port of 0 means accept packets on any port 81 | sockaddr_in local_addr; 82 | sockaddr_in local_mask; 83 | 84 | arp_cache* arp; 85 | F* callback; 86 | }; 87 | 88 | public: 89 | 90 | // receive at least one packet on the socket. No more than num (defaults 91 | // to 1000). For each received packet, callback is called with the following 92 | // arguments: (sockaddr_in* from, uint8_t* buffer, int len) 93 | // the buffer is valid until the callback returns 94 | // returns -1 on error 95 | template 96 | int receive(F callback, int num = 1000) 97 | { 98 | if (num <= 0) return 0; 99 | 100 | // TODO: should we just pass in "this" instead? and make it a member 101 | // function? 102 | receive_state st; 103 | st.handle = m_pcap; 104 | st.local_addr = m_our_addr; 105 | st.local_mask = m_mask; 106 | st.arp = this; 107 | st.callback = &callback; 108 | st.num = # 109 | 110 | switch (m_link_layer) 111 | { 112 | case DLT_NULL: st.link_header_size = 4; break; 113 | case DLT_EN10MB: st.link_header_size = 14; break; 114 | default: 115 | assert(false); 116 | } 117 | 118 | int r; 119 | 120 | bool reset_timeout = false; 121 | 122 | while (true) 123 | { 124 | if (m_closed) return -1; 125 | 126 | r = pcap_dispatch(m_pcap, num, &packet_handler, (uint8_t*)&st); 127 | 128 | if (r == -1) 129 | { 130 | fprintf(stderr, "pcap_dispatch() = %d \"%s\"\n", r, pcap_geterr(m_pcap)); 131 | if (r == -3) exit(2); 132 | return -1; 133 | } 134 | 135 | if (num <= 0) return 0; 136 | 137 | if (!reset_timeout) 138 | { 139 | r = pcap_set_timeout(m_pcap, 100); 140 | if (r == -1) 141 | fprintf(stderr, "pcap_set_timeout() = %d \"%s\"\n", r, pcap_geterr(m_pcap)); 142 | reset_timeout = true; 143 | } 144 | } 145 | 146 | if (reset_timeout) 147 | { 148 | r = pcap_set_timeout(m_pcap, 1); 149 | if (r == -1) 150 | fprintf(stderr, "pcap_set_timeout() = %d \"%s\"\n", r, pcap_geterr(m_pcap)); 151 | } 152 | } 153 | 154 | private: 155 | 156 | template 157 | static void packet_handler(u_char* user, const struct pcap_pkthdr* h 158 | , const u_char* bytes) 159 | { 160 | receive_state* st = (receive_state*)user; 161 | 162 | // TODO: support IPv6 also 163 | 164 | uint8_t const* ethernet_header = bytes; 165 | 166 | uint8_t const* ip_header = bytes + st->link_header_size; 167 | 168 | // we only support IPv4 for now, and no IP options, just 169 | // the 20 byte header 170 | 171 | // version and length. Ignore any non IPv4 packets and any packets 172 | // with IP options headers 173 | if (ip_header[0] != 0x45) { 174 | // this is noisy, don't print out for every IPv6 packet 175 | // fprintf(stderr, "ignoring IP packet version: %d header size: %d\n" 176 | // , ip_header[0] >> 4, (ip_header[0] & 0xf) * 4); 177 | return; 178 | } 179 | 180 | // flags (ignore any packet with more-fragments set) 181 | if (ip_header[6] & 0x20) { 182 | fprintf(stderr, "ignoring fragmented IP packet\n"); 183 | return; 184 | } 185 | 186 | // ignore any packet with fragment offset 187 | if ((ip_header[6] & 0x1f) != 0 || ip_header[7] != 0) { 188 | fprintf(stderr, "ignoring fragmented IP packet\n"); 189 | return; 190 | } 191 | 192 | // ignore any packet whose transport protocol is not UDP 193 | if (ip_header[9] != 0x11) { 194 | fprintf(stderr, "ignoring non UDP packet (protocol: %d)\n" 195 | , ip_header[9]); 196 | return; 197 | } 198 | 199 | uint8_t const* udp_header = ip_header + 20; 200 | 201 | // only look at packets to our listen port 202 | if (st->local_addr.sin_port != 0 && 203 | memcmp(&udp_header[2], &st->local_addr.sin_port, 2) != 0) 204 | { 205 | fprintf(stderr, "ignoring packet not to our port (port: %d)\n" 206 | , ntohs(*(uint16_t*)(udp_header+2))); 207 | return; 208 | } 209 | 210 | // only look at packets sent to the IP we bound to 211 | // port 0 means any address 212 | if (st->local_addr.sin_port != 0 && 213 | memcmp(&st->local_addr.sin_addr.s_addr, ip_header + 16, 4) != 0) 214 | { 215 | fprintf(stderr, "ignoring packet not to our address (%d.%d.%d.%d)\n" 216 | , ip_header[16] 217 | , ip_header[17] 218 | , ip_header[18] 219 | , ip_header[19]); 220 | return; 221 | } 222 | 223 | int payload_len = h->caplen - 28 - st->link_header_size; 224 | uint8_t const* payload = bytes + 28 + st->link_header_size; 225 | 226 | if (payload_len > 1500) 227 | { 228 | fprintf(stderr, "incoming packet too large\n"); 229 | return; 230 | } 231 | 232 | // copy from IP header 233 | sockaddr_in from; 234 | memset(&from, 0, sizeof(from)); 235 | #if !defined _WIN32 && !defined __linux__ 236 | from.sin_len = sizeof(sockaddr_in); 237 | #endif 238 | from.sin_family = AF_INET; 239 | 240 | // UDP header: src-port, dst-port, len, chksum 241 | memcpy(&from.sin_port, udp_header, 2); 242 | memcpy(&from.sin_addr, ip_header + 12, 4); 243 | 244 | // ETHERNET 245 | if (st->link_header_size == 14) 246 | { 247 | if (st->arp->has_entry(&st->local_addr, &from, &st->local_mask) == 0) 248 | { 249 | st->arp->add_arp_entry(&from, address_eth(ethernet_header + 6)); 250 | } 251 | } 252 | 253 | (*st->callback)(&from, payload, payload_len); 254 | 255 | --*st->num; 256 | } 257 | 258 | void init(char const* device); 259 | 260 | pcap_t* m_pcap; 261 | int m_link_layer; 262 | std::atomic m_closed; 263 | 264 | // the IP and port we send packets from 265 | sockaddr_in m_our_addr; 266 | 267 | // the network mask for this interface. This is used to maintain the 268 | // ARP cache 269 | sockaddr_in m_mask; 270 | 271 | // the ethernet address for this interface. Use for rendering ethernet 272 | // frames for outgoing packets. 273 | address_eth m_eth_addr; 274 | 275 | void send_thread(); 276 | 277 | // this mutex just protects the send buffer 278 | std::mutex m_mutex; 279 | 280 | // the thread that's used to send the packets put in the send queue 281 | std::vector m_send_threads; 282 | 283 | #ifdef USE_WINPCAP 284 | 285 | std::vector m_send_buffer; 286 | std::vector m_free_list; 287 | 288 | #else 289 | 290 | // this contains all packets we want to send as one contiguous array. Each 291 | // packet has a 2 byte, host order length prefix, followed by that many 292 | // bytes of payload. This is double buffered. Other threads write to one 293 | // buffer while the sending thread reads from the other. This lowers the 294 | // lock contention while sending 295 | std::vector m_send_buffer; 296 | 297 | // the cursor of where new outgoing packets should be written in the 298 | // send buffer 299 | int m_send_cursor; 300 | 301 | #endif 302 | }; 303 | 304 | struct packet_buffer 305 | { 306 | friend struct packet_socket; 307 | 308 | explicit packet_buffer(packet_socket& s); 309 | ~packet_buffer(); 310 | 311 | bool append(iovec const* v, int num, sockaddr_in const* to); 312 | 313 | bool append_impl(iovec const* v, int num, sockaddr_in const* to 314 | , sockaddr_in const* from); 315 | 316 | bool is_full(int buf_size) const 317 | { return m_send_cursor + buf_size + 28 + 30 > m_buf.size(); } 318 | 319 | private: 320 | int m_link_layer; 321 | #ifndef USE_WINPCAP 322 | int m_send_cursor; 323 | #endif 324 | sockaddr_in m_from; 325 | sockaddr_in m_mask; 326 | address_eth m_eth_from; 327 | arp_cache const& m_arp; 328 | #ifdef USE_WINPCAP 329 | pcap_send_queue* m_queue; 330 | pcap_t* m_pcap; 331 | #else 332 | std::vector m_buf; 333 | #endif 334 | }; 335 | 336 | #endif // SOCKET_PCAP_HPP 337 | 338 | -------------------------------------------------------------------------------- /test_announce.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | utrack is a very small an efficient BitTorrent tracker 3 | Copyright (C) 2010-2014 Arvid Norberg 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #ifndef _WIN32 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | #include 37 | 38 | #ifndef __linux__ 39 | #include 40 | #include 41 | #endif 42 | 43 | #else 44 | #include 45 | #include 46 | #endif 47 | 48 | #include "messages.hpp" 49 | #include "endian.hpp" 50 | #include "socket.hpp" 51 | 52 | std::atomic bytes_out(ATOMIC_VAR_INIT(0)); 53 | std::atomic dropped_bytes_out = ATOMIC_VAR_INIT(0); 54 | std::atomic connects = ATOMIC_VAR_INIT(0); 55 | std::atomic announces = ATOMIC_VAR_INIT(0); 56 | std::atomic m_quit(ATOMIC_VAR_INIT(false)); 57 | 58 | sha1_hash random_hash() 59 | { 60 | sha1_hash ret; 61 | for (int i = 0; i < 5; ++i) 62 | { 63 | ret.val[i] = (rand() << 16) ^ rand(); 64 | } 65 | return ret; 66 | } 67 | 68 | sha1_hash get_info_hash(int idx) 69 | { 70 | sha1_hash ret; 71 | for (int i = 0; i < 5; ++i) 72 | { 73 | ret.val[i] = idx & 0xff; 74 | } 75 | return ret; 76 | } 77 | 78 | void send_connect(sockaddr_in const* to, packet_buffer& buf, bool loopback) 79 | { 80 | static int idx = 0; 81 | 82 | // only announce 2^24 times. at 100k requests / second that's 83 | // still almost 3 minutes of runtime 84 | if (idx > 0xffffff) return; 85 | 86 | idx = (idx + 1) & 0xffffff; 87 | 88 | udp_announce_message req; 89 | req.connection_id = be64toh(0x41727101980LL); 90 | req.action = htonl(action_connect); 91 | req.transaction_id = htonl(idx); 92 | 93 | iovec b = { &req, 16 }; 94 | 95 | #if !defined USE_SYSTEM_SEND_SOCKET && defined USE_PCAP 96 | if (loopback) 97 | { 98 | sockaddr_in from; 99 | from.sin_family = AF_INET; 100 | from.sin_port = htons(1024 + idx); 101 | #if !defined _WIN32 && !defined __linux__ 102 | from.sin_len = sizeof(sockaddr_in); 103 | #endif 104 | from.sin_addr.s_addr = htonl(0x7f000001 + idx); 105 | 106 | buf.append_impl(&b, 1, to, &from); 107 | } 108 | else 109 | #endif // USE_PCAP 110 | { 111 | buf.append(&b, 1, to); 112 | } 113 | } 114 | 115 | void send_announce(int idx, uint64_t connection_id, sockaddr_in const* to 116 | , packet_buffer& buf, bool loopback) 117 | { 118 | udp_announce_message req; 119 | req.action = htonl(action_announce); 120 | req.connection_id = connection_id; 121 | req.transaction_id = htonl((1 << 24) | idx); 122 | req.hash = get_info_hash(idx); 123 | req.peer_id = random_hash(); 124 | req.event = htonl(event_started); 125 | req.ip = 0; 126 | req.key = htonl(idx); 127 | req.num_want = htonl(200); 128 | req.port = htons(1024 + idx); 129 | req.extensions = 0; 130 | 131 | iovec b = { &req, sizeof(req) }; 132 | 133 | #if !defined USE_SYSTEM_SEND_SOCKET && defined USE_PCAP 134 | if (loopback) 135 | { 136 | sockaddr_in from; 137 | from.sin_family = AF_INET; 138 | from.sin_port = htons(1024 + idx); 139 | #if !defined _WIN32 && !defined __linux__ 140 | from.sin_len = sizeof(sockaddr_in); 141 | #endif 142 | from.sin_addr.s_addr = htonl(0x7f000001 + idx); 143 | 144 | buf.append_impl(&b, 1, to, &from); 145 | } 146 | else 147 | #endif // USE_PCAP 148 | { 149 | buf.append(&b, 1, to); 150 | } 151 | } 152 | 153 | void incoming_packet(uint8_t const* buf, int size 154 | , sockaddr_in const* from, packet_buffer& send_buffer, bool loopback) 155 | { 156 | udp_announce_response const* resp = (udp_announce_response const*)buf; 157 | 158 | if (size < sizeof(udp_connect_response)) 159 | { 160 | fprintf(stderr, "incoming packet too small (%d)\n", size); 161 | return; 162 | } 163 | 164 | switch (ntohl(resp->action)) 165 | { 166 | case action_announce: 167 | { 168 | int idx = ntohl(resp->transaction_id); 169 | if ((idx >> 24) != 1) 170 | { 171 | fprintf(stderr, "invalid transaction id for announce response\n"); 172 | return; 173 | } 174 | idx &= 0xffffff; 175 | static int num_responses = 0; 176 | ++num_responses; 177 | if (num_responses == 0xffffff) m_quit = true; 178 | announces.fetch_add(1, std::memory_order_relaxed); 179 | break; 180 | } 181 | case action_connect: 182 | { 183 | udp_connect_response const* resp = (udp_connect_response const*)buf; 184 | int idx = ntohl(resp->transaction_id); 185 | if ((idx >> 24) != 0) 186 | { 187 | fprintf(stderr, "invalid transaction id for connect response\n"); 188 | return; 189 | } 190 | 191 | connects.fetch_add(1, std::memory_order_relaxed); 192 | send_announce(idx, resp->connection_id, from, send_buffer, loopback); 193 | if ((idx & 0x1) == 0) 194 | send_announce(idx, resp->connection_id, from, send_buffer, loopback); 195 | 196 | send_connect(from, send_buffer, loopback); 197 | 198 | // every 4th connect response, send an additional connect, to 199 | // keep ramping up the conect rate indefinitely (until we bump 200 | // up against the capacity and packets are lost) 201 | send_connect(from, send_buffer, loopback); 202 | break; 203 | } 204 | } 205 | } 206 | 207 | packet_socket* g_sock = nullptr; 208 | 209 | #ifdef _WIN32 210 | BOOL WINAPI sigint(DWORD s) 211 | #else 212 | void sigint(int s) 213 | #endif 214 | { 215 | m_quit = true; 216 | if (g_sock) g_sock->close(); 217 | #ifdef _WIN32 218 | return TRUE; 219 | #endif 220 | } 221 | 222 | #ifdef _WIN32 223 | static struct wsa_init_t { 224 | wsa_init_t() 225 | { 226 | WSADATA wsaData; 227 | WSAStartup(MAKEWORD(2, 2), &wsaData); 228 | } 229 | } dummy_initializer; 230 | #endif 231 | 232 | int main(int argc, char* argv[]) 233 | { 234 | if (argc < 4) 235 | { 236 | fprintf(stderr, "usage: ./udp_test device address port\n"); 237 | return EXIT_FAILURE; 238 | } 239 | 240 | int r; 241 | #ifndef _WIN32 242 | struct sigaction sa; 243 | memset(&sa, 0, sizeof(sa)); 244 | sa.sa_handler = &sigint; 245 | r = sigaction(SIGINT, &sa, 0); 246 | if (r == -1) 247 | { 248 | fprintf(stderr, "sigaction failed (%d): %s\n", errno, strerror(errno)); 249 | return 1; 250 | } 251 | r = sigaction(SIGTERM, &sa, 0); 252 | if (r == -1) 253 | { 254 | fprintf(stderr, "sigaction failed (%d): %s\n", errno, strerror(errno)); 255 | return 1; 256 | } 257 | #else 258 | if (SetConsoleCtrlHandler(&sigint, TRUE) == FALSE) 259 | { 260 | std::error_code ec(GetLastError(), std::system_category()); 261 | fprintf(stderr, "failed to register Ctrl-C handler: (%d) %s\n" 262 | , ec.value(), ec.message().c_str()); 263 | } 264 | #endif 265 | 266 | sockaddr_in to; 267 | memset(&to, 0, sizeof(to)); 268 | #if !defined _WIN32 && !defined __linux__ 269 | to.sin_len = sizeof(to); 270 | #endif 271 | to.sin_family = AF_INET; 272 | r = inet_pton(AF_INET, argv[2], &to.sin_addr); 273 | if (r < 0) 274 | { 275 | fprintf(stderr, "invalid target address \"%s\" (%d): %s\n", argv[1] 276 | , errno, strerror(errno)); 277 | return EXIT_FAILURE; 278 | } 279 | to.sin_port = htons(atoi(argv[3])); 280 | 281 | #if defined USE_SYSTEM_SEND_SOCKET || !defined USE_PCAP 282 | packet_socket sock(0); 283 | #else 284 | 285 | address_eth mac; 286 | 287 | // we need to find out the MAC address of the destination computer 288 | // since we're sending raw ethernet frames. This works on OSX and BSD: 289 | 290 | std::error_code ec; 291 | std::vector arp = arp_table(ec); 292 | if (ec) 293 | { 294 | fprintf(stderr, "failed to query ARP table: \"%s\"\n" 295 | , ec.message().c_str()); 296 | exit(1); 297 | } 298 | 299 | auto i = std::find_if(arp.begin(), arp.end() 300 | , [=](arp_entry const& a){ return sockaddr_eq(&a.addr, (sockaddr const*)&to); }); 301 | 302 | if (i == arp.end()) 303 | { 304 | fprintf(stderr, "WARNING: no ARP entry found for \"%s\". please ping it\n" 305 | , argv[2]); 306 | } 307 | 308 | packet_socket sock(argv[1]); 309 | if (i != arp.end()) 310 | sock.add_arp_entry(&to, i->hw_addr); 311 | #endif // USE_SYSTEM_SEND_SOCKET 312 | packet_buffer send_buffer(sock); 313 | 314 | g_sock = &sock; 315 | 316 | typedef std::chrono::high_resolution_clock clock; 317 | using std::chrono::duration_cast; 318 | using std::chrono::milliseconds; 319 | using std::chrono::seconds; 320 | 321 | sockaddr_in ep; 322 | sock.local_endpoint(&ep); 323 | bool loopback = (ep.sin_addr.s_addr == htonl(0x7f000001)); 324 | if (loopback) printf("loopback address\n"); 325 | 326 | clock::time_point start = clock::now(); 327 | 328 | for (int i = 0; i < 10000; ++i) 329 | send_connect(&to, send_buffer, loopback); 330 | sock.send(send_buffer); 331 | 332 | while (!m_quit.load()) 333 | { 334 | int r = sock.receive( 335 | [&](sockaddr_in const* from, uint8_t const* buf, int len) 336 | { 337 | incoming_packet(buf, len, from, send_buffer, loopback); 338 | }); 339 | 340 | sock.send(send_buffer); 341 | 342 | clock::time_point now = clock::now(); 343 | if (now - start > seconds(5)) 344 | { 345 | uint32_t last_connects = connects.exchange(0); 346 | uint32_t last_announces = announces.exchange(0); 347 | 348 | int ms = duration_cast(now - start).count(); 349 | 350 | printf("connects/s: %u announces/s: %u\n" 351 | , last_connects * 1000 / ms 352 | , last_announces * 1000 / ms); 353 | start = now; 354 | } 355 | if (r < 0) break; 356 | } 357 | 358 | clock::time_point end = clock::now(); 359 | 360 | uint32_t last_connects = connects.exchange(0); 361 | uint32_t last_announces = announces.exchange(0); 362 | 363 | int ms = duration_cast(end - start).count(); 364 | 365 | printf("connects/s: %u announces/s: %u\n" 366 | , last_connects * 1000 / ms 367 | , last_announces * 1000 / ms); 368 | 369 | return EXIT_SUCCESS; 370 | } 371 | 372 | -------------------------------------------------------------------------------- /socket_pcap.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | utrack is a very small an efficient BitTorrent tracker 3 | Copyright (C) 2013-2014 Arvid Norberg 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | */ 18 | 19 | #include "socket.hpp" 20 | #include "config.hpp" 21 | #include "utils.hpp" 22 | #include "stack.hpp" 23 | 24 | #include // for stderr 25 | #include // for errno 26 | #include // for strerror 27 | #include // for exit 28 | #include 29 | 30 | #ifdef _WIN32 31 | #include 32 | #include 33 | #define snprintf _snprintf 34 | #else 35 | #include // for close 36 | #include // for poll 37 | #include // for F_GETFL and F_SETFL 38 | #include // for iovec 39 | #include // for sockaddr 40 | #include // for ifreq 41 | #include 42 | #include // for inet_ntop 43 | 44 | #endif 45 | 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | 52 | #ifndef MSG_NOSIGNAL 53 | #define MSG_NOSIGNAL 0 54 | #endif 55 | 56 | extern std::atomic bytes_out; 57 | extern std::atomic dropped_bytes_out; 58 | 59 | packet_socket::packet_socket(char const* device) 60 | : m_pcap(nullptr) 61 | , m_closed(ATOMIC_VAR_INIT(0)) 62 | #ifndef USE_WINPCAP 63 | , m_send_cursor(0) 64 | #endif 65 | { 66 | #ifdef USE_WINPCAP 67 | m_send_buffer.reserve(21); 68 | #else 69 | m_send_buffer.resize(send_buffer_size); 70 | #endif 71 | 72 | std::error_code ec; 73 | std::vector devices = interfaces(ec); 74 | if (ec) 75 | { 76 | fprintf(stderr, "failed to list network interfaces: \"%s\"\n" 77 | , ec.message().c_str()); 78 | exit(2); 79 | } 80 | 81 | // resolve source IP and network mask from device 82 | bool found = false; 83 | for (auto const& dev : devices) 84 | { 85 | printf("device: %s\n", dev.name); 86 | printf(" hw: %s\n", to_string(dev.hardware_addr).c_str()); 87 | if (strcmp(dev.name, device) != 0) continue; 88 | 89 | // just pick the first IPv4 address 90 | auto i = std::find_if(dev.addresses.begin(), dev.addresses.end() 91 | , [=](network const& a) { return a.ip.sa_family == AF_INET; }); 92 | 93 | if (i == dev.addresses.end()) 94 | { 95 | fprintf(stderr, "could not find an IPv4 address on device: \"%s\"\n" 96 | , device); 97 | exit(2); 98 | } 99 | 100 | found = true; 101 | m_eth_addr = dev.hardware_addr; 102 | m_mask = (sockaddr_in&)i->mask; 103 | m_our_addr = (sockaddr_in&)i->ip; 104 | } 105 | 106 | if (!found) 107 | { 108 | fprintf(stderr, "could not find device: \"%s\"\n", device); 109 | exit(2); 110 | } 111 | 112 | init(device); 113 | } 114 | 115 | packet_socket::packet_socket(sockaddr const* bind_addr) 116 | : m_pcap(nullptr) 117 | , m_closed(ATOMIC_VAR_INIT(0)) 118 | #ifndef USE_WINPCAP 119 | , m_send_cursor(0) 120 | #endif 121 | { 122 | #ifdef USE_WINPCAP 123 | m_send_buffer.reserve(21); 124 | #else 125 | m_send_buffer.resize(send_buffer_size); 126 | #endif 127 | 128 | if (bind_addr->sa_family != AF_INET) 129 | { 130 | fprintf(stderr, "only IPv4 supported\n"); 131 | exit(2); 132 | return; 133 | } 134 | 135 | m_our_addr = *(sockaddr_in*)bind_addr; 136 | 137 | std::error_code ec; 138 | std::vector devices = interfaces(ec); 139 | if (ec) 140 | { 141 | fprintf(stderr, "failed to list network interfaces: \"%s\"\n" 142 | , ec.message().c_str()); 143 | exit(2); 144 | } 145 | 146 | // resolve device and network mask from bind_addr 147 | char device[IFNAMSIZ]; 148 | bool found = false; 149 | for (auto const& dev : devices) 150 | { 151 | printf("device: %s\n", dev.name); 152 | printf(" hw: %s\n", to_string(dev.hardware_addr).c_str()); 153 | 154 | auto i = std::find_if(dev.addresses.begin(), dev.addresses.end() 155 | , [=](network const& a) { return sockaddr_eq(&a.ip, (sockaddr const*)&m_our_addr); }); 156 | 157 | if (i == dev.addresses.end()) continue; 158 | 159 | found = true; 160 | m_eth_addr = dev.hardware_addr; 161 | m_mask = (sockaddr_in&)i->mask; 162 | strncpy(device, dev.name, IFNAMSIZ); 163 | } 164 | 165 | if (!found) 166 | { 167 | fprintf(stderr, "failed to bind: no device found with that address\n"); 168 | exit(2); 169 | } 170 | 171 | init(device); 172 | } 173 | 174 | void packet_socket::init(char const* device) 175 | { 176 | char error_msg[PCAP_ERRBUF_SIZE]; 177 | m_pcap = pcap_create(device, error_msg); 178 | if (m_pcap == nullptr) 179 | { 180 | fprintf(stderr, "failed to create packet capture handle: %s" 181 | , error_msg); 182 | exit(2); 183 | return; 184 | } 185 | 186 | // capture whole packets 187 | pcap_set_snaplen(m_pcap, 1500); 188 | 189 | int r = pcap_setnonblock(m_pcap, 0, error_msg); 190 | if (r == -1) 191 | { 192 | fprintf(stderr, "failed to set blocking mode: %s\n", error_msg); 193 | return; 194 | } 195 | 196 | r = pcap_setdirection(m_pcap, PCAP_D_IN); 197 | if (r == -1) 198 | fprintf(stderr, "pcap_setdirection() = %d \"%s\"\n", r, pcap_geterr(m_pcap)); 199 | 200 | r = pcap_set_buffer_size(m_pcap, socket_buffer_size); 201 | if (r == -1) 202 | fprintf(stderr, "pcap_set_buffer_size() = %d \"%s\"\n", r, pcap_geterr(m_pcap)); 203 | 204 | r = pcap_set_timeout(m_pcap, 1); 205 | if (r == -1) 206 | fprintf(stderr, "pcap_set_timeout() = %d \"%s\"\n", r, pcap_geterr(m_pcap)); 207 | 208 | r = pcap_setdirection(m_pcap, PCAP_D_IN); 209 | if (r == -1) 210 | fprintf(stderr, "pcap_setdirection() = %d \"%s\"\n", r, pcap_geterr(m_pcap)); 211 | 212 | uint32_t ip = ntohl(m_our_addr.sin_addr.s_addr); 213 | uint32_t mask = ntohl(m_mask.sin_addr.s_addr); 214 | 215 | printf("bound to %d.%d.%d.%d\n" 216 | , (ip >> 24) & 0xff 217 | , (ip >> 16) & 0xff 218 | , (ip >> 8) & 0xff 219 | , ip & 0xff); 220 | 221 | printf("mask %d.%d.%d.%d\n" 222 | , (mask >> 24) & 0xff 223 | , (mask >> 16) & 0xff 224 | , (mask >> 8) & 0xff 225 | , mask & 0xff); 226 | 227 | printf("hw: %s\n", to_string(m_eth_addr).c_str()); 228 | 229 | r = pcap_activate(m_pcap); 230 | if (r != 0) 231 | { 232 | fprintf(stderr, "pcap_activate() = %d \"%s\"\n", r, pcap_geterr(m_pcap)); 233 | exit(-1); 234 | } 235 | 236 | m_link_layer = pcap_datalink(m_pcap); 237 | if (m_link_layer < 0) 238 | { 239 | fprintf(stderr, "pcap_datalink() = %d \"%s\"\n", r, pcap_geterr(m_pcap)); 240 | exit(-1); 241 | } 242 | 243 | printf("link layer: "); 244 | switch (m_link_layer) 245 | { 246 | case DLT_NULL: printf("loopback\n"); break; 247 | case DLT_EN10MB: printf("ethernet\n"); break; 248 | default: printf("unknown\n"); break; 249 | } 250 | 251 | std::string program_text = "udp"; 252 | if (m_our_addr.sin_port != 0) 253 | { 254 | program_text += " dst port "; 255 | program_text += std::to_string(ntohs(m_our_addr.sin_port)); 256 | 257 | char buf[100]; 258 | program_text += " and dst host "; 259 | program_text += inet_ntop(AF_INET, &m_our_addr.sin_addr.s_addr 260 | , buf, sizeof(buf)); 261 | } 262 | 263 | fprintf(stderr, "capture filter: \"%s\"\n", program_text.c_str()); 264 | 265 | bpf_program p; 266 | r = pcap_compile(m_pcap, &p, program_text.c_str(), 1, 0xffffffff); 267 | if (r == -1) 268 | fprintf(stderr, "pcap_compile() = %d \"%s\"\n", r, pcap_geterr(m_pcap)); 269 | 270 | r = pcap_setfilter(m_pcap, &p); 271 | if (r == -1) 272 | fprintf(stderr, "pcap_setfilter() = %d \"%s\"\n", r, pcap_geterr(m_pcap)); 273 | 274 | for (int i = 0; i < 3; ++i) 275 | m_send_threads.emplace_back(&packet_socket::send_thread, this); 276 | } 277 | 278 | packet_socket::~packet_socket() 279 | { 280 | close(); 281 | for (auto& t : m_send_threads) t.join(); 282 | #ifdef USE_WINPCAP 283 | for (auto i : m_free_list) 284 | pcap_sendqueue_destroy(i); 285 | for (auto i : m_send_buffer) 286 | pcap_sendqueue_destroy(i); 287 | #endif 288 | if (m_pcap) pcap_close(m_pcap); 289 | } 290 | 291 | void packet_socket::close() 292 | { 293 | m_closed = 1; 294 | if (m_pcap) 295 | pcap_breakloop(m_pcap); 296 | } 297 | 298 | bool packet_socket::send(packet_buffer& packets) 299 | { 300 | #ifdef USE_WINPCAP 301 | assert(packets.m_queue != 0); 302 | std::unique_lock l(m_mutex); 303 | if (m_send_buffer.size() > 20) 304 | { 305 | l.unlock(); 306 | 307 | // if the send queue is too large, just send 308 | // it synchronously 309 | int r = pcap_sendqueue_transmit(m_pcap, packets.m_queue, 0); 310 | if (r < 0) 311 | fprintf(stderr, "pcap_setfilter() = %d \"%s\"\n", r, pcap_geterr(m_pcap)); 312 | return true; 313 | } 314 | 315 | assert(packets.m_queue != NULL); 316 | m_send_buffer.push_back(packets.m_queue); 317 | if (!m_free_list.empty()) 318 | { 319 | packets.m_queue = m_free_list.back(); 320 | m_free_list.erase(m_free_list.end()-1); 321 | } 322 | else 323 | { 324 | l.unlock(); 325 | packets.m_queue = pcap_sendqueue_alloc(0x100000); 326 | if (packets.m_queue == NULL) 327 | { 328 | fprintf(stderr, "failed to allocate send queue\n"); 329 | exit(1); 330 | } 331 | } 332 | return true; 333 | #else 334 | std::lock_guard l(m_mutex); 335 | 336 | if (packets.m_send_cursor == 0) return true; 337 | 338 | if (m_send_cursor + packets.m_send_cursor > m_send_buffer.size()) 339 | { 340 | dropped_bytes_out.fetch_add(packets.m_send_cursor, std::memory_order_relaxed); 341 | packets.m_send_cursor = 0; 342 | return false; 343 | } 344 | 345 | bytes_out.fetch_add(packets.m_send_cursor, std::memory_order_relaxed); 346 | 347 | memcpy(&m_send_buffer[m_send_cursor] 348 | , packets.m_buf.data(), packets.m_send_cursor); 349 | 350 | m_send_cursor += packets.m_send_cursor; 351 | packets.m_send_cursor = 0; 352 | #endif 353 | return true; 354 | } 355 | 356 | packet_buffer::packet_buffer(packet_socket& s) 357 | : m_link_layer(s.m_link_layer) 358 | #ifndef USE_WINPCAP 359 | , m_send_cursor(0) 360 | #endif 361 | , m_from(s.m_our_addr) 362 | , m_mask(s.m_mask) 363 | , m_eth_from(s.m_eth_addr) 364 | , m_arp(s) 365 | #ifdef USE_WINPCAP 366 | , m_queue(pcap_sendqueue_alloc(0x100000)) 367 | , m_pcap(s.m_pcap) 368 | #else 369 | , m_buf(0x100000) 370 | #endif 371 | { 372 | #ifdef USE_WINPCAP 373 | if (m_queue == NULL) 374 | { 375 | fprintf(stderr, "failed to allocate send queue\n"); 376 | exit(1); 377 | } 378 | #endif 379 | } 380 | 381 | packet_buffer::~packet_buffer() 382 | { 383 | #ifdef USE_WINPCAP 384 | pcap_sendqueue_destroy(m_queue); 385 | #endif 386 | } 387 | 388 | bool packet_buffer::append(iovec const* v, int num 389 | , sockaddr_in const* to) 390 | { 391 | return append_impl(v, num, to, &m_from); 392 | } 393 | 394 | bool packet_buffer::append_impl(iovec const* v, int num 395 | , sockaddr_in const* to, sockaddr_in const* from) 396 | { 397 | int buf_size = 0; 398 | for (int i = 0; i < num; ++i) buf_size += v[i].iov_len; 399 | 400 | if (buf_size > 1500 - 28 - 30) 401 | { 402 | fprintf(stderr, "append: packet too large\n"); 403 | return false; 404 | } 405 | 406 | #ifdef USE_WINPCAP 407 | std::uint8_t buffer[1500]; 408 | std::uint8_t* ptr = buffer; 409 | int len = 0; 410 | #else 411 | if (m_send_cursor + buf_size + 28 + 30 > m_buf.size()) 412 | { 413 | dropped_bytes_out.fetch_add(buf_size, std::memory_order_relaxed); 414 | return false; 415 | } 416 | 417 | std::uint8_t* ptr = &m_buf[m_send_cursor]; 418 | 419 | std::uint8_t* prefix = ptr; 420 | ptr += 2; 421 | #endif 422 | 423 | #ifdef USE_SYSTEM_SEND_SOCKET 424 | int len = 0; 425 | 426 | memcpy(ptr, to, sizeof(sockaddr_in)); 427 | ptr += sizeof(sockaddr_in); 428 | len += sizeof(sockaddr_in); 429 | for (int i = 0; i < num; ++i) 430 | { 431 | memcpy(ptr, v[i].iov_base, v[i].iov_len); 432 | ptr += v[i].iov_len; 433 | len += v[i].iov_len; 434 | } 435 | m_send_cursor += len + 2; 436 | 437 | #else 438 | 439 | if (to->sin_family != AF_INET) 440 | { 441 | fprintf(stderr, "unsupported network protocol (only IPv4 is supported)\n"); 442 | return false; 443 | } 444 | 445 | int len = 0; 446 | int r; 447 | 448 | switch (m_link_layer) 449 | { 450 | case DLT_NULL: 451 | { 452 | std::uint32_t proto = 2; 453 | memcpy(ptr, &proto, 4); 454 | ptr += 4; 455 | len += 4; 456 | break; 457 | } 458 | case DLT_EN10MB: 459 | { 460 | r = render_eth_frame(ptr, 1500 - len, to, from, &m_mask 461 | , m_eth_from, m_arp); 462 | if (r < 0) return false; 463 | ptr += len; 464 | len += r; 465 | break; 466 | } 467 | default: 468 | // unsupported link layer 469 | fprintf(stderr, "unsupported data link layer (%d)\n", m_link_layer); 470 | return false; 471 | } 472 | 473 | r = render_ip_frame(ptr, 1500 - len, v, num, to, from); 474 | 475 | if (r < 0) return false; 476 | len += r; 477 | 478 | #ifdef USE_WINPCAP 479 | pcap_pkthdr hdr; 480 | hdr.caplen = len; 481 | hdr.len = len; 482 | memset(&hdr.ts, 0, sizeof(hdr.ts)); 483 | int r = pcap_sendqueue_queue(m_queue, &hdr, buffer); 484 | #else 485 | prefix[0] = (len >> 8) & 0xff; 486 | prefix[1] = len & 0xff; 487 | 488 | m_send_cursor += len + 2; 489 | #endif 490 | 491 | #endif // USE_SYSTEM_SEND_SOCKET 492 | 493 | return true; 494 | } 495 | 496 | #ifdef USE_WINPCAP 497 | void packet_socket::send_thread() 498 | { 499 | std::vector local_buffer; 500 | 501 | // exponential back-off. The more read operations that return 502 | // no packets, the longer we wait until we read again. This 503 | // balances CPU usage when idle with wasting less time when busy 504 | const static int sleep_timers[] = {0, 1, 5, 10, 50, 100, 500}; 505 | int sleep = 0; 506 | while (!m_closed) 507 | { 508 | if (sleep > 0) 509 | { 510 | // we did not see any packets in the buffer last cycle 511 | // through. sleep for a while to see if there are any in 512 | // a little bit 513 | // printf("sleep %d ms\n", sleep_timers[sleep-1]); 514 | std::this_thread::sleep_for( 515 | std::chrono::milliseconds(sleep_timers[sleep-1])); 516 | } 517 | 518 | { 519 | std::lock_guard l(m_mutex); 520 | if (m_send_buffer.empty()) 521 | { 522 | if (sleep < sizeof(sleep_timers)/sizeof(sleep_timers[0])) 523 | ++sleep; 524 | continue; 525 | } 526 | 527 | 528 | local_buffer.swap(m_send_buffer); 529 | } 530 | 531 | sleep = 0; 532 | 533 | for (auto i : local_buffer) 534 | { 535 | int r = pcap_sendqueue_transmit(m_pcap, i, 0); 536 | if (r < 0) 537 | fprintf(stderr, "pcap_setfilter() = %d \"%s\"\n", r, pcap_geterr(m_pcap)); 538 | 539 | 540 | std::lock_guard l(m_mutex); 541 | m_free_list.push_back(i); 542 | } 543 | local_buffer.clear(); 544 | } 545 | 546 | } 547 | #else 548 | void packet_socket::send_thread() 549 | { 550 | std::vector local_buffer; 551 | local_buffer.resize(send_buffer_size); 552 | int end; 553 | 554 | #ifdef USE_SYSTEM_SEND_SOCKET 555 | // socket used for sending 556 | int sock = socket(PF_INET, SOCK_DGRAM, 0); 557 | if (sock < 0) 558 | { 559 | fprintf(stderr, "failed to open send socket (%d): %s\n" 560 | , errno, strerror(errno)); 561 | exit(1); 562 | } 563 | int one = 1; 564 | if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) 565 | { 566 | fprintf(stderr, "failed to set SO_REUSEADDR on send socket (%d): %s\n" 567 | , errno, strerror(errno)); 568 | } 569 | 570 | #ifdef SO_REUSEPORT 571 | if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)) < 0) 572 | { 573 | fprintf(stderr, "failed to set SO_REUSEPORT on send socket (%d): %s\n" 574 | , errno, strerror(errno)); 575 | } 576 | #endif 577 | sockaddr_in bind_addr; 578 | memset(&bind_addr, 0, sizeof(bind_addr)); 579 | bind_addr.sin_family = AF_INET; 580 | bind_addr.sin_addr.s_addr = INADDR_ANY; 581 | bind_addr.sin_port = m_our_addr.sin_port; 582 | int r = bind(sock, (sockaddr*)&bind_addr, sizeof(bind_addr)); 583 | if (r < 0) 584 | { 585 | fprintf(stderr, "failed to bind send socket to port %d (%d): %s\n" 586 | , ntohs(m_our_addr.sin_port), errno, strerror(errno)); 587 | exit(1); 588 | } 589 | 590 | int opt = socket_buffer_size; 591 | r = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt)); 592 | if (r == -1) 593 | { 594 | fprintf(stderr, "failed to set send socket buffer size (%d): %s\n" 595 | , errno, strerror(errno)); 596 | } 597 | #endif 598 | 599 | // exponential back-off. The more read operations that return 600 | // no packets, the longer we wait until we read again. This 601 | // balances CPU usage when idle with wasting less time when busy 602 | const static int sleep_timers[] = {0, 1, 5, 10, 50, 100, 500}; 603 | int sleep = 0; 604 | while (!m_closed) 605 | { 606 | if (sleep > 0) 607 | { 608 | // we did not see any packets in the buffer last cycle 609 | // through. sleep for a while to see if there are any in 610 | // a little bit 611 | // printf("sleep %d ms\n", sleep_timers[sleep-1]); 612 | std::this_thread::sleep_for( 613 | std::chrono::milliseconds(sleep_timers[sleep-1])); 614 | } 615 | 616 | { 617 | std::lock_guard l(m_mutex); 618 | if (m_send_cursor == 0) 619 | { 620 | if (sleep < sizeof(sleep_timers)/sizeof(sleep_timers[0])) 621 | ++sleep; 622 | continue; 623 | } 624 | 625 | local_buffer.swap(m_send_buffer); 626 | 627 | end = m_send_cursor; 628 | m_send_cursor = 0; 629 | } 630 | 631 | sleep = 0; 632 | 633 | for (int i = 0; i < end;) 634 | { 635 | int len = (local_buffer[i] << 8) | local_buffer[i+1]; 636 | assert(len <= 1500); 637 | assert(len > 0); 638 | i += 2; 639 | assert(local_buffer.size() - i >= len); 640 | 641 | #ifdef USE_SYSTEM_SEND_SOCKET 642 | assert(len >= sizeof(sockaddr_in)); 643 | sockaddr_in* to = (sockaddr_in*)(local_buffer.data() + i); 644 | 645 | int r = sendto(sock 646 | , local_buffer.data() + i + sizeof(sockaddr_in) 647 | , len - sizeof(sockaddr_in), 0, (sockaddr*)to, sizeof(sockaddr_in)); 648 | if (r == -1) 649 | fprintf(stderr, "sendto() = %d \"%s\"\n", r 650 | , strerror(errno)); 651 | #else 652 | int r = pcap_sendpacket(m_pcap, local_buffer.data() + i 653 | , len); 654 | 655 | if (r == -1) 656 | fprintf(stderr, "pcap_sendpacket() = %d \"%s\"\n", r 657 | , pcap_geterr(m_pcap)); 658 | #endif 659 | 660 | i += len; 661 | } 662 | } 663 | 664 | #ifdef USE_SYSTEM_SEND_SOCKET 665 | ::close(sock); 666 | #endif 667 | } 668 | #endif // !USE_WINPCAP 669 | 670 | void packet_socket::local_endpoint(sockaddr_in* addr) 671 | { 672 | *addr = m_our_addr; 673 | } 674 | 675 | --------------------------------------------------------------------------------