initial ipv6 work

This commit is contained in:
itismadness
2018-01-04 02:58:27 -11:00
parent 097374380c
commit 4e7e25027b
13 changed files with 380 additions and 127 deletions

18
CHANGES
View File

@@ -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:

View File

@@ -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/ocelot.conf>` - Path to config file. If unspecified, the current working directory is used.
* `-v` - Print queue status every time a flush is initiated.
* `-c <path/to/ocelot.conf>` or `--config <path/to/ocelot.conf>` - 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

17
Vagrantfile vendored
View File

@@ -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

View File

@@ -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-<date_time>.log" for you.
log = false
log_path = /tmp
peers_timeout = 7200
del_reason_lifetime = 86400
reap_peers_interval = 1800

View File

@@ -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), " +

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -1,6 +1,8 @@
#include <string>
#include <iostream>
#include <sstream>
#include <sys/socket.h>
#include <netinet/in.h>
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<unsigned char>(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<unsigned char>((in[i] & 0xF0) >> 4);
auto x = static_cast<unsigned char>((in[i] & 0xF0) >> 4);
if (x > 9) {
x += 'a' - 10;
} else {
x += '0';
}
out.push_back(x);
x = in[i] & 0x0F;
x = static_cast<unsigned char>(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 i<integer encoded in base ten ASCII>e.
* 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 <length>:<contents>. 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
*/

View File

@@ -1,11 +1,16 @@
#ifndef MISC_FUNCTIONS__H
#define MISC_FUNCTIONS__H
#include <string>
#include <netinet/in.h>
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

View File

@@ -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<spdlog::sinks::stdout_sink_st>());
}
if (conf->get_bool("log") && !conf->get_str("log_path").empty()) {
sinks.push_back(std::make_shared<spdlog::sinks::daily_file_sink_st>(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<spdlog::sinks::daily_file_sink_st>(log_path, "log", 23, 59));
}
auto combined_logger = std::make_shared<spdlog::logger>("logger", begin(sinks), end(sinks));

View File

@@ -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<std::string, peer> peer_list;

View File

@@ -9,6 +9,8 @@
#include <mutex>
#include <thread>
#include <spdlog/spdlog.h>
#include <netdb.h>
#include <arpa/inet.h>
#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<unsigned int>(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 &params, 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 &params, 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<char>(port >> 8));
p->ipv4_port.push_back(static_cast<char>(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<char>(port >> 8));
p->ipv6_port.push_back(static_cast<char>(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<peer_list::iterator, bool> insert
= tor.seeders.insert(std::pair<std::string, peer>(peer_key, *p));
std::pair<peer_list::iterator, bool> insert = tor.seeders.insert(std::pair<std::string, peer>(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<int>(tor.seeders.size()));
output += bencode_str("downloaded") + bencode_int(tor.completed);
output += bencode_str("incomplete") + bencode_int(static_cast<int>(tor.leechers.size()));
// ensure a more even distribution of announces/second
output += bencode_str("interval") + bencode_int(static_cast<int>(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());
}

View File

@@ -8,6 +8,7 @@
#include <iostream>
#include <mutex>
#include <ctime>
#include <cstdint>
#include <spdlog/spdlog.h>
#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<std::string> &_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 &params, 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 &params, params_type &headers, std::string &ip, uint16_t &ip_ver, client_opts_t &client_opts);
std::string scrape(const std::list<std::string> &infohashes, params_type &headers, client_opts_t &client_opts);
std::string update(params_type &params, client_opts_t &client_opts);