From 4e7e25027bdc77ecbfeb9d1167d205e882d43f6e Mon Sep 17 00:00:00 2001 From: itismadness Date: Thu, 4 Jan 2018 02:58:27 -1100 Subject: [PATCH] initial ipv6 work --- CHANGES | 18 ++- README.md | 8 +- Vagrantfile | 17 +++ ocelot.conf.dist | 5 + src/db.cpp | 13 +- src/db.h | 4 +- src/events.cpp | 12 +- src/misc_functions.cpp | 138 +++++++++++++++++++++- src/misc_functions.h | 7 +- src/ocelot.cpp | 12 +- src/ocelot.h | 6 +- src/worker.cpp | 262 +++++++++++++++++++++++++---------------- src/worker.h | 5 +- 13 files changed, 380 insertions(+), 127 deletions(-) diff --git a/CHANGES b/CHANGES index d1c60a2..dac890a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,21 @@ -- 1.1 (2017-10-xx) -.. +Done: +* Add Daemon mode (so you don't need to run Ocelot via screen) +* Use spdlog (instead of cout) for output, allowing for console and file loggers + * If not using daemon, will always log to console + * File logging can be configured via ocelot.conf + + +Not Done: +* Track both IPv4 and IPv6 peers and allow mixed swarms + * Implemented via BEP 0007 (http://www.bittorrent.org/beps/bep_0007.html) + * DB Changes: + * ALTER TABLE xbt_snatched CHANGE `IP` `ipv4` varchar(15); + * ALTER TABLE xbt_snatched ADD COLUMN `ipv6` varchar(45); + * ALTER TABLE xbt_files_users CHANGE `ip` `ipv4` varchar(15); + * ALTER TABLE xbt_files_users ADD COLUMN `ipv6` varchar(45); +* Implement compact peer lists (BEP 0023) (http://www.bittorrent.org/beps/bep_0023.html) + -- 1.0 (2015-01-26) NOTE: This version requires the following database change: diff --git a/README.md b/README.md index 8e76ea9..536ad8f 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,14 @@ Ocelot is a BitTorrent tracker written in C++ for the [Gazelle](http://whatcd.gi * [libev](http://software.schmorp.de/pkg/libev.html) (required) * [MySQL++](http://tangentsoft.net/mysql++/) (3.2.0+ required) * [TCMalloc](http://goog-perftools.sourceforge.net/doc/tcmalloc.html) (optional, but strongly recommended) +* [spdlog](https://github.com/gabime/spdlog) (0.11.0+ required) ## Installation ### Debian Jessie ```bash sudo apt-get install pkg-config libev-dev libboost-all-dev +git clone https://github.com/gabime/spdlog src/spdlog ./configure --with-boost-libdir=/usr/lib/x86_64-linux-gnu ``` @@ -42,8 +44,10 @@ The [Gazelle installation guides](https://github.com/WhatCD/Gazelle/wiki/Gazelle ### Run-time options: -* `-c ` - Path to config file. If unspecified, the current working directory is used. -* `-v` - Print queue status every time a flush is initiated. +* `-c ` or `--config ` - Path to config file. If unspecified, the current working directory is used. +* `-v` or `--verbose` - Print queue status every time a flush is initiated. +* `-V` or `--version` - Print Ocelot version and exit. +* `-d` or `--daemonize` - Run Ocelot as a daemon ### Signals diff --git a/Vagrantfile b/Vagrantfile index dd621d1..424dda7 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -6,11 +6,28 @@ sudo apt-get -y install build-essential autoconf sudo apt-get -y install libboost-iostreams-dev libboost-system-dev sudo apt-get -y install libev-dev sudo apt-get -y install libmysqlclient-dev libmysql++-dev + +debconf-set-selections <<< 'mariadb-server mysql-server/root_password password em%G9Lrey4^N' +debconf-set-selections <<< 'mariadb-server mysql-server/root_password_again password em%G9Lrey4^N' +sudo apt-get install -y mariadb-server mariadb-client + +mysql -uroot -pem%G9Lrey4^N < /vagrant/gazelle.sql +mysql -uroot -pem%G9Lrey4^N -e "CREATE USER 'gazelle'@'%' IDENTIFIED BY 'password';" +mysql -uroot -pem%G9Lrey4^N -e "GRANT ALL ON *.* TO 'gazelle'@'%';" +mysql -uroot -pem%G9Lrey4^N -e "FLUSH PRIVILEGES;" +sudo sed -i "s/^bind-address/\# bind-address/" /etc/mysql/my.cnf +sudo sed -i "s/^skip-external-locking/\# skip-external-locking/" /etc/mysql/my.cnf + +sudo service mysql restart + SCRIPT Vagrant.configure("2") do |config| config.vm.box = "debian/contrib-jessie64" + config.vm.network :forwarded_port, guest: 3306, host: 36000 + config.vm.network :forwarded_port, guest: 34000, host: 34000 + config.vm.synced_folder ".", "/vagrant" config.vm.provision "shell", inline: $script diff --git a/ocelot.conf.dist b/ocelot.conf.dist index 4f6f567..fa06ccd 100644 --- a/ocelot.conf.dist +++ b/ocelot.conf.dist @@ -24,6 +24,11 @@ mysql_db = report_password = 00000000000000000000000000000000 site_password = 00000000000000000000000000000000 +# Set log settings. Set the path to be the full or relative path to where you want the log file to be, +# it'll then be named "ocelot-.log" for you. +log = false +log_path = /tmp + peers_timeout = 7200 del_reason_lifetime = 86400 reap_peers_interval = 1800 diff --git a/src/db.cpp b/src/db.cpp index 347d5e2..f9a6710 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -255,15 +255,16 @@ void mysql::record_torrent(const std::string &record) { update_torrent_buffer += record; } -void mysql::record_peer(const std::string &record, const std::string &ip, const std::string &peer_id, const std::string &useragent) { +void mysql::record_peer(const std::string &record, const std::string &ipv4, const std::string &ipv6, const std::string &peer_id, const std::string &useragent) { if (update_heavy_peer_buffer != "") { update_heavy_peer_buffer += ","; } mysqlpp::Query q = conn.query(); - q << record << mysqlpp::quote << ip << ',' << mysqlpp::quote << peer_id << ',' << mysqlpp::quote << useragent << "," << time(NULL) << ')'; + q << record << mysqlpp::quote << ipv4 << ',' << mysqlpp::quote << peer_id << ',' << mysqlpp::quote << useragent << "," << time(NULL) << ')'; update_heavy_peer_buffer += q.str(); } + void mysql::record_peer(const std::string &record, const std::string &peer_id) { if (update_light_peer_buffer != "") { update_light_peer_buffer += ","; @@ -274,12 +275,12 @@ void mysql::record_peer(const std::string &record, const std::string &peer_id) { update_light_peer_buffer += q.str(); } -void mysql::record_snatch(const std::string &record, const std::string &ip) { +void mysql::record_snatch(const std::string &record, const std::string &ipv4, const std::string &ipv6) { if (update_snatch_buffer != "") { update_snatch_buffer += ","; } mysqlpp::Query q = conn.query(); - q << record << ',' << mysqlpp::quote << ip << ')'; + q << record << ',' << mysqlpp::quote << ipv4 << ')'; update_snatch_buffer += q.str(); } @@ -363,7 +364,7 @@ void mysql::flush_snatches() { if (update_snatch_buffer == "" ) { return; } - sql = "INSERT INTO xbt_snatched (uid, fid, tstamp, IP) VALUES " + update_snatch_buffer; + sql = "INSERT INTO xbt_snatched (uid, fid, tstamp, ipv4) VALUES " + update_snatch_buffer; snatch_queue.push(sql); update_snatch_buffer.clear(); if (!s_active) { @@ -399,7 +400,7 @@ void mysql::flush_peers() { peer_queue.pop(); } sql = "INSERT INTO xbt_files_users (uid,fid,active,uploaded,downloaded,upspeed,downspeed,remaining,corrupt," + - std::string("timespent,announced,ip,peer_id,useragent,mtime) VALUES ") + update_heavy_peer_buffer + + std::string("timespent,announced,ipv4,peer_id,useragent,mtime) VALUES ") + update_heavy_peer_buffer + " ON DUPLICATE KEY UPDATE active=VALUES(active), uploaded=VALUES(uploaded), " + "downloaded=VALUES(downloaded), upspeed=VALUES(upspeed), " + "downspeed=VALUES(downspeed), remaining=VALUES(remaining), " + diff --git a/src/db.h b/src/db.h index 9de9846..6d853d9 100644 --- a/src/db.h +++ b/src/db.h @@ -68,8 +68,8 @@ class mysql { void record_user(const std::string &record); // (id,uploaded_change,downloaded_change) void record_torrent(const std::string &record); // (id,seeders,leechers,snatched_change,balance) - void record_snatch(const std::string &record, const std::string &ip); // (uid,fid,tstamp) - void record_peer(const std::string &record, const std::string &ip, const std::string &peer_id, const std::string &useragent); // (uid,fid,active,peerid,useragent,ip,uploaded,downloaded,upspeed,downspeed,left,timespent,announces,tstamp) + void record_snatch(const std::string &record, const std::string &ipv4, const std::string &ipv6); // (uid,fid,tstamp) + void record_peer(const std::string &record, const std::string &ipv4, const std::string &ipv6, const std::string &peer_id, const std::string &useragent); // (uid,fid,active,peerid,useragent,ip,uploaded,downloaded,upspeed,downspeed,left,timespent,announces,tstamp) void record_peer(const std::string &record, const std::string &peer_id); // (fid,peerid,timespent,announces,tstamp) void record_token(const std::string &record); diff --git a/src/events.cpp b/src/events.cpp index 8ef3eaf..10ce87e 100644 --- a/src/events.cpp +++ b/src/events.cpp @@ -7,6 +7,7 @@ #include "schedule.h" #include "response.h" #include "events.h" +#include "misc_functions.h" // Define the connection mother (first half) and connection middlemen (second half) @@ -188,14 +189,19 @@ void connection_middleman::handle_read(ev::io &watcher, int events_flags) { response = error("GET string too long", client_opts); } else { char ip[INET_ADDRSTRLEN]; - sockaddr_in client_addr; + sockaddr_storage client_addr{}; socklen_t addr_len = sizeof(client_addr); getpeername(connect_sock, (sockaddr *) &client_addr, &addr_len); - inet_ntop(AF_INET, &(client_addr.sin_addr), ip, INET_ADDRSTRLEN); + uint16_t ip_ver = 4; + if (client_addr.ss_family == AF_INET6) { + ip_ver = 6; + } + + inet_ntop(client_addr.ss_family, get_in_addr((struct sockaddr *) &client_addr), ip, sizeof ip); std::string ip_str = ip; //--- CALL WORKER - response = work->work(request, ip_str, client_opts); + response = work->work(request, ip_str, ip_ver, client_opts); request.clear(); request_size = 0; } diff --git a/src/misc_functions.cpp b/src/misc_functions.cpp index 2b616f9..7549576 100644 --- a/src/misc_functions.cpp +++ b/src/misc_functions.cpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include int32_t strtoint32(const std::string& str) { std::istringstream stream(str); @@ -28,7 +30,7 @@ std::string inttostr(const int i) { std::string hex_decode(const std::string &in) { std::string out; out.reserve(20); - unsigned int in_length = in.length(); + unsigned long in_length = in.length(); for (unsigned int i = 0; i < in_length; i++) { unsigned char x = '0'; if (in[i] == '%' && (i + 2) < in_length) { @@ -50,7 +52,7 @@ std::string hex_decode(const std::string &in) { x += static_cast(in[i]-48); } } else { - x = in[i]; + x = (unsigned char) in[i]; } out.push_back(x); } @@ -62,14 +64,14 @@ std::string bintohex(const std::string &in) { size_t length = in.length(); out.reserve(2*length); for (unsigned int i = 0; i < length; i++) { - unsigned char x = static_cast((in[i] & 0xF0) >> 4); + auto x = static_cast((in[i] & 0xF0) >> 4); if (x > 9) { x += 'a' - 10; } else { x += '0'; } out.push_back(x); - x = in[i] & 0x0F; + x = static_cast(in[i] & 0x0F); if (x > 9) { x += 'a' - 10; } else { @@ -79,3 +81,131 @@ std::string bintohex(const std::string &in) { } return out; } + +void *get_in_addr(struct sockaddr *sa) +{ + if (sa->sa_family == AF_INET) { + return &(((struct sockaddr_in*) sa)->sin_addr); + } + else { + return &(((struct sockaddr_in6 *) sa)->sin6_addr); + } +} + +/** + * Encodes an integer into the proper bencode format. An integer is encoded as ie. + * Leading zeros are not allowed (although the number zero is still represented as "0"). Negative values are encoded + * by prefixing the number with a minus sign. The number 42 would thus be encoded as i42e, 0 as i0e, and -42 as i-42e. + * Negative zero is not permitted. + * + * @param data + * @return Bencode string + */ +std::string bencode_int(int data) { + std::string bencoded_int = "i"; + bencoded_int += std::to_string(data); + bencoded_int += "e"; + return bencoded_int; +} + +/** + * Encodes a string into the proper bencode format. A byte string (a sequence of bytes, not necessarily characters) + * is encoded as :. The length is encoded in base 10, like integers, but must be non-negative + * (zero is allowed); the contents are just the bytes that make up the string. The string "spam" would be encoded as + * 4:spam. The specification does not deal with encoding of characters outside the ASCII set; to mitigate this, + * some BitTorrent applications explicitly communicate the encoding (most commonly UTF-8) in various non-standard + * ways. This is identical to how netstrings work, except that netstrings additionally append a comma suffix after + * the byte sequence. + * + * @param data + * @return + */ +std::string bencode_str(std::string data) { + std::string bencoded_str = std::to_string(data.size()); + bencoded_str += ":"; + bencoded_str += data; + return bencoded_str; +} + + +/** + * Returns a boolean for whether or not a given IPv4 address is a private/reserved address or not. A user should not + * be able to use one of these private addresses. The list of private/reserved IP addresses that we disallow are + * taken from https://en.wikipedia.org/wiki/IPv4#Special-use_addresses. + * For a very good primer on what masks means for IPv4 addresses and then why we're using bitwise operations: + * https://www.linuxquestions.org/questions/linux-networking-3/what-does-8-32-etc-mean-for-ip-240380/#post1223010 + * + * We test for (in order): + * 1. 10.0.0.0/8 + * 2. 100.64.0.0/10 + * 3. 127.0.0.0/8 + * 4. 169.254.0.0/16 + * 5. 172.16.0.0/12 + * 6. 192.168.0.0/16 + * 7. 192.0.2.0/24 + * 8. 198.51.100.0/24 + * 9. 203.0.113.0/24 + * @param addr + * @return + */ +bool private_ipv4(in_addr addr) { + uint32_t address = ntohl(addr.s_addr); + return (address & 0xff000000) == 0x0a000000 || + (address & 0xffc00000) == 0x64400000 || + (address & 0xff000000) == 0x7f000000 || + (address & 0xffff0000) == 0xa9fe0000 || + (address & 0xfff00000) == 0xac100000 || + (address & 0xffff0000) == 0xc0a80000 || + (address & 0xffffff00) == 0xC0000200 || + (address & 0xffffff00) == 0xC6336400 || + (address & 0xffffff00) == 0xCB007100; +} + +/** + * Returns a boolean for whether or not a given IPv6 address is a private/reserved address or not. A user should not + * be able to use one of these private addresses. The list of these IP addresses that are disallowed are taken + * from https://en.wikipedia.org/wiki/IPv6_address#Special_addresses. + * + * IPv6 is 128 bytes possible where there are 8 groups of 16 bits that are represented via 4 hexidecimal digits. + * How this gets represented can be read more about on Wikipedia (https://en.wikipedia.org/wiki/IPv6#Address_representation) + * + * The in6_addr structure then has (at least on the architectures we care about): + * s6_addr32[4] + * s6_addr16[8] + * s6_addr8[16] + * which allow for different granularity of referencing the address space. + * + * We are returning true for addresses in the following spaces: + * 1. ::1/128 + * 2. ::ffff/96 + * 3. fe80::/10 + * 4. fc00::/7 + * 5. fec0::/16 + * 6. 3ffe::/16 + * 7. 2001:0db8::/32 + * 8. 2001:0000::/32 + * 9. 2002::/16 + * + * @param addr + * @return + */ +bool private_ipv6(in6_addr addr) { + uint32_t addr32[4]; + for (int i = 0; i < 4; i++) { + addr32[i] = ntohl(addr.s6_addr32[i]); + } + return (addr32[0] == 0x00000000 && addr32[0] == addr32[1] && addr32[0] == addr32[2] && addr32[3] == 0x00000001) || // + true ; +} + +/* + * if (ntohl(addr.s6_addr32[0]) == 0x00000000) return false; // Loopback / v4 compat v6 + if (ntohs(addr.s6_addr16[0]) == 0xfe80 ) return false; // Link local + if (ntohs(addr.s6_addr16[0]) == 0xfc00 ) return false; // Unique Local - private subnet + if (ntohs(addr.s6_addr16[0]) == 0xfec0 ) return false; // site-local [deprecated] + if (ntohs(addr.s6_addr16[0]) == 0x3ffe ) return false; // 6bone [deprecated] + if (ntohl(addr.s6_addr32[0]) == 0x20010db8) return false; // documentation examples, unroutable + if (ntohl(addr.s6_addr32[0]) == 0x20010000) return false; // Teredo + if (ntohs(addr.s6_addr16[0]) == 0x2002 ) return false; // 6to4 + */ + diff --git a/src/misc_functions.h b/src/misc_functions.h index fbe164d..dbb04e0 100644 --- a/src/misc_functions.h +++ b/src/misc_functions.h @@ -1,11 +1,16 @@ #ifndef MISC_FUNCTIONS__H #define MISC_FUNCTIONS__H #include +#include int32_t strtoint32(const std::string& str); int64_t strtoint64(const std::string& str); std::string inttostr(int i); std::string hex_decode(const std::string &in); std::string bintohex(const std::string &in); - +void *get_in_addr(struct sockaddr *sa); +std::string bencode_int(int data); +std::string bencode_str(std::string data); +bool private_ipv4(in_addr addr); +bool private_ipv6(in6_addr addr); #endif diff --git a/src/ocelot.cpp b/src/ocelot.cpp index 10e4eda..14f1414 100644 --- a/src/ocelot.cpp +++ b/src/ocelot.cpp @@ -94,22 +94,22 @@ int main(int argc, char **argv) { std::string conf_file_path("./ocelot.conf"); for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "-v") == 0) { + if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) { verbose = true; } else if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--daemonize") == 0) { daemonize = true; } - else if (strcmp(argv[i], "-c") == 0 && i < argc - 1) { + else if ((strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--config")) && i < argc - 1) { conf_arg = true; conf_file_path = argv[++i]; } else if(strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0) { - std::cout << "Ocelot, version 1.1" << std::endl; + std::cout << "Ocelot, version 1.1-dev" << std::endl; return 0; } else { - std::cout << "Usage: " << argv[0] << " [-v] [-c configfile]" << std::endl; + std::cout << "Usage: " << argv[0] << " [-v|--verbose] [-d|--daemonize] [-c configfile]" << std::endl; return 0; } } @@ -134,7 +134,9 @@ int main(int argc, char **argv) { sinks.push_back(std::make_shared()); } if (conf->get_bool("log") && !conf->get_str("log_path").empty()) { - sinks.push_back(std::make_shared(conf->get_str("log_path"), "log", 23, 59)); + std::string log_path = conf->get_str("log_path"); + log_path = log_path + ((conf->get_str("log_path").back() == '/') ? "ocelot" : "/ocelot"); + sinks.push_back(std::make_shared(log_path, "log", 23, 59)); } auto combined_logger = std::make_shared("logger", begin(sinks), end(sinks)); diff --git a/src/ocelot.h b/src/ocelot.h index 0a300a3..7196f4b 100644 --- a/src/ocelot.h +++ b/src/ocelot.h @@ -27,8 +27,10 @@ typedef struct { bool visible; bool invalid_ip; user_ptr user; - std::string ip_port; - std::string ip; + std::string ipv4; + std::string ipv4_port; + std::string ipv6; + std::string ipv6_port; } peer; typedef std::map peer_list; diff --git a/src/worker.cpp b/src/worker.cpp index b45430e..75a2116 100644 --- a/src/worker.cpp +++ b/src/worker.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include "ocelot.h" #include "config.h" @@ -65,8 +67,8 @@ bool worker::shutdown() { } } -std::string worker::work(const std::string &input, std::string &ip, client_opts_t &client_opts) { - unsigned int input_length = input.length(); +std::string worker::work(const std::string &input, std::string &ip, uint16_t &ip_ver, client_opts_t &client_opts) { + unsigned int input_length = static_cast(input.length()); //---------- Parse request - ugly but fast. Using substr exploded. if (input_length < 60) { // Way too short to be anything useful @@ -113,12 +115,14 @@ std::string worker::work(const std::string &input, std::string &ip, client_opts_ action = REPORT; pos += 6; break; + default: + break; } if (input[pos] != '?') { // No parameters given. Probably means we're not talking to a torrent client client_opts.html = true; - return response("Nothing to see here", client_opts); + return response("Tracker is running", client_opts); } // Parse URL params @@ -239,7 +243,7 @@ std::string worker::work(const std::string &input, std::string &ip, client_opts_ if (user_it == users_list.end()) { return error("Passkey not found", client_opts); } - user_ptr u = user_it->second; + user_ptr user = user_it->second; ul_lock.unlock(); if (action == ANNOUNCE) { @@ -261,13 +265,13 @@ std::string worker::work(const std::string &input, std::string &ip, client_opts_ return error("Unregistered torrent", client_opts); } } - return announce(input, tor->second, u, params, headers, ip, client_opts); + return announce(input, tor->second, user, params, headers, ip, ip_ver, client_opts); } else { return scrape(infohashes, headers, client_opts); } } -std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u, params_type ¶ms, params_type &headers, std::string &ip, client_opts_t &client_opts) { +std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u, params_type ¶ms, params_type &headers, std::string &ip, uint16_t &ip_ver, client_opts_t &client_opts) { cur_time = time(NULL); if (params["compact"] != "1") { @@ -290,6 +294,13 @@ std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u bool invalid_ip = false; bool inc_l = false, inc_s = false, dec_l = false, dec_s = false; userid_t userid = u->get_id(); + std::string ipv4 = "", ipv6 = ""; + if (ip_ver == 4) { + ipv4 = ip; + } + else { + ipv6 = ip; + } params_type::const_iterator peer_id_iterator = params.find("peer_id"); if (peer_id_iterator == params.end()) { @@ -394,12 +405,14 @@ std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u p->corrupt = corrupt; p->announces = 1; peer_changed = true; - } else if (uploaded < p->uploaded || downloaded < p->downloaded) { + } + else if (uploaded < p->uploaded || downloaded < p->downloaded) { p->announces++; p->uploaded = uploaded; p->downloaded = downloaded; peer_changed = true; - } else { + } + else { int64_t uploaded_change = 0; int64_t downloaded_change = 0; int64_t corrupt_change = 0; @@ -434,7 +447,8 @@ std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u if (tor.free_torrent == NEUTRAL) { downloaded_change = 0; uploaded_change = 0; - } else if (tor.free_torrent == FREE || sit != tor.tokened_users.end()) { + } + else if (tor.free_torrent == FREE || sit != tor.tokened_users.end()) { if (sit != tor.tokened_users.end()) { expire_token = true; std::stringstream record; @@ -457,51 +471,105 @@ std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u params_type::const_iterator param_ip = params.find("ip"); if (param_ip != params.end()) { - ip = param_ip->second; - } else if ((param_ip = params.find("ipv4")) != params.end()) { - ip = param_ip->second; - } else { - auto head_itr = headers.find("x-forwarded-for"); - if (head_itr != headers.end()) { - size_t ip_end_pos = head_itr->second.find(','); - if (ip_end_pos != std::string::npos) { - ip = head_itr->second.substr(0, ip_end_pos); - } else { - ip = head_itr->second; - } + struct addrinfo hint, *res = NULL; + memset(&hint, 0, sizeof hint); + hint.ai_family = AF_UNSPEC; + hint.ai_flags = AI_NUMERICHOST; + getaddrinfo(ip.c_str(), NULL, &hint, &res); + if (res->ai_family == AF_INET) { + ipv4 = param_ip->second; } + else { + ipv6 = param_ip->second; + } + freeaddrinfo(res); + } + else if ((param_ip = params.find("ipv4")) != params.end()) { + ipv4 = param_ip->second; + } + else if ((param_ip = params.find("ipv6")) != params.end()) { + ipv6 = param_ip->second; + } + else { + auto header_ip = headers.find("x-forwarded-for"); + if (header_ip != headers.end()) { + size_t ip_end_pos = header_ip->second.find(','); + std::string ip_tmp = (ip_end_pos != std::string::npos) ? header_ip->second.substr(0, ip_end_pos) : header_ip->second; + struct addrinfo hint{}, *res = NULL; + memset(&hint, 0, sizeof hint); + hint.ai_family = AF_UNSPEC; + hint.ai_flags = AI_NUMERICHOST; + getaddrinfo(ip.c_str(), NULL, &hint, &res); + if (res->ai_family == AF_INET) { + ipv4 = ip_tmp; + } + else { + ipv6 = ip_tmp; + } + freeaddrinfo(res); + } + } + + struct sockaddr_in sa{}; + if(!ipv4.empty() && inet_pton(AF_INET, hex_decode(ipv4).c_str(), &(sa.sin_addr)) > 0){ + // IP is 4 bytes for IPv4 + if (private_ipv4(sa.sin_addr)) { + ipv4.clear(); + } + else { + char str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &(sa.sin_addr), str, INET_ADDRSTRLEN); + ipv4.assign(str); + } + } + else { + ipv4.clear(); + } + + struct sockaddr_in6 sa6{}; + if(!ipv6.empty() && inet_pton(AF_INET6, hex_decode(ipv6).c_str(), &(sa6.sin6_addr)) > 0){ + // IP is 16 bytes for IPv6 + if (private_ipv6(sa6.sin6_addr)) { + ipv6.clear(); + } + else { + char str[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &(sa6.sin6_addr), str, INET6_ADDRSTRLEN); + ipv6.assign(str); + } + } + else { + ipv6.clear(); + } + + if (ipv4.empty() && ipv6.empty()) { + return error("Invalid IP detected.", client_opts); } uint16_t port = strtoint32(params["port"]) & 0xFFFF; // Generate compact ip/port string - if (inserted || port != p->port || ip != p->ip) { + if (inserted || port != p->port || ipv4 != p->ipv4 || ipv6 != p->ipv6) { p->port = port; - p->ip = ip; - p->ip_port = ""; - char x = 0; - for (size_t pos = 0, end = ip.length(); pos < end; pos++) { - if (ip[pos] == '.') { - p->ip_port.push_back(x); - x = 0; - continue; - } else if (!isdigit(ip[pos])) { - invalid_ip = true; - break; - } - x = x * 10 + ip[pos] - '0'; + p->ipv4 = ""; + p->ipv6 = ""; + p->ipv4_port = ""; + p->ipv6_port = ""; + + if (!ipv4.empty()) { + p->ipv4 = ipv4; + // IP+Port is 6 bytes for IPv4 + p->ipv4_port = ipv4; + p->ipv4_port.push_back(static_cast(port >> 8)); + p->ipv4_port.push_back(static_cast(port & 0xFF)); } - if (!invalid_ip) { - p->ip_port.push_back(x); - p->ip_port.push_back(port >> 8); - p->ip_port.push_back(port & 0xFF); + + if(!ipv6.empty()){ + p->ipv6 = ipv6; + // IP+Port is 18 bytes for IPv6 + p->ipv6_port = ipv6; + p->ipv6_port.push_back(static_cast(port >> 8)); + p->ipv6_port.push_back(static_cast(port & 0xFF)); } - if (p->ip_port.length() != 6) { - p->ip_port.clear(); - invalid_ip = true; - } - p->invalid_ip = invalid_ip; - } else { - invalid_ip = p->invalid_ip; } // Update the peer @@ -513,14 +581,11 @@ std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u if (peer_changed) { record << '(' << userid << ',' << tor.id << ',' << active << ',' << uploaded << ',' << downloaded << ',' << upspeed << ',' << downspeed << ',' << left << ',' << corrupt << ',' << (cur_time - p->first_announced) << ',' << p->announces << ','; std::string record_str = record.str(); - std::string record_ip; - if (u->is_protected()) { - record_ip = ""; - } else { - record_ip = ip; - } - db->record_peer(record_str, record_ip, peer_id, headers["user-agent"]); - } else { + std::string record_ipv4 = (u->is_protected()) ? "" : ipv4; + std::string record_ipv6 = (u->is_protected()) ? "" : ipv6; + db->record_peer(record_str, record_ipv4, record_ipv6, peer_id, headers["user-agent"]); + } + else { record << '(' << userid << ',' << tor.id << ',' << (cur_time - p->first_announced) << ',' << p->announces << ','; std::string record_str = record.str(); db->record_peer(record_str, peer_id); @@ -529,39 +594,33 @@ std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u // Select peers! uint32_t numwant; params_type::const_iterator param_numwant = params.find("numwant"); - if (param_numwant == params.end()) { - numwant = numwant_limit; - } else { - numwant = std::min((int32_t)numwant_limit, strtoint32(param_numwant->second)); - } + numwant = (param_numwant == params.end()) ? numwant_limit : (uint32_t) std::min((int32_t)numwant_limit, strtoint32(param_numwant->second)); if (stopped_torrent) { numwant = 0; if (left > 0) { dec_l = true; - } else { + } + else { dec_s = true; } - } else if (completed_torrent) { + } + else if (completed_torrent) { snatched = 1; update_torrent = true; tor.completed++; std::stringstream record; - std::string record_ip; - if (u->is_protected()) { - record_ip = ""; - } else { - record_ip = ip; - } + std::string record_ipv4 = (u->is_protected()) ? "" : ipv4; + std::string record_ipv6 = (u->is_protected()) ? "" : ipv6; + record << '(' << userid << ',' << tor.id << ',' << cur_time; std::string record_str = record.str(); - db->record_snatch(record_str, record_ip); + db->record_snatch(record_str, record_ipv4, record_ipv6); // User is a seeder now! if (!inserted) { - std::pair insert - = tor.seeders.insert(std::pair(peer_key, *p)); + std::pair insert = tor.seeders.insert(std::pair(peer_key, *p)); tor.leechers.erase(peer_it); peer_it = insert.first; p = &peer_it->second; @@ -571,15 +630,20 @@ std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u s_comm->expire_token(tor.id, userid); tor.tokened_users.erase(userid); } - } else if (!u->can_leech() && left > 0) { + } + else if (!u->can_leech() && left > 0) { numwant = 0; } - std::string peers; + std::string peers_v4; + std::string peers_v6; + if (numwant > 0) { - peers.reserve(numwant*6); + peers_v4.reserve(numwant*6); + peers_v6.reserve(numwant*18); unsigned int found_peers = 0; - if (left > 0) { // Show seeders to leechers first + // Show seeders to leechers first + if (left > 0) { if (tor.seeders.size() > 0) { // We do this complicated stuff to cycle through the seeder list, so all seeders will get shown to leechers @@ -616,8 +680,17 @@ std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u ++i; continue; } - peers.append(i->second.ip_port); - found_peers++; + + + // Only show IPv6 peers to other IPv6 peers + if ((!p->ipv6.empty()) && (!i->second.ipv6_port.empty())) { + peers_v6.append(i->second.ipv6_port); + found_peers++; + } else if (!i->second.ipv4_port.empty()) { + peers_v4.append(i->second.ipv4_port); + found_peers++; + } + tor.last_selected_seeder = i->first; ++i; } @@ -626,11 +699,11 @@ std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u if (found_peers < numwant && tor.leechers.size() > 1) { for (peer_list::const_iterator i = tor.leechers.begin(); i != tor.leechers.end() && found_peers < numwant; ++i) { // Don't show users themselves or leech disabled users - if (i->second.user->is_deleted() || i->second.ip_port == p->ip_port || i->second.user->get_id() == userid || !i->second.visible) { + if (i->second.user->is_deleted() || i->second.ipv4_port == p->ipv4_port || i->second.user->get_id() == userid || !i->second.visible) { continue; } found_peers++; - peers.append(i->second.ip_port); + peers_v4.append(i->second.ipv4_port); } } @@ -641,7 +714,7 @@ std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u continue; } found_peers++; - peers.append(i->second.ip_port); + peers_v4.append(i->second.ipv4_port); } } } @@ -704,25 +777,16 @@ std::string worker::announce(const std::string &input, torrent &tor, user_ptr &u return error("Access denied, leeching forbidden", client_opts); } - std::string output = "d8:completei"; + std::string output = "d"; output.reserve(350); - output += inttostr(tor.seeders.size()); - output += "e10:downloadedi"; - output += inttostr(tor.completed); - output += "e10:incompletei"; - output += inttostr(tor.leechers.size()); - output += "e8:intervali"; - output += inttostr(announce_interval + std::min((size_t)600, tor.seeders.size())); // ensure a more even distribution of announces/second - output += "e12:min intervali"; - output += inttostr(announce_interval); - output += "e5:peers"; - if (peers.length() == 0) { - output += "0:"; - } else { - output += inttostr(peers.length()); - output += ":"; - output += peers; - } + output += bencode_str("complete") + bencode_int(static_cast(tor.seeders.size())); + output += bencode_str("downloaded") + bencode_int(tor.completed); + output += bencode_str("incomplete") + bencode_int(static_cast(tor.leechers.size())); + // ensure a more even distribution of announces/second + output += bencode_str("interval") + bencode_int(static_cast(announce_interval + std::min((size_t)600, tor.seeders.size()))); + output += bencode_str("min interval") + bencode_int(announce_interval); + output += bencode_str("peers") + bencode_str(peers_v4); + if (invalid_ip) { output += warning("Illegal character found in IP address. IPv6 is not supported"); } @@ -1167,7 +1231,7 @@ std::string worker::get_del_reason(int code) } /* Peers should be invisible if they are a leecher without - download privs or their IP is invalid */ + download privs */ bool worker::peer_is_visible(user_ptr &u, peer *p) { - return (p->left == 0 || u->can_leech()) && !p->invalid_ip; + return (p->left == 0 || u->can_leech()); } diff --git a/src/worker.h b/src/worker.h index e0a04bb..d9cea2e 100644 --- a/src/worker.h +++ b/src/worker.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "site_comm.h" #include "ocelot.h" @@ -48,8 +49,8 @@ class worker { public: worker(config * conf_obj, torrent_list &torrents, user_list &users, std::vector &_whitelist, mysql * db_obj, site_comm * sc); void reload_config(config * conf); - std::string work(const std::string &input, std::string &ip, client_opts_t &client_opts); - std::string announce(const std::string &input, torrent &tor, user_ptr &u, params_type ¶ms, params_type &headers, std::string &ip, client_opts_t &client_opts); + std::string work(const std::string &input, std::string &ip, uint16_t &ip_ver, client_opts_t &client_opts); + std::string announce(const std::string &input, torrent &tor, user_ptr &u, params_type ¶ms, params_type &headers, std::string &ip, uint16_t &ip_ver, client_opts_t &client_opts); std::string scrape(const std::list &infohashes, params_type &headers, client_opts_t &client_opts); std::string update(params_type ¶ms, client_opts_t &client_opts);