Listing 2. Hole-Punching Implementation

/* Copyright Girish Venkatachalam 2006
 * girish1729@gmail.com
 *
 * You are free to modify it to suit your requirements.
 * The licensing terms of this program is along the lines
 * of the BSD license. You are free to use this for commercial
 * purposes provide you retain this notice.
 *
 *
 *
 */
#include "p2pcommon.h"

struct sockaddr_in sa[4];
struct sockaddr_in myu[4];


void setreuse(int sock) {
        const int one = 1;

#ifdef SO_REUSEADDR
        if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
                                (const void*)&one, sizeof(one)) < 0)
                perrdie("setsockopt(SO_REUSEADDR)");
#endif
#ifdef SO_REUSEPORT
        if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT,
                                (const void*)&one, sizeof(one)) < 0)
                perrdie("setsockopt(SO_REUSEPORT)");
#endif
}

void setnonblock(int sock)
{
#ifdef WIN32
        unsigned long fl = 1;
        if (ioctlsocket(sock, FIONBIO, &fl) < 0)
                perrdie("socketioctl");
#else
        int fl = fcntl(sock, F_GETFL);
        if (fl < 0)
                perrdie("fcntl");
        if (fcntl(sock, F_SETFL, fl | O_NONBLOCK) < 0)
                perrdie("fcntl");
#endif
}
void setblock(int sock)
{
#ifdef WIN32
        unsigned long fl = 0;
        if (ioctlsocket(sock, FIOBIO, &fl) < 0)
                perrdie("socketioctl");
#else
        int fl = fcntl(sock, F_GETFL);
        if (fl < 0)
                perrdie("fcntl");
        if (fcntl(sock, F_SETFL, fl & ~O_NONBLOCK) < 0)
                perrdie("fcntl");
#endif
}


int recvudp(int udp,struct sockaddr_in *privpeer,struct sockaddr_in
*pubpeer,struct connection *conn)
{
        struct sockaddr_in tsin;
        socklen_t sinlen;
        struct reply rp;
        int rc;



        sinlen = sizeof(tsin);
        rc = recvfrom(udp,(char*)&rp, sizeof(rp), 0,
                        (struct sockaddr*)&tsin, &sinlen);
        if (rc < (signed)sizeof(rp)) {

                syslog(LOG_INFO,
                                "Received runt packet\n");
                return -1;
        }

        if (rp.magic != htonl(REPLMAGIC)) {

                syslog(LOG_INFO,
                                "Reply with bad magic value\n");
                return -1;
        }

        syslog(LOG_INFO, "Server reports my UDP address as %s:%d\n",
                        inet_ntoa(rp.pubaddr),
                        ntohs(rp.pubport));
        if(pubpeer) { /* hole punch packet */
                if(rp.pubaddr.s_addr == conn->ctx->mypriv.sin_addr.s_addr)
                        return 1;
                else if(rp.pubaddr.s_addr ==
conn->ctx->mypub.sin_addr.s_addr)
                        return 0;
                return -1;
        }
        else { /* UDP bounce packet */
                /* record the address reported by the server */
                privpeer->sin_family = AF_INET;
                privpeer->sin_addr = rp.pubaddr;
                privpeer->sin_port = rp.pubport;
        }

        return 0;
}

int holepunch(struct connection *conn) {
        struct msg m;
        int ret;

        if(conn->peerid == 0) /* We are just starting the p2p app */
                return holepunch_algo(conn,0,0,OUTBOUND);

        /* XXX What happens here actually is a bit confusing and
         *  I am not too happy with this design. Hopefully if time
         * permits I will change it one day...
         *
         * Once I get a holepunch request I send a request to the
         * mediation server to locate the private and public address
         * of the peer whose ID is given to me.
         * So all that happens here is send the request to mediation
         * server. Once the request is processed, two things can
         * happen.
         *
         * 1) The peer's address information is returned in a PEER_COORD
         *    message. Then this is recevied by the p2p code and the
         *    function holepunch_algo() is called with the appropriate
         *    arguments. Then once the holepunching finishes or fails,
         *   control is returned back to the user.
         * 2) The ID given is wrong and hence no matching peer could
         *    be found, so we have to return to the user saying that
         *    he has to try again with the right ID.
         *
         */
        m.op = GET_PEER;
        m_st.peerpl.id = conn->peerid;
        do {
        ret = send(conn->ctx->tcpsock,(char *)&m,sizeof(m),0);

        } while(eblock());

        if(ret == 0 || ret == -1) {
                perror("Send");
        }

        return 0;

}

/*            The UDP hole punching algorithm
 *
 * It is a very simple algorithm. How it works is, first it sends UDP
 * packets to five different hosts from the same port number to the same
 * port number. The last request is sent to the actual peer with which
 * we want to communicate.
 *
 * The peer's NAT drops the packet since it was an unsolicited packet.
 * At the same time the peer also sends these five packets. So this
 * packet arrives at our NAT device but our NAT lets it in since
 * we first sent out the packet to the same host and port...
 *
 * We are interested in finding out the port number
 * allocated to all these requests at the NAT device.
 *
 * In case we find that all these numbers match then we are happy and
 * happily return the UDP socket to the user.
 * The way we figure out the port number allocated at the NAT device is
 * by sending the NAT device's source port number in the body of the
 * response to the packets sent above.
 *
 * The real fun starts when there is a difference between the port
 * numbers allocated in the NAT device for the two requests.
 * In this case we find out the delta, the difference between these two
 * numbers and the algorithm assumes that the delta will be constant
 * under favourable circumstances.
 *
 * Now this is only half the story. Determining the source port is
 * important but it is not everything. In the next step, the peer sends
 * a packet to the same port number. Once the peers are able
 * to communicate using the  peer's port number and NAT IP, then we
 * hand over control pack to the p2p app.
 *
 * There is another twist to the algo. In case both peers are behind
 * the same NAT device and in the same network, then they can
 * communicate directly without UDP hole punching.
 * This is attempted first. Only when this approach doesn't succeed when
 * we go in for hole punching.
 *
 */


int holepunch_algo(struct connection *conn,struct sockaddr_in
*privpeer,struct sockaddr_in *pubpeer,int direction)
{
        struct sockaddr_in sinudp1,tmpsin;
        uint16_t tpport = 9856,ourport = 17000;
        struct request rq;
        struct timeval t;
        int udp1,udprecv;
        fd_set rfds,wfds;
        int ret,diff,diff2,consistent = 0,linkdown = 0;
        int i = 0,first,third,fds,selret;
#ifdef WIN32
        WSADATA wsaData;

        if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
                perrdie("Windows sockets 2.2 startup");
        {
                syslog(LOG_INFO, "Using %s (Status: %s)\n",
                                wsaData.szDescription,
wsaData.szSystemStatus);
                syslog(LOG_INFO, "with API versions %d.%d to %d.%d\n\n",
                                LOBYTE(wsaData.wVersion),
HIBYTE(wsaData.wVersion),
  LOBYTE(wsaData.wHighVersion), HIBYTE(wsaData.wHighVersion));
        }
#endif



        udp1 = mksock(SOCK_DGRAM);
        setreuse(udp1);


        /* Lookup the mediators */
        lookup(SERV1, &sa[0],tpport);
        lookup(SERV2, &sa[1],tpport);
        lookup(SERV3, &sa[2],ourport);
        lookup(SERV4, &sa[3],ourport);

        sinudp1.sin_family = AF_INET;
        sinudp1.sin_addr.s_addr = INADDR_ANY;
        sinudp1.sin_port = htons(tpport);

        if (bind(udp1, (struct sockaddr*)&sinudp1, sizeof(sinudp1)) < 0){
                perrdie("Bind 9856 UDP port");
                syslog(LOG_ERR,"Problem binding to UDP port 9856");
        }



        /* Clear the variables in which we collect
         * our various NATted public addresses
         * as we discover them. */
        tmpsin.sin_family = AF_INET;
        tmpsin.sin_addr.s_addr = INADDR_ANY;
        tmpsin.sin_port = 0;

        if((privpeer != NULL) && (pubpeer != NULL) && direction ==
OUTBOUND) {
                /* We are NOT connecting for the first time */
                udprecv = conn->ctx->clientsock;
        }
        else {
                udprecv = conn->ctx->udpsock;
        }

        for(i=0;i<4;i++)
                myu[i] = tmpsin;



        /* Send ping datagrams from our primary UDP socket
           to servers 1 and 2. */

        rq.magic = htonl(REQMAGIC);
        syslog(LOG_INFO,"Proceeding to punch hole");


        for(i=0;i<4;i++) {
                int sock;
                if(i == 0 || i == 1)
                        sock = udp1;
                else
                        sock = conn->ctx->udpsock;


                if (sendto(sock, (const char*)&rq, sizeof(rq), 0,
                                        (struct sockaddr*)&sa[i],
sizeof(sa[i])) < 0) {
                        syslog(LOG_ERR,"Problem sending to
%s:%d",inet_ntoa(sa[i].sin_addr),ntohs(sa[i].sin_port));
                        perrdie("sendto");
                }
        }


        first = 0, third = 0;
        for(;;) {
                fds = 0;
                FD_ZERO(&rfds);
                FD_ZERO(&wfds);
                if(first != 2) {
                        FD_SET(udp1,&rfds);
                        FD_SET(udp1,&wfds);
                        fds = udp1 + 1;
                }
                if(third != 2) {
                        FD_SET(udprecv,&rfds);
                        FD_SET(udprecv,&wfds);
                        fds = udprecv + 1 > fds ? udprecv + 1 : fds;
                }
                /* Right now we just assume that 4 seconds
                 * timeout is enough for most practical purposes
                 */
                t.tv_sec = 6;
                t.tv_usec = 0;

                if(fds == 0) /* We are done */
                        break;

                ret = select(fds,&rfds,NULL,NULL,&t);

                if(ret == -1) {
                        perror("Select");
                        syslog(LOG_ERR,"Select() failed,Yuck!");
                }
                if(ret == 0) {
                        linkdown = 1;
                        syslog(LOG_ERR,"Didn't get reply from UDP bounce
server(s)");
                        break;
                }
                else {
                        if(FD_ISSET(udp1,&rfds)) {
                                if(first == 0) {
                                        recvudp(udp1,&myu[0],NULL,conn);
                                        first = 1;
                                }
                                else {
                                        recvudp(udp1,&myu[1],NULL,conn);
                                        first = 2;
                                        close(udp1);
                                }

                        }
                        if(FD_ISSET(udprecv,&rfds)) {
                                if(third == 0) {
                                        recvudp(udprecv,&myu[2],NULL,conn);
                                        third = 1;
                                }
                                else {
                                        recvudp(udprecv,&myu[3],NULL,conn);
                                        third = 2;
                                }
                        }
                }
        }
        close(udp1);
        /* We are unable to contact any mediation servers possibly
         * because Internet is down. But still we run the hole
         * punching code to reach  the peer. And then
         * declare the hole punching failed
         */
        if(linkdown) {
                close(conn->ctx->udpsock);
                return LINK_DOWN;



        }
        /* Check for public address/port number consistency */
        if (myu[0].sin_addr.s_addr == myu[1].sin_addr.s_addr &&
                        myu[0].sin_port == myu[1].sin_port) {
                if (myu[2].sin_addr.s_addr == myu[3].sin_addr.s_addr &&
                                myu[3].sin_port == myu[3].sin_port) {

                        if((myu[0].sin_port != 0)
                                        || (myu[3].sin_port != 0)) {
                                consistent = 1;
                                syslog(LOG_INFO,"We do consistent UDP
translation, that is good news");
                        }
                }

        } else {
                diff = abs(ntohs(myu[1].sin_port) -
ntohs(myu[0].sin_port));
                diff2 = abs(ntohs(myu[3].sin_port) -
ntohs(myu[2].sin_port));
        }

        if((privpeer != NULL) && (pubpeer != NULL)) {

                /* Now send packets to both the private and the public
 * addreses of the peer*/
                ret = sendto(conn->ctx->udpsock,
                                (const char *)&rq,
                                sizeof(rq), 0,
                                (struct sockaddr *)privpeer,
                                sizeof(*privpeer));
                if(ret == -1) {

                        perrdie("Hole punch to private address sendto");
                }

                ret = sendto(conn->ctx->udpsock,
                                sizeof(rq), 0,
                                (struct sockaddr *)pubpeer,
                                sizeof(*pubpeer));
                if(ret == -1) {
                        perrdie("Hole punch to public address sendto");
                }
                /* Right now we just assume that 4 seconds
                 * timeout is enough for most practical purposes
                 */
                do {
                        t.tv_sec = 4;
                        t.tv_usec = 0;


                        FD_ZERO(&rfds);
                        FD_SET(udprecv,&rfds);
                        fds = udprecv + 1;

                        selret = select(fds,&rfds,NULL,NULL,&t);

                        if(selret == -1) {
                                perror("Select");
                                syslog(LOG_ERR,"Select() failed,Yuck!");
                        }
                        if(selret == 0) {
                                syslog(LOG_ERR,"Didn't get reply from
holepunching the peer");
                                ret = -1;
                                break;
                        }

                        ret = recvudp(udprecv,privpeer,pubpeer,conn);
                }while(0);
        }
        else { /* we are connecting for the first time */
                syslog(LOG_INFO,"Proceeding to return after first time
invocation of holepunching");
                conn->ctx->mypub = myu[3];
                if(!consistent && (diff == diff2)) {
                        uint16_t tmp;
                        tmp = ntohs(myu[3].sin_port);
                        tmp += 2*diff;
                        conn->ctx->mypub.sin_port = htons(tmp);

                }
                return 0;
        }

        if(ret == 0) { /* The public address is hole punched */
                struct msg m;
                int n;
                syslog(LOG_INFO,"The peer's public address is responding");
                conn->peer = *pubpeer;
                conn->ctx->mypub = myu[3];
                m.op = HOLEPUNCHING_COMPLETE;
                m_st.peerpl.id = conn->peerid;
                do {
                        n = send(conn->ctx->tcpsock,&m,sizeof(m),0);
                }while(eblock());
        } else if (ret == 1) { /* The private address is hole punched */
                struct msg m;
                int n;
                syslog(LOG_INFO,"The peer's private address is
responding");
                conn->peer = *privpeer;
                conn->ctx->mypub = myu[3];
                m.op = HOLEPUNCHING_COMPLETE;
                m_st.peerpl.id = conn->peerid;
                do {
                        n = send(conn->ctx->tcpsock,&m,sizeof(m),0);
                }while(eblock());


        } else if(ret == -1) { /* Good, no response :-) */
                if(consistent) { /* no problem, we only have to ask
                                  * the peer to send a datagram and
                                  * then we are set */
                        syslog(LOG_INFO,"No response but consistent
translation");
                        conn->ctx->mypub = myu[3];
                        conn->peer = *pubpeer;
                        peer_holepunch(conn);
                }
                else {
                        if(diff == diff2) {
                                syslog(LOG_INFO,"I get  the same delta
during translation, this may WORK");
                                uint16_t tmp;
                                conn->peer = *pubpeer;
                                conn->ctx->mypub = myu[3];
                                tmp = ntohs(myu[3].sin_port);
                                tmp += 2*diff;
                                conn->ctx->mypub.sin_port = htons(tmp);
                                peer_holepunch(conn);

                        }
                        else { /* Hard luck, we have to give up :-( */
                                close(conn->ctx->udpsock);

                        }
                }

        }

        return 0;

}

