package ejip123; import ejip123.util.Dbg; /** An instance of an TCP connection. Contains the TCP state machine and all other vital parts of a TCP conneciton. see RFC 793. */ public class TcpConnection{ // TCP connection states private final static int FREE = -1; private final static int CLOSED = 0; private final static int LISTEN = 1; private final static int SYN_RCVD = 2; private final static int SYN_SENT = 3; private final static int ESTABLISHED = 4; //private final static int CLOSE_WAIT = 5; private final static int LAST_ACK = 6; private final static int FIN_WAIT_1 = 7; private final static int FIN_WAIT_2 = 8; private final static int CLOSING = 9; private final static int TIME_WAIT = 10; // TCP flags TODO public because of the old html server //static final int FL_URG = 0x20; public static final int FL_ACK = 0x10; public static final int FL_PSH = 0x8; public static final int FL_RST = 0x4; public static final int FL_SYN = 0x2; public static final int FL_FIN = 0x1; private int state = FREE; private int localIp = 0; private int remoteIp = 0; private int localPort = 0; private int remotePort = 0; private TcpHandler th = null; /** The next expected sequence number. */ private int rcvNxt = 0; /** The last sent sequence number, offset of first byte in data stream. */ private int seqNum = 0; /** The outstanding packet. We only allow one packet on the fly per connection. */ private Packet outstanding = null; private int timeout = 0; private int closing = 0; /** round trip time. */ private int rtt = 500; private int sndMss = 536; private int rcvMss = 536; TcpConnection(){ } void loop(int cur){ synchronized(this){ if(state == FREE) return; /* if(state != ESTABLISHED){ Dbg.wr("\nCon "); Dbg.wr(this.toString()); Dbg.wr(" loop() state="); Dbg.intVal(state); } */ if(state == CLOSED){ th.closed(this); free(); } if(closing == 1 && outstanding == null){ Packet p = PacketPool.getFreshPacket(); if(p != null){ if(send(p, FL_FIN|FL_ACK, true, 0)){ //Dbg.wr(" LOOP: sent FIN"); state = FIN_WAIT_1; closing = 2; } } } if(outstanding != null){ if(timeout - cur < 0){ // Dbg.wr("\nCon "); // Dbg.wr(this.toString()); // Dbg.wr(" loop() state="); // Dbg.intVal(state); // Dbg.wr("\ntransmission timed out... \ncurrent rtt="); // Dbg.intVal(rtt); if(rtt < 0){ if(rtt < -60000){ // give up // Dbg.wr("giving up...\n"); Packet os = outstanding; outstanding = null; sendRst(os, 0); // Dbg.wr("rst3"); free(); } else{ // Dbg.wr("resending... "); // Dbg.intVal(outstanding.buf[6]); // Dbg.lf(); outstanding.testSetStatus(Packet.CON_ONFLY, Packet.CON_RDY); // double the round trip time with every timeout rtt = rtt<<1; timeout = cur - rtt; } } else{ // Dbg.wr("first resending... "); // Dbg.intVal(outstanding.buf[6]); // Dbg.lf(); // outstanding.print(0); outstanding.testSetStatus(Packet.CON_ONFLY, Packet.CON_RDY); rtt = (-rtt)<<1; timeout = cur - rtt; } } } else if(state == TIME_WAIT && timeout - cur < 0){ // Dbg.intVal(cur); free(); } } } private void sendRst(Packet p, int flags){ synchronized(this){ p.setLen(0); // Dbg.wr("\nCon "); // Dbg.wr(this.toString()); // Dbg.wr(" reset "); send(p, FL_RST|flags, true, 0); free(); } } public boolean send(Packet p, boolean push){ synchronized(this){ if(state != ESTABLISHED){ p.free(); return false; } return send(p, (push ? FL_PSH : 0)|FL_ACK, false, 0); } } public void close(){ synchronized(this){ if(closing == 0){ switch(state){ case CLOSED: case LISTEN: case SYN_SENT: free(); break; case SYN_RCVD: case ESTABLISHED: closing = 1; break; default: break; } } } } boolean open(int remIp, int locIp, int remPort, int locPort, TcpHandler handler){ synchronized(this){ localIp = locIp; remoteIp = remIp; localPort = locPort; remotePort = remPort; th = handler; Packet p = PacketPool.getFreshPacket(); if(p != null){ seqNum = (int)System.currentTimeMillis(); // TODO secure enough? if(send(p, FL_SYN, true, 0)){ state = SYN_SENT; return true; } } return false; } } void newIncoming(int remIp, int locIp, int remPort, int locPort, Packet p, int off){ synchronized(this){ th = Tcp.getHandler(locPort); // no handler found if(th == null || th.isBusy(this)){ // tcp does not generate port unreachable but sends resets. // this is done in handleState, if the connection remains in closed state. //Icmp.sendPortUnreachable(p.buf, off); //Dbg.wr('T'); //Dbg.intVal(locPort); state = CLOSED; } else{ state = LISTEN; } localIp = locIp; remoteIp = remIp; localPort = locPort; remotePort = remPort; // the advertised receive buffer should be limited either by mtu size of the local net or by the size of the packet buffers. // both calculations use minimal TCP and IP header lengths and are therefore too optimistic. // if the sender decides to transmit a packet with mss and use IP or TCP options, we would need to drop the packet. rcvMss = Math.min(Tcp.getMaxPayload(remIp), PacketPool.PACKET_SIZE() - (Tcp.OFFSET<<2)); handleState(p, off); } } /** Frees the connection and returns it to the pool. */ private void free(){ synchronized(this){ // Dbg.wr("\nCon "); // Dbg.wr(this.toString()); // Dbg.wr(" freed"); state = FREE; outstanding = null; rtt = 500; closing = 0; th = null; } } boolean matches(int remIp, int locIp, int remPort, int locPort){ synchronized(this){ return remIp == remoteIp && locIp == localIp && remPort == remotePort && locPort == localPort; } } /** Processes an incoming packet (if it belongs to this connection). @param off Offset where the TCP header starts @param remIp The remote IP address. @param locIp The local IP address. @param remPort The remote port. @param locPort The local port. @param p The received packet. @return True, if the packet got processed. False, if it does not belong to this connection. */ boolean processPacket(int remIp, int locIp, int remPort, int locPort, Packet p, int off){ synchronized(this){ if(remIp == remoteIp && locIp == localIp && remPort == remotePort && locPort == localPort){ handleState(p, off); return true; } else return false; } } /** Handles the TCP state machine. @param p incoming packet @param off Offset in 32b-words where the TCP header starts. */ private void handleState(Packet p, int off){ synchronized(this){ int[] buf = p.buf; int i = buf[off + 3]>>>16; int flags = i&0xff; int hlen = i>>>12; int datOff = off + hlen; int datLen = p.len() - (datOff<<2); if(state == CLOSED){ //Dbg.wr("TC closed!\n"); if((flags&FL_RST) != 0){ p.free(); free(); } else{ if((flags&FL_ACK) != 0){ seqNum = buf[off + 2]; sendRst(p, 0); } else{ seqNum = 0; rcvNxt = buf[off + 1] + datLen + (((flags&(FL_SYN|FL_RST)) != 0) ? 1 : 0); // not used because of stack mem! sendRst(p, FL_ACK); p.setLen(0); send(p, FL_RST|FL_ACK, true, 0); free(); } } return; } // Dbg.wr("\nCon "); // Dbg.wr(this.toString()); // Dbg.wr(" TCP state: "); // Dbg.intVal(state); // check received sequence number for all packets, if we can if(buf[off + 1] != rcvNxt && state != LISTEN && state != SYN_SENT){ //Dbg.intVal(buf[off + 1]); //Dbg.wr("is not the correct SEQuence number "); //Dbg.intVal(rcvNxt); //Dbg.wr("- dropping\n"); p.free(); return; } if((flags&FL_RST) != 0){ if(state >= ESTABLISHED){ th.reset(this); } p.free(); free(); return; } //Dbg.wr("datlen "); //Dbg.intVal(datLen); //Dbg.wr("off "); //Dbg.intVal(off); // process ack if((flags&FL_ACK) != 0){ // Dbg.intVal(buf[off + 2]); if(buf[off + 2] != seqNum){ // Dbg.wr("is not the expected ACKnowledge number "); // Dbg.intVal(seqNum); //Dbg.wr("- dropping\n"); if(state < ESTABLISHED){ /* If the connection is in any non-synchronized state (LISTEN, SYN-SENT, SYN-RECEIVED), and the incoming segment acknowledges something not yet sent (the segment carries an unacceptable ACK) a reset is sent. */ seqNum = buf[off + 2]; sendRst(p, 0); /* if((flags&FL_ACK) != 0){ seqNum = buf[off + 2]; sendRst(p, 0); } else{ seqNum = 0; rcvNxt = buf[off + 1] + datLen; sendRst(p, FL_ACK); } */ } else{ /* If the connection is in a synchronized state (ESTABLISHED, FIN-WAIT-1, FIN-WAIT-2, CLOSE-WAIT, CLOSING, LAST-ACK, TIME-WAIT), any unacceptable segment (out of window sequence number or unacceptable acknowledgment number) must elicit only an empty acknowledgment segment */ send(p, FL_ACK, true, 0); } return; } // Dbg.wr("correct ACK\n"); if(outstanding != null){ //Dbg.wr("correct ACK received... "); if(rtt >= 0){ //Dbg.wr("rtt old="); //Dbg.intVal(rtt); // int currtt = (int)System.currentTimeMillis() - (timeout - (rtt * 4)); // rtt = (rtt + currtt) / 2; rtt = (rtt + ((int)System.currentTimeMillis() - (timeout - (rtt<<2))))>>1; // timeout = now() + 4*rtt //Dbg.wr("rtt cur="); //Dbg.intVal(rttCur); //Dbg.wr("rtt new="); //Dbg.intVal(rtt); } else rtt = -rtt; Packet os = outstanding; outstanding = null; os.free(); if(flags == FL_ACK && datLen == 0 && state == ESTABLISHED){ // just an ack for sent user data //Dbg.wr("just an ACK "); p.free(); return; } } else if(state == SYN_SENT && (flags&FL_RST) != 0){ th.reset(this); p.free(); free(); return; } } // we handle only one packet at a time so we have to drop it. if(outstanding == null){ Label: if(state == LISTEN){ if((flags&FL_SYN) == 0) break Label; readOptions(buf, off, datOff); // Dbg.wr("sndMss="); // Dbg.intVal(sndMss); //Dbg.wr("LISTEN: new connection SYNACKing "); rcvNxt = buf[off + 1] + 1; seqNum = (int)System.currentTimeMillis(); // TODO? secure enough? if(send(p, FL_SYN|FL_ACK, true, 0)) state = SYN_RCVD; return; } else if(state == SYN_SENT){ if((flags&FL_SYN) == 0) break Label; readOptions(buf, off, datOff); // Dbg.wr("sndMss="); // Dbg.intVal(sndMss); rcvNxt = buf[off + 1] + 1; if(send(p, FL_ACK, true, 0)){ if((flags&FL_ACK) != 0){ state = ESTABLISHED; th.established(this); } else{ state = SYN_RCVD; } //Dbg.wr("SYN_SENT: sent ack on syn "); } return; } else if(state == SYN_RCVD){ if((flags&FL_ACK) != 0){ //Dbg.wr("SYN_RCVD: SYN acked "); state = ESTABLISHED; th.established(this); } } else if(state == ESTABLISHED){ if((flags&FL_FIN) != 0){ //Dbg.wr("ESTABLISHED: got FIN "); if(send(p, FL_FIN|FL_ACK, true, 1)){ state = LAST_ACK; } return; } // acknowledging before delivering p.setStatus(Packet.APP); if(th.request(this, p, datOff)){ if(p.freeIfApp()){ send(p, FL_ACK, true, datLen); } else{ Packet p_ack = PacketPool.getFreshPacket(); if(p_ack != null) send(p_ack, FL_ACK, true, datLen); } return; } else{ break Label; } // } else if(state == CLOSE_WAIT){// ESTABLISHED changes directly to LAST_ACK // we do not need this state as we are not interested in half open connections //Dbg.wr("CLOSE_WAIT, shouldn't happen!"); } else if(state == LAST_ACK){ if(outstanding == null){ //Dbg.wr("we received the last ACK "); state = CLOSED; } } else if(state == FIN_WAIT_1){ if((flags&(FL_FIN|FL_ACK)) == (FL_FIN|FL_ACK)){ if(send(p, FL_ACK, true, 1)){ // Dbg.wr(" changing to time_wait, timeout="); timeout = (int)System.currentTimeMillis() + (rtt<<1); // Dbg.intVal(timeout); // Dbg.wr("cur="); // Dbg.intVal((int)System.currentTimeMillis()); state = TIME_WAIT; } } else if((flags&FL_ACK) != 0){ state = FIN_WAIT_2; p.free(); } else if((flags&FL_FIN) != 0){ if(send(p, FL_ACK, true, 1)) state = CLOSING; } else break Label; return; } else if(state == FIN_WAIT_2){ if((flags&(FL_FIN)) != 0){ if(send(p, FL_ACK, true, 1)){ // Dbg.wr("FIN_WAIT_2 changing to time_wait, timeout="); timeout = (int)System.currentTimeMillis() + (rtt<<1); // Dbg.intVal(timeout); // Dbg.wr("cur="); // Dbg.intVal((int)System.currentTimeMillis()); state = TIME_WAIT; } return; } } else if(state == CLOSING){ if((flags&FL_ACK) != 0){ // Dbg.wr(" changing to time_wait, timeout="); timeout = (int)System.currentTimeMillis() + (rtt<<1); // Dbg.intVal(timeout); // Dbg.wr("cur="); // Dbg.intVal((int)System.currentTimeMillis()); state = TIME_WAIT; } } else if(state == TIME_WAIT){ } else{ } } p.free(); } } private void readOptions(int[] buf, int off, int datOff){ synchronized(this){ off += 5; // Dbg.wr(" options: "); int kind = -1; int len = 0; int mss = 0; for(int i = off; i < datOff; i++){ int tmp = buf[i]; for(int j = 3; j >= 0; j--){ int cur = (tmp>>>(j<<3))&0xFF; // Dbg.hexVal(cur); switch(kind){ case -1: if(cur == 0){ return; } else if(cur == 1){ continue; } else{ // begin of a new option kind = cur; } break; case 2: // mss if(len == 0){ len = 2; } else if(mss == 0){ mss = cur<<8; } else{ int max = mss + cur; // Dbg.wr("remote mss="); // Dbg.intVal(max); sndMss = Math.min(max, Tcp.getMaxPayload(remoteIp)); mss = 0; kind = -1; len = 0; } break; default: break; } } } } } /* @param rcvNxtChange how much should the rcvNxt value rise. Will be undone, if the packet could not be sent. */ private boolean send(Packet p, int fl, boolean controlOnly, int rcvNxtChange){ synchronized(this){ int buf[] = p.buf; int hdrOff = Ip.OFFSET; buf[hdrOff] = (localPort<<16) + remotePort; buf[hdrOff + 1] = seqNum; if((fl&FL_ACK) != 0){ rcvNxt += rcvNxtChange; buf[hdrOff + 2] = rcvNxt; } else{ buf[hdrOff + 2] = 0; } int dataOff = Tcp.OFFSET; if((fl&FL_SYN) != 0){ // hlen = 24, mss option buf[hdrOff + 3] = 0x60000000 + (fl<<16) + rcvMss; // 1 word of options, receive window = mss buf[hdrOff + 5] = 0x02040000 + rcvMss; // mss option p.setLen((hdrOff + 6)<<2); } else{ buf[hdrOff + 3] = 0x50000000 + (fl<<16) + rcvMss; // hlen = 20, no options if(controlOnly){ p.setLen(dataOff<<2); } else if(p.len() <= (dataOff<<2) || outstanding != null){ p.free(); return false; } else{ // ensure last word to be 0-padded int loff = p.len(); int last = loff&0x3; loff = (loff)>>2; // word offset if(last == 1){ buf[loff] = buf[loff]&0xff000000; } else if(last == 2){ buf[loff] = buf[loff]&0xffff0000; } else if(last == 3){ buf[loff] = buf[loff]&0xffffff00; } } } int datLen = p.len() - (dataOff<<2); if(datLen > sndMss){ Dbg.wr("TCP segment too big!\n"); p.free(); return false; } // inject pseudo header buf[hdrOff - 1] = remoteIp; buf[hdrOff - 2] = localIp; buf[hdrOff - 3] = (Tcp.PROTOCOL<<16)|(p.len() - (hdrOff<<2)); // set protocol and tcp length buf[hdrOff + 4] = 0; // reset checksum and urgent pointer buf[hdrOff + 4] = Ip.chkSum(buf, hdrOff - 3, p.len() - ((hdrOff - 3)<<2))<<16; // packets with data or having SYN or FIN set, need to be retransmitted. those will get freed, when acked. // everything else should get freed by the link layer. boolean retransmit = datLen > 0 || (fl&(FL_SYN|FL_FIN)) != 0; if(retransmit){ // Dbg.wr("mark for retransmit, "); // Dbg.intVal( datLen); p.setStatus(Packet.CON_PREP); } boolean ret = Ip.send(p, localIp, remoteIp, Tcp.PROTOCOL); if(ret){ // Dbg.wr("sent "); // Dbg.intVal(seqNum); // Dbg.lf(); if((fl&(FL_SYN|FL_FIN)) != 0){ seqNum++; } else{ seqNum += datLen; } if(retransmit){ outstanding = p; timeout = ((int)(System.currentTimeMillis()) + (rtt<<2)); // timeout = now() + 4*rtt } } else rcvNxt -= rcvNxtChange; return ret; } } boolean isUsed(){ synchronized(this){ return state != FREE; } } boolean hasOutStanding(){ synchronized(this){ return outstanding != null; } } }