summaryrefslogtreecommitdiff
path: root/ot_udp.c
blob: 912c7e400c44c8dfbfd1785ffe864600ab4df476 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
   It is considered beerware. Prost. Skol. Cheers or whatever.

   $id$ */

/* System */
#include <arpa/inet.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Libowfat */
#include "io.h"
#include "ip6.h"
#include "socket.h"

/* Opentracker */
#include "ot_rijndael.h"
#include "ot_stats.h"
#include "ot_udp.h"
#include "trackerlogic.h"

#if 0
static const uint8_t g_static_connid[8] = { 0x23, 0x42, 0x05, 0x17, 0xde, 0x41, 0x50, 0xff };
#endif
static uint32_t g_rijndael_round_key[44] = {0};
static uint32_t g_key_of_the_hour[2]     = {0};
static ot_time  g_hour_of_the_key;

static void     udp_generate_rijndael_round_key() {
  uint32_t key[16];
#ifdef WANT_ARC4RANDOM
  arc4random_buf(&key[0], sizeof(key));
#else
  key[0] = random();
  key[1] = random();
  key[2] = random();
  key[3] = random();
#endif
  rijndaelKeySetupEnc128(g_rijndael_round_key, (uint8_t *)key);

#ifdef WANT_ARC4RANDOM
  g_key_of_the_hour[0] = arc4random();
#else
  g_key_of_the_hour[0] = random();
#endif
  g_hour_of_the_key = g_now_minutes;
}

/* Generate current and previous connection id for ip */
static void udp_make_connectionid(uint32_t connid[2], const ot_ip6 remoteip, int age) {
  uint32_t plain[4], crypt[4];
  int      i;
  if (g_now_minutes + 60 > g_hour_of_the_key) {
    g_hour_of_the_key    = g_now_minutes;
    g_key_of_the_hour[1] = g_key_of_the_hour[0];
#ifdef WANT_ARC4RANDOM
    g_key_of_the_hour[0] = arc4random();
#else
    g_key_of_the_hour[0] = random();
#endif
  }

  memcpy(plain, remoteip, sizeof(plain));
  for (i = 0; i < 4; ++i)
    plain[i] ^= g_key_of_the_hour[age];
  rijndaelEncrypt128(g_rijndael_round_key, (uint8_t *)remoteip, (uint8_t *)crypt);
  connid[0] = crypt[0] ^ crypt[1];
  connid[1] = crypt[2] ^ crypt[3];
}

/* UDP implementation according to http://xbtt.sourceforge.net/udp_tracker_protocol.html */
int handle_udp6(int64 serversocket, struct ot_workstruct *ws) {
  ot_ip6    remoteip;
  uint32_t *inpacket  = (uint32_t *)ws->inbuf;
  uint32_t *outpacket = (uint32_t *)ws->outbuf;
  uint32_t  left, event, scopeid;
  uint32_t  connid[2];
  uint32_t  action;
  uint16_t  port, remoteport;
  size_t    byte_count, scrape_count;

  byte_count = socket_recv6(serversocket, ws->inbuf, G_INBUF_SIZE, remoteip, &remoteport, &scopeid);
  if (!byte_count)
    return 0;

  stats_issue_event(EVENT_ACCEPT, FLAG_UDP, (uintptr_t)remoteip);
  stats_issue_event(EVENT_READ, FLAG_UDP, byte_count);

  /* Minimum udp tracker packet size, also catches error */
  if (byte_count < 16)
    return 1;

  /* Get action to take. Ignore error messages and broken packets */
  action = ntohl(inpacket[2]);
  if (action > 2)
    return 1;

  /* Generate the connection id we give out and expect to and from
     the requesting ip address, this prevents udp spoofing */
  udp_make_connectionid(connid, remoteip, 0);

  /* Initialise hash pointer */
  ws->hash    = NULL;
  ws->peer_id = NULL;

  /* If action is not 0 (connect), then we expect the derived
     connection id in first 64 bit */
  if ((action > 0) && (inpacket[0] != connid[0] || inpacket[1] != connid[1])) {
    /* If connection id does not match, try the one that was
       valid in the previous hour. Only if this also does not
       match, return an error packet */
    udp_make_connectionid(connid, remoteip, 1);
    if (inpacket[0] != connid[0] || inpacket[1] != connid[1]) {
      const size_t s = sizeof("Connection ID missmatch.");
      outpacket[0]   = htonl(3);
      outpacket[1]   = inpacket[3];
      memcpy(&outpacket[2], "Connection ID missmatch.", s);
      socket_send6(serversocket, ws->outbuf, 8 + s, remoteip, remoteport, 0);
      stats_issue_event(EVENT_CONNID_MISSMATCH, FLAG_UDP, 8 + s);
      return 1;
    }
  }

  switch (action) {
  case 0: /* This is a connect action */
    /* look for udp bittorrent magic id */
    if ((ntohl(inpacket[0]) != 0x00000417) || (ntohl(inpacket[1]) != 0x27101980))
      return 1;

    outpacket[0] = 0;
    outpacket[1] = inpacket[3];
    outpacket[2] = connid[0];
    outpacket[3] = connid[1];

    socket_send6(serversocket, ws->outbuf, 16, remoteip, remoteport, 0);
    stats_issue_event(EVENT_CONNECT, FLAG_UDP, 16);
    break;
  case 1: /* This is an announce action */
    /* Minimum udp announce packet size */
    if (byte_count < 98)
      return 1;

    /* We do only want to know, if it is zero */
    left     = inpacket[64 / 4] | inpacket[68 / 4];

    event    = ntohl(inpacket[80 / 4]);
    port     = *(uint16_t *)(((char *)inpacket) + 96);
    ws->hash = (ot_hash *)(((char *)inpacket) + 16);

    OT_SETIP(ws->peer, remoteip);
    OT_SETPORT(ws->peer, &port);
    OT_PEERFLAG(ws->peer) = 0;

    switch (event) {
    case 1:
      OT_PEERFLAG(ws->peer) |= PEER_FLAG_COMPLETED;
      break;
    case 3:
      OT_PEERFLAG(ws->peer) |= PEER_FLAG_STOPPED;
      break;
    default:
      break;
    }

    if (!left)
      OT_PEERFLAG(ws->peer) |= PEER_FLAG_SEEDING;

    outpacket[0] = htonl(1); /* announce action */
    outpacket[1] = inpacket[12 / 4];

    if (OT_PEERFLAG(ws->peer) & PEER_FLAG_STOPPED) { /* Peer is gone. */
      ws->reply      = ws->outbuf;
      ws->reply_size = remove_peer_from_torrent(FLAG_UDP, ws);
    } else {
      /* Limit amount of peers to OT_MAX_PEERS_UDP */
      uint32_t numwant   = ntohl(inpacket[92 / 4]);
      size_t   max_peers = ip6_isv4mapped(remoteip) ? OT_MAX_PEERS_UDP4 : OT_MAX_PEERS_UDP6;
      if (numwant > max_peers)
        numwant = max_peers;

      ws->reply      = ws->outbuf + 8;
      ws->reply_size = 8 + add_peer_to_torrent_and_return_peers(FLAG_UDP, ws, numwant);
    }

    socket_send6(serversocket, ws->outbuf, ws->reply_size, remoteip, remoteport, 0);
    stats_issue_event(EVENT_ANNOUNCE, FLAG_UDP, ws->reply_size);
    break;

  case 2:                    /* This is a scrape action */
    outpacket[0] = htonl(2); /* scrape action */
    outpacket[1] = inpacket[12 / 4];

    for (scrape_count = 0; (scrape_count * 20 < byte_count - 16) && (scrape_count <= 74); scrape_count++)
      return_udp_scrape_for_torrent(*(ot_hash *)(((char *)inpacket) + 16 + 20 * scrape_count), ((char *)outpacket) + 8 + 12 * scrape_count);

    socket_send6(serversocket, ws->outbuf, 8 + 12 * scrape_count, remoteip, remoteport, 0);
    stats_issue_event(EVENT_SCRAPE, FLAG_UDP, scrape_count);
    break;
  }
  return 1;
}

static void *udp_worker(void *args) {
  int64                sock = (int64)args;
  struct ot_workstruct ws;
  memset(&ws, 0, sizeof(ws));

  ws.inbuf  = malloc(G_INBUF_SIZE);
  ws.outbuf = malloc(G_OUTBUF_SIZE);
#ifdef _DEBUG_HTTPERROR
  ws.debugbuf = malloc(G_DEBUGBUF_SIZE);
#endif

  while (g_opentracker_running)
    handle_udp6(sock, &ws);

  free(ws.inbuf);
  free(ws.outbuf);
#ifdef _DEBUG_HTTPERROR
  free(ws.debugbuf);
#endif
  return NULL;
}

void udp_init(int64 sock, unsigned int worker_count) {
  pthread_t thread_id;
  if (!g_rijndael_round_key[0])
    udp_generate_rijndael_round_key();
#ifdef _DEBUG
  fprintf(stderr, " installing %d workers on udp socket %ld\n", worker_count, (unsigned long)sock);
#endif
  while (worker_count--)
    pthread_create(&thread_id, NULL, udp_worker, (void *)sock);
}

const char *g_version_udp_c = "$Source$: $Revision$\n";