gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[GNUnet-SVN] r4076 - in GNUnet/src/transports: . upnp


From: grothoff
Subject: [GNUnet-SVN] r4076 - in GNUnet/src/transports: . upnp
Date: Wed, 27 Dec 2006 08:46:04 -0800 (PST)

Author: grothoff
Date: 2006-12-27 08:46:00 -0800 (Wed, 27 Dec 2006)
New Revision: 4076

Added:
   GNUnet/src/transports/upnp/
   GNUnet/src/transports/upnp/draft-cheshire-nat-pmp.txt
   GNUnet/src/transports/upnp/upnp.c
   GNUnet/src/transports/upnp/upnp.h
   GNUnet/src/transports/upnp/xmlnode.c
   GNUnet/src/transports/upnp/xmlnode.h
Log:
importing UPnP code from gaim

Added: GNUnet/src/transports/upnp/draft-cheshire-nat-pmp.txt
===================================================================
--- GNUnet/src/transports/upnp/draft-cheshire-nat-pmp.txt       2006-12-27 
14:27:24 UTC (rev 4075)
+++ GNUnet/src/transports/upnp/draft-cheshire-nat-pmp.txt       2006-12-27 
16:46:00 UTC (rev 4076)
@@ -0,0 +1,1160 @@
+Document: draft-cheshire-nat-pmp-02.txt                  Stuart Cheshire
+Internet-Draft                                             Marc Krochmal
+Category: Standards Track                           Apple Computer, Inc.
+Expires 14th March 2007                                      Kiren Sekar
+                                                         Sharpcast, Inc.
+                                                     14th September 2006
+
+                   NAT Port Mapping Protocol (NAT-PMP)
+
+                     <draft-cheshire-nat-pmp-02.txt>
+
+Status of this Memo
+
+   By submitting this Internet-Draft, each author represents that any
+   applicable patent or other IPR claims of which he or she is aware
+   have been or will be disclosed, and any of which he or she becomes
+   aware will be disclosed, in accordance with Section 6 of BCP 79.
+   For the purposes of this document, the term "BCP 79" refers
+   exclusively to RFC 3979, "Intellectual Property Rights in IETF
+   Technology", published March 2005.
+
+   Internet-Drafts are working documents of the Internet Engineering
+   Task Force (IETF), its areas, and its working groups.  Note that
+   other groups may also distribute working documents as Internet-
+   Drafts.
+
+   Internet-Drafts are draft documents valid for a maximum of six months
+   and may be updated, replaced, or obsoleted by other documents at any
+   time.  It is inappropriate to use Internet-Drafts as reference
+   material or to cite them other than as "work in progress."
+
+   The list of current Internet-Drafts can be accessed at
+   http://www.ietf.org/1id-abstracts.html
+
+   The list of Internet-Draft Shadow Directories can be accessed at
+   http://www.ietf.org/shadow.html
+
+
+Abstract
+
+   This document describes a protocol for automating the process of
+   creating Network Address Translation (NAT) port mappings. Included
+   in the protocol is a method for retrieving the public IP address of
+   a NAT gateway, thus allowing a client to make this public IP address
+   and port number known to peers that may wish to communicate with it.
+   This protocol is implemented in current Apple products including
+   Mac OS X, Bonjour for Windows, and AirPort wireless base stations.
+
+
+
+
+
+
+
+
+
+
+Expires 14th March 2007        Cheshire, et al.                 [Page 1]
+
+Internet Draft          NAT Port Mapping Protocol    14th September 2006
+
+
+1. Introduction
+
+   Network Address Translation (NAT) is a method of sharing one public
+   internet address with a number of devices. This document is focused
+   on what "IP Network Address Translator (NAT) Terminology and
+   Considerations" [RFC 2663] calls "NAPTs" (Network Address/Port
+   Translators). A full description of NAT is beyond the scope of this
+   document. The following brief overview will cover the aspects
+   relevant to this port mapping protocol. For more information on
+   NAT, see "Traditional IP Network Address Translator" [RFC 3022].
+
+   NATs have one or more public IP addresses. A private network is set
+   up behind the NAT. Devices behind the NAT are assigned private
+   addresses and the private address of the NAT device is used as the
+   gateway.
+
+   When a packet from any device behind the NAT is sent to an address on
+   the public internet, the packet first passes through the NAT box. The
+   NAT box looks at the source port and address. In some cases, a NAT
+   will also keep track of the destination port and address. The NAT
+   then creates a mapping from the private address and private port to a
+   public address and public port if a mapping does not already exist. 
+   The NAT box replaces the private address and port number in the
+   packet with the public entries from the mapping and sends the packet
+   on to the next gateway.
+
+   When a packet from any address on the internet is received on the
+   NAT's public side, the NAT will look up the destination port (public
+   port) in the list of mappings. If an entry is found, it will contain
+   the private address and port that the packet should be sent to. The
+   NAT gateway will then rewrite the destination address and port with
+   those from the mapping. The packet will then be forwarded to the new
+   destination addresses. If the packet did not match any mapping, the
+   packet will most likely be dropped. Various NATs implement different
+   strategies to handle this. The important thing to note is that if
+   there is no mapping, the NAT does not know which private address the
+   packet should be sent to.
+
+   Mappings are usually created automatically as a result of observing
+   outbound traffic. There are a few exceptions. Some NATs may allow
+   manually-created permanent mappings that map a public port to a
+   specific private IP address and port. Such a mapping allows incoming
+   connections to the device with that private address. Some NATs also
+   implement a default mapping where any inbound traffic that does not
+   match a mapping will always be forwarded to a specific private
+   address. Both types of mappings are usually set up manually through
+   some configuration tool.
+
+   Without these manually-created inbound port mappings, clients behind
+   the NAT would be unable to receive inbound connections, which
+   represents a loss of connectivity when compared to the original
+
+
+Expires 14th March 2007        Cheshire, et al.                 [Page 2]
+
+Internet Draft          NAT Port Mapping Protocol    14th September 2006
+
+
+   Internet architecture [ETEAISD]. For those who view this loss of
+   connectivity as a bad thing, NAT-PMP allows clients to operate much
+   more like a host directly connected to the unrestricted public
+   Internet, with an unrestricted public IP address. NAT-PMP allows
+   client hosts to communicate with the NAT gateway to request the
+   creation of inbound mappings on demand. Having created a NAT mapping
+   to allow inbound connections, the client can then record its public
+   IP address and public port number in a public registry (e.g. the
+   world-wide Domain Name System) or otherwise make it accessible to
+   peers that wish to communicate with it.
+
+
+2. Conventions and Terminology Used in this Document
+
+   The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+   "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+   document are to be interpreted as described in "Key words for use in
+   RFCs to Indicate Requirement Levels" [RFC 2119].
+
+
+3. Protocol and Packet Format
+
+   NAT Port Mapping Protocol runs over UDP. Every packet starts with an
+   8 bit version followed by an 8 bit operation code.
+
+   This document specifies version 0 of the protocol. Any NAT-PMP
+   gateway implementing this version of the protocol, receiving a
+   packet with a version number other than 0, MUST return result code 1
+   (Unsupported Version).
+
+   Opcodes between 0 and 127 are client requests. Opcodes from 128 to
+   255 are server responses. Responses always contain a 16 bit result
+   code in network byte order. A result code of zero indicates success. 
+   Responses also contain a 32 bit unsigned integer corresponding to the
+   number of seconds since the NAT gateway was rebooted or since its
+   port mapping state was reset.
+
+   This protocol SHOULD only be used when the client determines that
+   its primary IPv4 address is in one of the private IP address ranges
+   defined in "Address Allocation for Private Internets" [RFC 1918].
+   This includes the address ranges 10/8, 172.16/12, and 192.168/16.
+
+   Clients always send their Port Mapping Protocol requests to their
+   default gateway, as learned via DHCP [RFC 2131], or similar means.
+   This protocol is designed for small home networks, with a single
+   logical link (subnet) where the client's default gateway is also the
+   NAT translator for that network. For more complicated networks where
+   the NAT translator is some device other than the client's default
+   gateway, this protocol is not appropriate.
+
+
+
+
+Expires 14th March 2007        Cheshire, et al.                 [Page 3]
+
+Internet Draft          NAT Port Mapping Protocol    14th September 2006
+
+
+3.1 Requests and Responses
+
+   NAT gateways are often low-cost devices, with limited memory and
+   CPU speed. For this reason, to avoid making excessive demands on
+   the NAT gateway, clients machines SHOULD NOT issue multiple requests
+   simultaneously in parallel. If a client needs to perform multiple
+   requests (e.g. on boot, wake from sleep, network connection, etc.)
+   it SHOULD queue them and issue them serially one at a time. Once the
+   NAT gateway responds to one request the client machine may issue the
+   next. In the case of a fast NAT gateway, the client may be able to
+   complete requests at a rate of hundreds per second. In the case of
+   a slow NAT gateway that takes perhaps half a second to respond to
+   a NAT-PMP request, the client SHOULD respect this and allow the
+   NAT gateway to operate at the pace it can manage, and not overload
+   it by issuing requests faster than the rate it's answering them.
+
+   To determine the puclic IP address or request a port mapping,
+   a NAT-PMP client sends its request packet to port 5351 of its
+   configured gateway address, and waits 250ms for a response. If no
+   NAT-PMP response is received from the gateway after 250ms, the client
+   retransmits its request and waits 500ms. The client SHOULD repeat
+   this process with the interval between attempts doubling each time.
+   If, after sending its 9th attempt (and then waiting for 64 seconds),
+   the client has still received no response, then it SHOULD conclude
+   that this gateway does not support NAT Port Mapping Protocol and
+   MAY log an error message indicating this fact. In addition, if the
+   NAT-PMP client receives an "ICMP Port Unreachable" message from the
+   gateway for port 5351 then it can skip any remaining retransmissions
+   and conclude immediately that the gateway does not support NAT-PMP.
+
+   As a performance optimization the client MAY record this information
+   and use it to suppress further attempts to use NAT-PMP, but the
+   client should not retain this information for too long. In
+   particular, any event that may indicate a potential change of gateway
+   or a change in gateway configuration (hardware link change
+   indication, change of gateway MAC address, acquisition of new DHCP
+   lease, receipt of NAT-PMP announcement packet from gateway, etc.)
+   should cause the client to discard its previous information regarding
+   the gateway's lack of NAT-PMP support, and send its next NAT-PMP
+   request packet normally.
+
+
+3.2 Determining the Public Address
+
+   To determine the public address, the client behind the NAT sends the
+   following UDP payload to port 5351 of the configured gateway address:
+
+    0                   1
+    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   | Vers = 0      | OP = 0        |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+Expires 14th March 2007        Cheshire, et al.                 [Page 4]
+
+Internet Draft          NAT Port Mapping Protocol    14th September 2006
+
+
+   A compatible NAT gateway MUST generate a response with the following
+   format:
+
+    0                   1                   2                   3
+    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   | Vers = 0      | OP = 128 + 0  | Result Code                   |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   | Seconds Since Start of Epoch                                  |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   | Public IP Address (a.b.c.d)                                   |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+   This response indicates that the NAT gateway implements this version
+   of the protocol and returns the public IP address of the NAT gateway.
+   If the result code is non-zero, the value of Public IP Address is
+   undefined (MUST be set to zero on transmission, and MUST be ignored
+   on reception).
+
+   The NAT gateway MUST fill in the "Seconds Since Start of Epoch" field
+   with the time elapsed since its port mapping table was initialized on
+   startup or reset for any other reason (see Section 3.6 "Seconds Since
+   Start of Epoch").
+
+   Upon receiving the response packet, the client MUST check the source
+   IP address, and silently discard the packet if the address is not the
+   address of the gateway to which the request was sent.
+
+
+3.2.1 Announcing Address Changes
+
+   When the public IP address of the NAT changes, the NAT gateway MUST
+   send a gratuitous response to the link-local multicast address
+   224.0.0.1, port 5351 with the packet format above to notify clients
+   of the new public IP address. To accommodate packet loss, the
+   NAT gateway SHOULD multicast 10 address change notifications.
+   The interval between the first two notifications SHOULD be 250ms,
+   and the interval between each subsequent notification SHOULD double.
+
+   Upon receiving a gratuitous address change announcement packet,
+   the client MUST check the source IP address, and silently discard
+   the packet if the address is not the address of the client's
+   current configured gateway. This is to guard against inadvertent
+   misconfigurations where there may be more than one NAT gateway
+   active on the network.
+
+
+
+
+
+
+
+
+Expires 14th March 2007        Cheshire, et al.                 [Page 5]
+
+Internet Draft          NAT Port Mapping Protocol    14th September 2006
+
+
+3.3 Creating a Mapping
+
+   To create a mapping, the client sends a UDP packet to port 5351
+   of the gateway's private IP address with the following format:
+
+    0                   1                   2                   3
+    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   | Vers = 0      | OP = x        | Reserved (MUST be zero)       |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   | Private Port                  | Requested Public Port         |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   | Requested Port Mapping Lifetime in Seconds                    |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+   Opcodes supported:
+   1 - Map UDP
+   2 - Map TCP
+
+   The Reserved field MUST be set to zero on transmission and MUST
+   be ignored on reception.
+
+   The Private Port is set to the local port on which the client is
+   listening.
+
+   The Requested Public Port SHOULD usually be set to the same value as
+   the local Private Port, or zero if the client has no preference for
+   what port is assigned. However, the gateway is not obliged to assign
+   the port requested, and may choose not to, either for policy reasons
+   (e.g. port 80 is reserved and clients may not request it) or because
+   that port has already been assigned to some other client. Because
+   of this, some product developers have questioned the value of having
+   the Requested Public Port field at all. The reason is for failure
+   recovery. Most low-cost home NAT gateways do not record temporary
+   port mappings in persistent storage, so if the gateway crashes or is
+   rebooted, all the mappings are lost. A renewal packet is formatted
+   identically to an initial mapping request packet, except that for
+   renewals the client sets the Requested Public Port field to the
+   port the gateway actually assigned, rather than the port the client
+   originally wanted. When a freshly-rebooted NAT gateway receives a
+   renewal packet from a client, it appears to the gateway just like
+   an ordinary initial request for a port mapping, except that in this
+   case the Requested Public Port is likely to be one that the NAT
+   gateway *is* willing to allocate (it allocated it to this client
+   right before the reboot, so it should presumably be willing to
+   allocate it again).
+
+   The RECOMMENDED Port Mapping Lifetime is 3600 seconds.
+
+
+
+
+
+Expires 14th March 2007        Cheshire, et al.                 [Page 6]
+
+Internet Draft          NAT Port Mapping Protocol    14th September 2006
+
+
+   After sending the port mapping request, the client then waits for the
+   NAT gateway to respond. If after 250ms, the gateway doesn't respond,
+   the client SHOULD re-issue its request as described above in Section
+   3.1 "Requests and Responses".
+
+   The NAT gateway responds with the following packet format:
+
+    0                   1                   2                   3
+    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   | Vers = 0      | OP = 128 + x  | Result Code                   |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   | Seconds Since Start of Epoch                                  |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   | Private Port                  | Mapped Public Port            |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+   | Port Mapping Lifetime in Seconds                              |
+   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+   The 'x' in the OP field MUST match what the client requested. Some
+   NAT gateways are incapable of creating a UDP port mapping without
+   also creating a corresponding TCP port mapping, and vice versa, and
+   these gateways MUST NOT implement NAT Port Mapping Protocol until
+   this deficiency is fixed. A NAT gateway which implements this
+   protocol MUST be able to create TCP-only and UDP-only port mappings. 
+
+   If a NAT gateway silently creates a pair of mappings for a client
+   that only requested one mapping, then it may expose that client to
+   receiving inbound UDP packets or inbound TCP connection requests
+   that it did not ask for and does not want.
+
+   While a NAT gateway MUST NOT automatically create mappings for TCP
+   when the client requests UDP, and vice versa, the NAT gateway MUST
+   reserve the companion port so the same client can choose to map it
+   in the future. For example, if a client requests to map TCP port 80,
+   as long as the client maintains the lease for that TCP port mapping,
+   another client with a different IP address MUST NOT be able to
+   successfully acquire the mapping for UDP port 80.
+
+   The client normally requests the public port matching the private
+   port. If that public port is not available, the NAT gateway MUST
+   return a public port that is available or return an error code if
+   no ports are available.
+
+   The source address of the packet MUST be used for the private address
+   in the mapping. This protocol is not intended to facilitate one
+   device behind a NAT creating mappings for other devices. If there
+   are legacy devices that require inbound mappings, permanent mappings
+   can be created manually by the administrator, just as they are today.
+
+
+
+
+Expires 14th March 2007        Cheshire, et al.                 [Page 7]
+
+Internet Draft          NAT Port Mapping Protocol    14th September 2006
+
+
+   If a mapping already exists for a given private port on a given local
+   client (whether that mapping was created explicitly using NAT-PMP,
+   implicitly as a result of an outgoing TCP SYN packet, or manually by
+   a human administrator) and that client requests another mapping for
+   the same private port (possibly requesting a different public port)
+   then the mapping request should succeed, returning the already-
+   assigned public port. This is necessary to handle the case where
+   a client requests a mapping with requested public port X, and is
+   granted a mapping with actual public port Y, but the acknowledgement
+   packet gets lost. When the client retransmits its mapping request,
+   it should get back the same positive acknowledgement as was sent (and
+   lost) the first time.
+
+   The NAT gateway SHOULD NOT accept mapping requests destined to the
+   NAT gateway's public IP address or received on its public network
+   interface. Only packets received on the private interface(s) with
+   a destination address matching the private address(es) of the NAT
+   gateway should be allowed.
+
+   The NAT gateway MUST fill in the "Seconds Since Start of Epoch" field
+   with the time elapsed since its port mapping table was initialized on
+   startup or reset for any other reason (see Section 3.6 "Seconds Since
+   Start of Epoch").
+
+   The Port Mapping Lifetime is an unsigned integer in seconds. The NAT
+   gateway MAY reduce the lifetime from what the client requested. The
+   NAT gateway SHOULD NOT offer a lease lifetime greater than that
+   requested by the client.
+
+   Upon receiving the response packet, the client MUST check the source
+   IP address, and silently discard the packet if the address is not the
+   address of the gateway to which the request was sent.
+
+   The client SHOULD begin trying to renew the mapping halfway to expiry
+   time, like DHCP. The renewal packet should look exactly the same as
+   a request packet, except that the client SHOULD set the requested
+   public port to what the NAT gateway previously mapped, not what the
+   client originally requested. As described above, this enables the
+   gateway to automatically recover its mapping state after a crash or
+   reboot.
+
+
+3.4 Destroying a Mapping
+
+   A mapping may be destroyed in a variety of ways. If a client fails
+   to renew a mapping, then when its lifetime expires the mapping MUST
+   be automatically deleted. In the common case where the gateway
+   device is a combined DHCP server and NAT gateway, when a client's
+   DHCP address lease expires, the gateway device MAY automatically
+   delete any mappings belonging to that client. Otherwise a new client
+   being assigned the same IP address could receive unexpected inbound
+
+
+Expires 14th March 2007        Cheshire, et al.                 [Page 8]
+
+Internet Draft          NAT Port Mapping Protocol    14th September 2006
+
+
+   UDP packets or inbound TCP connection requests that it did not ask
+   for and does not want.
+
+   A client MAY also send an explicit packet to request deletion of a
+   mapping that is no longer needed. A client requests explicit
+   deletion of a mapping by sending a message to the NAT gateway
+   requesting the mapping, with the Requested Lifetime in Seconds set
+   to 0. The requested public port MUST be set to zero by the client
+   on sending, and MUST be ignored by the gateway on reception.
+
+   When a mapping is destroyed successfully as a result of the client
+   explicitly requesting the deletion, the NAT gateway MUST send a
+   response packet which is formatted as defined in section 3.3
+   "Creating a Mapping". The response MUST contain a result code of 0,
+   the private port as indicated in the deletion request, a public port
+   of 0, and a lifetime of 0. The NAT gateway MUST respond to a request
+   to destroy a mapping that does not exist as if the request were
+   successful. This is because of the case where the acknowledgement is
+   lost, and the client retransmits its request to delete the mapping. 
+   In this case the second request to delete the mapping MUST return the
+   same response packet as the first request.
+
+   If the deletion request was unsuccessful, the response MUST contain a
+   non-zero result code and the requested mapping; the lifetime is
+   undefined (MUST be set to zero on transmission, and MUST be ignored
+   on reception). If the client attempts to delete a port mapping which
+   was manually assigned by some kind of configuration tool, the NAT
+   gateway MUST respond with a 'Not Authorized' error, result code 2.
+
+   When a mapping is destroyed as a result of its lifetime expiring or
+   for any other reason, if the NAT gateway's internal state indicates
+   that there are still active TCP connections traversing that now-
+   defunct mapping, then the NAT gateway SHOULD send appropriately-
+   constructed TCP RST (reset) packets both to the local client and to
+   the remote peer on the Internet to terminate that TCP connection.
+
+   A client can request the explicit deletion of all its UDP or TCP
+   mappings by sending the same deletion request to the NAT gateway
+   with public port, private port, and lifetime set to 0. A client MAY
+   choose to do this when it first acquires a new IP address in order to
+   protect itself from port mappings that were performed by a previous
+   owner of the IP address. After receiving such a deletion request,
+   the gateway MUST delete all its UDP or TCP port mappings (depending
+   on the opcode). The gateway responds to such a deletion request with
+   a response as described above, with the private port set to zero. If
+   the gateway is unable to delete a port mapping, for example, because
+   the mapping was manually configured by the administrator, the gateway
+   MUST still delete as many port mappings as possible, but respond with
+   a non-zero result code. The exact result code to return depends on
+   the cause of the failure. If the gateway is able to successfully
+   delete all port mappings as requested, it MUST respond with a result
+   code of 0.
+
+Expires 14th March 2007        Cheshire, et al.                 [Page 9]
+
+Internet Draft          NAT Port Mapping Protocol    14th September 2006
+
+
+3.5 Result Codes
+
+   Currently defined result codes:
+   0 - Success
+   1 - Unsupported Version
+   2 - Not Authorized/Refused
+       (e.g. box supports mapping, but user has turned feature off)
+   3 - Network Failure
+       (e.g. NAT box itself has not obtained a DHCP lease)
+   4 - Out of resources
+       (NAT box cannot create any more mappings at this time)
+   5 - Unsupported opcode
+
+   If the result code is non-zero, the format of the packet following
+   the result code may be truncated. For example, if the client sends a
+   request to the server with an opcode of 17 and the server does not
+   recognize that opcode, the server SHOULD respond with a message where
+   the opcode is 17 + 128 and the result code is 5 (opcode not
+   supported). Since the server does not understand the format of
+   opcode 17, it may not know what to place after the result code. In
+   some cases, relevant data may follow the opcode to identify the
+   operation that failed. For example, a client may request a mapping
+   but that mapping may fail due to resource exhaustion. The server
+   SHOULD respond with the result code to indicate resource exhaustion
+   (4) followed by the requested port mapping so the client may identify
+   which operation failed.
+
+   Clients MUST be able to properly handle result codes not defined in
+   this document. Undefined results codes MUST be treated as fatal
+   errors of the request.
+
+
+3.6 Seconds Since Start of Epoch
+
+   Every packet sent by the NAT gateway includes a "Seconds since start
+   of epoch" field (SSSOE). If the NAT gateway resets or loses the
+   state of its port mapping table, due to reboot, power failure, or any
+   other reason, it MUST reset its epoch time and begin counting SSSOE
+   from 0 again. Whenever a client receives any packet from the NAT
+   gateway, either gratuitously or in response to a client request, the
+   client computes its own conservative estimate of the expected SSSOE
+   value by taking the SSSOE value in the last packet it received from
+   the gateway and adding 7/8 (87.5%) of the time elapsed since that
+   packet was received. If the SSSOE in the newly received packet is
+   less than the client's conservative estimate by more than one second,
+   then the client concludes that the NAT gateway has undergone a reboot
+   or other loss of port mapping state, and the client MUST immediately
+   renew all its active port mapping leases as described in Section 3.7
+   "Recreating Mappings On NAT Gateway Reboot".
+
+
+
+
+Expires 14th March 2007        Cheshire, et al.                [Page 10]
+
+Internet Draft          NAT Port Mapping Protocol    14th September 2006
+
+
+3.7 Recreating Mappings On NAT Gateway Reboot
+
+   The NAT gateway MAY store mappings in persistent storage so when it
+   is powered off or rebooted, it remembers the port mapping state of
+   the network.
+
+   However, maintaining this state is not essential for correct
+   operation. When the NAT gateway powers on or clears its port mapping
+   state as the result of a configuration change, it MUST reset the
+   epoch time and re-announce its IP address as described in Section
+   3.2.1 "Announcing Address Changes". Reception of this packet where
+   time has apparently gone backwards serves as a hint to clients
+   on the network that they SHOULD immediately send renewal packets
+   (to immediately recreate their mappings) instead of waiting until
+   the originally scheduled time for those renewals. Clients who miss
+   receiving those gateway announcement packets for any reason will
+   still renew their mappings at the originally scheduled time and cause
+   their mappings to be recreated; it will just take a little longer for
+   these clients.
+
+   A mapping renewal packet is formatted identically to an original
+   mapping request; from the point of view of the client it is a
+   renewal of an existing mapping, but from the point of view of the
+   freshly-rebooted NAT gateway it appears as a new mapping request.
+
+   This self-healing property of the protocol is very important.
+
+   The remarkable reliability of the Internet as a whole derives
+   in large part from the fact that important state is held in the
+   endpoints, not in the network itself [ETEAISD]. Power-cycling an
+   Ethernet switch results only in a brief interruption in the flow
+   of packets; established TCP connections through that switch are not
+   broken, merely delayed for a few seconds. Indeed, an old Ethernet
+   switch can even be replaced with a new one, and as long as the cables
+   are transferred over reasonably quickly, after the upgrade all the
+   TCP connections that were previously going though the old switch will
+   be unbroken and now going through the new one. The same is true of
+   IP routers, wireless base stations, etc. The one exception is NAT
+   gateways. Because the port mapping state is required for the NAT
+   gateway to know where to forward inbound packets, loss of that state
+   breaks connectivity through the NAT gateway. By allowing clients to
+   detect when this loss of NAT gateway state has occurred, and recreate
+   it on demand, we turn hard state in the network into soft state, and
+   allow it to be recovered automatically when needed.
+
+   Without this automatic recreation of soft state in the NAT gateway,
+   reliable long-term networking would not be achieved. As mentioned
+   above, the reliability of the Internet does not come from trying
+   to build a perfect network in which errors never happen, but from
+   accepting that in any sufficiently large system there will always be
+   some component somewhere that's failing, and designing mechanisms
+
+
+Expires 14th March 2007        Cheshire, et al.                [Page 11]
+
+Internet Draft          NAT Port Mapping Protocol    14th September 2006
+
+
+   that can handle those failures and recover. To illustrate this point
+   with an example, consider the following scenario: Imagine a network
+   security camera that has a web interface and accepts incoming
+   connections from web browser clients. Imagine this network security
+   camera uses NAT-PMP or a similar protocol to set up an inbound
+   port mapping in the NAT gateway so that it can receive incoming
+   connections from clients the other side of the NAT gateway.
+   Now, this camera may well operate for weeks, months, or even years.
+   During that time it's possible that the NAT gateway could experience
+   a power failure or be rebooted. The user could upgrade the NAT
+   gateway's firmware, or even replace the entire NAT gateway device
+   with a newer model. The general point is that if the camera operates
+   for a long enough period of time, some kind of disruption to the NAT
+   gateway becomes inevitable. The question is not whether the NAT
+   gateway will lose its port mappings, but when, and how often.
+   If the network camera and devices like it on the network can detect
+   when the NAT gateway has lost its port mappings, and recreate them
+   automatically, then these disruptions are self-correcting and
+   invisible to the end user. If, on the other hand, the disruptions are
+   not self-correcting, and after a NAT gateway reboot the user has to
+   manually reset or reboot all the other devices on the network too,
+   then these disruptions are *very* visible to the end user. This
+   aspect of the design is what makes the difference between a protocol
+   that keeps on working indefinitely over a time scale of months or
+   years, and a protocol that works in brief testing, but in the real
+   world is continually failing and requiring manual intervention to get
+   it going again.
+
+   When a client renews its port mappings as the result of receiving
+   a packet where the "Seconds since start of epoch" field (SSSOE)
+   indicates that a reboot or similar loss of state has occurred,
+   the client MUST first delay by a random amount of time selected
+   with uniform random distribution in the range 0 to 5 seconds, and
+   then send its first port mapping request. After that request is
+   acknowledged by the gateway, the client may then send its second
+   request, and so on, as rapidly as the gateway allows. The requests
+   SHOULD be issued serially, one at a time; the client SHOULD NOT issue
+   multiple requests simultaneously in parallel.
+
+   The discussion in this section focusses on recreating inbound port
+   mappings after loss of NAT gateway state, because that is the more
+   serious problem. Losing port mappings for outgoing connections
+   destroys those currently active connections, but does not prevent
+   clients from establishing new outgoing connections. In contrast,
+   losing inbound port mappings not only destroys all existing inbound
+   connections, but also prevents the reception of any new inbound
+   connections until the port mapping is recreated. Accordingly,
+   we consider recovery of inbound port mappings the more important
+   priority. However, clients that want outgoing connections to survive
+   a NAT gateway reboot can also achieve that using NAT-PMP. After
+   initiating an outbound TCP connection (which will cause the NAT
+
+
+Expires 14th March 2007        Cheshire, et al.                [Page 12]
+
+Internet Draft          NAT Port Mapping Protocol    14th September 2006
+
+
+   gateway to establish an implicit port mapping) the client should send
+   the NAT gateway a port mapping request for the source port of its TCP
+   connection, which will cause the NAT gateway to send a response
+   giving the public port it allocated for that mapping. The client can
+   then store this information, and use later to recreate the mapping
+   if it determines that the NAT gateway has lost its mapping state.
+
+
+3.8 NAT Gateways with NAT Function Disabled
+
+   Note that *only* devices currently acting in the role of NAT gateway
+   should participate in NAT-PMP protocol exchanges with clients.
+   A network device that is capable of NAT (and NAT-PMP), but is
+   currently configured not to perform that function, (e.g. it is
+   acting as a traditional IP router, forwarding packets without
+   modifying them), MUST NOT respond to NAT-PMP requests from clients,
+   or send spontaneous NAT-PMP address-change announcements.
+
+   In particular, a network device not currently acting in the role of
+   NAT gateway should not even respond to NAT-PMP requests by returning
+   an error code such as "2 - Not Authorized/Refused", since to do so
+   is misleading to clients -- it suggests that NAT port mapping is
+   necessary on this network for the client to successfully receive
+   inbound connections, but is not available because the administrator
+   has chosen to disable that functionality.
+
+   Clients should also be careful to avoid making unfounded assumptions,
+   such as the assumption that if the client has an IPv4 address in
+   one of the RFC 1918 private IP address ranges then that means
+   NAT necessarily must be in use. Net 10/8 has enough addresses
+   to build a private network with millions of hosts and thousands
+   of interconnected subnets, all without any use of NAT. Many
+   organizations have built such private networks that benefit from
+   using standard TCP/IP technology, but by choice do not connect
+   to the public Internet. The purpose of NAT-PMP is to mitigate some
+   of the damage caused by NAT. It would be an ironic and unwanted
+   side-effect of this protocol if it were to lead well-meaning but
+   misguided developers to create products that refuse to work on a
+   private network *unless* they can find a NAT gateway to talk to.
+   Consequently, a client finding that NAT-PMP is not available on its
+   network should not give up, but should proceed on the assumption
+   that the network may be a traditional routed IP network, with no
+   address translation being used. This assumption may not always be
+   true, but it is better than the alternative of falsely assuming
+   the worst and not even trying to use normal (non-NAT) IP networking.
+
+   If a network device not currently acting in the role of NAT gateway
+   receives UDP packets addressed to port 5351, it SHOULD respond
+   immediately with an "ICMP Port Unreachable" message to tell the
+   client that it needn't continue with timeouts and retransmissions,
+   and it should assume that NAT-PMP is not available and not needed
+   on this network.
+
+Expires 14th March 2007        Cheshire, et al.                [Page 13]
+
+Internet Draft          NAT Port Mapping Protocol    14th September 2006
+
+
+4. UNSAF Considerations
+
+   The document "IAB Considerations for UNSAF Across NAT" [RFC 3424]
+   covers a number of issues when working with NATs. RFC 3424 outlines
+   some requirements for any document that attempts to work around
+   problems associated with NATs. This section addresses those
+   requirements.
+
+
+4.1 Scope
+
+   This protocol addresses the needs of TCP and UDP transport peers that
+   are separated from the public internet by exactly one NAT. Such
+   peers must have access to some form of directory server for
+   registering the public IP address and port at which they can be
+   reached.
+
+
+4.2 Transition Plan
+
+   Any client making use of this protocol SHOULD implement IPv6 support.
+   If a client supports IPv6 and is running on a device with a global
+   IPv6 address, that IPv6 address SHOULD be preferred to the IPv4
+   public address using this NAT mapping protocol. In case other
+   clients do not have IPv6 connectivity, both the IPv4 and IPv6
+   addresses SHOULD be registered with whatever form of directory server
+   is used. Preference SHOULD be given to IPv6 addresses when
+   available. By implementing support for IPv6 and using this protocol
+   for IPv4, vendors can ship products today that will work under both
+   scenarios. As IPv6 is more widely deployed, clients of this protocol
+   following these recommendations will transparently make use of IPv6.
+
+
+4.3 Failure Cases
+
+   Aside from NATs that do not implement this protocol, there are a
+   number of situations where this protocol may not work.
+
+
+4.3.1 NAT Behind NAT
+
+   Some people's primary IP address, assigned by their ISP, may itself
+   be a NAT address. In addition, some people may have a public IP
+   address, but may then double NAT themselves, perhaps by choice or
+   perhaps by accident. Although it might be possible in principle for
+   one NAT gateway to recursively request a mapping from the next one,
+   this document does not advocate that and does not try to prescribe
+   how it would be done.
+
+   It would be a lot of work to implement nested NAT port mapping
+   correctly, and there are a number of reasons why the end result might
+
+
+Expires 14th March 2007        Cheshire, et al.                [Page 14]
+
+Internet Draft          NAT Port Mapping Protocol    14th September 2006
+
+
+   not be as useful as we might hope. Consider the case of an ISP that
+   offers each of its customers only a single NAT address. This ISP
+   could instead have chosen to provide each customer with a single
+   public IP address, or, if the ISP insists on running NAT, it could
+   have chosen to allow each customer a reasonable number of addresses,
+   enough for each customer device to have its own NAT address directly
+   from the ISP. If instead this ISP chooses to allow each customer
+   just one and only one NAT address, forcing said customer to run
+   nested NAT in order to use more than one device, it seems unlikely
+   that such an ISP would be so obliging as to provide a NAT service
+   that supports NAT Port Mapping Protocol. Supposing that such an ISP
+   did wish to offer its customers NAT service with NAT-PMP so as to
+   give them the ability to receive inbound connections, this ISP could
+   easily choose to allow each client to request a reasonable number of
+   DHCP addresses from that gateway. Remember that Net 10/8 [RFC 1918]
+   allows for over 16 million addresses, so NAT addresses are not in any
+   way in short supply. A single NAT gateway with 16 million available
+   addresses is likely to run out of packet forwarding capacity before
+   it runs out of private addresses to hand out. In this way the ISP
+   could offer single-level NAT with NAT-PMP, obviating the need to
+   support nested NAT-PMP. In addition, an ISP that is motivated to
+   provide their customers with unhindered access to the Internet by
+   allowing incoming as well as outgoing connections has better ways
+   to offer this service. Such an ISP could offer its customers real
+   public IP addresses instead of NAT addresses, or could even choose
+   to offer its customers full IPv6 connectivity, where no mapping or
+   translation is required at all.
+
+
+4.3.2 NATs with Multiple Public IP Addresses
+
+   If a NAT maps private addresses to multiple public addresses,
+   then it SHOULD pick one of those addresses as the one it will
+   support for inbound connections, and for the purposes of this
+   protocol it SHOULD act as if that address were its only address.
+
+
+4.3.3 NATs and Routed Private Networks
+
+   In some cases, a large network may be subnetted. Some sites
+   may install a NAT gateway and subnet the private network.
+   Such subnetting breaks this protocol because the router address
+   is not necessarily the address of the device performing NAT.
+
+   Addressing this problem is not a high priority. Any site with the
+   resources to set up such a configuration should have the resources to
+   add manual mappings or attain a range of globally unique addresses.
+
+   Not all NATs will support this protocol. In the case where a client
+   is run behind a NAT that does not support this protocol, the software
+   relying on the functionality of this protocol may be unusable.
+
+
+Expires 14th March 2007        Cheshire, et al.                [Page 15]
+
+Internet Draft          NAT Port Mapping Protocol    14th September 2006
+
+
+4.3.4 Communication Between Hosts Behind the Same NAT
+
+   NAT gateways supporting NAT-PMP should also implement "hairpin
+   translation". Hairpin translation means supporting communication
+   between two local clients being served by the same NAT gateway.
+
+   Suppose device A is listening on private address and port 10.0.0.2:80
+   for incoming connections. Using NAT-PMP, device A has obtained a
+   mapping to public address and port x.x.x.x:80, and has recorded this
+   public address and port in a public directory of some kind. For
+   example, it could have created a DNS SRV record containing this
+   information, and recorded it, using DNS Dynamic Update [RFC 3007], in
+   a publicly accessible DNS server. Suppose then that device B, behind
+   the same NAT gateway as device A, but unknowing or uncaring of this
+   fact, retrieves device A's DNS SRV record and attempts to open a TCP
+   connection to x.x.x.x:80. The outgoing packets addressed to this
+   public Internet address will be sent to the NAT gateway for
+   translation and forwarding. Having translated the source address and
+   port number on the outgoing packet, the NAT gateway needs to be smart
+   enough to recognize that the destination address is in fact itself,
+   and then feed this packet back into its packet reception engine, to
+   perform the destination port mapping lookup to translate and forward
+   this packet to device A at address and port 10.0.0.2:80.
+
+4.3.5 Non UDP/TCP Transport Traffic
+
+   Any communication over transport protocols other than TCP and UDP
+   will not be served by this protocol. Examples are Generic Routing
+   Encapsulation (GRE), Authentication Header (AH) and Encapsulating
+   Security Payload (ESP).
+
+4.4 Long Term Solution
+
+   As IPv6 is deployed, clients of this protocol supporting IPv6 will be
+   able to bypass this protocol and the NAT when communicating with
+   other IPv6 devices. In order to ensure this transition, any client
+   implementing this protocol SHOULD also implement IPv6 and use this
+   solution only when IPv6 is not available to both peers.
+
+4.5 Existing Deployed NATs
+
+   Existing deployed NATs will not support this protocol. This protocol
+   will only work with NATs that are upgraded to support it.
+
+
+
+
+
+
+
+
+
+
+Expires 14th March 2007        Cheshire, et al.                [Page 16]
+
+Internet Draft          NAT Port Mapping Protocol    14th September 2006
+
+
+5. Security Considerations
+
+   As discussed in section 3.2 "Determining the Public Address", only
+   clients on the private side of the NAT may create port mappings, and
+   only on behalf of themselves. By using IP address spoofing, it's
+   possible for one client to delete the port mappings of another
+   client. It's also possible for one client to create port mappings on
+   behalf of another client. The best way to deal with this
+   vulnerability is to use IPSec [RFC 2401].
+
+   Since allowing incoming connections is often a policy decision, any
+   NAT gateway implementing this protocol SHOULD have an administrative
+   mechanism to disable it.
+
+   Some people view the property that NATs block inbound connections as
+   a security benefit which is undermined by this protocol. The authors
+   of this document have a different point of view. In the days before
+   NAT, all hosts had unique public IP addresses, and had unhindered
+   ability to communicate with any other host on the Internet. When NAT
+   came along it broke this unhindered connectivity, relegating many
+   hosts to second-class status, unable to receive inbound connections.
+   This protocol goes some way to undo some of that damage. The purpose
+   of a NAT gateway should be to allow several hosts to share a single
+   address, not to simultaneously impede those host's ability to
+   communicate freely. Security is most properly provided by end-to-end
+   cryptographic security, and/or by explicit firewall functionality, as
+   appropriate. Blocking of certain connections should occur only as a
+   result of explicit and intentional firewall policy, not as an
+   accidental side-effect of some other technology.
+
+
+6. IANA Considerations
+
+   No IANA services are required by this document.
+
+
+7. Acknowledgments
+
+   The concepts described in this document have been explored, developed
+   and implemented with help from Bob Bradley, Josh Graessley, Rob
+   Newberry, Roger Pantos, John Saxton, and James Woodyatt.
+
+
+8. Deployment History
+
+   NAT-PMP client software first became available to the public
+   through Apple's Darwin Open Source code in August 2004.
+   NAT-PMP implementations began shipping to end users in large
+   volumes (i.e. millions) with the launch of Mac OS X 10.4 Tiger
+   and Bonjour for Windows 1.0 in April 2005.
+
+
+
+Expires 14th March 2007        Cheshire, et al.                [Page 17]
+
+Internet Draft          NAT Port Mapping Protocol    14th September 2006
+
+
+   The NAT-PMP client in Mac OS X 10.4 Tiger and Bonjour for Windows
+   exists as part of the mDNSResponder system service. When a client
+   advertises a service using Wide Area Bonjour [DNS-SD], and the
+   machine is behind a NAT-PMP-capable NAT gateway, then if the machine
+   is so configured, the mDNSResponder system service automatically uses
+   NAT-PMP to set up an inbound port mapping, and then records the
+   public IP address and port in the global DNS. Existing client
+   software using the existing Bonjour programming APIs [Bonjour]
+   gets this functionality automatically. The logic is that if client
+   software publishes its information into the global DNS via Wide Area
+   Bonjour service advertising, then it's reasonable to infer an
+   expectation that this information should be usable by the peers
+   retrieving it. Generally speaking, recording a private IP address
+   like 10.0.0.2 in the public DNS is completely pointless because that
+   address is not reachable from clients on the other side of the NAT
+   gateway. In the case of a home user with a single computer directly
+   connected to their Cable or DSL modem, with a single global IPv4
+   address and no NAT gateway (a surprisingly common configuration),
+   publishing that IP address into the global DNS is useful because that
+   IP address is reachable. In contrast, a home user using a NAT gateway
+   to share a single global IPv4 address between several computers loses
+   this ability to receive inbound connections easily. This breaks many
+   peer-to-peer collaborative applications, like the multi-user text
+   editor SubEthaEdit [SEE]. Automatically creating the necessary
+   inbound port mappings helps remedy this unintended side-effect of
+   NAT.
+
+   The server side of the NAT-PMP protocol is implemented in Apple's
+   "AirPort Extreme" and "AirPort Express" wireless base stations.
+
+
+9. Copyright Notice
+
+   Copyright (C) The Internet Society (2006).
+
+   This document is subject to the rights, licenses and restrictions
+   contained in BCP 78, and except as set forth therein, the authors
+   retain all their rights. For the purposes of this document,
+   the term "BCP 78" refers exclusively to RFC 3978, "IETF Rights
+   in Contributions", published March 2005.
+
+   This document and the information contained herein are provided on
+   an "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE
+   REPRESENTS OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE
+   INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR
+   IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF
+   THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED
+   WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
+
+
+
+
+
+Expires 14th March 2007        Cheshire, et al.                [Page 18]
+
+Internet Draft          NAT Port Mapping Protocol    14th September 2006
+
+
+10. Normative References
+
+   [RFC 1918] Y. Rekhter et.al., "Address Allocation for Private
+              Internets", RFC 1918, February 1996.
+
+   [RFC 2119] RFC 2119 - Key words for use in RFCs to Indicate
+              Requirement Levels
+
+
+11. Informative References
+
+   [Bonjour]  Apple "Bonjour" <http://developer.apple.com/bonjour/>
+
+   [ETEAISD]  J. Saltzer, D. Reed and D. Clark: "End-to-end arguments in
+              system design", ACM Trans. Comp. Sys., 2(4):277-88, Nov.
+              1984
+
+   [DNS-SD]   Cheshire, S., and M. Krochmal, "DNS-Based Service
+              Discovery", Internet-Draft (work in progress),
+              draft-cheshire-dnsext-dns-sd-04.txt, August 2006.
+
+   [mDNS]     Cheshire, S., and M. Krochmal, "Multicast DNS",
+              Internet-Draft (work in progress),
+              draft-cheshire-dnsext-multicastdns-06.txt, August 2006.
+
+   [RFC 2131] R. Droms, "Dynamic Host Configuration Protocol", RFC 2131,
+              March 1997.
+
+   [RFC 2401] Atkinson, R. and S. Kent, "Security Architecture for the
+              Internet Protocol", RFC 2401, November 1998.
+
+   [RFC 2663] Srisuresh, P. and M. Holdrege, "IP Network Address
+              Translator (NAT) Terminology and Considerations", RFC
+              2663, August 1999.
+
+   [RFC 3007] Wellington, B., "Simple Secure Domain Name System
+              (DNS) Dynamic Update", RFC 3007, November 2000.
+
+   [SEE]      <http://www.codingmonkeys.de/subethaedit/>
+
+   [RFC 3022] RFC 3022 - Network Address Translator
+
+   [RFC 3424] RFC 3424 - IAB Considerations for UNilateral Self-Address
+              Fixing (UNSAF) Across Network Address Translation
+
+
+
+
+
+
+
+
+
+Expires 14th March 2007        Cheshire, et al.                [Page 19]
+
+Internet Draft          NAT Port Mapping Protocol    14th September 2006
+
+
+12. Authors' Addresses
+
+   Stuart Cheshire
+   Apple Computer, Inc.
+   1 Infinite Loop
+   Cupertino
+   California 95014
+   USA
+
+   Phone: +1 408 974 3207
+   EMail: rfc [at] stuartcheshire [dot] org
+
+
+   Marc Krochmal
+   Apple Computer, Inc.
+   1 Infinite Loop
+   Cupertino
+   California 95014
+   USA
+
+   Phone: +1 408 974 4368
+   EMail: marc [at] apple [dot] com
+
+
+   Kiren Sekar
+   Sharpcast, Inc.
+   250 Cambridge Ave, Suite 101
+   Palo Alto
+   California 94306
+   USA
+
+   Phone: +1 650 323 1960
+   EMail: ksekar [at] sharpcast [dot] com
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Expires 14th March 2007        Cheshire, et al.                [Page 20]


Property changes on: GNUnet/src/transports/upnp/draft-cheshire-nat-pmp.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: GNUnet/src/transports/upnp/upnp.c
===================================================================
--- GNUnet/src/transports/upnp/upnp.c   2006-12-27 14:27:24 UTC (rev 4075)
+++ GNUnet/src/transports/upnp/upnp.c   2006-12-27 16:46:00 UTC (rev 4076)
@@ -0,0 +1,982 @@
+/**
+ * @file upnp.c UPnP Implementation
+ * @ingroup core
+ *
+ * gaim
+ *
+ * Gaim is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#include "internal.h"
+
+#include "debug.h"
+#include "util.h"
+#include "proxy.h"
+#include "xmlnode.h"
+#include "network.h"
+#include "eventloop.h"
+#include "upnp.h"
+
+
+/***************************************************************
+** General Defines                                             *
+****************************************************************/
+#define HTTP_OK "200 OK"
+#define DEFAULT_HTTP_PORT 80
+#define DISCOVERY_TIMEOUT 1000
+
+/***************************************************************
+** Discovery/Description Defines                               *
+****************************************************************/
+#define NUM_UDP_ATTEMPTS 2
+
+/* Address and port of an SSDP request used for discovery */
+#define HTTPMU_HOST_ADDRESS "239.255.255.250"
+#define HTTPMU_HOST_PORT 1900
+
+#define SEARCH_REQUEST_DEVICE "urn:schemas-upnp-org:service:%s"
+
+#define SEARCH_REQUEST_STRING \
+       "M-SEARCH * HTTP/1.1\r\n" \
+       "MX: 2\r\n" \
+       "HOST: 239.255.255.250:1900\r\n" \
+       "MAN: \"ssdp:discover\"\r\n" \
+       "ST: urn:schemas-upnp-org:service:%s\r\n" \
+       "\r\n"
+
+#define WAN_IP_CONN_SERVICE "WANIPConnection:1"
+#define WAN_PPP_CONN_SERVICE "WANPPPConnection:1"
+
+/******************************************************************
+** Action Defines                                                 *
+*******************************************************************/
+#define HTTP_HEADER_ACTION \
+       "POST /%s HTTP/1.1\r\n" \
+       "HOST: %s:%d\r\n" \
+       "SOAPACTION: \"urn:schemas-upnp-org:service:%s#%s\"\r\n" \
+       "CONTENT-TYPE: text/xml ; charset=\"utf-8\"\r\n" \
+       "CONTENT-LENGTH: %" G_GSIZE_FORMAT "\r\n\r\n"
+
+#define SOAP_ACTION \
+       "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" \
+       "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"; " \
+               
"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\";>\r\n" \
+         "<s:Body>\r\n" \
+           "<u:%s xmlns:u=\"urn:schemas-upnp-org:service:%s\">\r\n" \
+             "%s" \
+           "</u:%s>\r\n" \
+         "</s:Body>\r\n" \
+       "</s:Envelope>"
+
+#define PORT_MAPPING_LEASE_TIME "0"
+#define PORT_MAPPING_DESCRIPTION "GAIM_UPNP_PORT_FORWARD"
+
+#define ADD_PORT_MAPPING_PARAMS \
+       "<NewRemoteHost></NewRemoteHost>\r\n" \
+       "<NewExternalPort>%i</NewExternalPort>\r\n" \
+       "<NewProtocol>%s</NewProtocol>\r\n" \
+       "<NewInternalPort>%i</NewInternalPort>\r\n" \
+       "<NewInternalClient>%s</NewInternalClient>\r\n" \
+       "<NewEnabled>1</NewEnabled>\r\n" \
+       "<NewPortMappingDescription>" \
+       PORT_MAPPING_DESCRIPTION \
+       "</NewPortMappingDescription>\r\n" \
+       "<NewLeaseDuration>" \
+       PORT_MAPPING_LEASE_TIME \
+       "</NewLeaseDuration>\r\n"
+
+#define DELETE_PORT_MAPPING_PARAMS \
+       "<NewRemoteHost></NewRemoteHost>\r\n" \
+       "<NewExternalPort>%i</NewExternalPort>\r\n" \
+       "<NewProtocol>%s</NewProtocol>\r\n"
+
+typedef enum {
+       GAIM_UPNP_STATUS_UNDISCOVERED = -1,
+       GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER,
+       GAIM_UPNP_STATUS_DISCOVERING,
+       GAIM_UPNP_STATUS_DISCOVERED
+} GaimUPnPStatus;
+
+typedef struct {
+       GaimUPnPStatus status;
+       gchar* control_url;
+       gchar service_type[20];
+       char publicip[16];
+       char internalip[16];
+       time_t lookup_time;
+} GaimUPnPControlInfo;
+
+typedef struct {
+       guint inpa;     /* gaim_input_add handle */
+       guint tima;     /* gaim_timeout_add handle */
+       int fd;
+       struct sockaddr_in server;
+       gchar service_type[25];
+       int retry_count;
+       gchar *full_url;
+} UPnPDiscoveryData;
+
+typedef struct {
+       unsigned short portmap;
+       gchar protocol[4];
+       gboolean add;
+       GaimUPnPCallback cb;
+       gpointer cb_data;
+} UPnPMappingAddRemove;
+
+static GaimUPnPControlInfo control_info = {
+       GAIM_UPNP_STATUS_UNDISCOVERED,
+       NULL, "\0", "\0", "\0", 0};
+
+static GSList *discovery_callbacks = NULL;
+
+static void gaim_upnp_discover_send_broadcast(UPnPDiscoveryData *dd);
+static void lookup_public_ip(void);
+static void lookup_internal_ip(void);
+
+static void
+fire_discovery_callbacks(gboolean success)
+{
+       while(discovery_callbacks) {
+               gpointer data;
+               GaimUPnPCallback cb = discovery_callbacks->data;
+               discovery_callbacks = g_slist_remove(discovery_callbacks, cb);
+               data = discovery_callbacks->data;
+               discovery_callbacks = g_slist_remove(discovery_callbacks, data);
+               cb(success, data);
+       }
+}
+
+static gboolean
+gaim_upnp_compare_device(const xmlnode* device, const gchar* deviceType)
+{
+       xmlnode* deviceTypeNode = xmlnode_get_child(device, "deviceType");
+       char *tmp;
+       gboolean ret;
+
+       if(deviceTypeNode == NULL) {
+               return FALSE;
+       }
+
+       tmp = xmlnode_get_data(deviceTypeNode);
+       ret = !g_ascii_strcasecmp(tmp, deviceType);
+       g_free(tmp);
+
+       return ret;
+}
+
+static gboolean
+gaim_upnp_compare_service(const xmlnode* service, const gchar* serviceType)
+{
+       xmlnode* serviceTypeNode;
+       char *tmp;
+       gboolean ret;
+
+       if(service == NULL) {
+               return FALSE;
+       }
+
+       serviceTypeNode = xmlnode_get_child(service, "serviceType");
+
+       if(serviceTypeNode == NULL) {
+               return FALSE;
+       }
+
+       tmp = xmlnode_get_data(serviceTypeNode);
+       ret = !g_ascii_strcasecmp(tmp, serviceType);
+       g_free(tmp);
+
+       return ret;
+}
+
+static gchar*
+gaim_upnp_parse_description_response(const gchar* httpResponse, gsize len,
+       const gchar* httpURL, const gchar* serviceType)
+{
+       gchar *xmlRoot, *baseURL, *controlURL, *service;
+       xmlnode *xmlRootNode, *serviceTypeNode, *controlURLNode, *baseURLNode;
+       char *tmp;
+
+       /* make sure we have a valid http response */
+       if(g_strstr_len(httpResponse, len, HTTP_OK) == NULL) {
+               gaim_debug_error("upnp",
+                       "parse_description_response(): Failed In HTTP_OK\n");
+               return NULL;
+       }
+
+       /* find the root of the xml document */
+       if((xmlRoot = g_strstr_len(httpResponse, len, "<root")) == NULL) {
+               gaim_debug_error("upnp",
+                       "parse_description_response(): Failed finding root\n");
+               return NULL;
+       }
+
+       /* create the xml root node */
+       if((xmlRootNode = xmlnode_from_str(xmlRoot,
+                       len - (xmlRoot - httpResponse))) == NULL) {
+               gaim_debug_error("upnp",
+                       "parse_description_response(): Could not parse xml root 
node\n");
+               return NULL;
+       }
+
+       /* get the baseURL of the device */
+       if((baseURLNode = xmlnode_get_child(xmlRootNode, "URLBase")) != NULL) {
+               baseURL = xmlnode_get_data(baseURLNode);
+       } else {
+               baseURL = g_strdup(httpURL);
+       }
+
+       /* get the serviceType child that has the service type as its data */
+
+       /* get urn:schemas-upnp-org:device:InternetGatewayDevice:1 and its 
devicelist */
+       serviceTypeNode = xmlnode_get_child(xmlRootNode, "device");
+       while(!gaim_upnp_compare_device(serviceTypeNode,
+                       "urn:schemas-upnp-org:device:InternetGatewayDevice:1") 
&&
+                       serviceTypeNode != NULL) {
+               serviceTypeNode = xmlnode_get_next_twin(serviceTypeNode);
+       }
+       if(serviceTypeNode == NULL) {
+               gaim_debug_error("upnp",
+                       "parse_description_response(): could not get 
serviceTypeNode 1\n");
+               g_free(baseURL);
+               xmlnode_free(xmlRootNode);
+               return NULL;
+       }
+       serviceTypeNode = xmlnode_get_child(serviceTypeNode, "deviceList");
+       if(serviceTypeNode == NULL) {
+               gaim_debug_error("upnp",
+                       "parse_description_response(): could not get 
serviceTypeNode 2\n");
+               g_free(baseURL);
+               xmlnode_free(xmlRootNode);
+               return NULL;
+       }
+
+       /* get urn:schemas-upnp-org:device:WANDevice:1 and its devicelist */
+       serviceTypeNode = xmlnode_get_child(serviceTypeNode, "device");
+       while(!gaim_upnp_compare_device(serviceTypeNode,
+                       "urn:schemas-upnp-org:device:WANDevice:1") &&
+                       serviceTypeNode != NULL) {
+               serviceTypeNode = xmlnode_get_next_twin(serviceTypeNode);
+       }
+       if(serviceTypeNode == NULL) {
+               gaim_debug_error("upnp",
+                       "parse_description_response(): could not get 
serviceTypeNode 3\n");
+               g_free(baseURL);
+               xmlnode_free(xmlRootNode);
+               return NULL;
+       }
+       serviceTypeNode = xmlnode_get_child(serviceTypeNode, "deviceList");
+       if(serviceTypeNode == NULL) {
+               gaim_debug_error("upnp",
+                       "parse_description_response(): could not get 
serviceTypeNode 4\n");
+               g_free(baseURL);
+               xmlnode_free(xmlRootNode);
+               return NULL;
+       }
+
+       /* get urn:schemas-upnp-org:device:WANConnectionDevice:1 and its 
servicelist */
+       serviceTypeNode = xmlnode_get_child(serviceTypeNode, "device");
+       while(serviceTypeNode && !gaim_upnp_compare_device(serviceTypeNode,
+                       "urn:schemas-upnp-org:device:WANConnectionDevice:1")) {
+               serviceTypeNode = xmlnode_get_next_twin(serviceTypeNode);
+       }
+       if(serviceTypeNode == NULL) {
+               gaim_debug_error("upnp",
+                       "parse_description_response(): could not get 
serviceTypeNode 5\n");
+               g_free(baseURL);
+               xmlnode_free(xmlRootNode);
+               return NULL;
+       }
+       serviceTypeNode = xmlnode_get_child(serviceTypeNode, "serviceList");
+       if(serviceTypeNode == NULL) {
+               gaim_debug_error("upnp",
+                       "parse_description_response(): could not get 
serviceTypeNode 6\n");
+               g_free(baseURL);
+               xmlnode_free(xmlRootNode);
+               return NULL;
+       }
+
+       /* get the serviceType variable passed to this function */
+       service = g_strdup_printf(SEARCH_REQUEST_DEVICE, serviceType);
+       serviceTypeNode = xmlnode_get_child(serviceTypeNode, "service");
+       while(!gaim_upnp_compare_service(serviceTypeNode, service) &&
+                       serviceTypeNode != NULL) {
+               serviceTypeNode = xmlnode_get_next_twin(serviceTypeNode);
+       }
+
+       g_free(service);
+       if(serviceTypeNode == NULL) {
+               gaim_debug_error("upnp",
+                       "parse_description_response(): could not get 
serviceTypeNode 7\n");
+               g_free(baseURL);
+               xmlnode_free(xmlRootNode);
+               return NULL;
+       }
+
+       /* get the controlURL of the service */
+       if((controlURLNode = xmlnode_get_child(serviceTypeNode,
+                       "controlURL")) == NULL) {
+               gaim_debug_error("upnp",
+                       "parse_description_response(): Could not find 
controlURL\n");
+               g_free(baseURL);
+               xmlnode_free(xmlRootNode);
+               return NULL;
+       }
+
+       tmp = xmlnode_get_data(controlURLNode);
+       if(baseURL && !gaim_str_has_prefix(tmp, "http://";) &&
+          !gaim_str_has_prefix(tmp, "HTTP://")) {
+               controlURL = g_strdup_printf("%s%s", baseURL, tmp);
+               g_free(tmp);
+       }else{
+               controlURL = tmp;
+       }
+       g_free(baseURL);
+       xmlnode_free(xmlRootNode);
+
+       return controlURL;
+}
+
+static void
+upnp_parse_description_cb(GaimUtilFetchUrlData *url_data, gpointer user_data,
+               const gchar *httpResponse, gsize len, const gchar 
*error_message)
+{
+       UPnPDiscoveryData *dd = user_data;
+       gchar *control_url = NULL;
+
+       if (len > 0)
+               control_url = gaim_upnp_parse_description_response(
+                       httpResponse, len, dd->full_url, dd->service_type);
+
+       g_free(dd->full_url);
+
+       if(control_url == NULL) {
+               gaim_debug_error("upnp",
+                       "gaim_upnp_parse_description(): control URL is NULL\n");
+       }
+
+       control_info.status = control_url ? GAIM_UPNP_STATUS_DISCOVERED
+               : GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER;
+       control_info.lookup_time = time(NULL);
+       control_info.control_url = control_url;
+       strncpy(control_info.service_type, dd->service_type,
+               sizeof(control_info.service_type));
+
+       fire_discovery_callbacks(control_url != NULL);
+
+       /* Look up the public and internal IPs */
+       if(control_url != NULL) {
+               lookup_public_ip();
+               lookup_internal_ip();
+       }
+
+       g_free(dd);
+}
+
+static void
+gaim_upnp_parse_description(const gchar* descriptionURL, UPnPDiscoveryData *dd)
+{
+       gchar* httpRequest;
+       gchar* descriptionXMLAddress;
+       gchar* descriptionAddress;
+       int port = 0;
+
+       /* parse the 4 above variables out of the descriptionURL
+          example description URL: http://192.168.1.1:5678/rootDesc.xml */
+
+       /* parse the url into address, port, path variables */
+       if(!gaim_url_parse(descriptionURL, &descriptionAddress,
+                       &port, &descriptionXMLAddress, NULL, NULL)) {
+               return;
+       }
+       if(port == 0 || port == -1) {
+               port = DEFAULT_HTTP_PORT;
+       }
+
+       /* for example...
+          GET /rootDesc.xml HTTP/1.1\r\nHost: 192.168.1.1:5678\r\n\r\n */
+       httpRequest = g_strdup_printf(
+               "GET /%s HTTP/1.1\r\n"
+               "Connection: close\r\n"
+               "Host: %s:%d\r\n\r\n",
+               descriptionXMLAddress, descriptionAddress, port);
+
+       g_free(descriptionXMLAddress);
+
+       dd->full_url = g_strdup_printf("http://%s:%d";,
+                       descriptionAddress, port);
+       g_free(descriptionAddress);
+
+       /* Remove the timeout because everything it is waiting for has
+        * successfully completed */
+       gaim_timeout_remove(dd->tima);
+       dd->tima = 0;
+
+       gaim_util_fetch_url_request(descriptionURL, TRUE, NULL, TRUE, 
httpRequest,
+                       TRUE, upnp_parse_description_cb, dd);
+
+       g_free(httpRequest);
+
+}
+
+static void
+gaim_upnp_parse_discover_response(const gchar* buf, unsigned int buf_len,
+       UPnPDiscoveryData *dd)
+{
+       gchar* startDescURL;
+       gchar* endDescURL;
+       gchar* descURL;
+
+       if(g_strstr_len(buf, buf_len, HTTP_OK) == NULL) {
+               gaim_debug_error("upnp",
+                       "parse_discover_response(): Failed In HTTP_OK\n");
+               return;
+       }
+
+       if((startDescURL = g_strstr_len(buf, buf_len, "http://";)) == NULL) {
+               gaim_debug_error("upnp",
+                       "parse_discover_response(): Failed In finding 
http://\n";);
+               return;
+       }
+
+       endDescURL = g_strstr_len(startDescURL, buf_len - (startDescURL - buf),
+                       "\r");
+       if(endDescURL == NULL) {
+               endDescURL = g_strstr_len(startDescURL,
+                               buf_len - (startDescURL - buf), "\n");
+               if(endDescURL == NULL) {
+                       gaim_debug_error("upnp",
+                               "parse_discover_response(): Failed In 
endDescURL\n");
+                       return;
+               }
+       }
+
+       /* XXX: I'm not sure how this could ever happen */
+       if(endDescURL == startDescURL) {
+               gaim_debug_error("upnp",
+                       "parse_discover_response(): endDescURL == 
startDescURL\n");
+               return;
+       }
+
+       descURL = g_strndup(startDescURL, endDescURL - startDescURL);
+
+       gaim_upnp_parse_description(descURL, dd);
+
+       g_free(descURL);
+
+}
+
+static gboolean
+gaim_upnp_discover_timeout(gpointer data)
+{
+       UPnPDiscoveryData* dd = data;
+
+       if (dd->inpa)
+               gaim_input_remove(dd->inpa);
+       dd->inpa = 0;
+       dd->tima = 0;
+
+       if (dd->retry_count < NUM_UDP_ATTEMPTS) {
+               /* TODO: We probably shouldn't be incrementing retry_count in 
two places */
+               dd->retry_count++;
+               gaim_upnp_discover_send_broadcast(dd);
+       } else {
+               if (dd->fd)
+                       close(dd->fd);
+
+               control_info.status = GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER;
+               control_info.lookup_time = time(NULL);
+               control_info.service_type[0] = '\0';
+               g_free(control_info.control_url);
+               control_info.control_url = NULL;
+
+               fire_discovery_callbacks(FALSE);
+
+               g_free(dd);
+       }
+
+       return FALSE;
+}
+
+static void
+gaim_upnp_discover_udp_read(gpointer data, gint sock, GaimInputCondition cond)
+{
+       int len;
+       UPnPDiscoveryData *dd = data;
+       gchar buf[65536];
+
+       do {
+               len = recv(dd->fd, buf,
+                       sizeof(buf) - 1, 0);
+
+               if(len > 0) {
+                       buf[len] = '\0';
+                       break;
+               } else if(errno != EINTR) {
+                       /* We'll either get called again, or time out */
+                       return;
+               }
+       } while (errno == EINTR);
+
+       gaim_input_remove(dd->inpa);
+       dd->inpa = 0;
+
+       close(dd->fd);
+       dd->fd = 0;
+
+       /* parse the response, and see if it was a success */
+       gaim_upnp_parse_discover_response(buf, len, dd);
+
+       /* We'll either time out or continue successfully */
+}
+
+static void
+gaim_upnp_discover_send_broadcast(UPnPDiscoveryData *dd)
+{
+       gchar *sendMessage = NULL;
+       gsize totalSize;
+       gboolean sentSuccess;
+
+       /* because we are sending over UDP, if there is a failure
+          we should retry the send NUM_UDP_ATTEMPTS times. Also,
+          try different requests for WANIPConnection and WANPPPConnection*/
+       for(; dd->retry_count < NUM_UDP_ATTEMPTS; dd->retry_count++) {
+               sentSuccess = FALSE;
+
+               if((dd->retry_count % 2) == 0) {
+                       strncpy(dd->service_type, WAN_IP_CONN_SERVICE, 
sizeof(dd->service_type));
+               } else {
+                       strncpy(dd->service_type, WAN_PPP_CONN_SERVICE, 
sizeof(dd->service_type));
+               }
+
+               sendMessage = g_strdup_printf(SEARCH_REQUEST_STRING, 
dd->service_type);
+
+               totalSize = strlen(sendMessage);
+
+               do {
+                       if(sendto(dd->fd, sendMessage, totalSize, 0,
+                                       (struct sockaddr*) &(dd->server),
+                                       sizeof(struct sockaddr_in)
+                                       ) == totalSize) {
+                               sentSuccess = TRUE;
+                               break;
+                       }
+               } while (errno == EINTR || errno == EAGAIN);
+
+               g_free(sendMessage);
+
+               if(sentSuccess) {
+                       dd->tima = gaim_timeout_add(DISCOVERY_TIMEOUT,
+                               gaim_upnp_discover_timeout, dd);
+                       dd->inpa = gaim_input_add(dd->fd, GAIM_INPUT_READ,
+                               gaim_upnp_discover_udp_read, dd);
+
+                       return;
+               }
+       }
+
+       /* We have already done all our retries. Make sure that the callback
+        * doesn't get called before the original function returns */
+       gaim_timeout_add(10, gaim_upnp_discover_timeout, dd);
+}
+
+void
+gaim_upnp_discover(GaimUPnPCallback cb, gpointer cb_data)
+{
+       /* Socket Setup Variables */
+       int sock;
+       struct hostent* hp;
+
+       /* UDP RECEIVE VARIABLES */
+       UPnPDiscoveryData *dd;
+
+       if (control_info.status == GAIM_UPNP_STATUS_DISCOVERING) {
+               if (cb) {
+                       discovery_callbacks = g_slist_append(
+                                       discovery_callbacks, cb);
+                       discovery_callbacks = g_slist_append(
+                                       discovery_callbacks, cb_data);
+               }
+               return;
+       }
+
+       dd = g_new0(UPnPDiscoveryData, 1);
+       if (cb) {
+               discovery_callbacks = g_slist_append(discovery_callbacks, cb);
+               discovery_callbacks = g_slist_append(discovery_callbacks,
+                               cb_data);
+       }
+
+       /* Set up the sockets */
+       sock = socket(AF_INET, SOCK_DGRAM, 0);
+       if(sock == -1) {
+               gaim_debug_error("upnp",
+                       "gaim_upnp_discover(): Failed In sock creation\n");
+               /* Short circuit the retry attempts */
+               dd->retry_count = NUM_UDP_ATTEMPTS;
+               gaim_timeout_add(10, gaim_upnp_discover_timeout, dd);
+               return;
+       }
+
+       dd->fd = sock;
+
+       /* TODO: Non-blocking! */
+       if((hp = gethostbyname(HTTPMU_HOST_ADDRESS)) == NULL) {
+               gaim_debug_error("upnp",
+                       "gaim_upnp_discover(): Failed In gethostbyname\n");
+               /* Short circuit the retry attempts */
+               dd->retry_count = NUM_UDP_ATTEMPTS;
+               gaim_timeout_add(10, gaim_upnp_discover_timeout, dd);
+               return;
+       }
+
+       memset(&(dd->server), 0, sizeof(struct sockaddr));
+       dd->server.sin_family = AF_INET;
+       memcpy(&(dd->server.sin_addr), hp->h_addr_list[0], hp->h_length);
+       dd->server.sin_port = htons(HTTPMU_HOST_PORT);
+
+       control_info.status = GAIM_UPNP_STATUS_DISCOVERING;
+
+       gaim_upnp_discover_send_broadcast(dd);
+}
+
+static void
+gaim_upnp_generate_action_message_and_send(const gchar* actionName,
+               const gchar* actionParams, GaimUtilFetchUrlCallback cb,
+               gpointer cb_data)
+{
+
+       gchar* soapMessage;
+       gchar* totalSendMessage;
+       gchar* pathOfControl;
+       gchar* addressOfControl;
+       int port = 0;
+
+       /* parse the url into address, port, path variables */
+       if(!gaim_url_parse(control_info.control_url, &addressOfControl,
+                       &port, &pathOfControl, NULL, NULL)) {
+               gaim_debug_error("upnp",
+                       "generate_action_message_and_send(): Failed In Parse 
URL\n");
+               /* XXX: This should probably be async */
+               if(cb)
+                       cb(NULL, cb_data, NULL, 0, NULL);
+       }
+       if(port == 0 || port == -1) {
+               port = DEFAULT_HTTP_PORT;
+       }
+
+       /* set the soap message */
+       soapMessage = g_strdup_printf(SOAP_ACTION, actionName,
+               control_info.service_type, actionParams, actionName);
+
+       /* set the HTTP Header, and append the body to it */
+       totalSendMessage = g_strdup_printf(HTTP_HEADER_ACTION "%s",
+               pathOfControl, addressOfControl, port,
+               control_info.service_type, actionName,
+               strlen(soapMessage), soapMessage);
+       g_free(pathOfControl);
+       g_free(soapMessage);
+
+       gaim_util_fetch_url_request(control_info.control_url, FALSE, NULL, TRUE,
+                       totalSendMessage, TRUE, cb, cb_data);
+
+       g_free(totalSendMessage);
+       g_free(addressOfControl);
+}
+
+const gchar *
+gaim_upnp_get_public_ip()
+{
+       if (control_info.status == GAIM_UPNP_STATUS_DISCOVERED
+                       && control_info.publicip
+                       && strlen(control_info.publicip) > 0)
+               return control_info.publicip;
+
+       /* Trigger another UPnP discovery if 5 minutes have elapsed since the
+        * last one, and it wasn't successful */
+       if (control_info.status < GAIM_UPNP_STATUS_DISCOVERING
+                       && (time(NULL) - control_info.lookup_time) > 300)
+               gaim_upnp_discover(NULL, NULL);
+
+       return NULL;
+}
+
+static void
+looked_up_public_ip_cb(GaimUtilFetchUrlData *url_data, gpointer user_data,
+               const gchar *httpResponse, gsize len, const gchar 
*error_message)
+{
+       gchar* temp, *temp2;
+
+       if ((error_message != NULL) || (httpResponse == NULL))
+               return;
+
+       /* extract the ip, or see if there is an error */
+       if((temp = g_strstr_len(httpResponse, len,
+                       "<NewExternalIPAddress")) == NULL) {
+               gaim_debug_error("upnp",
+                       "looked_up_public_ip_cb(): Failed Finding 
<NewExternalIPAddress\n");
+               return;
+       }
+       if(!(temp = g_strstr_len(temp, len - (temp - httpResponse), ">"))) {
+               gaim_debug_error("upnp",
+                       "looked_up_public_ip_cb(): Failed In Finding >\n");
+               return;
+       }
+       if(!(temp2 = g_strstr_len(temp, len - (temp - httpResponse), "<"))) {
+               gaim_debug_error("upnp",
+                       "looked_up_public_ip_cb(): Failed In Finding <\n");
+               return;
+       }
+       *temp2 = '\0';
+
+       strncpy(control_info.publicip, temp + 1,
+                       sizeof(control_info.publicip));
+
+       gaim_debug_info("upnp", "NAT Returned IP: %s\n", control_info.publicip);
+}
+
+static void
+lookup_public_ip()
+{
+       gaim_upnp_generate_action_message_and_send("GetExternalIPAddress", "",
+                       looked_up_public_ip_cb, NULL);
+}
+
+/* TODO: This could be exported */
+static const gchar *
+gaim_upnp_get_internal_ip()
+{
+       if (control_info.status == GAIM_UPNP_STATUS_DISCOVERED
+                       && control_info.internalip
+                       && strlen(control_info.internalip) > 0)
+               return control_info.internalip;
+
+       /* Trigger another UPnP discovery if 5 minutes have elapsed since the
+        * last one, and it wasn't successful */
+       if (control_info.status < GAIM_UPNP_STATUS_DISCOVERING
+                       && (time(NULL) - control_info.lookup_time) > 300)
+               gaim_upnp_discover(NULL, NULL);
+
+       return NULL;
+}
+
+static void
+looked_up_internal_ip_cb(gpointer data, gint source, const gchar 
*error_message)
+{
+       if (source) {
+               strncpy(control_info.internalip,
+                       gaim_network_get_local_system_ip(source),
+                       sizeof(control_info.internalip));
+               gaim_debug_info("upnp", "Local IP: %s\n",
+                               control_info.internalip);
+               close(source);
+       } else
+               gaim_debug_info("upnp", "Unable to look up local IP\n");
+
+}
+
+static void
+lookup_internal_ip()
+{
+       gchar* addressOfControl;
+       int port = 0;
+
+       if(!gaim_url_parse(control_info.control_url, &addressOfControl, &port,
+                       NULL, NULL, NULL)) {
+               gaim_debug_error("upnp",
+                       "lookup_internal_ip(): Failed In Parse URL\n");
+               return;
+       }
+       if(port == 0 || port == -1) {
+               port = DEFAULT_HTTP_PORT;
+       }
+
+       if(gaim_proxy_connect(NULL, NULL, addressOfControl, port,
+                       looked_up_internal_ip_cb, NULL) == NULL)
+       {
+               gaim_debug_error("upnp", "Get Local IP Connect Failed: Address: 
%s @@@ Port %d\n",
+                       addressOfControl, port);
+       }
+
+       g_free(addressOfControl);
+}
+
+static void
+done_port_mapping_cb(GaimUtilFetchUrlData *url_data, gpointer user_data,
+               const gchar *httpResponse, gsize len, const gchar 
*error_message)
+{
+       UPnPMappingAddRemove *ar = user_data;
+
+       gboolean success = TRUE;
+
+       /* determine if port mapping was a success */
+       if ((error_message != NULL) || (httpResponse == NULL) ||
+               (g_strstr_len(httpResponse, len, HTTP_OK) == NULL))
+       {
+               gaim_debug_error("upnp",
+                       "gaim_upnp_set_port_mapping(): Failed HTTP_OK\n%s\n",
+                       httpResponse ? httpResponse : "(null)");
+               success =  FALSE;
+       } else
+               gaim_debug_info("upnp", "Successfully completed port mapping 
operation\n");
+
+       if (ar->cb)
+               ar->cb(success, ar->cb_data);
+       g_free(ar);
+}
+
+static void
+do_port_mapping_cb(gboolean has_control_mapping, gpointer data)
+{
+       UPnPMappingAddRemove *ar = data;
+
+       if (has_control_mapping) {
+               gchar action_name[25];
+               gchar *action_params;
+               if(ar->add) {
+                       const gchar *internal_ip;
+                       /* get the internal IP */
+                       if(!(internal_ip = gaim_upnp_get_internal_ip())) {
+                               gaim_debug_error("upnp",
+                                       "gaim_upnp_set_port_mapping(): couldn't 
get local ip\n");
+                               /* UGLY */
+                               if (ar->cb)
+                                       ar->cb(FALSE, ar->cb_data);
+                               g_free(ar);
+                               return;
+                       }
+                       strncpy(action_name, "AddPortMapping",
+                                       sizeof(action_name));
+                       action_params = g_strdup_printf(
+                                       ADD_PORT_MAPPING_PARAMS,
+                                       ar->portmap, ar->protocol, ar->portmap,
+                                       internal_ip);
+               } else {
+                       strncpy(action_name, "DeletePortMapping", 
sizeof(action_name));
+                       action_params = g_strdup_printf(
+                               DELETE_PORT_MAPPING_PARAMS,
+                               ar->portmap, ar->protocol);
+               }
+
+               gaim_upnp_generate_action_message_and_send(action_name,
+                               action_params, done_port_mapping_cb, ar);
+
+               g_free(action_params);
+               return;
+       }
+
+
+       if (ar->cb)
+               ar->cb(FALSE, ar->cb_data);
+       g_free(ar);
+}
+
+static gboolean
+fire_port_mapping_failure_cb(gpointer data)
+{
+       do_port_mapping_cb(FALSE, data);
+       return FALSE;
+}
+
+void
+gaim_upnp_set_port_mapping(unsigned short portmap, const gchar* protocol,
+               GaimUPnPCallback cb, gpointer cb_data)
+{
+       UPnPMappingAddRemove *ar;
+
+       ar = g_new0(UPnPMappingAddRemove, 1);
+       ar->cb = cb;
+       ar->cb_data = cb_data;
+       ar->add = TRUE;
+       ar->portmap = portmap;
+       strncpy(ar->protocol, protocol, sizeof(ar->protocol));
+
+       /* If we're waiting for a discovery, add to the callbacks list */
+       if(control_info.status == GAIM_UPNP_STATUS_DISCOVERING) {
+               /* TODO: This will fail because when this cb is triggered,
+                * the internal IP lookup won't be complete */
+               discovery_callbacks = g_slist_append(
+                               discovery_callbacks, do_port_mapping_cb);
+               discovery_callbacks = g_slist_append(
+                               discovery_callbacks, ar);
+               return;
+       }
+
+       /* If we haven't had a successful UPnP discovery, check if 5 minutes has
+        * elapsed since the last try, try again */
+       if(control_info.status == GAIM_UPNP_STATUS_UNDISCOVERED ||
+                       (control_info.status == 
GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER
+                        && (time(NULL) - control_info.lookup_time) > 300)) {
+               gaim_upnp_discover(do_port_mapping_cb, ar);
+               return;
+       } else if(control_info.status == GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER) {
+               if (cb) {
+                       /* Asynchronously trigger a failed response */
+                       gaim_timeout_add(10, fire_port_mapping_failure_cb, ar);
+               } else {
+                       /* No need to do anything if nobody expects a response*/
+                       g_free(ar);
+               }
+               return;
+       }
+
+       do_port_mapping_cb(TRUE, ar);
+}
+
+void
+gaim_upnp_remove_port_mapping(unsigned short portmap, const char* protocol,
+               GaimUPnPCallback cb, gpointer cb_data)
+{
+       UPnPMappingAddRemove *ar;
+
+       ar = g_new0(UPnPMappingAddRemove, 1);
+       ar->cb = cb;
+       ar->cb_data = cb_data;
+       ar->add = FALSE;
+       ar->portmap = portmap;
+       strncpy(ar->protocol, protocol, sizeof(ar->protocol));
+
+       /* If we're waiting for a discovery, add to the callbacks list */
+       if(control_info.status == GAIM_UPNP_STATUS_DISCOVERING) {
+               discovery_callbacks = g_slist_append(
+                               discovery_callbacks, do_port_mapping_cb);
+               discovery_callbacks = g_slist_append(
+                               discovery_callbacks, ar);
+               return;
+       }
+
+       /* If we haven't had a successful UPnP discovery, check if 5 minutes has
+        * elapsed since the last try, try again */
+       if(control_info.status == GAIM_UPNP_STATUS_UNDISCOVERED ||
+                       (control_info.status == 
GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER
+                        && (time(NULL) - control_info.lookup_time) > 300)) {
+               gaim_upnp_discover(do_port_mapping_cb, ar);
+               return;
+       } else if(control_info.status == GAIM_UPNP_STATUS_UNABLE_TO_DISCOVER) {
+               if (cb) {
+                       /* Asynchronously trigger a failed response */
+                       gaim_timeout_add(10, fire_port_mapping_failure_cb, ar);
+               } else {
+                       /* No need to do anything if nobody expects a response*/
+                       g_free(ar);
+               }
+               return;
+       }
+
+       do_port_mapping_cb(TRUE, ar);
+}


Property changes on: GNUnet/src/transports/upnp/upnp.c
___________________________________________________________________
Name: svn:eol-style
   + native

Added: GNUnet/src/transports/upnp/upnp.h
===================================================================
--- GNUnet/src/transports/upnp/upnp.h   2006-12-27 14:27:24 UTC (rev 4075)
+++ GNUnet/src/transports/upnp/upnp.h   2006-12-27 16:46:00 UTC (rev 4076)
@@ -0,0 +1,111 @@
+/**
+ * @file upnp.h Universal Plug N Play API
+ * @ingroup core
+ *
+ * gaim
+ *
+ * Gaim is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _GAIM_UPNP_H_
+#define _GAIM_UPNP_H_
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**************************************************************************/
+/** @name UPnP API                                                        */
+/**************************************************************************/
+/address@hidden/
+
+/* typedef struct _GaimUPnPRequestData GaimUPnPRequestData; */
+
+typedef void (*GaimUPnPCallback) (gboolean success, gpointer data);
+
+/**
+ * Sends a discovery request to search for a UPnP enabled IGD that
+ * contains the WANIPConnection service that will allow us to recieve the
+ * public IP address of the IGD, and control it for forwarding ports.
+ * The result will be cached for further use.
+ *
+ * @param cb an optional callback function to be notified when the UPnP
+ *           discovery is complete
+ * @param cb_data Extra data to be passed to the callback
+ */
+void gaim_upnp_discover(GaimUPnPCallback cb, gpointer cb_data);
+
+#if 0
+/**
+ * Retrieve the current UPnP control info, if there is any available.
+ * This will only be filled in if gaim_upnp_discover() had been called,
+ * and finished discovering.
+ *
+ * @return The control URL for the IGD we'll use to use the IGD services
+ */
+const GaimUPnPControlInfo* gaim_upnp_get_control_info(void);
+#endif
+
+/**
+ * Gets the IP address from a UPnP enabled IGD that sits on the local
+ * network, so when getting the network IP, instead of returning the
+ * local network IP, the public IP is retrieved.  This is a cached value from
+ * the time of the UPnP discovery.
+ *
+ * @return The IP address of the network, or NULL if something went wrong
+ */
+const gchar* gaim_upnp_get_public_ip(void);
+
+/**
+ * Maps Ports in a UPnP enabled IGD that sits on the local network to
+ * this gaim client. Essentially, this function takes care of the port
+ * forwarding so things like file transfers can work behind NAT firewalls
+ *
+ * @param portmap The port to map to this client
+ * @param protocol The protocol to map, either "TCP" or "UDP"
+ * @param cb an optional callback function to be notified when the mapping
+ *           addition is complete
+ * @param cb_data Extra data to be passed to the callback
+ */
+void gaim_upnp_set_port_mapping(unsigned short portmap, const gchar* protocol,
+               GaimUPnPCallback cb, gpointer cb_data);
+
+/**
+ * Deletes a port mapping in a UPnP enabled IGD that sits on the local network
+ * to this gaim client. Essentially, this function takes care of deleting the
+ * port forwarding after they have completed a connection so another client on
+ * the local network can take advantage of the port forwarding
+ *
+ * @param portmap The port to delete the mapping for
+ * @param protocol The protocol to map to. Either "TCP" or "UDP"
+ * @param cb an optional callback function to be notified when the mapping
+ *           removal is complete
+ * @param cb_data Extra data to be passed to the callback
+ */
+void gaim_upnp_remove_port_mapping(unsigned short portmap,
+               const gchar* protocol, GaimUPnPCallback cb, gpointer cb_data);
+
+/address@hidden/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _GAIM_UPNP_H_ */


Property changes on: GNUnet/src/transports/upnp/upnp.h
___________________________________________________________________
Name: svn:eol-style
   + native

Added: GNUnet/src/transports/upnp/xmlnode.c
===================================================================
--- GNUnet/src/transports/upnp/xmlnode.c        2006-12-27 14:27:24 UTC (rev 
4075)
+++ GNUnet/src/transports/upnp/xmlnode.c        2006-12-27 16:46:00 UTC (rev 
4076)
@@ -0,0 +1,626 @@
+/**
+ * @file xmlnode.c XML DOM functions
+ *
+ * gaim
+ *
+ * Gaim is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* A lot of this code at least resembles the code in libxode, but since
+ * libxode uses memory pools that we simply have no need for, I decided to
+ * write my own stuff.  Also, re-writing this lets me be as lightweight
+ * as I want to be.  Thank you libxode for giving me a good starting point */
+
+#include "internal.h"
+
+#include <libxml/parser.h>
+#include <string.h>
+#include <glib.h>
+
+#include "dbus-maybe.h"
+#include "util.h"
+#include "xmlnode.h"
+
+#ifdef _WIN32
+# define NEWLINE_S "\r\n"
+#else
+# define NEWLINE_S "\n"
+#endif
+
+static xmlnode*
+new_node(const char *name, XMLNodeType type)
+{
+       xmlnode *node = g_new0(xmlnode, 1);
+
+       node->name = g_strdup(name);
+       node->type = type;
+
+       GAIM_DBUS_REGISTER_POINTER(node, xmlnode);
+
+       return node;
+}
+
+xmlnode*
+xmlnode_new(const char *name)
+{
+       g_return_val_if_fail(name != NULL, NULL);
+
+       return new_node(name, XMLNODE_TYPE_TAG);
+}
+
+xmlnode *
+xmlnode_new_child(xmlnode *parent, const char *name)
+{
+       xmlnode *node;
+
+       g_return_val_if_fail(parent != NULL, NULL);
+       g_return_val_if_fail(name != NULL, NULL);
+
+       node = new_node(name, XMLNODE_TYPE_TAG);
+
+       xmlnode_insert_child(parent, node);
+
+       return node;
+}
+
+void
+xmlnode_insert_child(xmlnode *parent, xmlnode *child)
+{
+       g_return_if_fail(parent != NULL);
+       g_return_if_fail(child != NULL);
+
+       child->parent = parent;
+
+       if(parent->lastchild) {
+               parent->lastchild->next = child;
+       } else {
+               parent->child = child;
+       }
+
+       parent->lastchild = child;
+}
+
+void
+xmlnode_insert_data(xmlnode *node, const char *data, gssize size)
+{
+       xmlnode *child;
+       gsize real_size;
+
+       g_return_if_fail(node != NULL);
+       g_return_if_fail(data != NULL);
+       g_return_if_fail(size != 0);
+
+       real_size = size == -1 ? strlen(data) : size;
+
+       child = new_node(NULL, XMLNODE_TYPE_DATA);
+
+       child->data = g_memdup(data, real_size);
+       child->data_sz = real_size;
+
+       xmlnode_insert_child(node, child);
+}
+
+void
+xmlnode_remove_attrib(xmlnode *node, const char *attr)
+{
+       xmlnode *attr_node, *sibling = NULL;
+
+       g_return_if_fail(node != NULL);
+       g_return_if_fail(attr != NULL);
+
+       for(attr_node = node->child; attr_node; attr_node = attr_node->next)
+       {
+               if(attr_node->type == XMLNODE_TYPE_ATTRIB &&
+                               !strcmp(attr_node->name, attr))
+               {
+                       if(node->child == attr_node) {
+                               node->child = attr_node->next;
+                       } else {
+                               sibling->next = attr_node->next;
+                       }
+                       if (node->lastchild == attr_node) {
+                               node->lastchild = sibling;
+                       }
+                       xmlnode_free(attr_node);
+                       return;
+               }
+               sibling = attr_node;
+       }
+}
+
+
+void
+xmlnode_remove_attrib_with_namespace(xmlnode *node, const char *attr, const 
char *xmlns)
+{
+       xmlnode *attr_node, *sibling = NULL;
+
+       g_return_if_fail(node != NULL);
+       g_return_if_fail(attr != NULL);
+
+       for(attr_node = node->child; attr_node; attr_node = attr_node->next)
+       {
+               if(attr_node->type == XMLNODE_TYPE_ATTRIB &&
+                  !strcmp(attr_node->name, attr) &&
+                  !strcmp(attr_node->xmlns, xmlns))
+               {
+                       if(node->child == attr_node) {
+                               node->child = attr_node->next;
+                       } else {
+                               sibling->next = attr_node->next;
+                       }
+                       if (node->lastchild == attr_node) {
+                               node->lastchild = sibling;
+                       }
+                       xmlnode_free(attr_node);
+                       return;
+               }
+               sibling = attr_node;
+       }
+}
+
+void
+xmlnode_set_attrib(xmlnode *node, const char *attr, const char *value)
+{
+       xmlnode *attrib_node;
+
+       g_return_if_fail(node != NULL);
+       g_return_if_fail(attr != NULL);
+       g_return_if_fail(value != NULL);
+
+       xmlnode_remove_attrib(node, attr);
+
+       attrib_node = new_node(attr, XMLNODE_TYPE_ATTRIB);
+
+       attrib_node->data = g_strdup(value);
+
+       xmlnode_insert_child(node, attrib_node);
+}
+
+void
+xmlnode_set_attrib_with_namespace(xmlnode *node, const char *attr, const char 
*xmlns, const char *value)
+{
+       xmlnode *attrib_node;
+
+       g_return_if_fail(node != NULL);
+       g_return_if_fail(attr != NULL);
+       g_return_if_fail(value != NULL);
+
+       xmlnode_remove_attrib_with_namespace(node, attr, xmlns);
+
+       attrib_node = new_node(attr, XMLNODE_TYPE_ATTRIB);
+
+       attrib_node->data = g_strdup(value);
+       attrib_node->xmlns = g_strdup(xmlns);
+
+       xmlnode_insert_child(node, attrib_node);
+}
+
+const char *
+xmlnode_get_attrib(xmlnode *node, const char *attr)
+{
+       xmlnode *x;
+
+       g_return_val_if_fail(node != NULL, NULL);
+
+       for(x = node->child; x; x = x->next) {
+               if(x->type == XMLNODE_TYPE_ATTRIB && !strcmp(attr, x->name)) {
+                       return x->data;
+               }
+       }
+
+       return NULL;
+}
+
+const char *
+xmlnode_get_attrib_with_namespace(xmlnode *node, const char *attr, const char 
*xmlns)
+{
+       xmlnode *x;
+
+       g_return_val_if_fail(node != NULL, NULL);
+
+       for(x = node->child; x; x = x->next) {
+               if(x->type == XMLNODE_TYPE_ATTRIB &&
+                  !strcmp(attr, x->name) && !strcmp(x->xmlns, xmlns)) {
+                       return x->data;
+               }
+       }
+
+       return NULL;
+}
+
+
+void xmlnode_set_namespace(xmlnode *node, const char *xmlns)
+{
+       g_return_if_fail(node != NULL);
+
+       g_free(node->xmlns);
+       node->xmlns = g_strdup(xmlns);
+}
+
+const char *xmlnode_get_namespace(xmlnode *node)
+{
+       g_return_val_if_fail(node != NULL, NULL);
+
+       return node->xmlns;
+}
+
+void
+xmlnode_free(xmlnode *node)
+{
+       xmlnode *x, *y;
+
+       g_return_if_fail(node != NULL);
+
+       x = node->child;
+       while(x) {
+               y = x->next;
+               xmlnode_free(x);
+               x = y;
+       }
+
+       g_free(node->name);
+       g_free(node->data);
+       g_free(node->xmlns);
+
+       GAIM_DBUS_UNREGISTER_POINTER(node);
+       g_free(node);
+}
+
+xmlnode*
+xmlnode_get_child(const xmlnode *parent, const char *name)
+{
+       return xmlnode_get_child_with_namespace(parent, name, NULL);
+}
+
+xmlnode *
+xmlnode_get_child_with_namespace(const xmlnode *parent, const char *name, 
const char *ns)
+{
+       xmlnode *x, *ret = NULL;
+       char **names;
+       char *parent_name, *child_name;
+
+       g_return_val_if_fail(parent != NULL, NULL);
+       g_return_val_if_fail(name != NULL, NULL);
+
+       names = g_strsplit(name, "/", 2);
+       parent_name = names[0];
+       child_name = names[1];
+
+       for(x = parent->child; x; x = x->next) {
+               const char *xmlns = NULL;
+               if(ns)
+                       xmlns = xmlnode_get_namespace(x);
+
+               if(x->type == XMLNODE_TYPE_TAG && name && !strcmp(parent_name, 
x->name)
+                               && (!ns || (xmlns && !strcmp(ns, xmlns)))) {
+                       ret = x;
+                       break;
+               }
+       }
+
+       if(child_name && ret)
+               ret = xmlnode_get_child(ret, child_name);
+
+       g_strfreev(names);
+       return ret;
+}
+
+char *
+xmlnode_get_data(xmlnode *node)
+{
+       GString *str = NULL;
+       xmlnode *c;
+
+       g_return_val_if_fail(node != NULL, NULL);
+
+       for(c = node->child; c; c = c->next) {
+               if(c->type == XMLNODE_TYPE_DATA) {
+                       if(!str)
+                               str = g_string_new("");
+                       str = g_string_append_len(str, c->data, c->data_sz);
+               }
+       }
+
+       if (str == NULL)
+               return NULL;
+
+       return g_string_free(str, FALSE);
+}
+
+static char *
+xmlnode_to_str_helper(xmlnode *node, int *len, gboolean formatting, int depth)
+{
+       GString *text = g_string_new("");
+       xmlnode *c;
+       char *node_name, *esc, *esc2, *tab = NULL;
+       gboolean need_end = FALSE, pretty = formatting;
+
+       g_return_val_if_fail(node != NULL, NULL);
+
+       if(pretty && depth) {
+               tab = g_strnfill(depth, '\t');
+               text = g_string_append(text, tab);
+       }
+
+       node_name = g_markup_escape_text(node->name, -1);
+       g_string_append_printf(text, "<%s", node_name);
+
+       if (node->xmlns) {
+               if(!node->parent || !node->parent->xmlns || strcmp(node->xmlns, 
node->parent->xmlns))
+               {
+                       char *xmlns = g_markup_escape_text(node->xmlns, -1);
+                       g_string_append_printf(text, " xmlns='%s'", xmlns);
+                       g_free(xmlns);
+               }
+       }
+       for(c = node->child; c; c = c->next)
+       {
+               if(c->type == XMLNODE_TYPE_ATTRIB) {
+                       esc = g_markup_escape_text(c->name, -1);
+                       esc2 = g_markup_escape_text(c->data, -1);
+                       g_string_append_printf(text, " %s='%s'", esc, esc2);
+                       g_free(esc);
+                       g_free(esc2);
+               } else if(c->type == XMLNODE_TYPE_TAG || c->type == 
XMLNODE_TYPE_DATA) {
+                       if(c->type == XMLNODE_TYPE_DATA)
+                               pretty = FALSE;
+                       need_end = TRUE;
+               }
+       }
+
+       if(need_end) {
+               g_string_append_printf(text, ">%s", pretty ? NEWLINE_S : "");
+
+               for(c = node->child; c; c = c->next)
+               {
+                       if(c->type == XMLNODE_TYPE_TAG) {
+                               int esc_len;
+                               esc = xmlnode_to_str_helper(c, &esc_len, 
pretty, depth+1);
+                               text = g_string_append_len(text, esc, esc_len);
+                               g_free(esc);
+                       } else if(c->type == XMLNODE_TYPE_DATA && c->data_sz > 
0) {
+                               esc = g_markup_escape_text(c->data, c->data_sz);
+                               text = g_string_append(text, esc);
+                               g_free(esc);
+                       }
+               }
+
+               if(tab && pretty)
+                       text = g_string_append(text, tab);
+               g_string_append_printf(text, "</%s>%s", node_name, formatting ? 
NEWLINE_S : "");
+       } else {
+               g_string_append_printf(text, "/>%s", formatting ? NEWLINE_S : 
"");
+       }
+
+       g_free(node_name);
+
+       g_free(tab);
+
+       if(len)
+               *len = text->len;
+
+       return g_string_free(text, FALSE);
+}
+
+char *
+xmlnode_to_str(xmlnode *node, int *len)
+{
+       return xmlnode_to_str_helper(node, len, FALSE, 0);
+}
+
+char *
+xmlnode_to_formatted_str(xmlnode *node, int *len)
+{
+       char *xml, *xml_with_declaration;
+
+       g_return_val_if_fail(node != NULL, NULL);
+
+       xml = xmlnode_to_str_helper(node, len, TRUE, 0);
+       xml_with_declaration =
+               g_strdup_printf("<?xml version='1.0' encoding='UTF-8' ?>" 
NEWLINE_S NEWLINE_S "%s", xml);
+       g_free(xml);
+
+       return xml_with_declaration;
+}
+
+struct _xmlnode_parser_data {
+       xmlnode *current;
+};
+
+static void
+xmlnode_parser_element_start_libxml(void *user_data,
+                                  const xmlChar *element_name, const xmlChar 
*prefix, const xmlChar *xmlns,
+                                  int nb_namespaces, const xmlChar 
**namespaces,
+                                  int nb_attributes, int nb_defaulted, const 
xmlChar **attributes)
+{
+       struct _xmlnode_parser_data *xpd = user_data;
+       xmlnode *node;
+       int i;
+
+       if(!element_name) {
+               return;
+       } else {
+               if(xpd->current)
+                       node = xmlnode_new_child(xpd->current, (const char*) 
element_name);
+               else
+                       node = xmlnode_new((const char *) element_name);
+
+               xmlnode_set_namespace(node, (const char *) xmlns);
+
+               for(i=0; i < nb_attributes * 5; i+=5) {
+                       char *txt;
+                       int attrib_len = attributes[i+4] - attributes[i+3];
+                       char *attrib = g_malloc(attrib_len + 1);
+                       memcpy(attrib, attributes[i+3], attrib_len);
+                       attrib[attrib_len] = '\0';
+                       txt = attrib;
+                       attrib = gaim_unescape_html(txt);
+                       g_free(txt);
+                       xmlnode_set_attrib(node, (const char*) attributes[i], 
attrib);
+                       g_free(attrib);
+               }
+
+               xpd->current = node;
+       }
+}
+
+static void
+xmlnode_parser_element_end_libxml(void *user_data, const xmlChar *element_name,
+                                const xmlChar *prefix, const xmlChar *xmlns)
+{
+       struct _xmlnode_parser_data *xpd = user_data;
+
+       if(!element_name || !xpd->current)
+               return;
+
+       if(xpd->current->parent) {
+               if(!xmlStrcmp((xmlChar*) xpd->current->name, element_name))
+                       xpd->current = xpd->current->parent;
+       }
+}
+
+static void
+xmlnode_parser_element_text_libxml(void *user_data, const xmlChar *text, int 
text_len)
+{
+       struct _xmlnode_parser_data *xpd = user_data;
+
+       if(!xpd->current)
+               return;
+
+       if(!text || !text_len)
+               return;
+
+       xmlnode_insert_data(xpd->current, (const char*) text, text_len);
+}
+
+static xmlSAXHandler xmlnode_parser_libxml = {
+       .internalSubset         = NULL,
+       .isStandalone           = NULL,
+       .hasInternalSubset      = NULL,
+       .hasExternalSubset      = NULL,
+       .resolveEntity          = NULL,
+       .getEntity              = NULL,
+       .entityDecl             = NULL,
+       .notationDecl           = NULL,
+       .attributeDecl          = NULL,
+       .elementDecl            = NULL,
+       .unparsedEntityDecl     = NULL,
+       .setDocumentLocator     = NULL,
+       .startDocument          = NULL,
+       .endDocument            = NULL,
+       .startElement           = NULL,
+       .endElement             = NULL,
+       .reference              = NULL,
+       .characters             = xmlnode_parser_element_text_libxml,
+       .ignorableWhitespace    = NULL,
+       .processingInstruction  = NULL,
+       .comment                = NULL,
+       .warning                = NULL,
+       .error                  = NULL,
+       .fatalError             = NULL,
+       .getParameterEntity     = NULL,
+       .cdataBlock             = NULL,
+       .externalSubset         = NULL,
+       .initialized            = XML_SAX2_MAGIC,
+       ._private               = NULL,
+       .startElementNs         = xmlnode_parser_element_start_libxml,
+       .endElementNs           = xmlnode_parser_element_end_libxml,
+       .serror                 = NULL
+};
+
+xmlnode *
+xmlnode_from_str(const char *str, gssize size)
+{
+       struct _xmlnode_parser_data *xpd;
+       xmlnode *ret;
+       gsize real_size;
+
+       g_return_val_if_fail(str != NULL, NULL);
+
+       real_size = size < 0 ? strlen(str) : size;
+       xpd = g_new0(struct _xmlnode_parser_data, 1);
+
+       if (xmlSAXUserParseMemory(&xmlnode_parser_libxml, xpd, str, real_size) 
< 0) {
+               while(xpd->current && xpd->current->parent)
+                       xpd->current = xpd->current->parent;
+               if(xpd->current)
+                       xmlnode_free(xpd->current);
+               xpd->current = NULL;
+       }
+       ret = xpd->current;
+       g_free(xpd);
+       return ret;
+}
+
+xmlnode *
+xmlnode_copy(xmlnode *src)
+{
+       xmlnode *ret;
+       xmlnode *child;
+       xmlnode *sibling = NULL;
+
+       g_return_val_if_fail(src != NULL, NULL);
+
+       ret = new_node(src->name, src->type);
+       if(src->data) {
+               if(src->data_sz) {
+                       ret->data = g_memdup(src->data, src->data_sz);
+                       ret->data_sz = src->data_sz;
+               } else {
+                       ret->data = g_strdup(src->data);
+               }
+       }
+
+       for(child = src->child; child; child = child->next) {
+               if(sibling) {
+                       sibling->next = xmlnode_copy(child);
+                       sibling = sibling->next;
+               } else {
+                       ret->child = xmlnode_copy(child);
+                       sibling = ret->child;
+               }
+               sibling->parent = ret;
+       }
+
+       ret->lastchild = sibling;
+
+       return ret;
+}
+
+xmlnode *
+xmlnode_get_next_twin(xmlnode *node)
+{
+       xmlnode *sibling;
+       const char *ns = xmlnode_get_namespace(node);
+
+       g_return_val_if_fail(node != NULL, NULL);
+       g_return_val_if_fail(node->type == XMLNODE_TYPE_TAG, NULL);
+
+       for(sibling = node->next; sibling; sibling = sibling->next) {
+               const char *xmlns = NULL;
+               if(ns)
+                       xmlns = xmlnode_get_namespace(sibling);
+
+               if(sibling->type == XMLNODE_TYPE_TAG && !strcmp(node->name, 
sibling->name) &&
+                               (!ns || (xmlns && !strcmp(ns, xmlns))))
+                       return sibling;
+       }
+
+       return NULL;
+}


Property changes on: GNUnet/src/transports/upnp/xmlnode.c
___________________________________________________________________
Name: svn:eol-style
   + native

Added: GNUnet/src/transports/upnp/xmlnode.h
===================================================================
--- GNUnet/src/transports/upnp/xmlnode.h        2006-12-27 14:27:24 UTC (rev 
4075)
+++ GNUnet/src/transports/upnp/xmlnode.h        2006-12-27 16:46:00 UTC (rev 
4076)
@@ -0,0 +1,265 @@
+/**
+ * @file xmlnode.h XML DOM functions
+ * @ingroup core
+ *
+ * gaim
+ *
+ * Gaim is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef _GAIM_XMLNODE_H_
+#define _GAIM_XMLNODE_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * The valid types for an xmlnode
+ */
+typedef enum _XMLNodeType
+{
+       XMLNODE_TYPE_TAG,               /**< Just a tag */
+       XMLNODE_TYPE_ATTRIB,            /**< Has attributes */
+       XMLNODE_TYPE_DATA               /**< Has data */
+} XMLNodeType;
+
+/**
+ * An xmlnode.
+ */
+typedef struct _xmlnode xmlnode;
+struct _xmlnode
+{
+       char *name;                     /**< The name of the node. */
+       char *xmlns;            /**< The namespace of the node */
+       XMLNodeType type;               /**< The type of the node. */
+       char *data;                     /**< The data for the node. */
+       size_t data_sz;                 /**< The size of the data. */
+       struct _xmlnode *parent;        /**< The parent node or @c NULL.*/
+       struct _xmlnode *child;         /**< The child node or @c NULL.*/
+       struct _xmlnode *lastchild;     /**< The last child node or @c NULL.*/
+       struct _xmlnode *next;          /**< The next node or @c NULL. */
+};
+
+/**
+ * Creates a new xmlnode.
+ *
+ * @param name The name of the node.
+ *
+ * @return The new node.
+ */
+xmlnode *xmlnode_new(const char *name);
+
+/**
+ * Creates a new xmlnode child.
+ *
+ * @param parent The parent node.
+ * @param name   The name of the child node.
+ *
+ * @return The new child node.
+ */
+xmlnode *xmlnode_new_child(xmlnode *parent, const char *name);
+
+/**
+ * Inserts a node into a node as a child.
+ *
+ * @param parent The parent node to insert child into.
+ * @param child  The child node to insert into parent.
+ */
+void xmlnode_insert_child(xmlnode *parent, xmlnode *child);
+
+/**
+ * Gets a child node named name.
+ *
+ * @param parent The parent node.
+ * @param name   The child's name.
+ *
+ * @return The child or NULL.
+ */
+xmlnode *xmlnode_get_child(const xmlnode *parent, const char *name);
+
+/**
+ * Gets a child node named name in a namespace.
+ *
+ * @param parent The parent node.
+ * @param name   The child's name.
+ * @param xmlns  The namespace.
+ *
+ * @return The child or NULL.
+ */
+xmlnode *xmlnode_get_child_with_namespace(const xmlnode *parent, const char 
*name, const char *xmlns);
+
+/**
+ * Gets the next node with the same name as node.
+ *
+ * @param node The node of a twin to find.
+ *
+ * @return The twin of node or NULL.
+ */
+xmlnode *xmlnode_get_next_twin(xmlnode *node);
+
+/**
+ * Inserts data into a node.
+ *
+ * @param node   The node to insert data into.
+ * @param data   The data to insert.
+ * @param size   The size of the data to insert.  If data is
+ *               null-terminated you can pass in -1.
+ */
+void xmlnode_insert_data(xmlnode *node, const char *data, gssize size);
+
+/**
+ * Gets data from a node.
+ *
+ * @param node The node to get data from.
+ *
+ * @return The data from the node.  You must g_free
+ *         this string when finished using it.
+ */
+char *xmlnode_get_data(xmlnode *node);
+
+/**
+ * Sets an attribute for a node.
+ *
+ * @param node  The node to set an attribute for.
+ * @param attr  The name of the attribute.
+ * @param value The value of the attribute.
+ */
+void xmlnode_set_attrib(xmlnode *node, const char *attr, const char *value);
+
+/**
+ * Sets a namespaced attribute for a node
+ *
+ * @param node  The node to set an attribute for.
+ * @param attr  The name of the attribute to set
+ * @param xmlns The namespace of the attribute to ste
+ * @param value The value of the attribute
+ */
+void xmlnode_set_attrib_with_namespace(xmlnode *node, const char *attr, const 
char *xmlns, const char *value);
+
+/**
+ * Gets an attribute from a node.
+ *
+ * @param node The node to get an attribute from.
+ * @param attr The attribute to get.
+ *
+ * @return The value of the attribute.
+ */
+const char *xmlnode_get_attrib(xmlnode *node, const char *attr);
+
+/**
+ * Gets a namespaced attribute from a node
+ *
+ * @param node  The node to get an attribute from.
+ * @param attr  The attribute to get
+ * @param xmlns The namespace of the attribute to get
+ *
+ * @return The value of the attribute/
+ */
+const char *xmlnode_get_attrib_with_namespace(xmlnode *node, const char *attr, 
const char *xmlns);
+
+/**
+ * Removes an attribute from a node.
+ *
+ * @param node The node to remove an attribute from.
+ * @param attr The attribute to remove.
+ */
+void xmlnode_remove_attrib(xmlnode *node, const char *attr);
+
+/**
+ * Removes a namespaced attribute from a node
+ *
+ * @param node  The node to remove an attribute from
+ * @param attr  The attribute to remove
+ * @param xmlns The namespace of the attribute to remove
+ */
+void xmlnode_remove_attrib_with_namespace(xmlnode *node, const char *attr, 
const char *xmlns);
+
+/**
+ * Sets the namespace of a node
+ *
+ * @param node The node to qualify
+ * @param xmlns The namespace of the node
+ */
+void xmlnode_set_namespace(xmlnode *node, const char *xmlns);
+
+/**
+ * Returns the namespace of a node
+ *
+ * @param node The node to get the namepsace from
+ * @return The namespace of this node
+ */
+const char *xmlnode_get_namespace(xmlnode *node);
+
+/**
+ * Returns the node in a string of xml.
+ *
+ * @param node The starting node to output.
+ * @param len  Address for the size of the string.
+ *
+ * @return The node represented as a string.  You must
+ *         g_free this string when finished using it.
+ */
+char *xmlnode_to_str(xmlnode *node, int *len);
+
+/**
+ * Returns the node in a string of human readable xml.
+ *
+ * @param node The starting node to output.
+ * @param len  Address for the size of the string.
+ *
+ * @return The node as human readable string including
+ *         tab and new line characters.  You must
+ *         g_free this string when finished using it.
+ */
+char *xmlnode_to_formatted_str(xmlnode *node, int *len);
+
+/**
+ * Creates a node from a string of XML.  Calling this on the
+ * root node of an XML document will parse the entire document
+ * into a tree of nodes, and return the xmlnode of the root.
+ *
+ * @param str  The string of xml.
+ * @param size The size of the string, or -1 if @a str is
+ *             NUL-terminated.
+ *
+ * @return The new node.
+ */
+xmlnode *xmlnode_from_str(const char *str, gssize size);
+
+/**
+ * Creates a new node from the source node.
+ *
+ * @param src The node to copy.
+ *
+ * @return A new copy of the src node.
+ */
+xmlnode *xmlnode_copy(xmlnode *src);
+
+/**
+ * Frees a node and all of it's children.
+ *
+ * @param node The node to free.
+ */
+void xmlnode_free(xmlnode *node);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _GAIM_XMLNODE_H_ */


Property changes on: GNUnet/src/transports/upnp/xmlnode.h
___________________________________________________________________
Name: svn:eol-style
   + native





reply via email to

[Prev in Thread] Current Thread [Next in Thread]