CSC 175 Optional Assignment: Datagram Firewall Negotiation Protocol. This assignment will ask you to write a "Datagram Firewall Negotiator". You will be writing a multi-threaded TCP server program. The purpose of the program is to serve as a third party negotiator for two parties to connect directly using UDP by piercing through existing firewalls. Most routers/firewall/nat-boxes for end users will have rules equivalent to: iptables -I INPUT 1 -p udp -m state --state ESTABLSIHED -j ACCEPT iptables -I INPUT 2 -p udp -m state --state NEW -j DROP This will allow hosts to send out upd requests to, for example, DNS resolvers and receive their replies. But it will not allow other parties to initiate UDP "connections" to you. When the Linux connection tracking engine detects the outgoing packet from srcip:srcport to destip:destport, it assumes that this packet is an active connection from client to server, and destinates the "connection" as in "NEW" state. When it receives a response packet from destip:destport to srcip:srcport, the connection is considered "ESTABLISHED". It will remain established as long as there's further communication between the two ports with 180 seconds. If your router is a NAT box, the SNAT rule from the NAT box will have a similar effect, even without any DROP rules: once the outgoing packet from the SNATed address and port has been sent, the NAT box will be able to redirect replies to the original sender. But without the outgoing packet, nothing can be redirected inward without an explicit DNAT. However, when both parties are behind NAT boxes or firewalls, connections between UPD programs becomes difficult. Each party will have to know the other's ip address and port associated with their datagram socket as well as the EXACT timing of when they want to communicate. At the scheduled time, one side will have to send an "enticement" packet to the other's ip address and port (more precisely the other's NATed ip and port): Aip:Aport ---> Bip:Bport The contents of the enticement packet is not important. In fact, most likely the packet will never reach its destination. It's purpose is to ENTICE A's local firewall to anticipate the response packet from B within a few seconds (typically less than 2 minutes, which is the max RTT of the internet). B can't see this packet, but if B knows A's ip and port that the packet is supposed to be sent from, as well as the timing of this packet, it will send a "response" to A: Bip:Bport ---> Aip:Aport -->> ESTABLISHED This time, the packet will go through A's firewall, which is expecting it! This packet will also entice B's firewall to accept further packets from A. Both firewalls have been "pierced" (or "negotiated", "traversed", etc). It does not matter if Aip:Aport and/or Bip:Bport is the real ip and port of A or B's datagram socket, or the NATed ip and port of the NAT boxes that they're likely behind. At this point, A and B can exchange further packets and keep the "connection" alive. However, it's unlikely that B will know when A wants to communicate even if it knows Aip and Aport, which is also unlikely (you don't know who's going to call you and when). So for technologies like VOIP to work, there needs to be a THIRD PARTY NEGOTIATOR. However, unlike a proxy relay, the neotiator will not relay data payload. Its only purpose is to facilitate the establishment of "connections" between parties running UDP applications behind typical NAT boxes and firewalls. Once the "connection" is established, both parties will exchange data directly using their UPD sockets without further help from the negotiator. The negotiator, which is the program you will write, will communicate with clients using TCP, which can be kept alive for much longer than UPD connections. Each client first "registers" itself with the negotiator when it connects. The registration information sent through the TCP socket should consists of: 1. The IP address of A's Nat box (the real ip address that A uses on the internet). This can be derived directly from the connected socket, and doesn't have to be send explicitly by A). 2. The port number that A's UDP SOCKET is bound to (not this tcp socket). This port number should be the same on the A's NAT box. The TCP socket for this communication MUST BE KEPT OPEN: both sides can set the SO_KEEPALIVE TCP option. (in a more elaborate setting, A can also request authentication information but we'll keep the protocol to a minimum as it's an academic exercise). The negotiator must record this information in a local data structure, such as a hash table, which maps ip addresses to (udp) ports. It also needs to spawn a new thread to listen to further requests from A. The negotiator behave asynchronously as it must be able to receive registrations and requests from multiple clients. When A wishes to connect to B, both of which are registered (TCP-connected to the negotiator), it will send the negotiator the following information on its persistent TCP socket: 1. B's ip address (4 bytes) The negotiator responds with (after consulting it's local database) 1. B's upd port number: 2 bytes If B has not registered with the negotiator, the port number will be 0, which A must interpret to mean that the request failed. If A's request is valid, the negotiator will now send to B, on its persistent TCP socket with B: 1. A's ip address (4 bytes) 2. A's UDP prot (s bytes) === At this point, the negotiator's job is over, but the TCP sockets to A and B are kept open === A and B now knows each other's ip and port, and B knows that A now wants to communicate. A will sent its enticement packet to B, and B will wait a few seconds, and send the reply to A. But that's not something you will have to worry about when implementing the negotiator: your job is done until you receive a new request for commmunication from somebody. *** More Detailed Description of the Negotiator Program, with Java hints: *** 1. Use your SAPMP client program to connect to deepdish (10.1.0.98) and request port 290xy to be forwarded to your TCP port 290xy if you are 10.1.0.xy. For example, if you are 10.1.0.6 you should request deepdish to forward TCP port 29006 to 10.1.0.6:29006. A special version of the SAPMP server has been activated for this assignment: it's listening on deepdish at the designated SAPMP port (6391). If successful your server will be globally reachable at 96.57.41.74:290xy. Outline of the DFN Negotiator (server) program: 2. Bind a SeverSocket to port 290xy 3. Create a simple class to represent information for each registered client: class clientinfo { Socket csocket; // persistent TCP comm socket to client DataInputStream din; // comm streams for csocket DataOutputStream dout; String clientip; // client ip (also can be extracted from csocket) int dgport; // client's UDP port (not the same as csocket.getPort()!!!) Thread clientthread; // pointer to clients' thread object. // alternatively, you can just make clientinfo itself a Thread object // (class clientinfo extends Thread). // ... add other information if you think it would be useful. } 4. create a java.util.Hashtable to map ip addresses (in string form) to clientinfo objects. A "Hashtable" is a thread-safe version of a "Hashmap". Given such a table, table.get("24.78.112.14") will return the object associated with it or null if there's no entry; table.put("147.4.180.112",new clientinfo(..)) will add or replace an entry. Multiple threads calling these operations will not interfere with eachother. 5. Once you receive a client connection with (something like) Socket csocket = dfnserversocket.accept(); Do csocket.setKeepAlive(true); // to enable SO_KEEPALIVE option Do String clientip = csocket.getInetAddress().getHostAddress(); To extract the connecting client's ip address AS A STRING. You can use the string form to keep track of the client locally, but all comm with the clients must be in binary form. 6. Create DataInput/OutputStreams and do a int cport = .readUnsignedShort() to read the client'd datagram socket port. Create a clientinfo object and insert it into the hash table. This means you also have to create a new Thread object and .start() it. 7. Create a class clientthread extends Thread with a public void run() method. This method should: while (true) // or some other more refined condition { try { // Listen for active request for communication with peer, // which will be in the form of a 4 byte ip address (din.read(...)). // Convert the binary ip to String form (Netutils.ipstring) // Lookup the clientinfo entry for this ip in the Hashtable. // Send back to client (A) 2-byte UDP port (.dgport) of the peer (B). // If Hashtable lookup returns null, send back 0 and -1 to client. // Otherwise, (using socket information from the clientinfo for peer) // send to peer (B): // 4-byte ip address of A (use Netutils.ipbytes to convert) // 2-byte udp port of A } catch (Exception e) {e.printStackTrace();} } The try-catch inside the while loop will prevent the sever from crashing: but to be robust you should be more refined with your exception handling. 8. Test you program by downloading the professor's client-side program, DFNclient.java, which will use your sever open a udp connection for a simple text-based chat program. Compile and run it as follows. To run a passive client: java DFNclient passive 96.57.41.74 290xy To run an active client java DFNclient active (peer-ip-address) 96.57.41.74 290xy Unfortunately, since the negotiator only identifies a host by its reachable ip address, you can't run an active and a passive client from the same host. Besides, NAT boxes might get confused between your local ip address and the NATed address if you do. So find someone else to do the test on. Both machines should be OUTSIDE our 10.1 internal network (just use your computer from home). To see what real Ip address you have, (Internet "Global Unicast Address"), log onto your 10.1 server and type the command 'who'. The professor's IP address from home is 69.113.97.121. ===== more stuff you can use to make your program better ===== Here's a class you can use to synchronize threads. Each thread must have a pointer to the same mutex object. Then to protect a "critical section": sharedmutex.lock(); // do important stuff that needs to be thread-safe sharedmutex.unlock(); class mutex { boolean m = true; public synchronized void lock() throws InterruptedException { if (!m) wait(); m = false; } public synchronized void unlock() { m = true; notify(); } }// simple synchronization mechanism, emulates pthread_mutex in C Here's a class that I use to limit the number of active client threads: it's basically a "semaphore". You will have to figure out how and where to use it: class threadbroker // access to threadcount must synchronize { int threadcount = 0; static int maxcount = 16; synchronized void inc() throws InterruptedException { while (threadcount>=maxcount) { System.out.println("max threads exceeded"); wait(); } threadcount++; }//inc synchronized void dec() // will throttle against DOS attacks { if (threadcount>0) { threadcount--; notify(); try {Thread.sleep(500);} catch(Exception e) {} } } }//threadbroker