Issue #304

If you do VoIP applications, especially with open sources like pjsip, you may encounter kamalio and rtpproxy to serve SIP requests. Due to limitation of NAT traversals, rtpproxy is needed to work around NAT. All SIP handshake requests go through a proxy server, but rtpproxy can also relay voice, video or any RTP stream of data. When I played with rtpproxy, it was before version 2.0 and I need to handle IP handover. This refers to the scenario when user switches between different network, for example from Wifi to 4G and they get new IP. Normally this means ending in the SIP call, but the expectation is that we can retry and continue the call if possible for users.

That’s why I forked rtpproxy and add IP handover support. You can check the GitHub repo at rtpproxy.

Use src_cnt to track the number of consecutive packets from different address. When this number exceeds THRESHOLD (10 for RTP and 2 for RTCP), I switch to this new address

This way

  • Client can ALWAYS change IP when he switches from 3G to Wifi, or from this Wifi hotspot to another

  • There’s no chance for attack, unless attacker sends > 10 (RTP THRESHOLD) packets in 20ms (supposed my client sends packets every 20ms)

This idea is borrowed from http://www.pjsip.org/pjmedia/docs/html/group__PJMEDIA__CONFIG.htm

There is a macro PJMEDIA_RTP_NAT_PROBATION_CNT. Basically, it is

“See if source address of RTP packet is different than the configured address, and switch RTP remote address to source packet address after several consecutive packets have been received.”

Mobile clients now change IP frequently, from these hotspots to those. So if rtpproxy can support this feature, it would be nicer.

Take a look at https://github.com/onmyway133/rtpproxy/blob/master/rtpp_session.h

// IP Handover Count how many consecutive different packets are received, 0 is for callee, 1 is for caller    unsigned int src_count[2];

And how it actions in https://github.com/onmyway133/rtpproxy/blob/master/main.c

static void
rxmit_packets(struct cfg *cf, struct rtpp_session *sp, int ridx,
  double dtime)
{
    int ndrain, i, port;
    struct rtp_packet *packet = NULL;

/* Repeat since we may have several packets queued on the same socket */
    for (ndrain = 0; ndrain < 5; ndrain++) {
 if (packet != NULL)
     rtp_packet_free(packet);

packet = rtp_recv(sp->fds[ridx]);
 if (packet == NULL)
     break;
 packet->laddr = sp->laddr[ridx];
 packet->rport = sp->ports[ridx];
 packet->rtime = dtime;

i = 0;
 // IP Handover do not need canupdate
 // Use src_count
 if (sp->addr[ridx] != NULL) {
     /* Check that the packet is authentic, drop if it isn't */
     if (sp->asymmetric[ridx] == 0) {
  /*
  if (memcmp(sp->addr[ridx], &packet->raddr, packet->rlen) != 0) {
      if (sp->canupdate[ridx] == 0) {
   //
   // Continue, since there could be good packets in
   // queue.
   //
   continue;
      }
                    
      // Signal that an address has to be updated
      rtpp_log_write(RTPP_LOG_ERR, cf->glog, "IP Handover Set i 1st ridx %d",ridx);
      i = 1;
  } else if (sp->canupdate[ridx] != 0 &&
    sp->last_update[ridx] != 0 &&
    dtime - sp->last_update[ridx] > UPDATE_WINDOW) 
  {
      sp->canupdate[ridx] = 0;
      rtpp_log_write(RTPP_LOG_ERR, cf->glog, "IP Handover Set canupdate to 0 1st ridx %d",ridx);
  }
  */
   
  if (memcmp(sp->addr[ridx], &packet->raddr, packet->rlen) == 0) { 
   sp->src_count[ridx] = 0;
  } 
  else {
   sp->src_count[ridx]++;
   // IP Handover RTCP packet sends at larger interval, so must use smaller THRESHOLD
   // Check to see if port is odd or even
   if(sp->ports[ridx] % 2 == 0) {
    if(sp->src_count[ridx] >= 10) {
     i = 1;
    } 
   }
   else {
    if(sp->src_count[ridx] >= 2) {
     i = 1;
    }
   }
   
  }

} else {
  /*
   * For asymmetric clients don't check
   * source port since it may be different.
   */
  rtpp_log_write(RTPP_LOG_ERR, cf->glog, "IP Handover We are in asymmetric ridx %d",ridx);
  if (!ishostseq(sp->addr[ridx], sstosa(&packet->raddr)))
      /*
       * Continue, since there could be good packets in
       * queue.
       */
      continue;
     }
     sp->pcount[ridx]++;
 } else {
     sp->pcount[ridx]++;
     sp->addr[ridx] = malloc(packet->rlen);
     if (sp->addr[ridx] == NULL) {
  sp->pcount[3]++;
  rtpp_log_write(RTPP_LOG_ERR, sp->log,
    "can't allocate memory for remote address - "
    "removing session");
  remove_session(cf, GET_RTP(sp));
  /* Break, sp is invalid now */
  break;
     }
     /* Signal that an address have to be updated. */
     rtpp_log_write(RTPP_LOG_ERR, cf->glog, "IP Handover Set i 2nd ridx %d",ridx); 
     i = 1;
 }

/*
  * Update recorded address if it's necessary. Set "untrusted address"
  * flag in the session state, so that possible future address updates
  * from that client won't get address changed immediately to some
  * bogus one.
  */
 if (i != 0) {
     sp->untrusted_addr[ridx] = 1;
     memcpy(sp->addr[ridx], &packet->raddr, packet->rlen);
 
     // IP Handover Do not use canupdate
     // After update, reset src_count
     /*
     if (sp->prev_addr[ridx] == NULL || memcmp(sp->prev_addr[ridx],
       &packet->raddr, packet->rlen) != 0) 
     {
         sp->canupdate[ridx] = 0;
  if(sp->prev_addr[ridx] == NULL)
  {
     rtpp_log_write(RTPP_LOG_ERR, cf->glog, "IP Handover prev_addr NULL ridx %d",ridx); 
  }
  rtpp_log_write(RTPP_LOG_ERR, cf->glog, "IP Handover Set canupdate to 0 2nd ridx %d",ridx);
     }
     */

sp->src_count[ridx] = 0;

port = ntohs(satosin(&packet->raddr)->sin_port);

rtpp_log_write(RTPP_LOG_INFO, sp->log,
       "%s's address filled in: %s:%d (%s)",
       (ridx == 0) ? "callee" : "caller",
       addr2char(sstosa(&packet->raddr)), port,
       (sp->rtp == NULL) ? "RTP" : "RTCP");

/*
      * Check if we have updated RTP while RTCP is still
      * empty or contains address that differs from one we
      * used when updating RTP. Try to guess RTCP if so,
      * should be handy for non-NAT'ed clients, and some
      * NATed as well.
      */
     if (sp->rtcp != NULL && (sp->rtcp->addr[ridx] == NULL ||
       !ishostseq(sp->rtcp->addr[ridx], sstosa(&packet->raddr)))) {
  if (sp->rtcp->addr[ridx] == NULL) {
      sp->rtcp->addr[ridx] = malloc(packet->rlen);
      if (sp->rtcp->addr[ridx] == NULL) {
   sp->pcount[3]++;
   rtpp_log_write(RTPP_LOG_ERR, sp->log,
     "can't allocate memory for remote address - "
     "removing session");
   remove_session(cf, sp);
   /* Break, sp is invalid now */
   break;
      }
  }
  memcpy(sp->rtcp->addr[ridx], &packet->raddr, packet->rlen);
  satosin(sp->rtcp->addr[ridx])->sin_port = htons(port + 1);
  /* Use guessed value as the only true one for asymmetric clients */
  sp->rtcp->canupdate[ridx] = NOT(sp->rtcp->asymmetric[ridx]);
  rtpp_log_write(RTPP_LOG_INFO, sp->log, "guessing RTCP port "
    "for %s to be %d",
    (ridx == 0) ? "callee" : "caller", port + 1);
     }
 }

if (sp->resizers[ridx].output_nsamples > 0)
     rtp_resizer_enqueue(&sp->resizers[ridx], &packet);
 if (packet != NULL)
     send_packet(cf, sp, ridx, packet);
    }

if (packet != NULL)
 rtp_packet_free(packet);
}

Here are some useful resources that I read