mirror of
https://github.com/OPSnet/Ocelot.git
synced 2026-01-16 18:04:19 -05:00
initial ipv6 work
This commit is contained in:
18
CHANGES
18
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:
|
||||
|
||||
@@ -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
17
Vagrantfile
vendored
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
13
src/db.cpp
13
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), " +
|
||||
|
||||
4
src/db.h
4
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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
262
src/worker.cpp
262
src/worker.cpp
@@ -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 ¶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<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());
|
||||
}
|
||||
|
||||
@@ -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 ¶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<std::string> &infohashes, params_type &headers, client_opts_t &client_opts);
|
||||
std::string update(params_type ¶ms, client_opts_t &client_opts);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user