Module: OverSIP::Stun

Defined in:
ext/stun/stun_ruby.c

Class Method Summary collapse

Class Method Details

.parse_request(rb_stun_request, rb_source_ip, rb_source_port) ⇒ Object

Expects 3 arguments:

  • String containing a STUN Binding Request (MUST not be empty!!).

  • String containing the source IP of the request.

  • Fxinum containing the source port of the request.

Return value:

  • If it’s a valid STUN Binding Request, returns a Ruby String representing the STUN Binding Response.

  • If it seems a valid STUN message but not a valid STUN Binding Request, returns false.

  • Otherwise returns nil (so it could be a SIP message).



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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# File 'ext/stun/stun_ruby.c', line 54

VALUE Stun_parse_request(VALUE self, VALUE rb_stun_request, VALUE rb_source_ip, VALUE rb_source_port)
{
  TRACE();

  char *request = NULL;
  size_t request_len = 0;
  char *source_ip = NULL;
  short source_ip_is_ipv6 = 0;
  uint16_t source_port;

  char *transaction_id;
  uint16_t message_length;
  char *magic_cookie;
  short is_rfc3489_client = 0;

  struct in_addr in_addr_ipv4;
  struct in6_addr in_addr_ipv6;
  uint16_t xor_port;
  uint32_t xor_ipv4;
  unsigned char xor_ipv6[16];

  /* The size of our STUN Binding Response is the sum of:
   * - STUN message header: 20 bytes.
   * - One attribute (XOR-MAPPED-ADDRESS or MAPPED-ADDRESS).
   *    - Type + Length: 4 bytes.
   *    - XOR-MAPPED-ADDRESS or MAPPED-ADDRESS for IPv4: 4 + 4 = 8 bytes.
   *    - XOR-MAPPED-ADDRESS or MAPPED-ADDRESS for IPv6: 4 + 16 = 20 bytes.
   * - Size for a response with IPv4: 20 + 4 + 8 = 32 bytes.
   * - Size for a response with IPv6: 20 + 4 + 20 = 44 bytes.
   */
  char response[STUN_BINDING_SUCCESS_RESPONSE_IPV6_SIZE];

  if (TYPE(rb_stun_request) != T_STRING)
    rb_raise(rb_eTypeError, "First argument must be a String containing the STUN Binding Request");

  request = RSTRING_PTR(rb_stun_request);

  /* First octet of any STUN *request* must be 0. Return false otherwise. */
  if (request[0]) {
    LOG("first octet is not 0, so it's not a STUN request\n");
    return Qnil;
  }

  /* Any STUN message must contain, at least, 20 bytes. Return false otherwise. */
  if ((request_len = RSTRING_LEN(rb_stun_request)) < STUN_MESSAGE_MIN_SIZE) {
    LOG("ERROR: request length less than 20 bytes, invalid STUN message\n");
    return Qfalse;
  }

  if (TYPE(rb_source_ip) != T_STRING)
    rb_raise(rb_eTypeError, "Third argument must be a String containing the source IP");

  if (TYPE(rb_source_port) != T_FIXNUM)
    rb_raise(rb_eTypeError, "Fourth argument must be a Fixnum containing the source port");

  /*
   * RFC 5389 section 6.
   *
   *   a Binding request has class=0b00 (request) and method=0b000000000001 (Binding)
   *   and is encoded into the first 16 bits as 0x0001.
   *
   * So let's check the second byte which must be 0x1.
   */
  if ( request[1] != 0x1 ) {
    LOG("ERROR: not a valid STUN Binding Request, maybe an STUN Indication (so ignore it)\n");
    return Qfalse;
  }

  /*
   * RFC 5389 section 6.
   *
   *   The magic cookie field MUST contain the fixed value 0x2112A442 in network byte order.
   *
   * 0x21 = 33, 0x12 = 18, 0xA4 = -92, 0x42=66.
   */
  if (! (request[4] == 33 && request[5] == 18 && request[6] == -92 && request[7] == 66) ) {
    LOG("WARN: STUN magic cookie does not match, using backward compatibility with RFC 3489\n");

    /*
     * RFC 5389 section 12.2.
     *
     *  A STUN server can detect when a given Binding request message was
     *  sent from an RFC 3489 [RFC3489] client by the absence of the correct
     *  value in the magic cookie field.  When the server detects an RFC 3489
     *  client, it SHOULD copy the value seen in the magic cookie field in
     *  the Binding request to the magic cookie field in the Binding response
     *  message, and insert a MAPPED-ADDRESS attribute instead of an
     *  XOR-MAPPED-ADDRESS attribute.
     *
     */
    is_rfc3489_client = 1;
  }

  /* Get the Magic Cookie. */
  magic_cookie = ((char *)request)+4;

  /* Get the Transaction ID. */
  transaction_id = ((char *)request)+8;

  /*
   * RFC 5389 section 6.
   *   "The message length MUST contain the size, in bytes, of the message
   *    not including the 20-byte STUN header.  Since all STUN attributes are
   *    padded to a multiple of 4 bytes, the last 2 bits of this field are
   *    always zero.  This provides another way to distinguish STUN packets
   *    from packets of other protocols."
   *
   */
  message_length = ntohs(*(uint16_t *)(request+2));



  /*
   * Create the STUN Binding Response.
   */

  /* A Binding response has class=0b10 (success response) and method=*0b000000000001,
   * and is encoded into the first 16 bits as 0x0101. */
  response[0] = 1;
  response[1] = 1;

  /* Add the received Magic Cookie (for RFC 3489 backward compatibility). */
  memcpy(response+4, magic_cookie, STUN_MAGIC_COOKIE_LEN);

  /* Add the received Transaction Id. */
  memcpy(response+8, transaction_id, STUN_TRANSACTION_ID_LEN);

  /*
   * Add an attribute XOR-MAPPED-ADDRESS (or MAPPED-ADDRESS if it's a RFC 3489 client).
   */

  /*
   * STUN Attribute.
   * 
   *  0                   1                   2                   3
   *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   *  |         Type                  |            Length             |
   *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   *  |                         Value (variable)                ....
   *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   *
   * 
   * XOR-MAPPED-ADDRESS.
   *
   *  0                   1                   2                   3
   *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   *  |x x x x x x x x|    Family     |         X-Port                |
   *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   *  |                X-Address (Variable)
   *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   *
   * 
   * MAPPED-ADDRESS.
   * 
   *  0                   1                   2                   3
   *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   *  |0 0 0 0 0 0 0 0|    Family     |           Port                |
   *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   *  |                                                               |
   *  |                 Address (32 bits or 128 bits)                 |
   *  |                                                               |
   *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * 
   */

  source_ip = StringValueCStr(rb_source_ip);

  /* Check the IP and its type (IPv4 or IPv6). */
  switch(inet_pton(AF_INET, source_ip, &in_addr_ipv4)) {
    /* It's valid IPv4. */
    case 1:
      break;
    /* Not valid INET family, hummmm. */
    case -1:
      LOG("ERROR: Family AF_INET (IPv4) not supported\n");
      return Qfalse;
      break;
    /* Let's check with IPv6. */
    case 0:
      switch(inet_pton(AF_INET6, source_ip, &in_addr_ipv6)) {
        /* It's valid IPv6. */
        case 1:
          source_ip_is_ipv6 = 1;
          break;
          /* Not valid INET family, hummmm. */
        case -1:
          LOG("ERROR: Family AF_INET6 (IPv6) not supported\n");
          return Qfalse;
          break;
        /* The string is neither an IPv4 or IPv6. */
        case 0:
          LOG("ERROR: Unknown Address Family\n");
          return Qfalse;
          break;
      }
  }

  /* Get the port in an integer (two bytes) */
  source_port = (uint16_t)(FIX2INT(rb_source_port));

  /* It's a RFC 5389 compliant client, so add XOR-MAPPED-ADDRESS. */
  if (! is_rfc3489_client) {
    /* STUN attribute type: 0x0020: XOR-MAPPED-ADDRESS */
    response[20] = 0x00;
    response[21] = 0x20;

    /*
     *  XOR-MAPPED-ADDRESS fields.
     */

    /* First byte must be 0x00. */
    response[24] = 0x00;

    /* Second byte is the IP Family (0x01:IPv4, 0x02:IPv6). */
    if (source_ip_is_ipv6)
      response[25] = 0x02;
    else
      response[25] = 0x01;

    /* Bytes 3 and 4 are the X-Port. X-Port is computed by taking the mapped port in
     * host byte order, XOR'ing it with the most significant 16 bits of the magic cookie,
     * and then the converting the result to network byte order. */
    xor_port = htons(source_port ^ *(uint16_t *)(magic_cookie));

    memcpy(response+26, &xor_port, 2);

    /* Next bytes are the IP in network byte order with XOR stuff. */

    /* IPv4. */
    if (! source_ip_is_ipv6) {
      /* If the IP address family is IPv4, X-Address is computed by taking the mapped IP
       * address in host byte order, XOR'ing it with the magic cookie, and converting the
       * result to network byte order. */
      xor_ipv4 = htons((uint32_t)(in_addr_ipv4.s_addr) ^ *(uint32_t *)(magic_cookie));

      memcpy(response+28, &xor_ipv4, 4);
      /* So set the attribute Length to 8. */
      response[22] = 0;
      response[23] = 8;
      /* So set the STUN Response Message Length to 12 bytes. */
      response[2] = 0;
      response[3] = 12;

      /* Return the Ruby string containing the response. */
      return rb_str_new(response, STUN_BINDING_SUCCESS_RESPONSE_IPV4_SIZE);
    }
    /* IPv6. */
    else {
      /* If the IP address family is IPv6, X-Address is computed by taking the
       * mapped IP address in host byte order, XOR'ing it with the concatenation
       * of the magic cookie and the 96-bit transaction ID, and converting the result
       * to network byte order. */
      /* NOTE: struct in_addr.s6_addr is an array of 16 char in network byte order (big-endian). */
      /* TODO: So do I need to convert in_addr_ipv6.s6_addr to host byte order and later the
       * whole result to network byte order? */
      int i;
      for(i=0; i<16; i++)
        xor_ipv6[i] = in_addr_ipv6.s6_addr[i] ^ magic_cookie[i];

      memcpy(response+28, xor_ipv6, 16);

      /* So set the attribute Length to 20. */
      response[22] = 0;
      response[23] = 20;
      /* So set the STUN Response Message Length to 24 bytes. */
      response[2] = 0;
      response[3] = 24;

      /* Return the Ruby string containing the response. */
      return rb_str_new(response, STUN_BINDING_SUCCESS_RESPONSE_IPV6_SIZE);
    } 
  }

  /* It's a RFC 3489 compliant client, so add MAPPED-ADDRESS. */
  else {
    /* STUN attribute type: 0x0001: MAPPED-ADDRESS */
    response[20] = 0x00;
    response[21] = 0x01;

    /*
     *  MAPPED-ADDRESS fields.
     */

    /* First byte must be 0x00. */
    response[24] = 0x00;

    /* Second byte is the IP Family (0x01:IPv4, 0x02:IPv6). */
    if (source_ip_is_ipv6)
      response[25] = 0x02;
    else
      response[25] = 0x01;

    /* Bytes 3 and 4 are the Port in network byte order. */
    source_port = htons(source_port);
    memcpy(response+26, &source_port, 2);

    /* Next bytes are the IP in network byte order. */

    /* IPv4. */
    if (! source_ip_is_ipv6) {
      memcpy(response+28, &in_addr_ipv4.s_addr, 4);
      /* So set the attribute Length to 8. */
      response[22] = 0;
      response[23] = 8;
      /* So set the STUN Response Message Length to 12 bytes. */
      response[2] = 0;
      response[3] = 12;

      /* Return the Ruby string containing the response. */
      return rb_str_new(response, STUN_BINDING_SUCCESS_RESPONSE_IPV4_SIZE);
    }
    /* IPv6. */
    else {
      memcpy(response+28, &in_addr_ipv6.s6_addr, 16);
      /* So set the attribute Length to 20. */
      response[22] = 0;
      response[23] = 20;
      /* So set the STUN Response Message Length to 24 bytes. */
      response[2] = 0;
      response[3] = 24;

      /* Return the Ruby string containing the response. */
      return rb_str_new(response, STUN_BINDING_SUCCESS_RESPONSE_IPV6_SIZE);
    }
  }

}