/* * Copyright (c) 2006-2007 Graz University of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. The names "Graz University of Technology" and "IAIK of Graz University of * Technology" must not be used to endorse or promote products derived from * this software without prior written permission. * * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE LICENSOR BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package ejip.jtcpip; import ejip.jtcpip.util.Debug; import ejip.jtcpip.util.NumFunctions; /** * Represents a TCP Connection. * Contains the Connection State and input/output * buffers. The size of the pool of available connections and the sizes of the * buffers can be set through constants. * * @author Tobias Kellner * @author Ulrich Feichter * @author Christof Rath * @version $Rev: 989 $ $Date: 2007/09/04 00:56:05 $ */ public class TCPConnection { public boolean wakeup = false; /** Connection state: Closed */ public final static byte STATE_CLOSED = 0; /** Connection state: Listen */ public final static byte STATE_LISTEN = 1; /** Connection state: SYN Sent */ public final static byte STATE_SYN_SENT = 2; /** Connection state: SYN Received */ public final static byte STATE_SYN_RCVD = 3; /** Connection state: Established */ public final static byte STATE_ESTABLISHED = 4; /** Connection state: FIN Wait 1 */ public final static byte STATE_FIN_WAIT_1 = 5; /** Connection state: FIN Wait 2 */ public final static byte STATE_FIN_WAIT_2 = 6; /** Connection state: Close Wait */ public final static byte STATE_CLOSE_WAIT = 7; /** Connection state: Closing */ public final static byte STATE_CLOSING = 8; /** Connection state: Last ACK */ public final static byte STATE_LAST_ACK = 9; /** Connection state: Time Wait */ public final static byte STATE_TIME_WAIT = 10; /** Free <code>TCPConnection</code> */ public final static byte TCP_CONN_FREE = 0x01; /** Used <code>TCPConnection</code> */ public final static byte TCP_CONN_USED = 0x02; /** The <code>TCPConnection</code> pool */ protected static TCPConnection[] pool ; public static void init(){ pool = new TCPConnection[StackParameters.TCP_CONNECTION_POOL_SIZE]; } /** * If > -1 try every NwLoop cycle the get a payload and send a portion of * pool[retryToSendData] */ protected static int retryToSendData = -1; /** * Stores the status of a certain connection. * <b>Note:</b> state != status * state is the TCP state as in RFC status: Connection in use or free */ protected byte status; /** * The default recieve buffer. * <p> * If the default buffer is used we limit the required memory but if one * closes the connection and someone else opens the connection again both * parties have read/write access to the same stream!! */ protected TCPInputStream defaultInputStream; /** * The default send buffer. * <p> * If the default buffer is used we limit the required memory but if one * closes the connection and someone else opens the connection again both * parties have read/write access to the same stream!! */ protected TCPOutputStream defaultOutputStream; /** * The actual recieve buffer. Might be the default stream, or an connection * own stream */ public TCPInputStream iStream; /** * The actual send buffer. Might be the default stream, or an connection own * stream */ public TCPOutputStream oStream; /** ip of the remote host */ public int remoteIP; /** port of the remote host */ public short remotePort; /** port of the local host */ public short localPort; /** state of the connection */ private byte state; /** previous state */ private byte prevState; /** * last acknoledged sequence number. beginnign of the unacknowledged * sequence numbers */ protected int sndUnack; /** time when the seq number in sndUnack was first acknowledged */ protected int sndUnackTime; /** next sequence number to send */ protected int sndNext; /** send window */ protected short sndWindow; /** * sequence number of the incoming segment with whom a window update was * done (SND.WL1) */ protected int sndWndLastUpdateSeq; /** * acknowledge number of the incoming segment with whom a window update was * done (SND.WL2) */ protected int sndWndLastUpdateAck; /** initial sequence number (ISS) */ protected int initialSeqNr; /** initial remote sequence number (IRS) */ protected int initialRemoteSeqNr; /** The next sequence number we expect to receive */ protected int rcvNext; /** recieve window */ protected short rcvWindow; /** timestamp from the last packet received from the remote side */ protected int timeLastRemoteActivity; /** * maximum segment size wich will be set from the remote host in the MSS * option or set by the Constant in StackParameters */ protected int maxSndSegSize; /** * Send the remaining data and then close the connection */ protected boolean flushAndClose; /** * is true if a syn is to send. the sequencenumber of the syn is then stored * in synToSendSeq * */ protected boolean synToSend; /** * sequence number of the syn (needed for retransmitt and acknowledging) * this is the sequence number which must be acknowledged so that the syn is * acknowledged */ protected int synToSendSeq; /** * is true if a fin is to send. The sequencenumber of the fin is then stored * in finToSendSeq */ protected boolean finToSend; /** * sequence number of the fin (needed for retransmitt and acknowledging) * this is the sequence number which must be acknowledged so that the fin is * acknowledged */ protected int finToSendSeq; /** * in this variable the retransmissions of the same sequence number are counted. * if then a acknowledge comes the counter is set to zero */ protected int numRetransmissions; /** * connection number (just for debugging use!) is the index of the connection in pool */ protected int connNum; /** * Create a <code>TCPConnection</code>. This constructor is private, * because <code>TCPConnection</code>s can only be got through * {@link TCPConnection#newConnection}. Initializes some internal * variables. Creates a {@link TCPInputStream} and a {@link TCPOutputStream}. * * @param port */ private TCPConnection(short port) { state = STATE_CLOSED; status = TCP_CONN_FREE; flushAndClose = false; defaultInputStream = new TCPInputStream(StackParameters.TCP_CONNECTION_RCV_BUFFER_SIZE); defaultOutputStream = new TCPOutputStream(StackParameters.TCP_CONNECTION_SND_BUFFER_SIZE); iStream = defaultInputStream; oStream = defaultOutputStream; cleanUpConnection(this); localPort = port; } /** * Returns the name of a given state. * * @param state * the state * @return the state name */ protected static String getStateName(byte state) { switch (state) { case STATE_LISTEN: return "listen"; case STATE_SYN_SENT: return "syn sent"; case STATE_SYN_RCVD: return "syn received"; case STATE_ESTABLISHED: return "established"; case STATE_FIN_WAIT_1: return "fin wait 1"; case STATE_FIN_WAIT_2: return "fin wait 2"; case STATE_CLOSE_WAIT: return "close wait"; case STATE_CLOSING: return "closing"; case STATE_LAST_ACK: return "last ack"; case STATE_TIME_WAIT: return "time wait"; case STATE_CLOSED: return "closed"; default: return "UNKNOWN!"; } } /** * Returns the previous state of the connection. * * @return the previous state of the connection */ protected byte getPreviousState() { return prevState; } /** * Returns the current state of the connection. * * @return the current state of the connection */ public byte getState() { return state; } /** * Sets the state of the connection. * * @param state * the state to set */ public synchronized void setState(byte state) { prevState = this.state; this.state = state; } /** * Returns true if a given Payload is part of this connection. * * @param pay * @return boolean */ protected boolean isIncomingPayPartOfConn(Payload pay) { return (IPPacket.getSrcAddr(pay) == remoteIP && TCPPacket.getSourcePort(pay) == remotePort && TCPPacket .getDestPort(pay) == localPort); } /** * Look for a TCPConnection with matching source IP and port and destination * port. Returns null if no matching connection was found. * * @param dstPort * The destination port to look for * @param srcIP * The source IP to look for * @param srcPort * The source port to look for * @return <code>TCPConnection</code> object if available or null if no * connection was found */ protected static TCPConnection getConnection(short dstPort, int srcIP, int srcPort) { for (int i = 0; i < StackParameters.TCP_CONNECTION_POOL_SIZE; i++) { if ((pool[i] != null) && (pool[i].status == TCP_CONN_USED) && (pool[i].localPort == dstPort)) { // local port matches destination port if (pool[i].state == STATE_LISTEN) { // found matching listening connection - but check if there // is a matching one first for (int j = i + 1; j < StackParameters.TCP_CONNECTION_POOL_SIZE; j++) if ((pool[j] != null) && (pool[j].status == TCP_CONN_USED) && (pool[j].localPort == dstPort)) if ((pool[j].remoteIP == srcIP) && (pool[j].remotePort == srcPort)) // found a better match return pool[j]; // didn't find an exact match - return listening connection return pool[i]; } if ((pool[i].remoteIP == srcIP) && (pool[i].remotePort == srcPort)) // found an exact match return pool[i]; } } // found no match return null; } /** * Create a new <code>TCPConnection</code>. Tries to create a new * <code>TCPConnection</code>. If the connection pool is full, or * there is already a listening connection at the specified port, * null is returned. * * @param port * @return The new <code>TCPConnection</code> or null if unsuccessful */ public static TCPConnection newConnection(short port) { for (byte i = 0; i < StackParameters.TCP_CONNECTION_POOL_SIZE; i++) { if (pool[i] == null) { pool[i] = new TCPConnection(port); pool[i].connNum = i; } if (pool[i].status == TCP_CONN_USED) { if (pool[i].localPort == port && pool[i].state == STATE_LISTEN) return null; continue; } // Found the first free connection // Now test if the rest of the connections do not listen on the same // port for (int j = i + 1; j < StackParameters.TCP_CONNECTION_POOL_SIZE; j++) if ((pool[j] != null) && (pool[j].status == TCP_CONN_USED) && (pool[j].localPort == port) && (pool[j].state == STATE_LISTEN)) return null; // The connection at position i is free and no one else is // using the same port pool[i].status = TCP_CONN_USED; cleanUpConnection(pool[i]); pool[i].localPort = port; return pool[i]; } return null; } /** * Relinquish a <code>TCPConnection</code>. The connection is marked as * free. * ATTENTION: User might have some references which will be wrong after * invoking. Use free to avoid such conflicts * * @see #abort() * @param conn * The connection to delete */ protected static void deleteConnection(TCPConnection conn) { if (conn == null) return; conn.oStream.close(); conn.iStream.close(); /* * if the connection is released the default streams get in place again * (just in case the connection had its own streams) */ conn.oStream = conn.defaultOutputStream; conn.iStream = conn.defaultInputStream; conn.setState(STATE_CLOSED); conn.status = TCP_CONN_FREE; } /** * Prepares a connection to remove possible artefacts of an old connection. * * @param conn */ private static void cleanUpConnection(TCPConnection conn) { conn.remoteIP = 0; conn.remotePort = 0; conn.localPort = 0; conn.state = STATE_CLOSED; conn.sndUnack = 0; conn.sndNext = 0; conn.sndWindow = 0; conn.initialSeqNr = 0; conn.initialRemoteSeqNr = 0; conn.rcvNext = 0; conn.rcvWindow = StackParameters.TCP_INITIAL_WINDOW_SIZE; conn.maxSndSegSize = StackParameters.TCP_INITIAL_SND_MAX_SEGMENT_SIZE; conn.flushAndClose = false; conn.oStream.reOpen(); conn.iStream.reOpen(); conn.finToSend = false; conn.finToSendSeq = 0; conn.synToSend = false; conn.synToSendSeq = 0; conn.timeLastRemoteActivity = (int) (System.currentTimeMillis() & 0xFFFFFFFF); conn.numRetransmissions = 0; } /** * @return a random unused port number */ protected static short newLocalPort() { int randPort = NumFunctions.rand.nextInt() & 0xFFFF; // FIXME: I don't like recursive calls for (int i = 0; i < StackParameters.TCP_CONNECTION_POOL_SIZE; i++) if (pool[i] != null && pool[i].status == TCP_CONN_USED && pool[i].localPort == randPort) return newLocalPort(); return (short) randPort; } /** * Checks if there is some data to retransmit. If there is it changes the * parameters of the connection so that the next transmission is done with * the old data. * * @return false if there was no answer after MAX_TRY_TO_RETRANSMIT_TIME * milliseconds else true */ protected boolean checkAndprepareRetransmission() { if (checkforRetransmission()) { if(getState() == STATE_SYN_SENT) if(numRetransmissions >= StackParameters.TCP_MAX_TIMES_SYN_RETRANSMIT) return false; if (Debug.enabled) { // Debug.println("sndUnackTime + sndUnackTime + current time + (int) (System.currentTimeMillis() & 0xFFFFFFFF)", Debug.DBG_TCP); // Debug.println("sndNext alt = + sndNext", Debug.DBG_TCP); Debug.println("Starting retransmission", Debug.DBG_TCP); } sndNext = sndUnack; // maybe some other things to set... if(!oStream.isBufferEmpty()) oStream.setPtrForRetransmit(); numRetransmissions++; // so that is not startet soon after sndUnackTime = (int) (System.currentTimeMillis() & 0xFFFFFFFF); if (!NumFunctions.isBetweenOrEqualSmaller(timeLastRemoteActivity, timeLastRemoteActivity + StackParameters.TCP_MAX_TRY_TO_RETRANSMIT_TIME, (int) (System .currentTimeMillis() & 0xFFFFFFFF))) return false; // no answer from other side since // MAX_TRY_TO_RETRANSMIT_TIME milliseconds -> // close } return true; } /** * Checks if retransmission is needed. * * @return true if retransission is needed, else false */ private boolean checkforRetransmission() { if (sndNext == sndUnack) return false; int timeout = StackParameters.TCP_RETRANSMISSION_TIMEOUT; int current_time = (int) (System.currentTimeMillis() & 0xFFFFFFFF); if (getState() == STATE_SYN_SENT) { timeout *= StackParameters.TCP_SYN_RETRANSMIT_TIMEOUT_MULTIPLYER; } return (!NumFunctions.isBetweenOrEqualSmaller(sndUnackTime, sndUnackTime + timeout, current_time)); } /** * Is continously called by <code>NWLoopThread</code> after a defined timeout. * Here all the tcp-timeouts are processed, new user data is sent and * retransmission is initiaded when needed. * * @return true if nothing to send or if we got a payload to send a portion, * false if no <code>Payload</code> was available */ protected synchronized boolean pollConnection() { // if (Debug.enabled) // Debug.println("------- Polling TCP conn ----------", Debug.DBG_TCP); // Debug.println("------- Polling TCP conn " + connNum + " (state: " + getStateName(state) // + ") ----------", Debug.DBG_TCP); if (state == STATE_LISTEN || state == STATE_CLOSED) return true; boolean sendSomething = false; // check for zero window and open it if possible if (rcvWindow == 0) { if (iStream.getFreeBufferSpace() >= StackParameters.TCP_INITIAL_WINDOW_SIZE) { if (Debug.enabled) Debug.println("Opening window for conn", Debug.DBG_TCP); rcvWindow = StackParameters.TCP_INITIAL_WINDOW_SIZE; sndUnackTime = (int) (System.currentTimeMillis() & 0xFFFFFFFF); sendSomething = true; } } switch (state) { case STATE_LAST_ACK: // close connection if no response came for // MAX_TRY_TO_RETRANSMIT_TIME because ack might be lost if (!NumFunctions.isBetweenOrEqualBigger(timeLastRemoteActivity, timeLastRemoteActivity + StackParameters.TCP_MAX_TRY_TO_RETRANSMIT_TIME, (int) (System .currentTimeMillis() & 0xFFFFFFFF))) { if (Debug.enabled) Debug.println("Closing connection", Debug.DBG_TCP); deleteConnection(this); } return true; case STATE_TIME_WAIT: // close connection if no response came for TCP_TIME_WAIT_TIME if (!NumFunctions.isBetweenOrEqualBigger(timeLastRemoteActivity, timeLastRemoteActivity + StackParameters.TCP_TIME_WAIT_TIME, (int) (System.currentTimeMillis() & 0xFFFFFFFF))) { if (Debug.enabled) Debug.println("Closing connection in TIME_WAIT state after timeout", Debug.DBG_TCP); deleteConnection(this); } return true; case STATE_SYN_SENT: if (flushAndClose) { // user called close() if (Debug.enabled) Debug.println("Entering state: CLOSED", Debug.DBG_TCP); deleteConnection(this); return true; } if(!checkforRetransmission()) return true; break; case STATE_LISTEN: if (flushAndClose) { // user called close() if (Debug.enabled) Debug.println("Entering state: CLOSED", Debug.DBG_TCP); deleteConnection(this); return true; } break; } // send data if neccesary if (oStream.isNoMoreDataToRead() && !checkforRetransmission() && !sendSomething && !flushAndClose) return true; Payload pay = TCP.createEmptyPayload(); if (pay == null) { if (Debug.enabled) Debug.println("no more Payload... ", Debug.DBG_TCP); return false; } TCP.sendPackets(this, pay); return true; } /** * Set own streams for a connection. The default streams get back in place * in {@link #deleteConnection(TCPConnection)} * ATENTION: do it before reading and writing something from the streams! * Or clone the streams before setting new ones. * Else the stack will get a bit confused... * * @param in * The new input stream * @param out * The new output stream */ protected void setOwnStreams(TCPInputStream in, TCPOutputStream out) { iStream = in; oStream = out; } /** * Aborts the connection. * That means that all the user's streams are closed and the state turns * to Closed. This is needed that the user can take note that something * went wrong and finally he can call <code>close()</code> so that the * <code>TCPConnection</code> object is released. * * @see #deleteConnection * @see #close() * */ protected synchronized void abort() { oStream.close(); iStream.close(); setState(STATE_CLOSED); // Wake up listening connections // TODO //notifyAll(); } /** * Close the connection. * Closes the output stream and later a FIN will be send. After this call * both streams are closed, so no new data can be sent and received. * */ public void close() { if (Debug.enabled) Debug.println("close call from user on conn", Debug.DBG_TCP); if (getState() == STATE_CLOSED) { deleteConnection(this); return; } oStream.close(); iStream.close(); /* * following is not possible on jop because no class loader * try { * synchronized(Class.forName("ejip.jtcpip.TCP")) * { * flushAndClose = true; * } * } * catch (ClassNotFoundException e) { e.printStackTrace(); } */ TCP.flushAndClose(this); // just to use tcps monitior pollConnection(); /* * Payload pay = TCP.createEmptyPacket(0); if(pay == null) return; * TCP.sendPackets(this, pay); */ } }