[Top][All Lists]
[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
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [GNUnet-SVN] r4076 - in GNUnet/src/transports: . upnp,
grothoff <=