CSC 175 Programming Assignment 1 SAPMP: Super Awesome Port Mapping Protocol For your first programming assignment you must implement a tcp client that would work with my server. The java/c source code of my server has been provided for you (so you can test your client by running the server yourself). You can write your client program in either C (pure C) or in Java. But don't just copy and paste from the server program without thinking if it really makes sense to have in your client program (which will be somewhat simpler than the server). First, be sure to install java and/or C on your server: apt-get install default-jdk gcc The purpose of the assignment is to design our own protocol that works similarly in principle to the NAT-PMP protocol, which allows a client behind a NAT box to dynamically request the forwarding (DNAT) of external ports. The SAPMP server is running on 10.1.0.3. But you can also download it and run it on your localhost. The SAPMP server must be run as root (unless you comment out the iptables command). The SAPMP server listens on port 6391. This is the fixed SAPMP server port. The SAPMP server expects a connecting client to send it the following four pieces of information, all in binary form: A. 4 bytes representing the internal (client) host's ip address B. 2 bytes representing the interal host's port where packets need to be mapped to. C. 2 bytes representing the requested port on the NAT box that should be mapped to the internal port. D. 1 byte representing the protocol (6=tcp, 17=udp). Only 6 and 17 are allowed. For example, if A = 10.1.0.5, B = 80, C = 8000, D = 6, then the server, which is running on a NAT box, will execute the command iptables -t nat -A PREROUTING -p 6 --dport 8000 -j DNAT --to 10.1.0.5:80 If C (the desired external port) is unavailable, the server will pick some unused port instead. Additional restrictions: C cannot be less than 1024 (no reserved port on the NAT box can be hijacked). Address A must be in 10.0.0.0/8, so the DNAT will only redirect a packet inwards - into the internal network and not to who-knows where (but this is also preventable by iptables -I FORWARD 1 -i eth0 -o eth0 -j DROP). Also, since "-A" is used in the iptables rule, any previous rule that mapped the same external port will NOT be overridden. The SAPMP server will send back the following 2 pieces of information: E. 2 bytes representing the external port that was successfully mapped (this could be different from the requested port if it's unavailable). This number will be 0 if the mapping request was denied. F. 4 bytes representing the external ip address of the NAT box (for example, 96.57.41.74), if the mapping succeeded. If the mapping failed, F will contain error messages (see below) The idea is that the internal host can then tell everyone else how to open a connection to it from outside of the NATed network. If the port mapping failed (because of invalid input) then E will be 0 and F should be interpreted as 4 separate 1-byte error codes, which could be the following 0: not used (this byte does not contain an error code) 1: read timed out (client didn't send enough info) 2: requested internal ip is not in 10.0.0.0/8 3: requested external port is not in range 1024-65535 4: requested protocol is not 6 (tcp) or 17 (udp) 5: redundant request: mapping already exists 6: client has made too many requests: defense against inadvertent DOS attacks, or from unimaginative hackers. UP to four of these errors will be reported, though your code may contain more than that. YOUR PROGRAM MUST PRINT THESE CODES: byte[] F = ... for(int i=0;i<4;i++) if (F[i]>0) System.out.println("error code: "+F[i]); ===============Random hints:================ Study the main server while-loop of the server program to see what kind of communications with your client are expected. The code for your client will be much simpler than the server. The program is a bit difficult because we are doing binary instead of text communication. Using strings would not only be inefficient but open up a lot of vulnerabilities. You therefore need to use java's commands for reading/writing with DataInputStream and DataOutputStream very carefully. For example, given DataInputStream din, int p = din.readUnsignedShort() reads in 2 bytes and convert it to a signed 4-byte integer (because ports are positive, so a unsigned 16 bit number need to be stored in an int). To write an integer p to DataOutputStream dout as a short, you need to type-cast it first: dout.writeShort((short)p); When you type cast an integer such as 60000 into a short, the short will become a negative number. Don't worry about it: no bits were altered, and you'll get the same positive number back if on the other side you read the two bytes using .readUnsignedShort(). Given byte[] buffer= new byte[4]; din.read(buffer,0,4) //will read up to 4 bytes into buffer starting at index 0. *and* return the number of bytes actually read If you use Java you don't have to worry about byte-ordering conversion. If you use C, you'll also have to make sure that all multi-byte numerical values are converted to the network byte order form before it's sent over a socket, because that's what the server expects. See sample programs on homepage for the C versions of tcp client and server programs. ***Your client should print the information received from the server so we can see that it's working. One tricky part of the assignment (in java) is to convert a string representation of an ip address, like "10.1.0.5" into a 4-byte array (byte[]), and vice versa. I've written these functions for you (Java might also have these built-in, check the InetAddress class on the API docs). // function to convert string format IP into 4-byte array public static byte[] ipbytes(String addr) { byte[] B = new byte[4]; // array to be constructed String[] S = addr.split("\\p{Punct}",4); // splits string into 4 parts //System.out.printf("after split: %s:%s:%s:%s\n",S[0],S[1],S[2],S[3]); for(int i=0;i<4;i++) B[i] = (byte)Integer.parseInt(S[i]); return B; } //ipbytes //function to return string-format IP address given byte array: public static String ipstring(byte[] B) { String addr = ""; // string to be returned for(int i=0;i<4;i++) { int x = B[i]; if (x<0) x = x+256; // signed to unsigned conversion addr = addr + ""+ x; if (i<3) addr = addr+"."; } return addr; }// ipstring You can put these functions into your java class. In C, if you #include, then you can just call inet_addr, which converts string ips to 4-byte binaries, and inet_ntoa, which does it in reverse.