├── 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