Subject: [Ietf-behave] New revision of draft-hoffman-behave-tcp, now -02

From: Saikat Guha <[email-masked]>
Date: Tue, 19 Jul 2005 18:55:11 -0700 (PDT)

Hi all,

As if this list doesn't already generate enough traffic, I'd like to
bring to your attention a new revision of draft-hoffman-behave-tcp here:
http://www.ietf.org/internet-drafts/draft-hoffman-behave-tcp-02.txt

A quick summary -- it has three requirements. Paraphrased, they are:

1. All TCP packets in either direction should be allowed
   (i.e. to allow simultaneous-open etc)
2. How to respond to unsolicited inbound SYN -- 
   - silently drop is the recommendation (current consensus)
   - RST is a big no-no (also a consensus)
   - ICMP is optional (more on this below)
3. Mapping timer for TCP connections in various states
   - 2 hours for established connections (as per RFC 1122)
   - 4 minutes for connections in setup or teardown phase
     (as per RFC 1122)
   - closed connections (think RST) left unspecified
     (ignore, 0 seconds, 4 minutes etc whatever the vendor decides)

I believe it reflects the IETF 62 consensus and list discussions. The
rest of this mail has detailed justifications for each of the
requirements above and a comparison against the other behave-tcp drafts.
Coffee is RECOMMENDED to the brave reader.

-------------- Justifications/Comparison ----------------

====== Background ======
4 basic STUN-like TCP NAT-traversal approaches have been proposed. Each
approach can be tweaked slightly to perform better under some scenarios.
See http://nutss.gforge.cis.cornell.edu/pub/draft-imc-stunt.pdf for a
summary. Success and failure rates and reasons thereof are also
presented in that paper.

====== REQ 1 ======
REQ-1: A NAT MUST allow all TCP packets in either direction on any 
      open NAT mapping that is a TCP connection.

This is mainly to enable simultaneous-TCP-like connection setup. Note
that the requirement is not restricted to SYN-in after SYN-out. More
generally, it allows the end-host stack to take any transition as it
sees fit whether or not the NAT vendor recognizes that transition.

In particular, here are some transitions that do not always work today:

1) SYN-out SYN-in (simultaneous open)
2) SYNACK-out (simultaneous open)
3) FIN-in FIN/ACK-out LAST_ACK-in (regular TCP)
   -- this one leads to hanging TCBs sometimes when the
      final ACK is filtered.

Concerns:
1) How does this affect security?
        This is at most as secure as the end host. If the endhost stack,
        for example, is vulnerable to a particular combination of flags
        or sequence of packets with flags set by an adversary that the
        NAT would have otherwise filtered, then the endhost will still
        be vulnerable.
        
2) How does this affect functionality?
        It is more functional than existing NATs. Simultaneous open is
        in the RFC, all NATs don't support this at the moment.
        
3) What would be most secure and offer best functionality?
        A NAT that tracks the exact TCP state (not a simplified state
        machine) of the endhost behind it and allows packets with valid
        flags. NAT vendors may not be willing to track the full blown
        TCP state machine.
        
4) Why not just say allow incoming SYN?
        Because there are other transitions not currently supported.
        Simultaneous open, for example, requires outgoing SYNACKs. A
        protocol could trigger the simultaneous-close (where the FIN's
        cross in network) and break if the NAT doesn't support that
        transition.
        
5) The current wording precludes the "best" implementation as outlined
in concern #3.
        Sort of. It'd be nice if it said 'allow all valid packets', but
        that ventures into the land of firewalls and the definition of
        'valid' would be fuzzy -- had the vendors read the TCP RFC in
        the first place, they'd have seen the simultaneous open case.
        Perhaps it's fine to say that functionality wise a _NAT_ must
        allow all TCP packets, and leave the the security/flag-
        sanitization part to a _firewall_. 

====== REQ 2 ======
REQ-2: A NAT MUST NOT send a RST packet in response to a SYN packet 
      sent from the external side to an address and port mapping that
      does not exist.
      a) A NAT MAY send an ICMP Host Unreachable (Type=3, Code=1) in
         response to such a SYN packet.

The basic idea here is that a fatal error must not be generated in
response to a SYN packet. That makes simultaneous TCP setup very hard
and timing sensitive since then the two SYNs absolutely must cross in
the network and one cannot arrive at the NAT before the other exits that
NAT.

The current consensus is that the packet should be silently rejected.
While thats fine for simultaneous setup, thats not fine for regular TCP.
For regular TCP, the SYN is sent (and resent) for up to 30 seconds on
windows and over a minute on Linux. If no response is received, then the
application (email, web browser etc) blocks for 30s to 1m30s depending. 

Ideally, the NAT signals a non-fatal error so that the application can
decide whether to give up (email, web etc) or to keep waiting for TCP
simultaneous open to be triggered (peer to peer apps). A non-fatal error
can be some (not all) ICMP Destination Unreachable etc.

The current group consensus is conducive to p2p apps, but not to regular
old TCP apps like ssh, telnet, email, web browsers etc. At the moment,
the recommendation above is silent-drop -- in line with consensus ...
but ideally I think a non-fatal error would be much better.

Concerns:
1) Current wording is insufficient. It allows the NAT to respond with an
ICMP port-unreachable -- which is a fatal error as per RFC 1122
        Correct -- this needs to be fixed. Fatal and non-fatal errors
        need to be defined. Fatal errors will include TCP RST, ICMP Port
        Unreachable and other "hard" errors as per RFC 1122. Non-Fatal
        errors will include "soft" errors from RFC 1122. The structure
        of the current requirement is -- MUST NOT return fatal error.
        MAY return non-fatal error.
        
2) The WG already has consensus on silent drop.
        I am not sure all the options were evaluated properly before
        consensus was reached. (the last meeting's minutes don't mention
        the possibility of returning an ICMP soft-error). Effects on non
        P2P TCP was not considered. If the consensus was that RST (or a
        fatal error) must not be returned, this proposal preserves that.

3) Type=3, Code=1 doesn't describe the condition well.
        It need not be that. We can figure out what the soft-error
        should be. There was some discussion on this list earlier about
        using Code 10 ... but RFC 1122 doesn't explicitly designate that
        a soft-error. We could add a new code and define it a soft-
        error. Or overload an existing soft-error. Or use an existing
        code that has no soft/hard designation and explicitly designate
        it a soft-error. The exact error needs to be decided upon.

4) ICMP would break legacy NATs.
        Not necessarily. If the legacy NAT aborts on seeing the ICMP,
        then the connection attempt can be reversed so the legacy NAT is
        not on the ICMP receiving side. The problem arises only when the
        legacy NAT aborts on the ICMP _and_ responds with RST to inbound
        SYNs (thus preventing the connection reversal). Currently, less
        than 1% (~0.5%) NATs fall into that category. So returning ICMP
        does not affect TCP connectivity establishment much even today,
        let alone over time as legacy NATs firmwares are upgraded to be
        behave compliant. 

====== REQ 3 ======
REQ-3: A NAT TCP mapping timer SHOULD NOT expire in less than 4
      minutes for a TCP connection in SYN_SENT, SYN_RCVD, CLOSING,
      LAST_ACK or TIME_WAIT state.  A NAT TCP mapping timer SHOULD NOT
      expire in less than 2 hours for a TCP connection in any other
      state except CLOSED.
      a) When a NAT closes an open TCP mapping, it MUST send RST packets
         to each end of the connection.
      b) To maximize application transparency, a NAT MAY expire mappings
         only when it is low on resources.

The basic idea here is that connections have 3 phases ... startup, 
established and closing (the sivakumar draft also makes the same
argument). There is a short-timer during the startup and closing phases
to allow inflight packets to arrive -- 2xMSL as defined in TCP with MSL
defined as 2 minutes in RFC 1122. There is a long-timer for established
connections to accommodate long-lived idle connections -- TCP provides a
keepalive mechanism to make sure the other endpoint didn't crash -- the
default is 2 hours (RFC 1122). The NAT should respect these default
timers.

Nevertheless, a NAT may need to timeout connections -- it inform the
endhost so the TCP is not left hanging (only to be discovered stale when
data actually needs to be sent). Thats what the RST is for.

As for keepalives, the RFC does not mandate keepalives to be sent on
idle TCPs. So many apps may not even send keepalives every 2-hours. The
NAT can try to accommodate them if it can ... but it is merely a
suggestion.

Concerns:
1) Why is this a SHOULD and not a MUST
        Absolutely requiring a NAT to examine flags etc is more in the
        land of firewalls -- it needs to understand that FIN exchange
        closes etc. A vendor can be compliant by setting a blanket 2-
        hour timeout for any and all TCP states - no flag analysis
        required. Yes, that would be inefficient and the vendor may want
        to do some lightweight flag analysis, but thats up to the
        vendor. The requirement does not force flag analysis.

2) Why does the NAT send a RST packet and not probe for liveness? 
        The vendor is free to probe for liveness via keep-alive packets
        and preferably kill dead and expired mappings before live but
        expired mappings ... but it is not required to do so. Probing is
        an efficiency concern (since it allows the NAT to reclaim state
        sooner) and a vendor may choose to do so. 
        
3) Why is RST a MUST?
        If a mapping is to be killed due to timeout -- either the
        application/host crashed or it is alive but idle. Since crashes
        are the exception and not the norm, if a mapping expires then
        the app is most likely alive and well. In addition, since it is
        holding the connection open, it expects to send data later --
        otherwise why would it hold it open. Therefore if a mapping
        expires, then it is most likely that the application will notice
        sooner or later. Explicit RST informs the app of the problem as
        soon as possible. Though, admittedly, this requires the NAT to
        track sequence numbers, tcp timestamp etc to generate a in-
        window RST. So there is a valid argument to making this a
        SHOULD. Thoughts?
        
4) This is hard to implement. So many TCP states!
        Not really. The states above were chosen in a principled way to
        simplify implementation. For instance, until both ends send an
        ACK, one cannot be sure the connection has been established.
        Therefore all the TCP states that come before both ends have
        sent an ACK are in the short-timer phase. Implementation wise, a
        NAT just needs to keep track of the ACK flag in either direction
        -- as soon as it sees both ACKs, it can bump up the timer to
        2hrs. Second, the connection is not closed until both sides send
        a FIN (both sides need to send the FIN since data can still be
        exchanged if only one side has closed the connection). So all
        TCP states that come after seeing FIN's going in opposite
        direction are again in the short-timer phase. Implementation
        wise this is simple too -- just track the FIN flags for inbound
        and outbound packets. Ofcourse a NAT doesn't need to do anything
        if it sets all timers to 2h, but thats vendor slack. The
        justifcation for REQ-3 text hints at the implementation ease if
        vendors do care to be resource-efficient.
        
5) What about packets in CLOSED state?
        CLOSED (the state after a RST) is intentionally left
        unspecified. The vendor can choose to kill the mapping as soon
        as it sees the RST or can choose to allow more packets (such as
        SYN) and decide to extend the mapping if such packets are
        exchanged. Not killing the mapping instantly helps P2P TCP with
        a legacy NAT that is spewing RST packets, while killing the
        mapping instantly can help reclaim resources and perhaps make it
        harder to hijack established sessions. The tradeoff is up to the
        vendor.   

======== Differences from draft-sivakumar ===========

1. Requirements common to UDP, like endpoint independent mapping (read
Cone behavior) etc are inherited from behave-udp and not re-justified.

2. TCP phase tracking is recommended, not a must. See REQ-3 concern #1
above.

3. REQ-1 above is more general than 'allow SYN packet in a particular
phase'. Also see concerns #4 and #5 in REQ-1 above.

4. DoS attacks -- basically out-of-window RST attack, mentioned in the
security concerns section. SYN-flood ... handled by short timers.
Admittedly, the RFC recommendation of 4 minutes is excessive and most
OS's use a 1 minute thing -- the exact timer value is a compliance vs.
practice choice I guess.

5. NAT need not initiate keepalives, but may. See REQ-3 concern #2. 
   (same as sivakumar actually, but not explicitly mentioned)

6. NAT MUST sent RST, instead of SHOULD. Concern #3 REQ-3

========= Differences from draft-modadugu ===========

1. Sequence number adjustment not mentioned. Up to the vendor.
   (mostly important for ALGs and that too as a correctness issue.
    if a vendor doesn't implement an ALG, no need to recommend this)

2. The silent drop vs. RST issue is handled in REQ-2 with the added
soft-error option.

3. Incoming SYN generalized in REQ-1. Concern #4,5 in REQ-1

----------

If you wish to discuss any of these requirements on the list, please
make a separate new thread for each requirement and for new
requirements/omissions. :)

cheers,
-- 
Saikat
Attachment:
signature.asc