diff --git a/norp/TODO.txt b/norp/TODO.txt new file mode 100644 index 0000000..2ec0200 --- /dev/null +++ b/norp/TODO.txt @@ -0,0 +1,37 @@ + + +The basic NORP server state machine is in place and pretty much ready for testing. There are some details that need to be wrapped up: + +1) Implement ACK-based flow control using double-buffer markers, explict positive acknowledgement, (DONE) 1a) Add option to NORM to +only ACK after application has read stream data. (DONE) + +2) Clean up debugging messages. (SOME) + +3) Provide NORM congestion control options (DONE - CCE added) + +4) Session configuration options (ouch!) 4a) Src/Dst based proxy methods and parameters + +5) Implement auto handoff to TCP direct connnect on port unreachable (what about NORP request timeout, too?) (both DONE) + +6) Use protolibd for "safer" socket state + +7) norm_rx_stream can hangup shutdown - fix it (DONE) + +8) Win32 build and test + +8a) Win32 NORM-CCE support that's not WinPcap dependent + +9) Send / handle negative SOCKS replies + + +10) IMPORTANT: Detect remote "norp" failure _or_ make sure that we can _always_ detect when our local TCP socket (originator client, or +correspondent remote) closes. Right now, we stop TCP socket input notification upon NORM flow control block condition (e.g. failed +watermark) ... we probably should have some failed watermark timeout condition, perhaps in conjunction with using an +application-defined command as a "keep-alive" mechanism. For example, have each NORM sender send a "keep-alive" heart beat +periodically, but upon multiple periods of failed watermark ACK _and_ missing "keep-alive", initiate session teardown? Otherwise, this +creates a condition of persistent requests of ACK indefinitely. + + +11) To provide easier firewall traversal, should we split the "correspondent" norm tx & rx sessions so that it can "talk back" to the +originator source port. Can also use the NORP messaging to signal the norm ports to use. + diff --git a/norp/VERSION.TXT b/norp/VERSION.TXT new file mode 100644 index 0000000..ba836ac --- /dev/null +++ b/norp/VERSION.TXT @@ -0,0 +1,29 @@ +NORP Version History + +Version 0.1b3 +============= + - Added "correspondent " command to allow 'routing' + of proxied connections through specified remote 'norp' peer + instead of directly to connection destination address. + - Includes fix to NORM source code of receiver rate estimation + measured during 'slow start' phase of congestion control + +Version 0.1b2 +============= + - Adopted NORM_SYNC_STREAM policy to guarantee synchronization + of stream start at byte offset zero of proxied TCP stream data + - Added norp "persist" command-line option to allow control of how + persistently norp tries to deliver TCP data in disruptive network + environments. A -1 persistence value is infinitely persistent. + The default persistence is 120 seconds (2 minutes). + +Version 0.1b1 +============= + - Fixed bugs with "forward" command recently added + - Updated "norm" with bug fix in normSession.cpp + +Version 0.1 +============= + - Started versioning + - This version adds the "forward" command for non-SOCKS proxying + diff --git a/norp/doc/norpUserGuide.html b/norp/doc/norpUserGuide.html new file mode 100644 index 0000000..5388c85 --- /dev/null +++ b/norp/doc/norpUserGuide.html @@ -0,0 +1,23 @@ + + + norp User's Guide

norp User's Guide

Abstract

The NRL Nack-Oriented Proxy (norp) project includes software for an RFC 1928 SOCKS5-compatible proxy server daemon that is able to use the RFC 5740 Nack-Oriented Reliable Multicast (NORM) transport protocol for efficient and robust data transfer between norp proxy instances. The norp proxy automatically supports conventional SOCKS TCP proxy operation when a remote norp peer is unavailable. This software was developed by the Naval Research Laboratory (NRL) PROTocol Engineering Advanced Networking Research Group. The NRL reference implementation of NORM used here is available from http://www.nrl.navy.mil/itd/ncs/products/norm.


1. Overview

The norp application ....

2. Theory of Operation

The norp program acts as a SOCKS proxy server. It supports the SOCKS5 "CONNECT", "BIND" and "UDP-ASSOCIATE" proxy methods for conventional SOCKS proxy operation. The current norp implementation does not require (or support) any client authentication. Future versions may provide authentication or other access control mechanisms. The current norp implementation only provides NORM transport for the SOCKS TCP "CONNECT" requests. SOCKS "UDP-ASSOCIATE" over NORM will be supported in a future version.

Unlike a conventional SOCKS server, it is expected that the norp daemon can be installed and run as a local "loopback" server that is co-resident on the host running applications that wish to take advantage of NORM transport benefits. The norp daemon implements its own signaling protocol that will automatically determine, upon TCP (or UDP) connection establishment, if a remote destination is also similarly "norp-enabled" and establish a NORM transport connection as the proxy connection. Otherwise a "business-as-usual" TCP (or UDP) connection is established on the application's behalf and thus compatibility with "non-norp" hosts is also supported. Figure 1, “NORP Concept of Operation” illustrates this high level concept of operation.

Figure 1. NORP Concept of Operation

NORP Concept of Operation


TBD - provide some more details on norp signaling for peer detection and NORM session establishment

Note that as an alternative to making proxied connections directly to connection destination addresses as illustrated above, a remote norp peer "correspondent" can be specified as part of the forward command or, for SOCKS connections, with the correspondent command (see command descriptions below). Future versions of norp will include more sophisticated "routing" options for different destinations and traffic types.

2.1. SOCKS Loopback and Intermediate System

As noted above the principal use case for norp is to act as a local, "loopback" SOCKS server that can be used in conjunction with a properly configured SOCKS client. In this way, all of the configuration parameters are localized and implicit and no precoordinated configuration with norp peers (or non-norp hosts) is required other than using a common UDP port number for NORP signaling.

However, there may be use cases where it may be desirable to deploy norp on intermediate systems at the connection originating site (or domain) and/or the destination site(s) (or domain(s)). This is easily supported by the norp design and future norp versions will provide configuration options for this type of deployment.

2.2. NORM Protocol Usage

TBD - describe how the NORM streaming capability is used in a flow-controlled, positively-acknowledged fashion to provide a reliable TCP proxy function. Also describe the NORM congestion control options here.

3. Usage

Typically, norp can be run in its default configuration with no command-line options required. However, a number of options are available via the command-line. This is a summary of norp usage:

norp [interface <ifaceName>][address <publicAddr>][sport <socksPort>][port <norpPort>]
+     [norm {on|off}][id <normId>][nport <normPort>][cce | ccl | rate <bits/sec>]
+     [limit <bits/sec>][persist <seconds>][correspondent <remoteNorpAddr>]
+     [forward <tcpPort>,<destAddr>/<destPort>[,<remoteNorpAddr>]][version][debug <level>]
+     [trace][dlog <debugLog>][lport <localNorpPort>][rport <remoteNorpPort>]

The norp program command-line options include ...

Table 1. norp Command-line Options

interface <interfaceName>The given <interfaceName> specifies the name (or IP address) of the host network interface norp uses as its "public" proxy address. Currently a single interface may be designated for an instance of norp. Future version of norp may allow for multiple interfaces to be designated depending upon the source and/or destination address of SOCKS proxy connections.
address <publicAddr>This is similar to the "interface" command, but allows a specific address to be set. For example, hosts with multiple addresses assigned may wish to use a specific address for proxy functions.
sport <socksPort>This command is used to specify the port number on which the norp server listens for SOCKS client connections. The default port is currently port number 7000.
port <norpPort>This command is used to specify the UDP port number used for norp session setup signaling. The default norp UDP signaling port is 7001. The configured norp port number (and NORM port number) MUST be unblocked by any network firewalls between norp peers. The given port number is used by norp to listen for remote connection request and is used as the destination port to signal remote norp peers.
norm {on | off}By default, norp attempts to signal the SOCKS connection endpoint to setup a NORM transport connection to handle reliable data transfer for the TCP connection being instantiated. This command with the "off" argument will disable this function and norp will act as a conventional SOCKS proxy server.
id <normId>By default, norp will attempt to self-configure a NORM protocol node identifier using the IP address of the server host. This command allows a specific NORM node identifier value to be set. It is generally not necessary to explicitly set this value for norp unicast proxy connections.
nport <normPort>This command can be used to specify a UDP port number that will be used for NORM protocol transport connections. The default NORM port number used by norp is 7002. The configured NORM port number (and norp UDP signaling port number) MUST be unblocked by any network firewalls between norp peers.
cceThis option enables NORM-CCE congestion control operation that uses Explicit Congestion Notification (ECN) information for NORM protocol end-to-end transmission rate adaption. This is an alternative to the TCP-friendly congestion control mechanism used for NORM by default. Routers in the path of the norp peers using the NORM-CCE option MUST be configured for ECN packet marking in response to congestion.
cclThis option enables experimental NORM-CCL ("Loss Tolerant") congestion control operation that uses some simple heuristics to try to differentiate packet loss due to congestion versus duo to channel bit errors. This is another alternative to the TCP-friendly congestion control mechanism used for NORM by default. No special intermediate system configuration is required, and while more loss tolerant than the default TCP-friendly behavior, is not as effective as the NORM-CCE mode of operation.
rate <bits/sec>This option causes norp to use a preset and fixed transmission rate for each proxied data flow (e.g. TCP connection). This should only be used when the network connectivity usage is carefully pre-planned and previsioned for the expected (i.e. a priori known) flows. At this time, one common transmission rate is used for all flows.
limit <bits/sec>This option sets a limit for the cumulative transmit rate for all flows that norp is proxying. For automated congestion operation, this can also work to "jump start" the usual "slow start" transport rate control by setting the lower bound of rate adjustment based on the limit <bits/sec> / <numFlows>. For example, a single flow will immediately "jump" to close the full limit rate, while the second of two flows would "jump" to half of the "limit" rate. Also, by setting a limit based on a priori connectivity information, this can avoid rate adjustment "overshoot" and help congestion control operate more effectively as compared to a "blind" situation. IMPORTANT: This options should only be applied when the connectivity path is well known and the impact of the lower bound enforcement here will not adversely impact other network traffic flows. A future option may be provided to further reduce or eliminate the lower bound enforcement that would eliminate this concern in less controlled network deployments. A limit value of "-1.0" (default) disables the limit enforcement.
correspondent <remoteNorpAddr>This option causes norp to "route" connections through a norp peer at the specified <remoteNorpAddr>. This is an alternative to the default behavior where norp attempts to connect directly to the connection destination addresses.
persist <seconds>This option controls how persistently norp attempts to deliver data to the remote endpoint when the remote endpoint fails to acknowledge reception. A persist value of -1 makes norp infinitely persistent and the corresponding norp session remains in place until all data is delivered. If not, an orphaned session will remain in place if the remote endpoint is permanently disconnected. The default persist value is 120 seconds (2 minutes).
forward <tcpPort>,<dstAddr>/<dstPort>[,<norpAddr>]This command sets up a "preset" TCP proxy (non-SOCKS) port forwarding session by listening on the specified TCP <tcpPort> for connections and then connecting to the given remote <dstAddr>/<dstPort>. Optionally, a separate remote <norpAddr> may be given. Otherwise, a norp proxy connection is attempted to the given <dstAddr> platform on the norp <port> (or <rport> if specified). Note that multiple such "preset" proxy sessions may be specified on the command-line and each "preset" proxy session can handle multiple connections as needed.
debug <debugLevel>
This command can be used to control the verbosity of norp debug logging output. Generally, the range of the value is 0-12. A higher value results in more verbose, detailed debug output.
traceThis command enables NORM send and receive packet trace logging.
dlog <fileName>
This command can be used to direct norp debug logging output to a given file. The default norp debug logging is to STDERR.
lport <localNorpPort>This command can enable single host, loopback testing by a having norp listen on a different port number than which it uses as the destination port for remote norp peer signaling. E.g., two norp instances on a single machine can be set up with unique <localNorpPort> values and then use the "rport" command to specify each other's destination norp port numbers.
rport <remoteNorpPort>This command is intended to be used in conjunction with the "lport" command to allow separate specification of the destination port number used for remote norp peer signaling.

3.1. SOCKS Client Configuration

TBD - provide overview and examples (for specific SOCKS clients of note such as Dante, Proxifier, etc) of SOCKS client configuration

3.2. Usage Examples

The SOCKS client(s) must be configured to use the norp server unless a preset TCP port forward is specified. For example the Dante proxy distribution (available from http://www.inet.no/dante/) has a socksify command that is installed and can be used to launch existing network applications so their socket communications are directed through the configured server. With Dante, a SOCKS configuration file (typically /etc/socks.conf) or the SOCKS5_SERVER environment variable can be used to set the server address and port number.

The norp "server" is a lightweight module and can be installed on the same end systems requiring the performance benefits of NORM transport. In this case the SOCKS client server configuration is the loopback address and norp SOCKS port number (i.e. 127.0.0.1:7000). The locally installed norp SOCKS server will signal remote network destinations (e.g., upon TCP connection initiation) to determine if the destination is norp-capable. If possible, it will establish a NORM-connection to the remote norp correspondent that connects to the final destination. Otherwise a direct TCP connection (or UDP relay) will be made to the remote destination.

4. The "NORP" UDP Signaling Message Format

The norp proxy uses UDP signaling to confirm presence of a remote norp peer and to set up (and tear down) NORM transport protocol sessions to support the proxied TCP (and eventually UDP) transport connections. The norp instance originating a SOCKS session request is referred to here as the "originator" and the remote norp peer to which the request is directed is referred to as the "correspondent". The norp "originator" is the server associated with the SOCKS client making a request while the "correspondent" establishes connections with the remote SOCKS destination.

The following UDP payload format is used for NORP signaling:

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
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+|   msgType     |    reserved   |           sessionId           |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+|                          normNodeId                           |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+|          normSrcPort          |           normDstPort         |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+|                         timestamp_sec                         |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+|                         timestamp_usec                        |
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+|                           content ...
++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+

The NORP message types include:

SOCKS_REQ

The message content contains a SOCKS5 Request message from the "originator" to the "correspondent" norp server.

ACK_REQ

The message is used to acknowledge receipt of a SOCKS_REQ message. There is no "content"

SOCKS_REP

The message content contains a SOCKS5 Reply message from the "correspondent" norp server.

ACK_REP

The message is used to acknoledge receipt of a SOCKS_REP message. There is no "content".

ORIG_END

This message indicates the "originator" norp server is terminating the given session. There is no "content".

CORR_END

This message indicates the "correspondent" norp server is terminating the given session. There is no "content".

ACK_END

This message is used to acknowledge receipt of either an ORIG_END or CORR_END message. There is no "content".

TBD - describe NORP signaling and the message format given here.

5. Future Plans

There are a number of additional features and refinements planned for the norp implementation. Some of these include:

  1. Source / destination configuration and "routing" options

  2. Data compression options

  3. Security features

\ No newline at end of file diff --git a/norp/doc/norpUserGuide.pdf b/norp/doc/norpUserGuide.pdf new file mode 100644 index 0000000..05e2b7d Binary files /dev/null and b/norp/doc/norpUserGuide.pdf differ diff --git a/norp/doc/norpUserGuide.xml b/norp/doc/norpUserGuide.xml new file mode 100644 index 0000000..a79330a --- /dev/null +++ b/norp/doc/norpUserGuide.xml @@ -0,0 +1,567 @@ + + + +
+ + <inlinemediaobject> + <imageobject> + <imagedata align="center" fileref="resources/ProteanLogo.png" + scale="30"/> + </imageobject> + </inlinemediaobject> <emphasis>norp</emphasis> User's Guide + + norp User's Guide + + + The NRL Nack-Oriented Proxy (norp) project + includes software for an RFC 1928 SOCKS5-compatible proxy server daemon + that is able to use the RFC 5740 Nack-Oriented Reliable Multicast (NORM) + transport protocol for efficient and robust data transfer between + norp proxy instances. The norp + proxy automatically supports conventional SOCKS TCP proxy operation when + a remote norp peer is unavailable. This software + was developed by the Naval + Research Laboratory (NRL) PROTocol Engineering Advanced + Networking Research Group. + The NRL reference implementation of NORM used here is available from + http://www.nrl.navy.mil/itd/ncs/products/norm. + + + + + Overview + + The norp application .... + + + + Theory of Operation + + The norp program acts as a SOCKS proxy server. + It supports the SOCKS5 "CONNECT", "BIND" and "UDP-ASSOCIATE" proxy methods + for conventional SOCKS proxy operation. The current + norp implementation does not require (or support) any + client authentication. Future versions may provide authentication or other + access control mechanisms. The current norp + implementation only provides NORM transport for the SOCKS TCP "CONNECT" + requests. SOCKS "UDP-ASSOCIATE" over NORM will be supported in a future + version. + + Unlike a conventional SOCKS server, it is expected that the + norp daemon can be installed and run as a local + "loopback" server that is co-resident on the host running applications + that wish to take advantage of NORM transport benefits. The + norp daemon implements its own signaling protocol + that will automatically determine, upon TCP (or UDP) connection + establishment, if a remote destination is also similarly + "norp-enabled" and establish a NORM transport + connection as the proxy connection. Otherwise a "business-as-usual" TCP + (or UDP) connection is established on the application's behalf and thus + compatibility with "non-norp" hosts is also + supported. illustrates this high level + concept of operation. + +
+ NORP Concept of Operation + + + + + + +
+ + TBD - provide some more details on norp signaling for peer + detection and NORM session establishment + + Note that as an alternative to making proxied connections directly + to connection destination addresses as illustrated above, a remote + norp peer "correspondent" can be specified as part of + the forward command or, for SOCKS connections, with the + correspondent command (see command descriptions below). + Future versions of norp will include more + sophisticated "routing" options for different destinations and traffic + types. + + + SOCKS Loopback and Intermediate System + + As noted above the principal use case for + norp is to act as a local, "loopback" SOCKS server + that can be used in conjunction with a properly configured SOCKS client. + In this way, all of the configuration parameters are localized and + implicit and no precoordinated configuration with + norp peers (or non-norp hosts) + is required other than using a common UDP port number for NORP + signaling. + + However, there may be use cases where it may be desirable to + deploy norp on intermediate systems at the + connection originating site (or domain) and/or the destination site(s) + (or domain(s)). This is easily supported by the + norp design and future norp versions will provide + configuration options for this type of deployment. + + + + NORM Protocol Usage + + TBD - describe how the NORM streaming capability is used + in a flow-controlled, positively-acknowledged fashion to provide a + reliable TCP proxy function. Also describe the NORM congestion control + options here. + +
+ + + Usage + + Typically, norp can be run in its default + configuration with no command-line options required. However, a number of + options are available via the command-line. This is a summary of + norp usage: + + + norp [interface <ifaceName>][address <publicAddr>][sport <socksPort>][port <norpPort>] + [norm {on|off}][id <normId>][nport <normPort>][cce | ccl | rate <bits/sec>] + [limit <bits/sec>][persist <seconds>][correspondent <remoteNorpAddr>] + [forward <tcpPort>,<destAddr>/<destPort>[,<remoteNorpAddr>]][version][debug <level>] + [trace][dlog <debugLog>][lport <localNorpPort>][rport <remoteNorpPort>] + + The norp program command-line options + include ... + + + + <emphasis><emphasis>norp</emphasis></emphasis> Command-line + Options + + + + + + + + + + + interface + <interfaceName> + + The given <interfaceName> + specifies the name (or IP address) of the host network interface + norp uses as its "public" proxy address. + Currently a single interface may be designated for an instance of + norp. Future version of + norp may allow for multiple interfaces to be + designated depending upon the source and/or destination address of + SOCKS proxy connections. + + + + address <publicAddr> + + This is similar to the "interface" + command, but allows a specific address to be set. For example, + hosts with multiple addresses assigned may wish to use a specific + address for proxy functions. + + + + sport <socksPort> + + This command is used to specify the port number on which + the norp server listens for SOCKS client + connections. The default port is currently port number + 7000. + + + + port <norpPort> + + This command is used to specify the UDP port number used + for norp session setup signaling. The default + norp UDP signaling port is 7001. The + configured norp port number (and NORM port + number) MUST be unblocked by any network firewalls between + norp peers. The given port number is used by + norp to listen for remote connection request + and is used as the destination port to signal remote + norp peers. + + + + norm {on | off} + + By default, norp attempts to signal + the SOCKS connection endpoint to setup a NORM transport connection + to handle reliable data transfer for the TCP connection being + instantiated. This command with the "off" + argument will disable this function and norp + will act as a conventional SOCKS proxy server. + + + + id <normId> + + By default, norp will attempt to + self-configure a NORM protocol node identifier using the IP + address of the server host. This command allows a specific NORM + node identifier value to be set. It is generally not necessary to + explicitly set this value for norp unicast + proxy connections. + + + + nport <normPort> + + This command can be used to specify a UDP port number that + will be used for NORM protocol transport connections. The default + NORM port number used by norp is 7002. The + configured NORM port number (and norp UDP + signaling port number) MUST be unblocked by any + network firewalls between norp peers. + + + + cce + + This option enables NORM-CCE congestion control operation + that uses Explicit Congestion Notification (ECN) information for + NORM protocol end-to-end transmission rate adaption. This is an + alternative to the TCP-friendly congestion control mechanism used + for NORM by default. Routers in the path of the + norp peers using the NORM-CCE option MUST be + configured for ECN packet marking in response to + congestion. + + + + ccl + + This option enables experimental NORM-CCL ("Loss Tolerant") + congestion control operation that uses some simple heuristics to + try to differentiate packet loss due to congestion versus duo to + channel bit errors. This is another alternative to the + TCP-friendly congestion control mechanism used for NORM by + default. No special intermediate system configuration is required, + and while more loss tolerant than the default TCP-friendly + behavior, is not as effective as the NORM-CCE mode of + operation. + + + + rate <bits/sec> + + This option causes norp to use a + preset and fixed transmission rate for each proxied data flow + (e.g. TCP connection). This should only be used when the network + connectivity usage is carefully pre-planned and previsioned for + the expected (i.e. a priori known) flows. At + this time, one common transmission rate is used for all + flows. + + + + limit <bits/sec> + + This option sets a limit for the + cumulative transmit rate for + all flows that norp is + proxying. For automated congestion operation, this can also work + to "jump start" the usual "slow start" transport rate control by + setting the lower bound of rate adjustment based on the limit + <bits/sec> / <numFlows>. For + example, a single flow will immediately "jump" to close the full + limit rate, while the second of two flows would "jump" to half of + the "limit" rate. Also, by setting a limit based on a + priori connectivity information, this can avoid rate + adjustment "overshoot" and help congestion control operate more + effectively as compared to a "blind" situation. IMPORTANT: This + options should only be applied when the connectivity path is well + known and the impact of the lower bound enforcement here will not + adversely impact other network traffic flows. A future option may + be provided to further reduce or eliminate the lower bound + enforcement that would eliminate this concern in less controlled + network deployments. A limit value of "-1.0" (default) disables + the limit enforcement. + + + + correspondent + <remoteNorpAddr> + + This option causes norp to "route" + connections through a norp peer at the + specified <remoteNorpAddr>. This is + an alternative to the default behavior where + norp attempts to connect directly to the + connection destination addresses. + + + + persist <seconds> + + This option controls how persistently norp attempts to + deliver data to the remote endpoint when the remote endpoint fails + to acknowledge reception. A persist value of -1 makes norp + infinitely persistent and the corresponding norp session remains + in place until all data is delivered. If not, an orphaned session + will remain in place if the remote endpoint is permanently + disconnected. The default persist value is 120 seconds (2 + minutes). + + + + forward + <tcpPort>,<dstAddr>/<dstPort>[,<norpAddr>] + + This command sets up a "preset" TCP proxy (non-SOCKS) port + forwarding session by listening on the specified TCP + <tcpPort> for connections and then + connecting to the given remote + <dstAddr>/<dstPort>. + Optionally, a separate remote + <norpAddr> may be given. Otherwise, a + norp proxy connection is attempted to the + given <dstAddr> platform on the + norp <port> (or + <rport> if specified). Note that + multiple such "preset" proxy sessions may be specified on the + command-line and each "preset" proxy session can handle multiple + connections as needed. + + + + debug <debugLevel> + + + + + This command can be used to control the verbosity of + norp debug logging output. Generally, + the range of the value is 0-12. A higher value results in + more verbose, detailed debug output. + + + + + + + trace + + This command enables NORM send and receive packet trace + logging. + + + + dlog <fileName> + + + + + This command can be used to direct + norp debug logging output to a given + file. The default norp debug logging is + to STDERR. + + + + + + + lport <localNorpPort> + + This command can enable single host, loopback testing by a + having norp listen on a different port number + than which it uses as the destination port for remote + norp peer signaling. E.g., two + norp instances on a single machine can be set + up with unique <localNorpPort> values and + then use the "rport" command to specify each + other's destination norp port + numbers. + + + + rport <remoteNorpPort> + + This command is intended to be used in conjunction with the + "lport" command to allow separate specification + of the destination port number used for remote + norp peer signaling. + + + +
+ + + SOCKS Client Configuration + + TBD - provide overview and examples (for specific SOCKS + clients of note such as Dante, Proxifier, etc) of SOCKS client + configuration + + + + Usage Examples + + The SOCKS client(s) must be configured to use the + norp server unless a preset TCP port + forward is specified. For example the Dante proxy + distribution (available from http://www.inet.no/dante/) has a + socksify command that is installed and can be used + to launch existing network applications so their socket communications + are directed through the configured server. With Dante, a SOCKS + configuration file (typically /etc/socks.conf) or + the SOCKS5_SERVER environment variable can be used + to set the server address and port number. + + The norp "server" is a lightweight module and + can be installed on the same end systems requiring the performance + benefits of NORM transport. In this case the SOCKS client server + configuration is the loopback address and norp + SOCKS port number (i.e. 127.0.0.1:7000). The locally + installed norp SOCKS server will signal remote network destinations + (e.g., upon TCP connection initiation) to determine if the destination + is norp-capable. If possible, it will establish a + NORM-connection to the remote norp correspondent + that connects to the final destination. Otherwise a direct TCP + connection (or UDP relay) will be made to the remote destination. + +
+ + + The "NORP" UDP Signaling Message Format + + The norp proxy uses UDP signaling to confirm + presence of a remote norp peer and to set up (and + tear down) NORM transport protocol sessions to support the proxied TCP + (and eventually UDP) transport connections. The norp instance originating + a SOCKS session request is referred to here as the "originator" and the + remote norp peer to which the request is directed is referred to as the + "correspondent". The norp "originator" is the server + associated with the SOCKS client making a request while the + "correspondent" establishes connections with the remote SOCKS + destination. + + The following UDP payload format is used for NORP signaling: + + 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 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| msgType | reserved | sessionId | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| normNodeId | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| normSrcPort | normDstPort | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| timestamp_sec | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| timestamp_usec | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| content ... ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + + + The NORP message types include: + + + + SOCKS_REQ + + + The message content contains a SOCKS5 Request message from the + "originator" to the "correspondent" norp + server. + + + + + ACK_REQ + + + The message is used to acknowledge receipt of a SOCKS_REQ + message. There is no "content" + + + + + SOCKS_REP + + + The message content contains a SOCKS5 Reply message from the + "correspondent" norp server. + + + + + ACK_REP + + + The message is used to acknoledge receipt of a SOCKS_REP + message. There is no "content". + + + + + ORIG_END + + + This message indicates the "originator" + norp server is terminating the given session. + There is no "content". + + + + + CORR_END + + + This message indicates the "correspondent" + norp server is terminating the given session. + There is no "content". + + + + + ACK_END + + + This message is used to acknowledge receipt of either an + ORIG_END or CORR_END message. There is no "content". + + + + + TBD - describe NORP signaling and the message format given + here. + + + + Future Plans + + There are a number of additional features and refinements planned + for the norp implementation. Some of these + include: + + + + Source / destination configuration and "routing" options + + + + Data compression options + + + + Security features + + + +
diff --git a/norp/doc/resources/ProteanLogo.png b/norp/doc/resources/ProteanLogo.png new file mode 100644 index 0000000..4422ee1 Binary files /dev/null and b/norp/doc/resources/ProteanLogo.png differ diff --git a/norp/doc/resources/norpConcept.png b/norp/doc/resources/norpConcept.png new file mode 100644 index 0000000..35fc65e Binary files /dev/null and b/norp/doc/resources/norpConcept.png differ diff --git a/norp/include/norp.h b/norp/include/norp.h new file mode 100644 index 0000000..3872131 --- /dev/null +++ b/norp/include/norp.h @@ -0,0 +1,644 @@ +#ifndef _NORP +#define _NORP + +#include "protoSocket.h" +#include "protoTimer.h" +#include "protoTree.h" +#include "protoDispatcher.h" +#include "protoPktSOCKS.h" +#include "normApi.h" + +class Norp; + +// A "NorpSession" associates a SOCKS client session with either a remote +// NORP endpoint (if one was or can be established) or a "direct connect" +// (TCP, UDP) if the remote endpoint is not NORP-enabled + +// SOCKS requests/replies relayed via NORM command transmissions +// are prepended with this to uniquely identify the command +// (SOCKS request or reply and associate it with a NorpSession +// TBD - or should the endpoint socket binding addrs/ports be used instead?? + +// +// ProtoPktNORP is the packet format that NORP uses to relay SOCKS +// requests and replies via the NORM application-defined command +// signaling mechanism. The format of these commands is: +// +// 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 +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | msgType | reserved | sessionId | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | normNodeId | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | normSrcPort | normDstPort | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | timestamp_sec | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | timestamp_usec | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// | Content ... | +// + + +class PortPool +{ + public: + PortPool(UINT16 basePort); + + UINT16 GrabPort(); + void ReleasePort(UINT16 thePort); + + private: + UINT16 base_port; + bool port_array[50]; +}; // end class PortPool + +class NorpMsg : public ProtoPkt +{ + public: + NorpMsg(UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool initFromBuffer = true, + bool freeOnDestruct = false); + ~NorpMsg(); + + bool InitFromBuffer(unsigned int msgLength, + UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool freeOnDestruct = false) + {return ProtoPkt::InitFromBuffer(msgLength, bufferPtr, numBytes, freeOnDestruct);} + + enum Type + { + MSG_INVALID = 0, + SOCKS_REQ, // "content" is a SOCKS5 request + REQ_ACK, // request acknowledgment (no content) + SOCKS_REP, // "content" is a SOCKS5 request + REP_ACK, // reply acknowledgment (no content) + ORIG_END, // sent by originator to end session + CORR_END, // sent by correspondent to end session + ACK_END // end session acknowledgment (by either) + }; + + Type GetType() const + {return (Type)((UINT8*)buffer_ptr)[OFFSET_TYPE];} + + UINT16 GetSessionId() const + {return (Type)ntohs(((UINT16*)buffer_ptr)[OFFSET_SESSION_ID]);} + + // note "GetUINT32()" does ntohl() conversion + NormNodeId GetNodeId() const + {return (NormNodeId)GetUINT32(buffer_ptr + OFFSET_NODE_ID);} + + UINT16 GetSourcePort() const + {return (Type)ntohs(((UINT16*)buffer_ptr)[OFFSET_SRC_PORT]);} + + UINT16 GetDestinationPort() const + {return (Type)ntohs(((UINT16*)buffer_ptr)[OFFSET_DST_PORT]);} + + void GetTimestamp(struct timeval& timestamp) const + { + timestamp.tv_sec = GetUINT32(buffer_ptr + OFFSET_SEC); + timestamp.tv_usec = GetUINT32(buffer_ptr + OFFSET_USEC); + } + + void GetTimestamp(ProtoTime& theTime) const + { + struct timeval& timestamp = theTime.AccessTimeVal(); + timestamp.tv_sec = GetUINT32(buffer_ptr + OFFSET_SEC); + timestamp.tv_usec = GetUINT32(buffer_ptr + OFFSET_USEC); + } + + const UINT32* GetContentPtr() const + {return ((const UINT32*)AccessContentPtr());} + + UINT32* AccessContentPtr() const + {return (buffer_ptr + OFFSET_CONTENT);} + + unsigned int GetContentLength() const + {return (GetLength() - (OFFSET_CONTENT*4));} + + // Packet building methods + + void Init() + { + SetType(MSG_INVALID); + SetLength(0); + } + + void SetType(Type msgType) + { + ((UINT8*)buffer_ptr)[OFFSET_TYPE] = (UINT8)msgType; + pkt_length = (pkt_length < 1) ? 1 : pkt_length; + } + + void SetSessionId(UINT16 sessionId) + { + ((UINT8*)buffer_ptr)[OFFSET_RESERVED] = 0; + ((UINT16*)buffer_ptr)[OFFSET_SESSION_ID] = htons(sessionId); + pkt_length = (pkt_length < 4) ? 4 : pkt_length; + } + + void SetNodeId(NormNodeId nodeId) + { + SetUINT32(buffer_ptr + OFFSET_NODE_ID, nodeId); + pkt_length = (pkt_length<8) ? 8 : pkt_length; + } + + void SetSourcePort(UINT16 srcPort) + { + ((UINT16*)buffer_ptr)[OFFSET_SRC_PORT] = htons(srcPort); + pkt_length = (pkt_length < 10) ? 10 : pkt_length; + } + + void SetDestinationPort(UINT16 dstPort) + { + ((UINT16*)buffer_ptr)[OFFSET_DST_PORT] = htons(dstPort); + pkt_length = (pkt_length < 12) ? 12 : pkt_length; + } + + void SetTimestamp(struct timeval& timestamp) + { + SetUINT32(buffer_ptr+OFFSET_SEC, timestamp.tv_sec); + SetUINT32(buffer_ptr+OFFSET_USEC, timestamp.tv_usec); + pkt_length = (pkt_length < 20) ? 20 : pkt_length; + } + + bool SetContent(const char* data, unsigned int numBytes) + { + if ((numBytes + (4*OFFSET_CONTENT)) > GetBufferLength()) return false; + memcpy((char*)(buffer_ptr+OFFSET_CONTENT), data, numBytes); + SetContentLength(numBytes); + return true; + } + + void SetContentLength(unsigned int numBytes) + {pkt_length = (4*OFFSET_CONTENT) + numBytes;} + + private: + enum + { + OFFSET_TYPE = 0, // UINT8 offset + OFFSET_RESERVED = OFFSET_TYPE + 1, // UINT8 offset + OFFSET_SESSION_ID = (OFFSET_RESERVED+1)/2, // UINT16 offset + OFFSET_NODE_ID = (2*(OFFSET_SESSION_ID+1))/4, // UINT32 offset + OFFSET_SRC_PORT = (4*(OFFSET_NODE_ID+1))/2, // UINT16 offset + OFFSET_DST_PORT = OFFSET_SRC_PORT + 1, // UINT16 offset + OFFSET_SEC = (2*(OFFSET_DST_PORT+1))/4, // UINT32 offset + OFFSET_USEC = OFFSET_SEC + 1, // UINT32 offset + OFFSET_CONTENT = OFFSET_USEC + 1 // UINT32 offset + }; + +}; // end class NorpMsg + + + +// A NorpPreset is a non-SOCKS proxied session where a +// local TCP port is monitored and connections are proxied +// proxied to a preset remote destination +class NorpPreset : public ProtoTree::Item +{ + public: + NorpPreset(Norp& norpController, UINT16 tcpPort, const ProtoAddress& dstAddr, const ProtoAddress& norpAddr); + ~NorpPreset(); + + const ProtoAddress& GetDstAddr() const + {return dst_addr;} + const ProtoAddress& GetNorpAddr() const + {return (norp_addr.IsValid() ? norp_addr : dst_addr);} + + bool Listen(); + + ProtoSocket& AccessServerSocket() + {return server_socket;} + + private: + const char* GetKey() const + {return ((const char*)&tcp_port);} + unsigned int GetKeysize() const + {return (sizeof(UINT16) << 3);} + + + void OnSocketEvent(ProtoSocket& theSocket, ProtoSocket::Event theEvent); + + Norp& controller; + UINT16 tcp_port; + ProtoAddress dst_addr; + ProtoAddress norp_addr; + ProtoSocket server_socket; + +}; // end class NorpPreset + +class NorpPresetList : public ProtoTreeTemplate +{ + public: + NorpPreset* FindPreset(UINT16 tcpPort) const + {return Find((char*)&tcpPort, sizeof(UINT16) << 3);} +}; // end class NorpPresetList + +class NorpSession : public ProtoTree::Item +{ + public: + NorpSession(Norp& norpController, UINT16 sessionId, NormNodeId originatorId = NORM_NODE_NONE); + ~NorpSession(); + + enum SocksState + { + SOCKS_VOID, // when session gives self back to "controller" at Close + SOCKS_IDLE, // initial, inactive state (socks_client_socket is closed) + SOCKS_GET_AUTH_REQ, // first active state, accumulating auth request bytes from client + SOCKS_PUT_AUTH_REP, // next, writing auth reply bytes to client + SOCKS_GET_REQUEST, // next, accumulate SOCKS request bytes from client + SOCKS_PUT_REQUEST, // relaying request to remote NORP peer + SOCKS_CONNECTING, // attempt to satisfy request via remote connect, etc (this may need to be refined) + SOCKS_GET_REPLY, // awaiting reply from remote NORP peer + SOCKS_PUT_REPLY, // next, writing SOCK reply bytes to client + SOCKS_CONNECTED, // finally, reading/writing data from/to client + SOCKS_SHUTDOWN // session ended, in process of shutting down + }; + + void SetProxyAddress(const ProtoAddress& proxyAddr) + {proxy_addr = proxyAddr;} + + void SetNormRateBounds(double rateMin, double rateMax); + + NormNodeId GetNodeId() const; + + UINT16 GetSessionId() const + {return session_id.identifier;} + + NormNodeId GetOriginatorId() const + {return session_id.originator;} + + bool IsRemoteSession() const + {return (NORM_SESSION_INVALID != norm_session);} + + bool IsRemoteOriginator() const // assumes "IsRemoteSession()" + {return (NORM_NODE_NONE == GetOriginatorId());} + + bool AcceptClientConnection(ProtoSocket& serverSocket, bool normEnable = true); + + bool AcceptPresetClientConnection(NorpPreset& preset, bool normEnable); + + SocksState GetSocksState() const + {return socks_state;} + + const ProtoAddress& GetClientAddress() const + {return socks_client_socket.GetDestination();} + + void Shutdown(); // hard shutdown, including signaling remote via NORP messaging + + bool ShutdownComplete() const; // Tests for inband shutdown completion + + void Close(); // hard local shutdown and cleanup including session deletion by controller + + void OnNormEvent(NormEvent& theEvent); + + bool OnClientRequest(const ProtoPktSOCKS::Request request, const ProtoAddress& srcAddr); + + bool MakeDirectConnect(); // convert remote session to direct connect + + // handles request relayed from remote NORP server + bool OnRemoteRequest(const NorpMsg& theMsg, const ProtoAddress& senderAddr); + + bool OnRemoteRequestAcknowledgment(const NorpMsg& theMsg, const ProtoAddress& senderAddr); + + bool OnRemoteReply(const NorpMsg& theMsg, const ProtoAddress& senderAddr); + + bool OnRemoteReplyAcknowledgment(const NorpMsg& theMsg); + + typedef struct Moniker + { + NormNodeId originator; + UINT16 identifier; // TBD - make this UINT32? + } Moniker; + + static bool GetNormNodeAddress(NormNodeHandle nodeHandle, ProtoAddress& theAddr); + + static const double NORP_RTT_MIN; + static const double NORP_RTT_MAX; + static const double NORP_RTT_DEFAULT; + + private: + void ActivateTimer(ProtoTimer& theTimer); + + const char* GetKey() const + {return ((const char*)&session_id);} + unsigned int GetKeysize() const + {return (sizeof(Moniker) << 3);} + + void OnSocksClientEvent(ProtoSocket& theSocket, + ProtoSocket::Event theEvent); + + // Note our "norp_tx_socket" is "connected" so we get error message when "port unreachable" + bool SendMessage(const NorpMsg& msg) + { + unsigned int numBytes = msg.GetLength(); + return norp_tx_socket.Send((const char*)msg.GetBuffer(), numBytes); + } + + bool OnNorpMsgTimeout(ProtoTimer& theTimer); + + bool OnCloseTimeout(ProtoTimer& theTimer); + + void OnUdpRelayEvent(ProtoSocket& theSocket, + ProtoSocket::Event theEvent); + + // These methods are invoked as needed upon different events and SOCKS state transitions + bool GetClientAuthRequest(); + bool PutClientAuthReply(); + bool GetClientRequest(); + bool ConnectToRemote(const ProtoAddress& srcAddr); + bool BindRemote(UINT16 bindPort); + bool OpenUdpRelay(); + bool PutClientReply(); + bool GetClientData(); + bool PutClientData(); + + bool PutRemoteRequest(const ProtoPktSOCKS::Request& request, const ProtoAddress& remoteAddr); + bool OriginatorStartNorm(const ProtoAddress& senderAddr, + NormNodeId corrNormId, + UINT16 normSrcPort, + UINT16 normDstPort); + + bool PutRemoteReply(); + + void OnSocksRemoteEvent(ProtoSocket& theSocket, + ProtoSocket::Event theEvent); + // These methods are invoked as needed upon different events and SOCKS state transitions + bool GetRemoteData(); + bool PutRemoteData(); + + enum {SOCKS_BUFFER_SIZE = 16384}; + enum {NORP_BUFFER_SIZE = 512}; + enum {NORM_BUFFER_SIZE = (8192*1024)}; + + unsigned int WriteToNormStream(const char* buffer, unsigned int numBytes); + void FlushNormStream(bool eom, NormFlushMode flushMode); + unsigned int ComputeNormStreamBufferSegmentCount(unsigned int bufferBytes, UINT16 segmentSize, UINT16 blockSize); + + + // Member variables + Norp& controller; + bool is_preset; // special "preset" (non-SOCKS) proxy session + ProtoTimer close_timer; + Moniker session_id; + ProtoAddress proxy_addr; // address to advertise for remote connections + SocksState socks_state; + ProtoSocket socks_client_socket; // TCP connection from this server to SOCKS client + ProtoSocket socks_remote_socket; // TCP connection to remote when "direct connect" is used + ProtoSocket udp_relay_socket; // UDP relay socket (used for UDP_ASSOC requests) + ProtoAddress udp_client_addr; // address of local UDP_ASSOC client + + UINT32 client_buffer[SOCKS_BUFFER_SIZE/sizeof(UINT32)]; // usually data received from client + unsigned int client_pending; // total bytes pending reception or transmission + unsigned int client_index; // current index + UINT32 remote_buffer[SOCKS_BUFFER_SIZE/sizeof(UINT32)]; // usually for data received from remote + unsigned int remote_pending; // total bytes pending reception or transmission + unsigned int remote_index; // current index + + // NORM-related state + bool norm_enable; + ProtoSocket norp_tx_socket; // used to send NORP signaling messages + ProtoAddress norp_remote_addr; + ProtoTimer norp_msg_timer; + NorpMsg norp_msg; // current message being transmitted + UINT32 norp_msg_buffer[NORP_BUFFER_SIZE/sizeof(UINT32)]; + double norp_rtt_estimate; // from REQUEST<->ACK or REPLY<->ACK exchange + NormSessionHandle norm_session; + NormObjectHandle norm_tx_stream; + NormObjectHandle norm_rx_stream; + bool norm_rx_pending; + double persist_interval; // in seconds, to timeout NORM delivery persistence after local TCP disconnect + ProtoTime persist_start_time; // marks start of persistence timeout + double norm_rate_min; + double norm_rate_max; + + UINT16 norm_segment_size; + unsigned int norm_stream_buffer_max; + unsigned int norm_stream_buffer_count; + unsigned int norm_stream_bytes_remain; + bool norm_watermark_pending; + +}; // end class NorpSession + +class NorpSessionList : public ProtoTreeTemplate +{ + public: + NorpSession* FindSession(UINT16 sessionId, NormNodeId originatorId = NORM_NODE_NONE) const + { + NorpSession::Moniker id; + memset(&id, 0, sizeof(NorpSession::Moniker)); + id.originator = originatorId; + id.identifier = sessionId; + return Find((char*)&id, sizeof(NorpSession::Moniker) << 3); + } // end NorpSessionList::FindSession() +}; // end class NorpSessionList + + + +// The "Norp" class is an instance of a SOCKS server that manages +// SOCKS/NORM (or direct-connect) sessions +class Norp +{ + public: + Norp(ProtoDispatcher& theDispatcher); + ~Norp(); + + enum + { + DEFAULT_SOCKS_PORT = 7000, // SOCKS server TCP listen port used for NORP + DEFAULT_NORP_PORT = 7001, // Where NORP listens for UDP signaling, relayed commands, etc + DEFAULT_NORM_PORT = 7002 // Port used for NORM data transfer + }; + + static const double DEFAULT_TX_RATE; + static const double DEFAULT_PERSIST_INTERVAL; + + void SetSocksPort(UINT16 thePort) + {socks_port = thePort;} + + void SetProxyAddress(const ProtoAddress& proxyAddr) + {proxy_addr = proxyAddr;} + + const ProtoAddress& GetProxyAddress() const + {return proxy_addr;} + + bool StartServer(bool normEnable); + + void StopServer(); + + void SetLocalNorpPort(UINT16 thePort) + {norp_local_port = thePort;} + + void SetRemoteNorpPort(UINT16 thePort) + {norp_remote_port = thePort;} + + UINT16 GetNorpPort() const + {return norp_remote_port;} + + // In future, this will reference a "routing" + // table using the "destAddr" + bool GetRemoteNorpAddress(const ProtoAddress& destAddr, + ProtoAddress& norpAddr) const + { + norpAddr = norp_remote_addr.IsValid() ? norp_remote_addr : destAddr; + return true; + } + // This sets a remote "norp" peer address that is used instead of + // connecting directly to destination endpoints + // TBD - add more routing options than this + void SetRemoteNorpAddress(const ProtoAddress& remoteAddr) + {norp_remote_addr = remoteAddr;} + + void SetInitialRtt(double seconds) + {norp_rtt_init = seconds;} + + double GetInitialRtt() const + {return norp_rtt_init;} + + // NORM protocol parameters + void SetNormNodeId(NormNodeId nodeId) + {norm_node_id = nodeId;} + NormNodeId GetNormNodeId() const + {return norm_node_id;} + + NormInstanceHandle GetNormInstance() const + {return norm_instance;} + + void SetNormPort(UINT16 thePort) + {norm_port = thePort;} + UINT16 GetNormPort() const + {return norm_port;} + + /* + UINT16 GrabPort() + {return port_pool.GrabPort();} + void ReleasePort(UINT16 thePort) + {port_pool.ReleasePort(thePort);} + */ + + enum NormCC + { + NORM_CC, + NORM_CCE, + NORM_CCL, + NORM_FIXED + }; + + void SetNormCC(NormCC ccMode) + {norm_cc_mode = ccMode;} + NormCC GetNormCC() const + {return norm_cc_mode;} + + // For fixed-rate operation + void SetNormTxRate(double txRate) + {norm_tx_rate = txRate;} + double GetNormTxRate() const + {return norm_tx_rate;} + + // Sets upper bound on _cumulative_ NormSession + // transmit rates + void SetNormTxLimit(double txLimit) + {norm_tx_limit = txLimit;} + + double GetNormTxLimit() const + {return norm_tx_limit;} + + void SetNormTrace(bool enable) + {norm_trace = enable;} + bool GetNormTrace() const + {return norm_trace;} + + void SetNormSegmentSize(UINT16 numBytes) + {norm_segment_size = numBytes;} + UINT16 GetNormSegmentSize() const + {return norm_segment_size;} + + void SetNormBlockSize(UINT32 numSegments) + {norm_block_size = numSegments;} + UINT16 GetNormBlockSize() const + {return norm_block_size;} + + void SetNormParityCount(UINT16 numSegments) + {norm_parity_count = numSegments;} + UINT16 GetNormParityCount() const + {return norm_parity_count;} + + void SetNormParityAuto(UINT16 numSegments) + {norm_parity_auto = numSegments;} + UINT16 GetNormParityAuto() const + {return norm_parity_auto;} + + ProtoSocket::Notifier& GetSocketNotifier() const + {return static_cast(dispatcher);} + + void ActivateTimer(ProtoTimer& theTimer) + {dispatcher.ActivateTimer(theTimer);} + + bool SendMessage(const NorpMsg& msg, const ProtoAddress& dstAddr) + {return norp_rx_socket.SendTo((const char*)msg.GetBuffer(), msg.GetLength(), dstAddr);} + + void OnNorpSocketEvent(ProtoSocket& theSocket, + ProtoSocket::Event theEvent); + + void OnSessionClose(NorpSession& theSession); + + bool AddPreset(UINT16 tcpPort, const ProtoAddress& dstAddr, const ProtoAddress& norpAddr); + bool AcceptPresetClientConnection(NorpPreset& preset); + + private: + void AddSession(NorpSession& session); + void RemoveSession(NorpSession& session); + + void OnSocksServerEvent(ProtoSocket& theSocket, + ProtoSocket::Event theEvent); + + static void NormEventCallback(ProtoDispatcher::Descriptor descriptor, + ProtoDispatcher::Event theEvent, + const void* userData); + + void OnNormEvent(); + void OnRemoteRequest(const NorpMsg& theMsg, const ProtoAddress& senderAddr); + void OnRemoteReply(const NorpMsg& theMsg, const ProtoAddress& senderAddr); + + // Member variables + ProtoDispatcher& dispatcher; // TBD - make Norp multi-threaded w/ a pool of dispatchers + + ProtoSocket socks_server_socket; // (SOCKS clients connect to this) + UINT16 socks_port; + ProtoAddress proxy_addr; + UINT16 next_session_id; // used to dole out unique session ids + NorpSessionList session_list; // list of client and remote sessions + unsigned int session_count; + NorpPresetList preset_list; // list of preset proxies w/ listening TCP socket + + ProtoSocket norp_rx_socket; // UDP socket for receiving NORP signaling + UINT16 norp_local_port; // For loopback debugging, we allow "local" server port to be different than "remote" + UINT16 norp_remote_port; + ProtoAddress norp_remote_addr; // if unspecified, we proxy directly to connection destination address + double norp_rtt_init; // used as initial roundtrip estimate for NORP signaling + + bool norm_enable; + NormInstanceHandle norm_instance; + NormNodeId norm_node_id; + + // NORM parameters (including some for debugging + UINT16 norm_port; + NormCC norm_cc_mode; + double norm_tx_rate; // in bits/sec, only applicable for cc_mode == NORM_FIXED + double norm_tx_limit; // in bits/sec (_cumulative_ rate limit option) + char iface_name[64]; // Network interface + UINT16 norm_segment_size; // payload bytes per NORM_DATA message + UINT16 norm_block_size; // number of user data segments per FEC coding block + UINT16 norm_parity_count; // number of _computed_ parity segments per FEC coding block + UINT16 norm_parity_auto; // number of proactive (automatically sent) parity segments per block + + bool norm_trace; + + //PortPool port_pool; + +}; // end class Norp + +#endif // _NORP diff --git a/norp/include/norpVersion.h b/norp/include/norpVersion.h new file mode 100644 index 0000000..c3ad7d0 --- /dev/null +++ b/norp/include/norpVersion.h @@ -0,0 +1,3 @@ +#ifndef _NORP_VERSION +#define NORP_VERSION "0.1b5" +#endif // NORP_VERSION diff --git a/norp/include/protoPktSOCKS.h b/norp/include/protoPktSOCKS.h new file mode 100644 index 0000000..9624525 --- /dev/null +++ b/norp/include/protoPktSOCKS.h @@ -0,0 +1,379 @@ + +#ifndef _PROTO_SOCKS +#define _PROTO_SOCKS + +#include "protoPkt.h" +#include "protoAddress.h" + +// These classes provide message parsing and building for +// the RFC 1928 SOCKS5 protocol specification. +// NOTES: +// 1) Only the methods needed for SOCKS server operation are defined here so far +// 2) Additional external helper logic is needed to incrementally read data from a +// SOCKS TCP client-server socket for a full SOCKS message. The message parsing +// here assumes the message has been read up to the point of whatever field +// is being requested. + +namespace ProtoPktSOCKS +{ + // SOCKS5 message definitions per RFC1928 + + // SOCKS5 auth request: VERSION + NMETHODS + METHOD_LIST ... + // 1 byte + 1 byte + N bytes + + // SOCKS5 authentication methods + enum AuthType + { + AUTH_NONE = 0x00, + AUTH_GSSAPI = 0x01, + AUTH_USER = 0x02, // username / password + AUTH_INVALID = 0xff // no acceptable method + }; + + class AuthRequest : public ProtoPkt + { + public: + AuthRequest(UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool initFromBuffer = true, + bool freeOnDestruct = false); + ~AuthRequest(); + + bool InitFromBuffer(unsigned int reqLength, + UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool freeOnDestruct = false) + {return ProtoPkt::InitFromBuffer(reqLength, bufferPtr, numBytes, freeOnDestruct);} + + UINT8 GetVersion() const + {return ((pkt_length > OFFSET_VERSION) ? + (((UINT8*)buffer_ptr)[OFFSET_VERSION]) : 0);} + + UINT8 GetMethodCount() const + {return ((pkt_length > OFFSET_NMETHOD) ? + (((UINT8*)buffer_ptr)[OFFSET_NMETHOD]) : 0);} + + AuthType GetMethod(UINT8 index) const + {return ((pkt_length > (unsigned int)(OFFSET_METHODS + index)) ? + (AuthType)(((UINT8*)buffer_ptr)[OFFSET_METHODS + index]) : AUTH_INVALID);} + + // TBD - implement auth request building (don't need this for a server) + + private: + enum + { + OFFSET_VERSION = 0, // 1 byte + OFFSET_NMETHOD = OFFSET_VERSION + 1, // 1 byte + OFFSET_METHODS = OFFSET_NMETHOD + 1 // N bytes + }; + + }; // end class ProtoPktSOCKS::AuthRequest + + // SOCKS5 auth reply: VERSION + METHOD_SELECTED + // bytes: 1 + 1 + class AuthReply : public ProtoPkt + { + public: + AuthReply(UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool initFromBuffer = true, + bool freeOnDestruct = false); + ~AuthReply(); + + bool InitFromBuffer(unsigned int replyLength, + UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool freeOnDestruct = false) + {return ProtoPkt::InitFromBuffer(replyLength, bufferPtr, numBytes, freeOnDestruct);} + + UINT8 GetVersion() const + {return ((pkt_length > OFFSET_VERSION) ? + (((UINT8*)buffer_ptr)[OFFSET_VERSION]) : 0);} + + UINT8 GetMethod() const + {return ((pkt_length > OFFSET_METHOD) ? + (((UINT8*)buffer_ptr)[OFFSET_METHOD]) : AUTH_INVALID);} + + void SetVersion(UINT8 version) + { + ((UINT8*)buffer_ptr)[OFFSET_VERSION] = version; + pkt_length = 1; + } + + void SetMethod(AuthType method) + { + ((UINT8*)buffer_ptr)[OFFSET_METHOD] = method; + pkt_length = 2; + } + + private: + enum + { + OFFSET_VERSION = 0, // 1 byte + OFFSET_METHOD = OFFSET_VERSION + 1 // 1 byte + }; + + }; // end class ProtoPktSOCKS::AuthReply + + // SOCKS5 request: VERSION + REQ + RESV + ADDR_TYP + DST.ADDR + DST.PORT + // bytes: 1 + 1 + 1 + 1 + N + 2 + + // SOCKS5 address types (1 byte) + enum AddrType + { + ADDR_INVALID = 0x00, // invalid addr type (for implementation) + IPv4 = 0x01, // 4-bytes, network byte order + NAME = 0x03, // 1-byte of "name" length, followed by "name" + IPv6 = 0x04 // 16-bytes, network byte order + }; // end ProtoPktSOCKS::AddrType + + class Request : public ProtoPkt + { + public: + Request(UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool initFromBuffer = true, + bool freeOnDestruct = false); + ~Request(); + + enum Command + { + CMD_INVALID = 0x00, + CONNECT = 0x01, + BIND = 0x02, + UDP_ASSOC = 0x03 // Setup UDP association + }; // end ProtoPktSOCKS::Request::Command + + bool InitFromBuffer(unsigned int replyLength, + UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool freeOnDestruct = false) + {return ProtoPkt::InitFromBuffer(replyLength, bufferPtr, numBytes, freeOnDestruct);} + + UINT8 GetVersion() const + {return ((pkt_length > OFFSET_VERSION) ? + (((UINT8*)buffer_ptr)[OFFSET_VERSION]) : 0);} + + Command GetCommand() const + {return ((pkt_length > OFFSET_COMMAND) ? + (Command)(((UINT8*)buffer_ptr)[OFFSET_COMMAND]) : CMD_INVALID);} + + AddrType GetAddressType() const + {return ((pkt_length > OFFSET_ATYPE) ? + (AddrType)(((UINT8*)buffer_ptr)[OFFSET_ATYPE]) : ADDR_INVALID);} + + UINT8 GetAddressLength() const; + + const char* GetAddressPtr() const + {return (const char*)AccessAddressPtr();} + + UINT16 GetPort() const; + + bool GetAddress(ProtoAddress& theAddr) const; + + // TBD - implement request building (don't need this for a server) + + void SetVersion(UINT8 version) + { + ((UINT8*)buffer_ptr)[OFFSET_VERSION] = version; + pkt_length = 1; + } + void SetCommand(Command cmd) + { + ((UINT8*)buffer_ptr)[OFFSET_COMMAND] = (UINT8)cmd; + pkt_length = 2; + } + void SetAddressType(AddrType addrType) + { + ((UINT8*)buffer_ptr)[OFFSET_RESERVED] = 0; + ((UINT8*)buffer_ptr)[OFFSET_ATYPE] = (UINT8)addrType; + pkt_length = 4; + } + bool SetAddress(const ProtoAddress& theAddr); // note this sets port, too + bool SetName(const char* theName); + bool SetPort(UINT16 thePort); + + private: + enum + { + OFFSET_VERSION = 0, // 1 byte + OFFSET_COMMAND = OFFSET_VERSION + 1, // 1 byte + OFFSET_RESERVED = OFFSET_COMMAND + 1, // 1 byte + OFFSET_ATYPE = OFFSET_RESERVED + 1, // 1 byte + OFFSET_ADDR = OFFSET_ATYPE + 1 // N bytes + }; + + char* AccessAddressPtr() const + { + char* addrPtr = ((char*)buffer_ptr) + OFFSET_ADDR; + return (NAME == GetAddressType()) ? (addrPtr + 1) : addrPtr; + } + + }; // end class ProtoPktSOCKS::Request + + // SOCKS5 REPLY: VERSION + REP + RESV + ATYP + BIND.ADDR + BIND.PORT + // bytes: 1 + 1 + 1 + 1 + N + 2 + class Reply : public ProtoPkt + { + public: + Reply(UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool initFromBuffer = true, + bool freeOnDestruct = false); + ~Reply(); + + enum Type + { + SUCCESS = 0x00, // succeeded + FAILURE = 0x01, // general failure + DENIED = 0x02, // connection not allowed + NET_X = 0x03, // network unreachable + HOST_X = 0x04, // host unreachable + REFUSED = 0x05, // connection refused + TTL_X = 0x06, // TTL expired + CMD_X = 0x07, // command not supported + ADDR_X = 0x08, // address type not supported + TYPE_INVALID = 0xff // invalid reply type + }; // end ProtoPktSOCKS::Reply::Type + + bool InitFromBuffer(unsigned int replyLength, + UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool freeOnDestruct = false) + {return ProtoPkt::InitFromBuffer(replyLength, bufferPtr, numBytes, freeOnDestruct);} + + UINT8 GetVersion() const + {return ((pkt_length > OFFSET_VERSION) ? + (((UINT8*)buffer_ptr)[OFFSET_VERSION]) : 0);} + + Type GetType() const + {return ((pkt_length > OFFSET_TYPE) ? + (Type)(((UINT8*)buffer_ptr)[OFFSET_TYPE]) : TYPE_INVALID);} + + AddrType GetAddressType() const + {return ((pkt_length > OFFSET_ATYPE) ? + (AddrType)(((UINT8*)buffer_ptr)[OFFSET_ATYPE]) : ADDR_INVALID);} + + UINT8 GetAddressLength() const; + + const char* GetAddressPtr() const + {return (const char*)AccessAddressPtr();} + + UINT16 GetPort() const; + + bool GetAddress(ProtoAddress& theAddr) const; + + // Reply building (call these in order + void SetVersion(UINT8 version) + { + ((UINT8*)buffer_ptr)[OFFSET_VERSION] = version; + pkt_length = 1; + } + void SetType(Type replyType) + { + ((UINT8*)buffer_ptr)[OFFSET_TYPE] = (UINT8)replyType; + pkt_length = 2; + } + void SetAddress(AddrType addrType, const char* addrPtr, UINT8 addrLen); + void SetPort(UINT16 thePort); + void SetAddress(const ProtoAddress& theAddr); // sets address and port from ProtoAddress + + private: + enum + { + OFFSET_VERSION = 0, // 1 byte + OFFSET_TYPE = OFFSET_VERSION + 1, // 1 byte + OFFSET_RESERVED = OFFSET_TYPE + 1, // 1 byte + OFFSET_ATYPE = OFFSET_RESERVED + 1, // 1 byte + OFFSET_ADDR = OFFSET_ATYPE + 1 // N bytes + }; + + char* AccessAddressPtr() const + { + char* addrPtr = ((char*)buffer_ptr) + OFFSET_ADDR; + return (NAME == GetAddressType()) ? (addrPtr + 1) : addrPtr; + } + + }; // end class ProtoPktSOCKS::Reply + + // UDP relay header: RESV + FRAG + ATYP + DST.ADDR + DST.PORT + DATA + // bytes: 2 + 1 1 + N + 2 + K + + class UdpRequest : public ProtoPkt + { + public: + UdpRequest(UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool initFromBuffer = true, + bool freeOnDestruct = false); + ~UdpRequest(); + + bool InitFromBuffer(unsigned int requestLength, + UINT32* bufferPtr = NULL, + unsigned int numBytes = 0, + bool freeOnDestruct = false) + {return ProtoPkt::InitFromBuffer(requestLength, bufferPtr, numBytes, freeOnDestruct);} + + UINT8 GetFrag() const + {return ((pkt_length > OFFSET_FRAG) ? + (((UINT8*)buffer_ptr)[OFFSET_FRAG]) : 0);} + + AddrType GetAddressType() const + {return ((pkt_length > OFFSET_ATYPE) ? + (AddrType)(((UINT8*)buffer_ptr)[OFFSET_ATYPE]) : ADDR_INVALID);} + + UINT8 GetAddressLength() const; + + const char* GetAddressPtr() const + {return (const char*)AccessAddressPtr();} + + UINT16 GetPort() const; + + bool GetAddress(ProtoAddress& theAddr) const; + + const char* GetDataPtr() const + {return (const char*)AccessDataPtr();} + + unsigned int GetDataLength() const + {return (GetLength() - (GetDataPtr() - ((const char*)buffer_ptr)));} + + + // Packet building (call these in order) + void SetFragment(UINT8 fragId) + { + ((UINT8*)buffer_ptr)[OFFSET_RESERVED] = 0; + ((UINT8*)buffer_ptr)[OFFSET_FRAG] = fragId; + pkt_length = OFFSET_FRAG + 1; + } + void SetAddress(AddrType addrType, const char* addrPtr, UINT8 addrLen); + void SetPort(UINT16 thePort); + void SetAddress(const ProtoAddress& theAddr); // sets address and port from ProtoAddress + void SetData(const char* data, unsigned int numBytes) + { + memcpy(AccessDataPtr(), data, numBytes); + SetDataLength(numBytes); + } + void SetDataLength(unsigned int numBytes) + {pkt_length = (GetDataPtr() - ((char*)buffer_ptr)) + numBytes;} + + private: + enum + { + OFFSET_RESERVED = 0, // 2 bytes + OFFSET_FRAG = OFFSET_RESERVED + 2, // 1 byte + OFFSET_ATYPE = OFFSET_FRAG + 1, // 1 byte + OFFSET_ADDR = OFFSET_ATYPE + 1 // N bytes + }; + char* AccessAddressPtr() const + { + char* addrPtr = ((char*)buffer_ptr) + OFFSET_ADDR; + return (NAME == GetAddressType()) ? (addrPtr + 1) : addrPtr; + } + char* AccessDataPtr() const + {return (AccessAddressPtr() + GetAddressLength() + 2);} + + }; // end class ProtoPktSOCKS::UdpRequest + +}; // end namespace ProtoPktSOCKS + +#endif // _PROTO_SOCKS diff --git a/norp/makefiles/Makefile.common b/norp/makefiles/Makefile.common new file mode 100644 index 0000000..b8112e0 --- /dev/null +++ b/norp/makefiles/Makefile.common @@ -0,0 +1,54 @@ +######################################################################### +# "norp" common Makefile portion +# + +SHELL=/bin/sh + +.SUFFIXES: .cpp $(.SUFFIXES) + +# "chant" depends upon the NRL "norm" and "protolib" libraries +NORM = ../norm +PROTOLIB = $(NORM)/protolib +LIBPROTO = $(PROTOLIB)/lib/libprotokit.a +LIBNORM = $(NORM)/lib/libnorm.a + +COMMON = ../src/common + +NORP_SRC = $(COMMON)/norp.cpp $(COMMON)/norpApp.cpp \ + $(COMMON)/protoPktSOCKS.cpp $(SYSTEM_SRC) + +NORP_OBJ = $(NORP_SRC:.cpp=.o) + +INCLUDES = -I../include -I$(NORM)/include -I$(PROTOLIB)/include + +CFLAGS = -g -DPROTO_DEBUG -Wall -O -fPIC $(SYSTEM_CFLAGS) $(PROTO_CFLAGS) $(INCLUDES) + +LIBS = $(SYSTEM_LIBS) + +TARGETS = norp + +# Rule for C++ .cpp extension +.cpp.o: + $(CC) -c $(CFLAGS) -o $*.o $*.cpp + + +all: norp + +$(PROTOLIB)/lib/libprotokit.a: + $(MAKE) -C $(PROTOLIB)/makefiles -f Makefile.$(SYSTEM) libprotokit.a + +$(NORM)/lib/libnorm.a: + $(MAKE) -C $(NORM)/makefiles -f Makefile.$(SYSTEM) libnorm.a + +norp: $(LIBPROTO) $(LIBNORM) $(NORP_OBJ) + $(CC) $(LDFLAGS) -o $@ $(NORP_OBJ) $(LIBS) $(LIBNORM) $(LIBPROTO) + +clean: + rm -f norp $(COMMON)/*.o; + $(MAKE) -C $(NORM)/makefiles -f Makefile.$(SYSTEM) clean; + $(MAKE) -C $(PROTOLIB)/makefiles -f Makefile.$(SYSTEM) clean; + +distclean: clean + +# DO NOT DELETE THIS LINE -- mkdep uses it. +# DO NOT PUT ANYTHING AFTER THIS LINE, IT WILL GO AWAY. diff --git a/norp/makefiles/Makefile.linux b/norp/makefiles/Makefile.linux new file mode 100644 index 0000000..bc2ec0b --- /dev/null +++ b/norp/makefiles/Makefile.linux @@ -0,0 +1,27 @@ +# +# Linux wxWindows Makefile definitions +# + +# 1) Where to find the wxWindows files +WX_FLAGS = `wx-config --cxxflags` +WX_LIBS = `wx-config --libs` + +# 2) System specific additional libraries, include paths, etc +# (Where to find X11 libraries, etc) +# +SYSTEM_FLAGS = +SYSTEM_INCLUDES = +SYSTEM_LIBS = -lm -lpthread +SYSTEM_REZ = touch + + +PROTO_CFLAGS = -DLINUX -DUNIX -DHAVE_IPV6 -DHAVE_GETLOGIN -D_FILE_OFFSET_BITS=64 -DHAVE_LOCKF -DHAVE_OLD_SIGNALHANDLER -DHAVE_DIRFD -DHAVE_ASSERT + +SYSTEM = linux +CC = g++ +RANLIB = ranlib +AR = ar + +SYSTEM_SRC = + +include Makefile.common diff --git a/norp/makefiles/Makefile.macosx b/norp/makefiles/Makefile.macosx new file mode 100644 index 0000000..81f1984 --- /dev/null +++ b/norp/makefiles/Makefile.macosx @@ -0,0 +1,48 @@ +# +# MacOS-X wxWindows Makefile definitions +# + +# 1) wxWidgets flags and libs +WX_CONFIG_PATH = /usr/local/bin/wx-config +WX_CFLAGS = `$(WX_CONFIG_PATH) --cxxflags` +WX_LIBS = `$(WX_CONFIG_PATH) --libs` + +# 2) System-specific flags and libs +SYSTEM_CFLAGS = -arch x86_64 +SYSTEM_LIBS = -arch x86_64 -lresolv + +# 3) System-specific Protolib flags (_must_ correspond to Protolib SYSTEM_HAVES) +# Must choose appropriate for the following: +# +# A) -DHAVE_CUSERID (preferred) or -DHAVE_GETLOGIN for cuserid() or getlogin() +# functions to obtain user's login name (We may change this to getpwd() +# if that is better across different platforms and login environments) +# +# B) -DHAVE_LOCKF (preferred) or -D_LOCKF for lockf() or flock() file locking +# functions to attempt exclusive lock on writing to files +# +# C) Specify -DHAVE_DIRFD if you system provides the "dirfd()" function +# (Most don't have it defined ... but some do) +# +# D) Optionally specify -DHAVE_ASSERT if your system has a built-in ASSERT() +# routine. +# +# E) Some systems (SOLARIS/SUNOS) have a few gotchas which require +# some #ifdefs to avoid compiler warnings ... so you might need +# to specify -DSOLARIS or -DSUNOS depending on your OS. +# (We export these for other Makefiles as needed) +# + +PROTO_CFLAGS = -DUNIX -DHAVE_IPV6 -DHAVE_ASSERT -DHAVE_GETLOGIN -DHAVE_FLOCK -DHAVE_DIRFD + +# Additional system-specific source code to use +SYSTEM_SRC = + +SYSTEM = macosx +CC = c++ +RANLIB = ranlib +AR = ar + +SYSTEM_BUNDLE = chant.app + +include Makefile.common diff --git a/norp/norm b/norp/norm new file mode 120000 index 0000000..a96aa0e --- /dev/null +++ b/norp/norm @@ -0,0 +1 @@ +.. \ No newline at end of file diff --git a/norp/src/common/norp.cpp b/norp/src/common/norp.cpp new file mode 100644 index 0000000..16ee4ba --- /dev/null +++ b/norp/src/common/norp.cpp @@ -0,0 +1,2704 @@ + +#include "norp.h" +#include "protoNet.h" +#include "protoDebug.h" + + +// Note this "PortPool" was a simple helper class +// for a test where we needed to traverse a +// NAT / firewall. We may _later_ refine this +// as an optional feature where "norp" can be +// configured to use a specific set of port +// numbers for NORM traffic. + +PortPool::PortPool(UINT16 basePort) + : base_port(basePort) +{ + memset(port_array, 0, 50*sizeof(bool)); +} + +UINT16 PortPool::GrabPort() +{ + for (UINT16 i = 0; i < 50; i++) + { + if (!port_array[i]) + { + port_array[i] = true; + return (base_port + i); + } + } + return 0; // no port was available +} // end PortPool::GrabPort() + +void PortPool::ReleasePort(UINT16 thePort) +{ + int index = thePort - base_port; + if ((index < 50) && (index >=0)) + port_array[index] = false; +} // end PortPool::ReleasePort() + +NorpMsg::NorpMsg(UINT32* bufferPtr, unsigned int numBytes, bool initFromBuffer, bool freeOnDestruct) + : ProtoPkt(bufferPtr, numBytes, freeOnDestruct) +{ + if (initFromBuffer) InitFromBuffer(numBytes); +} + +NorpMsg::~NorpMsg() +{ +} + +const double NorpSession::NORP_RTT_MIN = 1.0e-03; +const double NorpSession::NORP_RTT_MAX = 5.0; // TBD - assess these values (make runtime configurable?) +const double NorpSession::NORP_RTT_DEFAULT = 1.0;//e-01; // 100 msec default +const double Norp::DEFAULT_TX_RATE = 1.0e+06; +const double Norp::DEFAULT_PERSIST_INTERVAL = 60.0; // times out unsuccessful NORM delivery attempt after successive ACK_FAILURE + + +NorpSession::NorpSession(Norp& theController, UINT16 sessionId, NormNodeId originatorId) + : controller(theController), is_preset(false), socks_state(SOCKS_IDLE), + socks_client_socket(ProtoSocket::TCP), socks_remote_socket(ProtoSocket::TCP), + udp_relay_socket(ProtoSocket::UDP), + client_pending(0), client_index(0), remote_pending(0), remote_index(0), + norm_enable(true), norp_tx_socket(ProtoSocket::UDP), norp_rtt_estimate(NORP_RTT_DEFAULT), + norm_session(NORM_SESSION_INVALID), norm_tx_stream(NORM_OBJECT_INVALID), + norm_rx_stream(NORM_OBJECT_INVALID), norm_rx_pending(false), + persist_interval(Norp::DEFAULT_PERSIST_INTERVAL), persist_start_time(0, 0), + norm_rate_min(-1.0), norm_rate_max(-1.0), + norm_segment_size(0), norm_stream_buffer_max(0), norm_stream_buffer_count(0), + norm_stream_bytes_remain(0), norm_watermark_pending(false) +{ + memset(&session_id, 0, sizeof(Moniker)); + session_id.originator = originatorId; + session_id.identifier = sessionId; + socks_client_socket.SetNotifier(&controller.GetSocketNotifier()); + //socks_client_socket.SetListener(this, &NorpSession::OnSocksClientEvent); + socks_remote_socket.SetNotifier(&controller.GetSocketNotifier()); + socks_remote_socket.SetListener(this, &NorpSession::OnSocksRemoteEvent); + udp_relay_socket.SetNotifier(&controller.GetSocketNotifier()); + udp_relay_socket.SetListener(this, &NorpSession::OnUdpRelayEvent); + + norp_tx_socket.SetUserData(this); + norp_tx_socket.SetNotifier(&controller.GetSocketNotifier()); + norp_tx_socket.SetListener(&controller, &Norp::OnNorpSocketEvent); + + norp_msg_timer.SetListener(this, &NorpSession::OnNorpMsgTimeout); + norp_msg_timer.SetInterval(controller.GetInitialRtt()); + norp_msg_timer.SetRepeat(20); + + norp_msg.AttachBuffer(norp_msg_buffer, NORP_BUFFER_SIZE, false); + + close_timer.SetListener(this, &NorpSession::OnCloseTimeout); + close_timer.SetInterval(0.0); + close_timer.SetRepeat(0); // one-shot timer +} + +NorpSession::~NorpSession() +{ + socks_state = SOCKS_VOID; + Close(); +} + +void NorpSession::SetNormRateBounds(double rateMin, double rateMax) +{ + norm_rate_min = rateMin; + norm_rate_max = rateMax; + if (NORM_SESSION_INVALID != norm_session) + { + NormSetTxRateBounds(norm_session, rateMin, rateMax); + if (Norp::NORM_FIXED == controller.GetNormCC()) + { + // cumulative rate limit may override fixed session rate + // (i.e. to share pipe among multiple flows) + if ((rateMin >= 0.0) && (rateMin < controller.GetNormTxRate())) + NormSetTxRate(norm_session, rateMin); + else + NormSetTxRate(norm_session, controller.GetNormTxRate()); + } + } +} // end NorpSession::SetNormRateBounds() + +NormNodeId NorpSession::GetNodeId() const +{ + return controller.GetNormNodeId(); +} // end NorpSession::GetNodeId() + +void NorpSession::ActivateTimer(ProtoTimer& theTimer) +{ + controller.ActivateTimer(theTimer); +} // end NorpSession::ActivateTimer() + +bool NorpSession::AcceptClientConnection(ProtoSocket& serverSocket, bool normEnable) +{ + ASSERT(socks_client_socket.IsClosed()); + if (!serverSocket.Accept(&socks_client_socket)) + { + PLOG(PL_ERROR, "NorpSession::AcceptClientConnection() error: server socket accept failure\n"); + return false; + } + // Assume the same notifier (for now) + socks_client_socket.SetListener(this, &NorpSession::OnSocksClientEvent); + norm_enable = normEnable; + // Init our socks_state + socks_state = SOCKS_GET_AUTH_REQ; + // Init client/remote buffer indices + client_pending = client_index = 0; + remote_pending = remote_index = 0; + return true; +} // end NorpSession::AcceptClientConnection() + +bool NorpSession::AcceptPresetClientConnection(NorpPreset& preset, bool normEnable) +{ + ASSERT(socks_client_socket.IsClosed()); + if (!preset.AccessServerSocket().Accept(&socks_client_socket)) + { + PLOG(PL_ERROR, "NorpSession::AcceptPresetClientConnection() error: server socket accept failure\n"); + return false; + } + is_preset = true; + // Assume the same notifier (for now) + socks_client_socket.SetListener(this, &NorpSession::OnSocksClientEvent); + + norm_enable = normEnable; + // Jump-start our socks_state to + socks_state = SOCKS_GET_REQUEST; + // Init client/remote buffer indices + client_pending = client_index = 0; + remote_pending = remote_index = 0; + + if (norm_enable) + { + // Make and send SOCKS request to remote peer NORP server (peer will TCP connect to endpoint and reply via NORM) + ProtoPktSOCKS::Request request(client_buffer, 32, false); + request.SetVersion(5); + request.SetCommand(ProtoPktSOCKS::Request::CONNECT); + request.SetAddress(preset.GetDstAddr()); + return PutRemoteRequest(request, preset.GetNorpAddr()); + } + else + { + return ConnectToRemote(preset.GetDstAddr()); // do direct TCP connect to remote endpoint + } + + return true; +} // end NorpSession::AcceptPresetSession() + +void NorpSession::Shutdown() +{ + // Initiate hard session shutdown, informing remote + if (IsRemoteSession()) + { + norp_msg.Init(); + if (IsRemoteOriginator()) + norp_msg.SetType(NorpMsg::ORIG_END); + else + norp_msg.SetType(NorpMsg::CORR_END); + norp_msg.SetSessionId(GetSessionId()); + norp_msg.SetNodeId(controller.GetNormNodeId()); + // The remaining fields don't matter, so we don't send them! + norp_remote_addr.SetPort(controller.GetNorpPort()); + if (!controller.SendMessage(norp_msg, norp_remote_addr)) + PLOG(PL_ERROR, "NorpSession::Shutdown() warning: NORP message transmission failed (will try again)!\n"); + // Set timer to repeat until ACK_END is received + if (norp_msg_timer.IsActive()) norp_msg_timer.Deactivate(); + norp_msg_timer.SetInterval(2.0*norp_rtt_estimate); // use most recent RTT estimate + ActivateTimer(norp_msg_timer); + // Here we use ProtoSocket::Shutdown() (sockets get a DISCONNECT event upon shutdown completion) + // TBD - we may need to do something with input/output notification here? + if (socks_client_socket.IsOpen()) socks_client_socket.Close(); + if (socks_remote_socket.IsOpen()) socks_remote_socket.Close(); + socks_state = SOCKS_SHUTDOWN; + } + else + { + // No remote, so just do a hard Close() + Close(); + } +} // end NorpSession::Shutdown() + +// This returns "true" when the conditions of shutdown completion are met +bool NorpSession::ShutdownComplete() const +{ + if (socks_remote_socket.IsOpen()) return false; + if (socks_client_socket.IsOpen()) return false; + if (NORM_OBJECT_INVALID != norm_tx_stream) return false; + if (NORM_OBJECT_INVALID != norm_rx_stream) return false; + return true; +} // end NorpSession::ShutdownComplete() + +void NorpSession::Close() +{ + if (NORM_SESSION_INVALID != norm_session) + { + NormDestroySession(norm_session); + norm_session = NORM_SESSION_INVALID; + } + norp_tx_socket.Close(); + socks_client_socket.Close(); + socks_remote_socket.Close(); + udp_relay_socket.Close(); + client_pending = client_index = 0; + remote_pending = remote_index = 0; + if (SOCKS_VOID != socks_state) + { + // We make the state void here because controller deletes us! + socks_state = SOCKS_VOID; + controller.OnSessionClose(*this); + } +} // end NorpSession::Close() + +bool NorpSession::OnCloseTimeout(ProtoTimer& /*theTimer*/) +{ + close_timer.Deactivate(); + controller.OnSessionClose(*this); + return false; // because we've been deleted +} // end NorpSession::OnCloseTimeout() + + +void NorpSession::OnSocksClientEvent(ProtoSocket& theSocket, + ProtoSocket::Event theEvent) +{ + PLOG(PL_DETAIL, "NorpSession::OnSocksClientEvent("); + switch (theEvent) + { + case ProtoSocket::INVALID_EVENT: + PLOG(PL_DETAIL, "INVALID_EVENT) ...\n"); + break; + case ProtoSocket::CONNECT: + PLOG(PL_DETAIL, "CONNECT) ...\n"); + break; + case ProtoSocket::ACCEPT: + PLOG(PL_DETAIL, "ACCEPT) ...\n"); // should never happen (was accepted on server socket) unless remote BIND + break; + case ProtoSocket::SEND: + PLOG(PL_DETAIL, "SEND) ...\n"); + switch (socks_state) + { + case SOCKS_VOID: + case SOCKS_IDLE: + ASSERT(0); + break; + case SOCKS_GET_AUTH_REQ: + ASSERT(0); + break; + case SOCKS_PUT_AUTH_REP: + if (!PutClientAuthReply()) + { + PLOG(PL_ERROR, "NorpSession::OnSocksClientEvent() error: PutClientAuthReply() failure!\n"); + Close(); + return; + } + break; + case SOCKS_GET_REQUEST: + ASSERT(0); + break; + case SOCKS_PUT_REQUEST: + ASSERT(0); + break; + case SOCKS_CONNECTING: + ASSERT(0); + break; + case SOCKS_GET_REPLY: + ASSERT(0); + break; + case SOCKS_PUT_REPLY: + if (!PutClientReply()) + { + PLOG(PL_ERROR, "NorpSession::OnSocksClientEvent() error: PutClientReply() failure!\n"); + Shutdown(); + return; + } + break; + case SOCKS_CONNECTED: + if (!PutClientData()) + { + PLOG(PL_ERROR, "NorpSession::OnSocksClientEvent() error: PutClientData() failure!\n"); + Shutdown(); + return; + } + break; + case SOCKS_SHUTDOWN: + ASSERT(0); + break; + } // end switch(socks_state) + break; + case ProtoSocket::RECV: + { + PLOG(PL_DETAIL, "RECV) ...\n"); + switch (socks_state) + { + case SOCKS_VOID: + case SOCKS_IDLE: // initial, inactive state (socks_client_socket is closed) + ASSERT(0); + break; + case SOCKS_GET_AUTH_REQ: // first active state, getting auth request bytes from client + if (!GetClientAuthRequest()) + { + PLOG(PL_ERROR, "NorpSession::OnSocksClientEvent() error: GetClientAuthRequest() failure!\n"); + Close(); + return; + } + break; + case SOCKS_GET_REQUEST: // next, accumulate SOCKS request bytes from client + if (!GetClientRequest()) + { + PLOG(PL_ERROR, "NorpSession::OnSocksClientEvent() error: GetClientRequest() failure!\n"); + Close(); + return; + } + break; + case SOCKS_PUT_AUTH_REP: + case SOCKS_PUT_REQUEST: + case SOCKS_CONNECTING: + case SOCKS_PUT_REPLY: + case SOCKS_GET_REPLY: + case SOCKS_SHUTDOWN: + { + unsigned int numBytes = SOCKS_BUFFER_SIZE; + // do a recv() - probably means client disconnected (prematurely) + if (theSocket.Recv((char*)client_buffer, numBytes)) + { + if (SOCKS_SHUTDOWN != socks_state) + PLOG(PL_WARN, "NorpSession::OnSocksClientEvent() warning: unexpectedly received %u bytes from SOCKS client!\n", numBytes); + } + else + { + PLOG(PL_ERROR, "NorpSession::OnSocksClientEvent() error: unexpected receive error from SOCKS client!\n"); + } + break; + } + case SOCKS_CONNECTED: // finally, reading/writing data from/to client + if (0 == client_pending) // we need this because we sometimes get extra notifications with old "protolib" + { + if (!GetClientData()) + { + PLOG(PL_ERROR, "NorpSession::OnSocksClientEvent() error: GetClientData() failure!\n"); + Shutdown(); + } + } + break; + } // end switch(socks_state) + break; + } + case ProtoSocket::DISCONNECT: + PLOG(PL_DETAIL, "DISCONNECT) ...\n"); + switch (socks_state) + { + case SOCKS_VOID: + case SOCKS_IDLE: + ASSERT(0); // will never happen + break; + case SOCKS_GET_AUTH_REQ: + case SOCKS_PUT_AUTH_REP: + case SOCKS_GET_REQUEST: + case SOCKS_PUT_REQUEST: + case SOCKS_CONNECTING: + case SOCKS_PUT_REPLY: + case SOCKS_GET_REPLY: + PLOG(PL_ERROR, "NorpSession::OnSocksClientEvent() error: premature SOCKS client disconnect!\n"); + Shutdown(); // unexpected, premature client disconnect + break; + case SOCKS_CONNECTED: + // Client finished sending (and receiving) data + socks_state = SOCKS_SHUTDOWN; + if (0 == client_pending) + PutRemoteData(); // to shut down socks_remote_socket or norm_tx_stream + case SOCKS_SHUTDOWN: + theSocket.Close(); + if (ShutdownComplete()) + Close(); + // else more shutdown actions pending + break; + } + break; + + case ProtoSocket::EXCEPTION: + PLOG(PL_DETAIL, "EXCEPTION) ...\n"); + ASSERT(0); + break; + + case ProtoSocket::ERROR_: + PLOG(PL_DETAIL, "ERROR_) ...\n"); + break; + } // end switch (theEvent) +} // end NorpSession::OnSocksClientEvent() + +bool NorpSession::GetClientAuthRequest() +{ + // This method incrementally receives/processes a SOCKS AuthRequest + // and updates our "socks_state" and buffer indices accordlingly + // ("numBytes" is set to how many needed for next socket read) + ASSERT(SOCKS_GET_AUTH_REQ == socks_state); + for(;;) // loops until auth request is read or no more input is ready + { + switch (client_index) + { + case 0: // need to read VERSION byte first + { + client_pending = 1; + break; + } + case 1: // validate VERSION and read NMETHODS byte + { + ProtoPktSOCKS::AuthRequest authReq(client_buffer, 1); + if (5 != authReq.GetVersion()) + { + PLOG(PL_ERROR, "NorpSession::GetClientAuthRequest() received incompatible version %d request!\n", authReq.GetVersion()); + return false; + } + client_pending = 2; + break; + } + case 2: // Check NMETHODS byte to see how much to read. + { + ProtoPktSOCKS::AuthRequest authReq(client_buffer, 2); + UINT8 numBytes = authReq.GetMethodCount(); + if (0 == numBytes) + { + PLOG(PL_ERROR, "NorpSession::GetClientAuthRequest() received AuthRequest with zero methods!\n"); + return false; + } + client_pending += numBytes; + break; + } + default: + { + if (client_index == client_pending) + { + PLOG(PL_DETAIL, "NorpSession::GetClientAuthRequest() session %u received SOCKS AuthRequest from client %s/%hu ...\n", + GetSessionId(), socks_client_socket.GetDestination().GetHostString(), socks_client_socket.GetDestination().GetPort()); + // We have received the full SOCKS AuthRequest + // Find best authentication "method" (currently only AUTH_NONE) + ProtoPktSOCKS::AuthType authType = ProtoPktSOCKS::AUTH_INVALID; + ProtoPktSOCKS::AuthRequest authReq(client_buffer, client_index); + for (UINT8 i = 0; i < authReq.GetMethodCount(); i++) + { + if (ProtoPktSOCKS::AUTH_NONE == authReq.GetMethod(i)) + { + authType = ProtoPktSOCKS::AUTH_NONE; + break; + } + } + if (ProtoPktSOCKS::AUTH_NONE == authType) + { + // Build our SOCKS AuthReply into "remote_buffer" for transmission to client + socks_state = SOCKS_PUT_AUTH_REP; + ProtoPktSOCKS::AuthReply authReply(remote_buffer, SOCKS_BUFFER_SIZE, false); + authReply.SetVersion(5); + authReply.SetMethod(ProtoPktSOCKS::AUTH_NONE); + remote_pending = authReply.GetLength(); + remote_index = 0; + client_index = client_pending = 0; + // TBD - should we pause client input notification here? + return PutClientAuthReply(); + } + else + { + PLOG(PL_ERROR, "NorpSession::GetClientAuthRequest() received AuthRequest with no compatible method!\n"); + return false; + } + } + break; + } + } // end switch(client_rx_index) + + // Read next chunk of AuthRequest (as long as socket has data to read) + unsigned int numBytes = client_pending - client_index; + if (!socks_client_socket.Recv(((char*)client_buffer)+client_index, numBytes)) + { + PLOG(PL_ERROR, "NorpSession::OnSocksClientEvent() error: client socket recv() failure!\n"); + return false; + } + client_index += numBytes; + if (client_index < client_pending) break; // no more to read, so wait until RECV notification for more + } // end for(;;) + return true; +} // end NorpSession::GetClientAuthRequest() + +bool NorpSession::PutClientAuthReply() +{ + ASSERT(SOCKS_PUT_AUTH_REP == socks_state); + // We have a SOCKS AuthReply in our "remote_buffer" pending transmission to the SOCKS client + unsigned int numBytes = remote_pending - remote_index; + if (socks_client_socket.Send(((char*)remote_buffer)+remote_index, numBytes)) + { + remote_index += numBytes; + if (remote_index < remote_pending) + { + // Only partially sent, cue output notification to complete + socks_client_socket.StartOutputNotification(); + } + else + { + // AuthReply was fully sent + socks_client_socket.StopOutputNotification(); + socks_state = SOCKS_GET_REQUEST; + remote_pending = remote_index = 0; + client_pending = 1; // To get VERSION byte of impending SOCKS Request + client_index = 0; + } + return true; + } + else + { + PLOG(PL_ERROR, "NorpSession::PutClientAuthReply() error: auth reply send failure!\n"); + return false; + } +} // end NorpSession::PutClientAuthReply() + +bool NorpSession::GetClientRequest() +{ + ASSERT(SOCKS_GET_REQUEST == socks_state); + // SOCKS5 request: VERSION + CMD + RESV + ADDR_TYP + DST.ADDR + DST.PORT + // bytes: 1 + 1 + 1 + 1 + N + 2 + for(;;) // loops until client request is read or no more input is ready + { + switch (client_index) + { + case 0: // need to read VERSION byte first + { + client_pending = 1; + break; + } + case 1: // Validate VERSION, and get REQ, if applicable + { + ProtoPktSOCKS::Request req(client_buffer, 1); + if (5 != req.GetVersion()) + { + PLOG(PL_ERROR, "NorpSession::GetClientRequest() received incompatible version %d request!\n", req.GetVersion()); + return false; + } + client_pending = 4; // ask for bytes up through ADDR_TYP + break; + } + case 2: // Handle request based on command type + case 3: + { + // no change, just CMD and/or RESV byte coming in + ASSERT(4 == client_pending); + break; + } + case 4: // Process ADDR_TYP to see how many more to ask for + { + ProtoPktSOCKS::Request req(client_buffer, 4); + switch (req.GetAddressType()) + { + case ProtoPktSOCKS::IPv4: + client_pending = 4 + (4 + 2); // IPv4 addr + port + break; + case ProtoPktSOCKS::NAME: + client_pending = 4 + 1; // ask for name length byte + break; + case ProtoPktSOCKS::IPv6: + client_pending = 4 + (16 + 2); // IPv6 addr + port + break; + default: + PLOG(PL_ERROR, "NorpSession::GetClientRequest() error: invalid address type!\n"); + return false; + } + break; + } + case 5: // Process NAME length byte + { + ProtoPktSOCKS::Request req(client_buffer, 5); + if (ProtoPktSOCKS::NAME == req.GetAddressType()) + client_pending = 5 + (req.GetAddressLength() + 2); // DNS name + port + break; + } + default: + { + if (client_index == client_pending) + { + // Entire SOCKS Request from local client has been received, so handle it ... + ProtoPktSOCKS::Request request(client_buffer, client_index); + client_pending = client_index = 0; // finished with client buffer for moment after handing request here + return OnClientRequest(request, socks_client_socket.GetDestination()); + } + break; + } + } + // Read next chunk of client Request (as long as socket has data to read) + unsigned int numBytes = client_pending - client_index; + if (!socks_client_socket.Recv(((char*)client_buffer)+client_index, numBytes)) + { + PLOG(PL_ERROR, "NorpSession::OnSocksClientEvent() error: client socket recv() failure!\n"); + return false; + } + client_index += numBytes; + if (client_index < client_pending) break; // no more to read, so wait until RECV notification for more + } // end for(;;;) + return true; +} // end NorpSession::GetClientRequest() + + +bool NorpSession::OnClientRequest(const ProtoPktSOCKS::Request request, const ProtoAddress& srcAddr) +{ + ProtoAddress destAddr; + if (!request.GetAddress(destAddr)) + { + PLOG(PL_ERROR, "NorpSession::HandleSocksRequest() error: request with invalid destination address!\n"); + return false; + } + switch (request.GetCommand()) + { + case ProtoPktSOCKS::Request::CONNECT: + PLOG(PL_INFO, "norp: session %u recvd CONNECT request dst addr/port = %s/%hu\n", GetSessionId(), destAddr.GetHostString(), destAddr.GetPort()); + if (norm_enable) + { + // TBD - Determine "route" for "destAddr" to potentially separate remote "norp" peer + // (for now we assume "norp" remote is collocated on "destAddr" endpoints unless + // the controller has been configured for a specific remote 'correspondent') + ProtoAddress norpAddr; + controller.GetRemoteNorpAddress(destAddr, norpAddr); + // Relay SOCKS request to remote peer NORP server (peer will TCP connect to endpoint and reply via NORM) + return PutRemoteRequest(request, norpAddr); + } + else + { + return ConnectToRemote(destAddr); // do direct TCP connect to remote endpoint + } + case ProtoPktSOCKS::Request::BIND: + PLOG(PL_INFO, "norp: session %u recvd BIND request dst addr/port = %s/%hu\n", GetSessionId(), destAddr.GetHostString(), destAddr.GetPort()); + return BindRemote(destAddr.GetPort()); + case ProtoPktSOCKS::Request::UDP_ASSOC: + PLOG(PL_INFO, "norp: session %u recvd UDP_ASSOC for %s/%hu\n", GetSessionId(), destAddr.GetHostString(), destAddr.GetPort()); + // Save our UDP client's address (use the control socket addr if unspecified in request) + if (destAddr.IsUnspecified()) + { + udp_client_addr = srcAddr; + udp_client_addr.SetPort(destAddr.GetPort()); + } + else + { + udp_client_addr = destAddr; + } + return OpenUdpRelay(); + default: + PLOG(PL_ERROR, "NorpSession::OnClientRequest() error: invalid request command!\n"); + return false; + } +} // end NorpSession::OnClientRequest() + +bool NorpSession::PutRemoteRequest(const ProtoPktSOCKS::Request& request, const ProtoAddress& remoteAddr) +{ + // The "request" here sits in our "client_buffer". We know this buffer has room to append our + // "session_id.identifier" to the end. This "identifier" gives the command a unique key in + // the context of the NorpSession "originator" NormNodeId to recognize if it is a repeated + // request command, etc. + if (NORM_SESSION_INVALID == norm_session) + { + norm_session = NormCreateSession(controller.GetNormInstance(), remoteAddr.GetHostString(), controller.GetNormPort(), controller.GetNormNodeId()); + if (NORM_SESSION_INVALID == norm_session) + { + PLOG(PL_ERROR, "NorpSession::PutRemoteRequest() NormCreateSession() failure!\n"); + return false; + } + NormSetMessageTrace(norm_session, controller.GetNormTrace()); + NormSetUserData(norm_session, this); + NormSetDefaultSyncPolicy(norm_session, NORM_SYNC_STREAM); + + // We use a 2-step sender/receiver startup process by first opening an idle, "tx-only" NormSession to + // send the Request command to the remote "controller" NormSession (The remote will get our "tx port" + // info as part of this request sent). Our controller will get the REQ_ACK and/or SOCKS_REPLY message, + // then inform us, and so we will get the remote "tx port" info so we can then start our own receiver + // and "connect" our NORM rx_socket to the remote tx_socket. The sender here is made "idle" by disabling + // disabling the normal NORM GRTT probing. We don't re-activate that until we know the remote end has + // a receiver running (This is done upon REQ_ACK or SOCKS_REPLY reception when we also start our own NORM + // receiver). + + char ifaceName[64]; + ifaceName[63] = '\0'; + if (ProtoNet::GetInterfaceName(controller.GetProxyAddress(), ifaceName, 63)) + NormSetMulticastInterface(norm_session, ifaceName); + else + PLOG(PL_ERROR, "NorpSession::PutRemoteRequest() warning: unable to get interface name for address %s\n", controller.GetProxyAddress().GetHostString()); + switch (controller.GetNormCC()) + { + case Norp::NORM_CC: // default TCP-friendly congestion control + // do nothing, this is NORM's default mode + break; + case Norp::NORM_CCE: // "wireless-ready" ECN-only congestion control + NormSetEcnSupport(norm_session, true, true); + break; + case Norp::NORM_CCL: // "loss tolerant", non-ECN congestion control + NormSetEcnSupport(norm_session, false, false, true); + break; + case Norp::NORM_FIXED: // fixed-rate operation + // if cumulative rate limit has been imposed, it can override the fixed rate + // (i.e. to share pipe among multiple flows) + if ((norm_rate_min >= 0) && (norm_rate_min < controller.GetNormTxRate())) + NormSetTxRate(norm_session, norm_rate_min); + else + NormSetTxRate(norm_session, controller.GetNormTxRate()); + break; + } + if ((norm_rate_min >= 0.0) || (norm_rate_max >= 0.0)) + NormSetTxRateBounds(norm_session, norm_rate_min, norm_rate_max); + + // Even though we call "NormStartSender()" to get a "tx port" value, we disable GRTT probing and thus + // defer "real" sender startup until we get an ACK to our request (see "OriginatorStartNorm()") + NormSetTxOnly(norm_session, true); + NormSetGrttProbingMode(norm_session, NORM_PROBE_NONE); // no probing until remote receiver is started + if (!NormStartSender(norm_session, GetSessionId(), NORM_BUFFER_SIZE, controller.GetNormSegmentSize(), + controller.GetNormBlockSize(), controller.GetNormParityCount())) + { + PLOG(PL_ERROR, "NorpSession::PutRemoteRequest() error: NormStartSender() failure!\n"); + return false; + } + //NormSetTxSocketBuffer(norm_session, 4096); + NormSetFlowControl(norm_session, 0.0); // disable timer-based flow control since we are ACK-limiting writes to stream + norm_segment_size = controller.GetNormSegmentSize(); + norm_stream_buffer_max = ComputeNormStreamBufferSegmentCount(NORM_BUFFER_SIZE, norm_segment_size, controller.GetNormBlockSize()); + norm_stream_buffer_max -= controller.GetNormBlockSize(); // a little safety margin + norm_stream_buffer_count = 0; + norm_stream_bytes_remain = 0; + norm_watermark_pending = false; + + PLOG(PL_INFO, "norp: originator %lu created NORM session with dest %s/%hu (srcPort %hu)\n", controller.GetNormNodeId(), + remoteAddr.GetHostString(), controller.GetNormPort(), NormGetTxPort(norm_session)); + + // We open a UDP socket for NORP signaling here and "connect() it so we can get ICMP error messages + // (I.e., an ICMP "port unreachable" indicates the remote destination is not NORP-enabled) + if (!norp_tx_socket.Open()) + { + PLOG(PL_ERROR, "NorpSession::PutRemoteRequest() error: unable to open norp_tx_socket!\n"); + return false; + } + norp_remote_addr = remoteAddr; + norp_remote_addr.SetPort(controller.GetNorpPort()); + if (!norp_tx_socket.Connect(norp_remote_addr)) + { + PLOG(PL_ERROR, "NorpSession::PutRemoteRequest() error: unable to open and connect norp_tx_socket!\n"); + return false; + } + } + socks_client_socket.StopInputNotification(); // don't accept more from client until connected + PLOG(PL_INFO, "norp: node %u relaying SOCKS request to NORP destination %s/%hu\n", controller.GetNormNodeId(), + remoteAddr.GetHostString(), controller.GetNorpPort()); + // Put the SOCKS request into a NorpMsg and relay it to remote NORP server + // (Note that the request is "robustly" transmitted via periodic repetition until a request acknowledgment (REQ_ACK) is received + ASSERT(!norp_msg_timer.IsActive()); + norp_msg.SetType(NorpMsg::SOCKS_REQ); + norp_msg.SetSessionId(GetSessionId()); + norp_msg.SetSourcePort(NormGetTxPort(norm_session)); // Our NORM tx port number (so remote can "connect()" + norp_msg.SetDestinationPort(NormGetRxPort(norm_session)); // Our NORM rx port (for info) + norp_msg.SetNodeId(controller.GetNormNodeId()); // our NORM node id + norp_msg.SetContent((const char*)request.GetBuffer(), request.GetLength()); + + struct timeval currentTime; + ProtoSystemTime(currentTime); + norp_msg.SetTimestamp(currentTime); + if (!SendMessage(norp_msg)) + PLOG(PL_ERROR, "NorpSession::PutRemoteRequest() warning: NORP message transmission failed (will try again)!\n"); + norp_msg_timer.SetInterval(2.0 * norp_rtt_estimate); + ActivateTimer(norp_msg_timer); + socks_state = SOCKS_PUT_REQUEST; + return true; +} // end NorpSession::PutRemoteRequest() + +bool NorpSession::MakeDirectConnect() +{ + // This assumes our "norp_msg" previously sent in attempt to connect + // to a remote "norp" peer is cached (per PutRemoveRequest() + if (NORM_SESSION_INVALID != norm_session) + { + NormDestroySession(norm_session); + norm_session = NORM_SESSION_INVALID; + } + if (norp_msg_timer.IsActive()) norp_msg_timer.Deactivate(); + if (NorpMsg::SOCKS_REQ != norp_msg.GetType()) + { + PLOG(PL_ERROR, "NorpSession::MakeDirectConnect() error: invalid norp_msg enqueued!\n"); + return false; + } + + ProtoPktSOCKS::Request req(norp_msg.AccessContentPtr(), norp_msg.GetContentLength(), true); + ProtoAddress destAddr; + if (!req.GetAddress(destAddr)) + { + PLOG(PL_ERROR, "NorpSession::MakeDirectConnect() error: norp_msg SOCKS_REQ with invalid destination address enqueued!\n"); + return false; + } + return ConnectToRemote(destAddr); +} // end NorpSession::MakeDirectConnect() + +bool NorpSession::OnRemoteRequestAcknowledgment(const NorpMsg& theMsg, const ProtoAddress& senderAddr) +{ + // Note we update our RTT estimate on ACKs received + ProtoTime recvTime; + recvTime.GetCurrentTime(); + ProtoTime sentTime; + theMsg.GetTimestamp(sentTime); + if (SOCKS_PUT_REQUEST == socks_state) + { + norp_rtt_estimate = ProtoTime::Delta(recvTime, sentTime); // initial rtt received + if (!OriginatorStartNorm(senderAddr, theMsg.GetNodeId(), theMsg.GetSourcePort(), theMsg.GetDestinationPort())) + { + PLOG(PL_ERROR, "NorpSession::OnRemoteRequestAcknowledgment() error: OriginatorStartNormSender() failure!\n"); + return false; + } + // Note that successful OriginatorStartNorm() sets socks_state to SOCKS_CONNECTING + } + else + { + // Just use duplicative ACKs to update RTT estimate via EWMA + ASSERT(norp_rtt_estimate > 0.0); + double rttNew = ProtoTime::Delta(recvTime, sentTime); + norp_rtt_estimate = 0.5*(norp_rtt_estimate + rttNew); // TBD - is this a reasonable "smoothing" factor + } + // Note it's OK if a bona fide measured RTT is less than NORP_RTT_MIN + if (norp_rtt_estimate <= 0.0) + { + norp_rtt_estimate = NORP_RTT_MIN; + } + else if (norp_rtt_estimate > NORP_RTT_MAX) + { + PLOG(PL_WARN, "NorpSession::OnRemoteRequestAcknowledgment() warning: measured RTT exceeds max value of %lf sec.\n", NORP_RTT_MAX); + norp_rtt_estimate = NORP_RTT_MAX; + } + return true; +} // end NorpSession::OnRemoteRequestAcknowledgment() + +bool NorpSession::OnRemoteReply(const NorpMsg& theMsg, const ProtoAddress& senderAddr) +{ + ProtoPktSOCKS::Reply reply(theMsg.AccessContentPtr(), theMsg.GetContentLength()); + PLOG(PL_INFO, "NorpSession::OnRemoteReply() session %hu reply type %d\n", GetSessionId(), reply.GetType()); + if (ProtoPktSOCKS::Reply::SUCCESS == reply.GetType()) + { + if (SOCKS_PUT_REQUEST == socks_state) + { + // Somehow we missed the NORP REQUEST_ACK and the reply is being received first + // (TBD - should we init RTT here like above???) + if (!OriginatorStartNorm(senderAddr, theMsg.GetNodeId(), theMsg.GetSourcePort(), theMsg.GetDestinationPort())) + { + PLOG(PL_ERROR, "NorpSession::OnRemoteReply() error: OriginatorStartNormSender() failure!\n"); + return false; + } + // Note that successful OriginatorStartNorm() sets socks_state to SOCKS_CONNECTING + } + if (is_preset) + { + // Go straight to connected state and start + // accepting recv data from client or remote + client_index = client_pending = 0; + socks_client_socket.StartInputNotification(); + remote_index = remote_pending = 0; + socks_state = SOCKS_CONNECTED; + } + else if (SOCKS_CONNECTING == socks_state) + { + // TBD - we may want to put the reply in the client_buffer instead??? + // (i.e. in case we start receiving remote data before reply is fully sent?) + socks_state = SOCKS_PUT_REPLY; + memcpy(remote_buffer, (char*)reply.GetBuffer(), reply.GetLength()); + remote_pending = reply.GetLength(); + remote_index = 0; + PLOG(PL_INFO, "NorpSession::OnRemoteReply() originator relaying reply to SOCKS client ...\n"); + if (!PutClientReply()) + { + PLOG(PL_ERROR, "NorpSession::OnRemoteReply() error: unable to relay SOCKS reply to client!\n"); + return false; + } + } + // Send an ACK to the received reply + UINT32 buffer[32/4]; + NorpMsg ack(buffer, 32, false); + ack.SetType(NorpMsg::REP_ACK); + ack.SetSessionId(GetSessionId()); // our session id (note it was set by request originator) + ack.SetSourcePort(NormGetTxPort(norm_session)); // our NORM tx port number (so remote can "connect()" + ack.SetDestinationPort(NormGetRxPort(norm_session)); // our NORM rx port (for info) + struct timeval sentTime; + theMsg.GetTimestamp(sentTime); + ack.SetTimestamp(sentTime); // echo the sender's timestamp for their RTT measurement + ack.SetNodeId(controller.GetNormNodeId()); // our NORM node id + if (!controller.SendMessage(ack, senderAddr)) + PLOG(PL_ERROR, "NorpSession::OnRemoteReply() warning: unsable to transmit NORP REP_ACK message!\n"); + } + else + { + PLOG(PL_ERROR, "NorpSession::OnRemoteReply() received negative reply ...\n"); + return false; + } + return true; +} // end NorpSession::OnRemoteReply() + +// Note the "socks_state" MUST be SOCKS_PUT_REQUEST when this is invoked +// (starts associated NORM session upon receipt of request ack or reply) +bool NorpSession::OriginatorStartNorm(const ProtoAddress& senderAddr, + NormNodeId corrNormId, + UINT16 normSrcPort, + UINT16 normDstPort) +{ + ASSERT(SOCKS_PUT_REQUEST == socks_state); + if (norp_msg_timer.IsActive()) norp_msg_timer.Deactivate(); // cancels ongoing request retransmission, if applicable + // TBD - log the norp_msg_timer repeat count? + PLOG(PL_INFO, "norp: originator %u starting NORM sender probing (and receiver), connected to remote NORM source port %hu ...\n", + controller.GetNormNodeId(), normSrcPort); + NormSetGrttEstimate(norm_session, norp_rtt_estimate); // init NORM with value learned from NORP setup handshake + NormSetRxPortReuse(norm_session, true, NULL, senderAddr.GetHostString(), normSrcPort); + if (!NormStartReceiver(norm_session, NORM_BUFFER_SIZE)) + { + PLOG(PL_ERROR, "NorpSession::OriginatorStartNorm() error: NormStartReceiver() failure!\n"); + return false; + } + // The "NormChangeDestination()" call here makes sure our NORM session sends its packets + // to the destination that the remote correspondent server wants us to use for NORM + NormChangeDestination(norm_session, senderAddr.GetHostString(), normDstPort); + if (Norp::NORM_FIXED != controller.GetNormCC()) + NormSetCongestionControl(norm_session, true); // Note this also re-enables GRTT probing that was disabled at session creation + else + NormSetGrttProbingMode(norm_session, NORM_PROBE_ACTIVE); // re-enables previously suspended GRTT probing + if (NORM_OBJECT_INVALID == (norm_tx_stream = NormStreamOpen(norm_session, NORM_BUFFER_SIZE))) + { + PLOG(PL_ERROR, "NorpSession::OriginatorStartNorm() NormStreamOpen() failure!\n"); + return false; + } + if (!NormAddAckingNode(norm_session, corrNormId)) + { + PLOG(PL_ERROR, "NorpSession::OriginatorStartNorm() NormAddAckingNode() failure!\n"); + return false; + } + socks_state = SOCKS_CONNECTING; + return true; +} // end NorpSession::OriginatorStartNorm() + + +bool NorpSession::OnRemoteRequest(const NorpMsg& theMsg, const ProtoAddress& senderAddr) +{ + ProtoPktSOCKS::Request request(theMsg.AccessContentPtr(), theMsg.GetContentLength(), true); + PLOG(PL_INFO, "NorpSession::OnRemoteRequest() session %hu request type %d (source port %hu)\n", + GetSessionId(), request.GetCommand(), theMsg.GetSourcePort()); + if (NORM_SESSION_INVALID == norm_session) + { + // 0) Store the sender address to transmit REPLY to when connected to remote + norp_remote_addr = senderAddr; + // 1) Create a NormSession with request sender as session (destination) address + UINT16 normSessionPort = controller.GetNormPort(); + norm_session = NormCreateSession(controller.GetNormInstance(), senderAddr.GetHostString(), normSessionPort, controller.GetNormNodeId()); + if (NORM_SESSION_INVALID == norm_session) + { + PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() NormCreateSession() failure!\n"); + return false; + } + NormSetMessageTrace(norm_session, controller.GetNormTrace()); + // Set the NormSession user data so NORM API events can be directed to this session + NormSetUserData(norm_session, this); + NormSetDefaultSyncPolicy(norm_session, NORM_SYNC_STREAM); + + // Start our NORM session as a receiver (we start the sender later when we get a REPLY_ACK, or NORM_DATA) + // (First, enable port reuse and "connect()" to the sender's NORM source addr/port) + char ifaceName[64]; + ifaceName[63] = '\0'; + if (ProtoNet::GetInterfaceName(controller.GetProxyAddress(), ifaceName, 63)) + NormSetMulticastInterface(norm_session, ifaceName); + else + PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() warning: unable to get interface name for address %s\n", controller.GetProxyAddress().GetHostString()); + switch (controller.GetNormCC()) + { + case Norp::NORM_CC: // default TCP-friendly congestion control + // do nothing, this is NORM's default mode + break; + case Norp::NORM_CCE: // "wireless-ready" ECN-only congestion control + NormSetEcnSupport(norm_session, true, true); + break; + case Norp::NORM_CCL: // "loss tolerant", non-ECN congestion control + NormSetEcnSupport(norm_session, false, false, true); + break; + case Norp::NORM_FIXED: // fixed-rate transmission (no congestion control) + // if cumulative rate limit has been imposed, it can override the fixed rate + // (i.e. to share pipe among multiple flows) + if ((norm_rate_min >= 0) && (norm_rate_min < controller.GetNormTxRate())) + NormSetTxRate(norm_session, norm_rate_min); + else + NormSetTxRate(norm_session, controller.GetNormTxRate()); + break; + } + if ((norm_rate_min >= 0.0) || (norm_rate_max >= 0.0)) + NormSetTxRateBounds(norm_session, norm_rate_min, norm_rate_max); + // The "NormSetRxPortReuse()" call here "connects" us to the remote NORM sender source addr/port + // (this creates a proper binding since we reuse the same port number for multiple NORM sessions) + NormSetRxPortReuse(norm_session, true, NULL, senderAddr.GetHostString(), theMsg.GetSourcePort()); + if (!NormStartReceiver(norm_session, NORM_BUFFER_SIZE)) + { + PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() error: NormStartReceiver() failure!\n"); + return false; + } + // This call makes sure we send our NORM packets to the originator's NORM port (i.e, if different than ours) + NormChangeDestination(norm_session, senderAddr.GetHostString(), theMsg.GetDestinationPort()); + if (!NormAddAckingNode(norm_session, theMsg.GetNodeId())) + { + PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() NormAddAckingNode() failure!\n"); + return false; + } + PLOG(PL_INFO, "norp: correspondent %lu created NORM session with dest %s/%hu (srcPort %hu) connected to NORM source port %hu\n", + controller.GetNormNodeId(), senderAddr.GetHostString(), controller.GetNormPort(), NormGetTxPort(norm_session), theMsg.GetSourcePort()); + + // 2) Initiate TCP connection to indicated endpoint address/port (should we do this first before setting up NORM?) + ProtoAddress destAddr; + if (!request.GetAddress(destAddr)) + { + PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() error: request with invalid address!\n"); + return false; + } + switch (request.GetCommand()) + { + case ProtoPktSOCKS::Request::CONNECT: + PLOG(PL_INFO, "norp: session %u recvd remote CONNECT request dst add/port = %s/%hu\n", GetSessionId(), destAddr.GetHostString(), destAddr.GetPort()); + if (!ConnectToRemote(destAddr)) + { + PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() error: ConnectToRemote() failure\n"); + // TBD - send failure reply + return false; + } + break; + case ProtoPktSOCKS::Request::BIND: + PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() error: remote SOCKS BIND not supported\n"); + // TBD - send negative reply + return false; + case ProtoPktSOCKS::Request::UDP_ASSOC: + PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() error: remote SOCKS UDP_ASSOC not supported\n"); + // TBD - send negative reply + return false; + default: + PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() error: invalid request command!\n"); + return false; + } + } // end if (NORM_SESSION_INVALID == norm_session) + else + { + // TBD - verify this request matches our existing session info + } + // Note the "ConnectToRemote()" call above transitioned us to SOCKS_CONNECTING state + if (SOCKS_CONNECTING == socks_state) + { + ASSERT(!norp_msg_timer.IsActive()); + // 3) Send a NormCmd request acknowledged (REQ_ACK) to "squelch" further request resends from remote + // (This is sent as a one-shot in response to NorpMsg::SOCKS_REQ messages) + UINT32 buffer[32/4]; + NorpMsg ack(buffer, 32, false); + ack.SetType(NorpMsg::REQ_ACK); + ack.SetSessionId(GetSessionId()); + ack.SetNodeId(controller.GetNormNodeId()); // our NORM node id + ack.SetSourcePort(NormGetTxPort(norm_session)); // Our NORM tx port number (so remote can "connect()" + ack.SetDestinationPort(NormGetRxPort(norm_session)); // Our NORM rx port (let's remote where to send NORM pkts) + struct timeval sentTime; + theMsg.GetTimestamp(sentTime); + ack.SetTimestamp(sentTime); + if (!controller.SendMessage(ack, senderAddr)) + PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() warning: NORP REQ_ACK message transmission failed (will try again)!\n"); + } + else if (SOCKS_PUT_REPLY == socks_state) + { + // The reply already sent upon connect was evidently not + // received (nor the REQ_ACK message(s) that were sent) + // Note calling this also resets the reply robust retransmission process + if (!PutRemoteReply()) + { + PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() error: unable to send SOCKS_REPLY\n"); + return false; + } + } + else if (SOCKS_CONNECTED == socks_state) + { + // Evidently a late arriving request, so we just ignore it + PLOG(PL_INFO, "NorpSession::OnRemoteRequest() late arriving remote SOCK_REQUEST\n"); + } + return true; +} // end NorpSession::OnRemoteRequest() + +bool NorpSession::PutRemoteReply() +{ + // Send SOCKS reply to remote NORP server via NORM command + ASSERT(IsRemoteSession()); + norp_msg.Init(); + norp_msg.SetType(NorpMsg::SOCKS_REP); + norp_msg.SetSessionId(GetSessionId()); // our session id (note was selected by request originator) + norp_msg.SetSourcePort(NormGetTxPort(norm_session)); // our NORM tx port number (so remote can "connect()" + norp_msg.SetDestinationPort(NormGetRxPort(norm_session)); // our NORM rx port (for info) + norp_msg.SetNodeId(controller.GetNormNodeId()); // our NORM node id + ProtoPktSOCKS::Reply reply(norp_msg.AccessContentPtr(), norp_msg.GetBufferLength() - norp_msg.GetLength(), false); + reply.SetVersion(5); + reply.SetType(ProtoPktSOCKS::Reply::SUCCESS); + reply.SetAddress(socks_remote_socket.GetSourceAddr()); + norp_msg.SetContentLength(reply.GetLength()); + + // If an existing ack or reply cmd is pending, we cancel it. This + // resets the robust transmission of the reply + if (norp_msg_timer.IsActive()) norp_msg_timer.Deactivate(); + struct timeval currentTime; + ProtoSystemTime(currentTime); + norp_msg.SetTimestamp(currentTime); + if (!controller.SendMessage(norp_msg, norp_remote_addr)) + PLOG(PL_ERROR, "NorpSession::OnRemoteRequest() warning: NORP REQ_ACK message transmission failed (will try again)!\n"); + norp_msg_timer.SetInterval(2.0 * norp_rtt_estimate); + ActivateTimer(norp_msg_timer); + socks_state = SOCKS_PUT_REPLY; + return true; +} // end NorpSession::PutRemoteReply() + +bool NorpSession::OnRemoteReplyAcknowledgment(const NorpMsg& theMsg) +{ + // Note we update our RTT estimate in either case + ProtoTime recvTime; + recvTime.GetCurrentTime(); + ProtoTime sentTime; + theMsg.GetTimestamp(sentTime); + + if (SOCKS_PUT_REPLY == socks_state) + { + // Initial rtt measurement + norp_rtt_estimate = ProtoTime::Delta(recvTime, sentTime); + PLOG(PL_INFO, "norp node %u starting sender probing (and sender) ...\n", controller.GetNormNodeId()); + if (Norp::NORM_FIXED != controller.GetNormCC()) + NormSetCongestionControl(norm_session, true); + if (!NormStartSender(norm_session, GetSessionId(), NORM_BUFFER_SIZE, controller.GetNormSegmentSize(), + controller.GetNormBlockSize(), controller.GetNormParityCount())) + { + PLOG(PL_ERROR, "NorpSession::OnRemoteReplyAcknowledgment() NormStartSender() failure!\n"); + return false; + } + //NormSetTxSocketBuffer(norm_session, 4096); + NormSetFlowControl(norm_session, 0.0); // disable timer-based flow control since we are ACK-limiting writes to stream + norm_segment_size = controller.GetNormSegmentSize(); + norm_stream_buffer_max = ComputeNormStreamBufferSegmentCount(NORM_BUFFER_SIZE, norm_segment_size, controller.GetNormBlockSize()); + norm_stream_buffer_max -= controller.GetNormBlockSize(); // a little safety margin + norm_stream_buffer_count = 0; + norm_stream_bytes_remain = 0; + norm_watermark_pending = false; + if (NORM_OBJECT_INVALID == (norm_tx_stream = NormStreamOpen(norm_session, NORM_BUFFER_SIZE))) + { + PLOG(PL_ERROR, "NorpSession::OnRemoteReplyAcknowledgment() NormStreamOpen() failure!\n"); + return false; + } + if (norp_msg_timer.IsActive()) norp_msg_timer.Deactivate(); // This cancels the robust (repeated) remote reply transmission + socks_remote_socket.StartInputNotification(); // enable flow of data from remote TCP socket + socks_state = SOCKS_CONNECTED; + remote_index = remote_pending = 0; + } + else + { + ASSERT(norp_rtt_estimate > 0.0); + // Only use duplicative ACKs to update RTT estimate via EWMA + double rttNew = ProtoTime::Delta(recvTime, sentTime); + norp_rtt_estimate = 0.5*(norp_rtt_estimate + rttNew); // TBD - is this a reasonable "smoothing" factor + } + // Note it's OK if a bona fide measured RTT is less than NORP_RTT_MIN + if (norp_rtt_estimate <= 0.0) + { + norp_rtt_estimate = NORP_RTT_MIN; + } + else if (norp_rtt_estimate > NORP_RTT_MAX) + { + PLOG(PL_WARN, "NorpSession::OnRemoteReplyAcknowledgment() warning: measured RTT exceeds max value of %lf sec.\n", NORP_RTT_MAX); + norp_rtt_estimate = NORP_RTT_MAX; + } + return true; +} // end NorpSession::OnRemoteReplyAcknowledgment() + +bool NorpSession::ConnectToRemote(const ProtoAddress& destAddr) +{ + // Make a direct TCP connection to remote destAddr + ASSERT(socks_remote_socket.IsClosed()); + if (!socks_remote_socket.Connect(destAddr)) + { + PLOG(PL_ERROR, "NorpSession::ConnectToRemote() error: remote socket connect() failure!\n"); + return false; + } + ASSERT(!socks_remote_socket.IsConnected()); // TBD - handle immediate connection case??? + PLOG(PL_DETAIL, "NorpSession::ConnectToRemote() connection to %s/%hu initiated ...\n", + destAddr.GetHostString(), destAddr.GetPort()); + if (socks_client_socket.IsOpen()) + socks_client_socket.StopInputNotification(); // don't accept more from client until connected + socks_state = SOCKS_CONNECTING; + return true; +} // end NorpSession::ConnectToRemote() + +bool NorpSession::BindRemote(UINT16 bindPort) +{ + // Open our remote socket in unbound state + if (!socks_remote_socket.Open(0, proxy_addr.GetType(), false)) + { + PLOG(PL_ERROR, "NorpSession::BindRemote() error: unable to open socket for remote connection\n"); + return false; + } + // Typically, the BIND request should be for port zero (any available port) + // But, in the case that it is not, we set port reuse for when the norp server + // is set up as a "local, loopback" server + if (0 != bindPort) socks_remote_socket.SetReuse(true); + bool result = socks_remote_socket.Bind(bindPort, &proxy_addr); + if (!result) result = socks_remote_socket.Bind(0, &proxy_addr); + if (!result) + { + PLOG(PL_ERROR, "NorpSession::BindRemote() error: unable to bind for remote connection\n"); + return false; + } + if (!socks_remote_socket.Listen()) + { + PLOG(PL_ERROR, "NorpSession::BindRemote() error: unable to listen for remote connection\n"); + return false; + } + // Construct the first reply to inform client of bind addr + ProtoPktSOCKS::Reply reply(remote_buffer, SOCKS_BUFFER_SIZE, false); + reply.SetVersion(5); + reply.SetType(ProtoPktSOCKS::Reply::SUCCESS); + ProtoAddress bindAddr = proxy_addr; + bindAddr.SetPort(socks_remote_socket.GetPort()); + PLOG(PL_INFO,"norp proxy listening on addr/port = %s/%hu\n", bindAddr.GetHostString(), bindAddr.GetPort()); + reply.SetAddress(bindAddr); + remote_pending = reply.GetLength(); + remote_index = 0; + socks_state = SOCKS_PUT_REPLY; + return PutClientReply(); +} // NorpSession::BindRemote() + +bool NorpSession::OpenUdpRelay() +{ + // 1) open udp_relay_socket + if (!udp_relay_socket.Open()) + { + PLOG(PL_ERROR, "NorpSession::OpenUdpRelay() error: udp_relay_socket open failed!\n"); + return false; + } + + // 2) Build the SOCKS reply into the remote_buffer ... + ProtoPktSOCKS::Reply reply(remote_buffer, SOCKS_BUFFER_SIZE, false); + reply.SetVersion(5); + reply.SetType(ProtoPktSOCKS::Reply::SUCCESS); + // We use the bind (local source) addr of our client socket as the SOCKS reply BIND.ADDR + // and the port number of our udp relay socket as the SOCKS reply BIND.PORT + ProtoAddress bindAddr = socks_client_socket.GetSourceAddr(); + PLOG(PL_INFO, "udp relay bind addr = %s/%hu\n", bindAddr.GetHostString(), bindAddr.GetPort()); + bindAddr.SetPort(udp_relay_socket.GetPort()); + reply.SetAddress(bindAddr); + remote_pending = reply.GetLength(); + remote_index = 0; + socks_state = SOCKS_PUT_REPLY; + return PutClientReply(); + +} // end NorpSession::OpenUdpRelay() + +bool NorpSession::PutClientReply() +{ + // Transmit SOCKS reply that's in "remote_buffer" to local SOCKS client + // (This buffering and indexing allows for possibly multiple TCP sockets + // writes to send the reply, even though that is not very likely given a + // SOCKS reply is smaller than the minimum IP MTU of 512 bytes) + ASSERT(SOCKS_PUT_REPLY == socks_state); + unsigned int numBytes = remote_pending - remote_index; + if (socks_client_socket.Send(((char*)remote_buffer)+remote_index, numBytes)) + { + remote_index += numBytes; + if (remote_index < remote_pending) + { + // Still more to send, keep output notification going + socks_client_socket.StartOutputNotification(); + } + else + { + // Reply fully sent, now transition to SOCKS_CONNECTED state + PLOG(PL_DETAIL, "NorpSession::PutClientReply() session %hu:%08x SOCKS reply sent to client ...\n", + GetSessionId(), GetOriginatorId()); + remote_index = remote_pending = 0; + socks_client_socket.StopOutputNotification(); + // Start accepting recv data from client or remote + client_index = client_pending = 0; + socks_client_socket.StartInputNotification(); + if (socks_remote_socket.IsOpen()) + socks_remote_socket.StartInputNotification(); + socks_state = SOCKS_CONNECTED; + } + } + else + { + PLOG(PL_ERROR, "NorpSession::PutClientReply() error: remote socket send failure\n"); + return false; + } + return true; +} // end NorpSession::PutClientReply() + +bool NorpSession::GetClientData() +{ + if (0 != client_pending) return true; + // Get whatever data is ready from client and start sending to remote + unsigned int numBytes = SOCKS_BUFFER_SIZE; + if (IsRemoteSession() && !IsRemoteOriginator()) + { + // We're the remote correspondent, so get client data from remote originator (via NORM) + if (norm_rx_pending) + { + ASSERT(0 == client_pending); + if (NormStreamRead(norm_rx_stream, (char*)client_buffer, &numBytes)) + { + client_pending = numBytes; + client_index = 0; + if (numBytes < SOCKS_BUFFER_SIZE) norm_rx_pending = false; + PLOG(PL_DETAIL, "NorpSession::GetClientData() correspondent read %lu bytes from NORM stream ..\n", numBytes); + if (0 != numBytes) + return PutRemoteData(); + // else wait for NORM_RX_OBJECT_UPDATED notification + } + else + { + PLOG(PL_ERROR, "NorpSession::GetClientData() NormStreamRead() error: break in stream!\n"); + return false; + } + } + // else wait for NORM_RX_OBJECT_UPDATED notification + } + else + { + // Originator server so get data from SOCKS client via TCP + ASSERT(0 == client_pending); + if (socks_client_socket.Recv((char*)client_buffer, numBytes)) + { + socks_client_socket.StopInputNotification(); + client_pending = numBytes; + client_index = 0; + PLOG(PL_DETAIL, "NorpSession::GetClientData() originator read %lu bytes from SOCKS client ...\n", numBytes); + if (0 != numBytes) + return PutRemoteData(); + // else socket will disconnect itself + } + else + { + PLOG(PL_ERROR, "NorpSession::GetClientData() client socket recv failure!\n"); + return false; + } + } + return true; +} // end NorpSession::GetClientData() + +bool NorpSession::PutClientData() +{ + // Transmit data that's in "remote_buffer" to client + unsigned int numBytes = remote_pending - remote_index; + if (IsRemoteSession() && !IsRemoteOriginator()) + { + if (0 != numBytes) + { + numBytes = WriteToNormStream(((char*)remote_buffer)+remote_index, numBytes); + } + PLOG(PL_DETAIL, "correspondent wrote %u bytes to NORM stream ...\n", numBytes); + remote_index += numBytes; + if (remote_index == remote_pending) + { + // All pending client data has been enqueued to remote + remote_index = remote_pending = 0; + if (SOCKS_CONNECTED == socks_state) + { + FlushNormStream(false, NORM_FLUSH_ACTIVE); + socks_remote_socket.StartInputNotification(); + } + else + { + ASSERT(SOCKS_SHUTDOWN == socks_state); + // No more data to put, so gracefully close norm_tx_stream + NormStreamClose(norm_tx_stream, true); // Note our "trick" here to do a graceful close, _then_ watermark + NormSetWatermark(norm_session, norm_tx_stream, true); // future NORM API will add "bool watermark" option to graceful close + } + } + // else wait for NORM_TX_QUEUE_VACANCY / EMPTY or watermark acknowledgemnt completion + } + else + { + // Relay data to local SOCKS client via TCP + if (0 != numBytes) + { + if (!socks_client_socket.Send(((char*)remote_buffer)+remote_index, numBytes)) + { + if (SOCKS_CONNECTED == socks_state) + { + PLOG(PL_ERROR, "NorpSession::PutClientData() error: remote socket send failure\n"); + return false; + } + else // SOCKS_SHUTDOWN == socks_state + { + PLOG(PL_WARN, "NorpSession::PutClientData() warning: remote socket send failure\n"); + return true; + } + } + } + PLOG(PL_DETAIL, "originator wrote %lu bytes to client TCP socket ...\n", numBytes); + remote_index += numBytes; + if (remote_index < remote_pending) + { + // Still more data to send, keep output notification going + socks_client_socket.StartOutputNotification(); + } + else + { + // Data fully sent, re-enable remote_socket input notifcation + remote_index = remote_pending = 0; + socks_client_socket.StopOutputNotification(); + if (SOCKS_CONNECTED == socks_state) + { + if (IsRemoteSession()) + { + // We're the remote session originator, so get more data if ready + if (norm_rx_pending) + return GetRemoteData(); + // else wait for NORM_RX_OBJECT_UPDATED notification + } + else + { + // Restart accepting recv data from remote + socks_remote_socket.StartInputNotification(); + } + } + else // SOCKS_SHUTDOWN == socks_state + { + if (socks_client_socket.IsConnected()) + { + socks_client_socket.Shutdown(); + } + else + { + socks_client_socket.Close(); + if (ShutdownComplete()) Shutdown(); + } + } + } + } + return true; +} // end NorpSession::PutClientData() + +void NorpSession::OnSocksRemoteEvent(ProtoSocket& theSocket, + ProtoSocket::Event theEvent) +{ + PLOG(PL_DETAIL, "NorpSession::OnSocksRemoteEvent("); + switch (theEvent) + { + case ProtoSocket::INVALID_EVENT: + { + // should never happen, just for completeness of switch cases + PLOG(PL_DETAIL, "INVALID_EVENT) ...\n"); + break; + } + case ProtoSocket::CONNECT: + { + PLOG(PL_DETAIL, "CONNECT) ...\n"); + ASSERT(SOCKS_CONNECTING == socks_state); + PLOG(PL_DETAIL, "NorpSession::OnSocksRemoteEvent(CONNECT) session %hu:%08x connected to destination %s/%hu ...\n", + GetSessionId(), GetOriginatorId(), + socks_remote_socket.GetDestination().GetHostString(), + socks_remote_socket.GetDestination().GetPort()); + if (is_preset) + { + // Go straight to connected state and start + // accepting recv data from client or remote + client_index = client_pending = 0; + socks_client_socket.StartInputNotification(); + remote_index = remote_pending = 0; + socks_state = SOCKS_CONNECTED; + } + else + { + // Don't get data from remote until reply sent to client + socks_remote_socket.StopInputNotification(); + socks_state = SOCKS_PUT_REPLY; + if (IsRemoteSession()) + { + if (!PutRemoteReply()) + { + PLOG(PL_ERROR, "NorpSession::OnSocksRemoteEvent(CONNECT) error: SOCKS CONNECT remote reply send failure!\n"); + Shutdown(); + } + } + else + { + // Build reply in "remote_buffer" to send to client + ProtoPktSOCKS::Reply reply(remote_buffer, SOCKS_BUFFER_SIZE, false); + reply.SetVersion(5); + reply.SetType(ProtoPktSOCKS::Reply::SUCCESS); + reply.SetAddress(socks_remote_socket.GetSourceAddr()); + remote_pending = reply.GetLength(); + remote_index = 0; + // Send SOCKS reply to locally connected client + if (!PutClientReply()) + { + PLOG(PL_ERROR, "NorpSession::OnSocksRemoteEvent(CONNECT) error: SOCKS CONNECT local reply send failure!\n"); + Shutdown(); + } + } + } + break; + } + case ProtoSocket::ACCEPT: + { + PLOG(PL_DETAIL, "ACCEPT) ...\n"); + // This will happen for a listening socket put in place for BIND request + // (we need to send a second reply indicating the remote connection accepted) + ASSERT(SOCKS_CONNECTED == socks_state); + if (!socks_remote_socket.Accept()) + { + PLOG(PL_ERROR, "NorpSession::OnSocksRemoteEvent(ACCEPT) error: SOCKS BIND accept failure!\n"); + Shutdown(); + break; + } + // TBD - should check that the accepted remote dst addr/port is what it's supposed to be + ProtoPktSOCKS::Reply reply(remote_buffer, SOCKS_BUFFER_SIZE, false); + reply.SetVersion(5); + reply.SetType(ProtoPktSOCKS::Reply::SUCCESS); + reply.SetAddress(socks_remote_socket.GetDestination()); + remote_pending = reply.GetLength(); + remote_index = 0; + socks_state = SOCKS_PUT_REPLY; + if (!PutClientReply()) + { + PLOG(PL_ERROR, "NorpSession::OnSocksRemoteEvent(ACCEPT) error: SOCKS BIND reply send failure!\n"); + Shutdown(); + } + break; + } + case ProtoSocket::SEND: + PLOG(PL_DETAIL, "SEND) ...\n"); + // write pending data from client_buffer to remote + if (!PutRemoteData()) + { + PLOG(PL_ERROR, "NorpSession::OnSocksRemoteEvent() error: PutRemoteData() failure!\n"); + Shutdown(); + } + break; + case ProtoSocket::RECV: + { + PLOG(PL_DETAIL, "RECV) ...\n"); + if (0 == remote_pending) // we need this check because of occasional extra notifications with old "protolib" + { + if (!GetRemoteData()) + { + PLOG(PL_ERROR, "NorpSession::OnSocksRemoteEvent(SEND) error: GetRemoteData() failure!\n"); + Shutdown(); + } + } + break; + } + case ProtoSocket::DISCONNECT: + PLOG(PL_DETAIL, "DISCONNECT) ...\n"); + switch (socks_state) + { + case SOCKS_VOID: + case SOCKS_IDLE: + case SOCKS_GET_AUTH_REQ: + case SOCKS_PUT_AUTH_REP: + case SOCKS_GET_REQUEST: + case SOCKS_PUT_REQUEST: + ASSERT(0); + break; + case SOCKS_CONNECTING: + case SOCKS_PUT_REPLY: + case SOCKS_GET_REPLY: + PLOG(PL_ERROR, "NorpSession::OnSocksRemoteEvent() error: premature SOCKS client disconnect!\n"); + Shutdown(); // unexpected, premature client disconnect + break; + case SOCKS_CONNECTED: + socks_state = SOCKS_SHUTDOWN; + if (0 == remote_pending) + PutClientData(); // to shutdown NORM stream or socks_client_socket + case SOCKS_SHUTDOWN: + // Client finished sending (and receiving) data + theSocket.Close(); + if (ShutdownComplete()) + Close(); + // else further shutdown actions pending + break; + } + break; + + case ProtoSocket::EXCEPTION: + PLOG(PL_DETAIL, "EXCEPTION) ...\n"); // shouldn't happen + break; + + case ProtoSocket::ERROR_: + PLOG(PL_DETAIL, "ERROR_) ...\n"); + Shutdown(); + break; + } +} // end NorpSession::OnSocksRemoteEvent() + +bool NorpSession::GetRemoteData() +{ + // Receive data from remote socket into remote_buffer + // Get some data from remote and start sending it to the client + ASSERT(0 == remote_pending); + unsigned int numBytes = SOCKS_BUFFER_SIZE; + if (IsRemoteSession() && IsRemoteOriginator()) + { + // We're the remote correspondent (i.e. not the remote session originator) + if (norm_rx_pending) + { + ASSERT(0 == remote_pending); + if (NormStreamRead(norm_rx_stream, (char*)remote_buffer, &numBytes)) + { + remote_pending = numBytes; + remote_index = 0; + if (numBytes < SOCKS_BUFFER_SIZE) norm_rx_pending = false; + if (0 != numBytes) + return PutClientData(); + // else wait for NORM_RX_OBJECT_UPDATED notification + } + else + { + PLOG(PL_ERROR, "NorpSession::GetRemoteData() NormStreamRead() error: break in stream!\n"); + return false; + } + } + // else wait for NORM_RX_OBJECT_UPDATED notification + } + else + { + // Remote correspondent or direct-connect, so use TCP + ASSERT(0 == remote_pending); + if (socks_remote_socket.Recv((char*)remote_buffer, numBytes)) + { + remote_pending = numBytes; + remote_index = 0; + socks_remote_socket.StopInputNotification(); + if (0 != numBytes) + return PutClientData(); + // else socket will disconnect itself and session will close + } + else + { + PLOG(PL_ERROR, "NorpSession::GetRemoteData() error: remote socket recv failure!\n"); + return false; + } + } + return true; +} // end NorpSession::GetRemoteData() + +bool NorpSession::PutRemoteData() +{ + // Transmit data in "client_buffer" to remote + unsigned int numBytes = client_pending - client_index; + if (IsRemoteSession() && IsRemoteOriginator()) + { + if (0 != numBytes) + numBytes = WriteToNormStream(((char*)client_buffer)+client_index, numBytes); + PLOG(PL_DETAIL, "originator wrote %u bytes to NORM stream ...\n", numBytes); + client_index += numBytes; + if (client_index == client_pending) + { + // All pending client data has been enqueued to remote + client_index = client_pending = 0; + if (SOCKS_CONNECTED == socks_state) + { + FlushNormStream(false, NORM_FLUSH_ACTIVE); + socks_client_socket.StartInputNotification(); // to get more data + } + else + { + ASSERT(SOCKS_SHUTDOWN == socks_state); + // gracefully close our tx stream + NormStreamClose(norm_tx_stream, true); // Note our "trick" here to do a graceful close, _then_ watermark + NormSetWatermark(norm_session, norm_tx_stream, true); // future NORM API will add "bool watermark" option to graceful close + } + } + // else wait for NORM_TX_QUEUE_VACANCY / EMPTY or watermark acknowledgment completion + } + else + { + // Use TCP to send data directly to remote endpoint + if (0 != numBytes) + { + if (!socks_remote_socket.Send(((char*)client_buffer)+client_index, numBytes)) + { + PLOG(PL_ERROR, "NorpSession::PutRemoteData() error: remote TCP socket send failure\n"); + return false; + } + } + PLOG(PL_DETAIL, "%s wrote %lu bytes to remote TCP socket ...\n", IsRemoteSession() ? "correspondent" : "server", numBytes); + client_index += numBytes; + if (client_index < client_pending) + { + socks_remote_socket.StartOutputNotification(); + } + else + { + // All pending client data has been sent to remote + socks_remote_socket.StopOutputNotification(); + client_index = client_pending = 0; + if (SOCKS_CONNECTED == socks_state) + { + if (IsRemoteSession()) + { + // We're the remote correspondent (i.e. not the remote session originator) + if (norm_rx_pending) + return GetClientData(); + // else wait for NORM_RX_OBJECT_UPDATED notification + } + else + { + socks_client_socket.StartInputNotification(); // prompt for more from client + } + } + else + { + ASSERT(SOCKS_SHUTDOWN == socks_state); + if (socks_remote_socket.IsConnected()) + { + socks_remote_socket.Shutdown(); + } + else + { + socks_remote_socket.Close(); + if (ShutdownComplete()) Shutdown(); + } + } + } + } + return true; +} // end NorpSession::PutRemoteData() + + +void NorpSession::OnUdpRelayEvent(ProtoSocket& theSocket, + ProtoSocket::Event theEvent) +{ + switch (theEvent) + { + case ProtoSocket::RECV: + { + UINT32 buffer[8192/4]; + ProtoAddress srcAddr; + char* bufPtr = ((char*)buffer) + 22; // allows room for IPv6 header to be applied + unsigned int numBytes = (8192 - 22); + while (udp_relay_socket.RecvFrom(bufPtr, numBytes, srcAddr)) + { + if (0 == numBytes) break; // no more data to read + if (srcAddr.IsEqual(udp_client_addr)) + { + + // Packet is from client, so relay it ... + // (First need to parse SOCKS header to get destination, etc + // The cast up to UINT32* here is safe because UdpRequest uses byte boundaries + ProtoPktSOCKS::UdpRequest udpReq((UINT32*)bufPtr, numBytes); + if (0 != udpReq.GetFrag()) + { + PLOG(PL_ERROR, "NorpSession::OnUdpRelayEvent() received fragmented packet from client!" + " (fragmentation not yet supported.)\n"); + numBytes = (8192 - 22); + continue; // get next packet + } + ProtoAddress dstAddr; + if (!udpReq.GetAddress(dstAddr)) + { + PLOG(PL_ERROR, "NorpSession::OnUdpRelayEvent() received packet with bad destination address from client!\n"); + numBytes = 8192; + continue; // get next packet + } + // Relay the packet (TBD - add buffering, etc?) + if (!udp_relay_socket.SendTo(udpReq.GetDataPtr(), udpReq.GetDataLength(), dstAddr)) + { + PLOG(PL_ERROR, "NorpSession::OnUdpRelayEvent() error: unable to send packet to %s/%hu!\n", + dstAddr.GetHostString(), dstAddr.GetPort()); + numBytes = (8192 - 22); + continue; // get next packet + } + } + else + { + // TBD - should we make sure the dest addr was for our client? + // Relay inbound packet to SOCKS client + // Note we left room for our SOCKS UdpRequest header to be applied in front of payload data + unsigned int headerOffset = 0; + if (ProtoAddress::IPv4 == srcAddr.GetType()) + headerOffset = 12; // IPv4 addr is 12 bytes shorter than IPv6 + else if (ProtoAddress::IPv6 != srcAddr.GetType()) + ASSERT(0); // can only receive IPv4 or IPv6 packets1 + char* reqPtr = ((char*)buffer) + headerOffset; + // The cast up to UINT32* here is safe because UdpRequest uses byte boundaries + ProtoPktSOCKS::UdpRequest udpReq((UINT32*)reqPtr, numBytes + 22 - headerOffset, false); + udpReq.SetFragment(0); + udpReq.SetAddress(srcAddr); + udpReq.SetDataLength(numBytes); + // Send to client + if (!udp_relay_socket.SendTo(reqPtr, udpReq.GetLength(), udp_client_addr)) + { + PLOG(PL_ERROR, "NorpSession::OnUdpRelayEvent() error: unable to relay inbound packet to %s/%hu!\n", + udp_client_addr.GetHostString(), udp_client_addr.GetPort()); + numBytes = (8192 - 22); + continue; // get next packet + } + } + numBytes = (8192 - 22); // reset "numBytes" + } + break; + } + default: + PLOG(PL_DETAIL, "NorpSession::OnUdpRelayEvent() unhandled event type %d\n", theEvent); + break; + } // end switch(theEvent) + +} // end NorpSession::OnUdpRelayEvent() + +bool NorpSession::GetNormNodeAddress(NormNodeHandle nodeHandle, ProtoAddress& theAddr) +{ + char addrBuffer[16]; + unsigned int addrLen = 16; + UINT16 portNum; + if (NormNodeGetAddress(nodeHandle, addrBuffer, &addrLen, &portNum)) + { + if (4 == addrLen) + theAddr.SetRawHostAddress(ProtoAddress::IPv4, addrBuffer, 4); + else + theAddr.SetRawHostAddress(ProtoAddress::IPv6, addrBuffer, 16); + theAddr.SetPort(portNum); + return true; + } + else + { + PLOG(PL_ERROR, "NorpSession::GetNormNodeAddress() error: NormNodeGetAddress() failed!\n"); + theAddr.Invalidate(); + return false; + } +} // end NorpSession::GetNormNodeAddress() + +unsigned int NorpSession::ComputeNormStreamBufferSegmentCount(unsigned int bufferBytes, UINT16 segmentSize, UINT16 blockSize) +{ + // This same computation is performed in NormStreamObject::Open() in "normObject.cpp" + unsigned int numBlocks = bufferBytes / (blockSize * segmentSize); + if (numBlocks < 2) numBlocks = 2; // NORM enforces a 2-block minimum buffer size + return (numBlocks * blockSize); +} // end NorpSession::ComputeNormStreamBufferSegmentCount() + +unsigned int NorpSession::WriteToNormStream(const char* buffer, + unsigned int numBytes) +{ + // This method uses NormStreamWrite(), but limits writes by explicit ACK-based flow control status + if (norm_stream_buffer_count < norm_stream_buffer_max) + { + // 1) How many buffer bytes are available? + unsigned int bytesAvailable = norm_segment_size * (norm_stream_buffer_max - norm_stream_buffer_count); + bytesAvailable -= norm_stream_bytes_remain; // unflushed segment portiomn + if (numBytes <= bytesAvailable) + { + unsigned int totalBytes = numBytes + norm_stream_bytes_remain; + unsigned int numSegments = totalBytes / norm_segment_size; + norm_stream_bytes_remain = totalBytes % norm_segment_size; + norm_stream_buffer_count += numSegments; + } + else + { + numBytes = bytesAvailable; + norm_stream_buffer_count = norm_stream_buffer_max; + } + // 2) Write to the stream + unsigned int bytesWritten = NormStreamWrite(norm_tx_stream, buffer, numBytes); + //ASSERT(bytesWritten == numBytes); // this could happen if timer-based flow control is left enabled + // or if stream is closing ... ??? + // We had a "stream is closing" case here somehow? Need to make sure we don't try + // to write to norm stream + + // 3) Do we need to issue a watermark ACK request? + if (!norm_watermark_pending && (norm_stream_buffer_count >= (norm_stream_buffer_max >> 1))) + { + //TRACE("NorpSession::WriteToNormStream() initiating watermark ACK request (buffer count:%lu max:%lu usage:%u)...\n", + // norm_stream_buffer_count, norm_stream_buffer_max, NormStreamGetBufferUsage(norm_tx_stream)); + NormSetWatermark(norm_session, norm_tx_stream); + norm_watermark_pending = true; + } + return bytesWritten; + } + else + { + PLOG(PL_DETAIL, "NorpSession::WriteToNormStream() is blocked pending acknowledgment from receiver\n"); + return 0; + } +} // end NorpSession::WriteToNormStream() + +void NorpSession::FlushNormStream(bool eom, NormFlushMode flushMode) +{ + // NormStreamFlush always will transmit pending runt segments, if applicable + // (thus we need to manage our buffer counting accordingly if pending bytes remain) + NormStreamFlush(norm_tx_stream, eom, flushMode); + if (0 != norm_stream_bytes_remain) + { + norm_stream_buffer_count++; + norm_stream_bytes_remain = 0; + if (!norm_watermark_pending && (norm_stream_buffer_count >= (norm_stream_buffer_max >> 1))) + { + //TRACE("NorpSession::FlushNormStream() initiating watermark ACK request (buffer count:%lu max:%lu usage:%u)...\n", + // norm_stream_buffer_count, norm_stream_buffer_max); + NormSetWatermark(norm_session, norm_tx_stream, NormStreamGetBufferUsage(norm_tx_stream)); + norm_watermark_pending = true; + } + } +} // end NorpSession::FlushNormStream() + +bool NorpSession::OnNorpMsgTimeout(ProtoTimer& theTimer) +{ + PLOG(PL_DETAIL, "NorpSession::OnNorpMsgTimeout() repeat count = %d\n", theTimer.GetRepeatCount()); + if (0 != theTimer.GetRepeatCount()) + { + struct timeval currentTime; + ProtoSystemTime(currentTime); + norp_msg.SetTimestamp(currentTime); + if (SOCKS_PUT_REQUEST == socks_state) + { + if (!SendMessage(norp_msg)) + PLOG(PL_ERROR, "NorpSession::OnNorpMsgTimeout() warning: unable to send message (retries remaining %d)\n", theTimer.GetRepeatCount()); + } + else + { + if (!controller.SendMessage(norp_msg, norp_remote_addr)) + PLOG(PL_ERROR, "NorpSession::OnNorpMsgTimeout() warning: unable to send message (retries remaining %d)\n", theTimer.GetRepeatCount()); + } + } + else + { + PLOG(PL_ERROR, "NorpSession::OnNorpMsgTimeout() error: message retransmission timeout with no reply\n"); + switch (socks_state) + { + case SOCKS_PUT_REQUEST: + if (!MakeDirectConnect()) + { + PLOG(PL_ERROR, "NorpSession::OnNorpMsgTimeout() error: also unable to make direct TCP connection\n"); + Shutdown(); + } + return false; // MakeDirectConnect() kills timer + break; + case SOCKS_PUT_REPLY: + Shutdown(); + break; + case SOCKS_SHUTDOWN: + Close(); + return false; + default: + ASSERT(0); // shouldn't be messaging in any other states + break; + } + } + return true; +} // end NorpSession::OnNorpMsgTimeout() + +void NorpSession::OnNormEvent(NormEvent& theEvent) +{ + PLOG(PL_DETAIL, "NorpSession::OnNormEvent("); + switch (theEvent.type) + { + case NORM_TX_QUEUE_VACANCY: + PLOG(PL_DETAIL, "NORM_TX_QUEUE_VACANCY)\n"); + case NORM_TX_QUEUE_EMPTY: + if (NORM_TX_QUEUE_EMPTY == theEvent.type) PLOG(PL_DETAIL, "NORM_TX_QUEUE_EMPTY)\n"); + if (IsRemoteOriginator()) + { + // If there's pending data from the SOCKS client, try to send it + if (0 != client_pending) + { + if (!PutRemoteData()) + { + PLOG(PL_ERROR, "NorpSession::OnNormEvent(TX_VACANCY) error: PutRemoteData() failure!\n"); + Shutdown(); + } + } + } + else + { + if (0 != remote_pending) + { + if (!PutClientData()) + { + PLOG(PL_ERROR, "NorpSession::OnNormEvent(TX_VACANCY) error: PutClientData() failure!\n"); + Shutdown(); + } + } + } + break; + case NORM_TX_FLUSH_COMPLETED: + PLOG(PL_DETAIL, "NORM_TX_FLUSH_COMPLETED)\n"); + break; + case NORM_TX_WATERMARK_COMPLETED: + PLOG(PL_DETAIL, "NORM_TX_WATERMARK_COMPLETED)\n"); + if (NORM_ACK_SUCCESS == NormGetAckingStatus(norm_session)) + { + PLOG(PL_DETAIL, " acking status: SUCCESS\n"); + //TRACE("successful watermark ACK (buffer count:%lu max:%lu)\n", norm_stream_buffer_count, norm_stream_buffer_max); + norm_watermark_pending = false; + norm_stream_buffer_count -= (norm_stream_buffer_max >> 1); + //TRACE(" (count reduced to %lu)\n", norm_stream_buffer_count); + // Use as prompt to send pending data to NORM stream + if (IsRemoteOriginator()) + { + // If there's pending data from the SOCKS client, try to send it + if (0 != client_pending) + { + if (!PutRemoteData()) + { + PLOG(PL_ERROR, "NorpSession::OnNormEvent(TX_VACANCY) error: PutRemoteData() failure!\n"); + Shutdown(); + } + } + } + else + { + if (0 != remote_pending) + { + if (!PutClientData()) + { + PLOG(PL_ERROR, "NorpSession::OnNormEvent(TX_VACANCY) error: PutClientData() failure!\n"); + Shutdown(); + } + } + } + if (!persist_start_time.IsZero()) + persist_start_time.Zeroize(); // resets persistence timeout + } + else if (NORM_ACK_FAILURE == NormGetAckingStatus(norm_session)) + { + PLOG(PL_DETAIL, " acking status: FAILURE\n"); + if (persist_interval >= 0.0) + { + // Finite persistence will be observed + if (persist_start_time.IsZero()) + { + // Initialize persistence timeout + persist_start_time.GetCurrentTime(); + } + else + { + ProtoTime currentTime; + currentTime.GetCurrentTime(); + if ((currentTime - persist_start_time) > persist_interval) + { + NormStreamClose(norm_tx_stream); + norm_tx_stream = NORM_OBJECT_INVALID; + Shutdown(); + break; + } + } + } + NormResetWatermark(norm_session); + } + break; + case NORM_TX_CMD_SENT: + PLOG(PL_DETAIL, "NORM_TX_CMD_SENT)\n"); + break; + case NORM_TX_OBJECT_SENT: + PLOG(PL_DETAIL, "NORM_TX_OBJECT_SENT)\n"); + break; + case NORM_TX_OBJECT_PURGED: + PLOG(PL_DETAIL, "NORM_TX_OBJECT_PURGED)\n"); + if (theEvent.object == norm_tx_stream) + { + ASSERT(SOCKS_SHUTDOWN == socks_state); + // do we need to call NormStreamClose() here? + NormStreamClose(norm_tx_stream); + norm_tx_stream = NORM_OBJECT_INVALID; + if (ShutdownComplete()) Shutdown(); + } + break; + case NORM_TX_RATE_CHANGED: + PLOG(PL_DETAIL, "NORM_TX_RATE_CHANGED)\n"); + break; + case NORM_LOCAL_SENDER_CLOSED: + PLOG(PL_DETAIL, "NORM_LOCAL_SENDER_CLOSED)\n"); + break; + case NORM_REMOTE_SENDER_NEW: + PLOG(PL_DETAIL, "NORM_REMOTE_SENDER_NEW)\n"); + break; + case NORM_REMOTE_SENDER_ACTIVE: + PLOG(PL_DETAIL, "NORM_REMOTE_SENDER_ACTIVE)\n"); + break; + case NORM_REMOTE_SENDER_INACTIVE: + PLOG(PL_DETAIL, "NORM_REMOTE_SENDER_INACTIVE)\n"); + break; + case NORM_REMOTE_SENDER_PURGED: + PLOG(PL_DETAIL, "NORM_REMOTE_SENDER_PURGED)\n"); + break; + case NORM_RX_CMD_NEW: + PLOG(PL_DETAIL, "NORM_RX_CMD_NEW)\n"); + break; + case NORM_RX_OBJECT_NEW: + PLOG(PL_DETAIL, "NORM_RX_OBJECT_NEW)\n"); + // TBD - confirm it's a stream, etc? + norm_rx_stream = theEvent.object; + break; + case NORM_RX_OBJECT_INFO: + PLOG(PL_DETAIL, "NORM_RX_OBJECT_INFO)\n"); + break; + case NORM_RX_OBJECT_UPDATED: + PLOG(PL_DETAIL, "NORM_RX_OBJECT_UPDATED)\n"); + ASSERT(theEvent.object == norm_rx_stream); + norm_rx_pending = true; // signals there is NORM rx data ready for reading + if (IsRemoteOriginator()) + { + // we're the remote session originator (read data from NORM and put to local SOCKS client) + if (0 == remote_pending) + { + // try to read more data from NORM into "remote_buffer" and put to SOCKS client + if (!GetRemoteData()) + { + PLOG(PL_ERROR, "NorpSession::OnNormEvent(RX_UPDATE) error: GetRemoteData() failure!\n"); + Shutdown(); // TBD - shut down more gracefully (informing remote) + } + } + // else there's still data in remote_buffer pending relay to SOCKS client + // TBD - should we go ahead and append more data to remote_buffer? + // (for now, PutClientData() will call GetRemoteData() for more when relay to client completes + } + else + { + // we're the remote correspondent (read data from NORM and put to remote) + if ((0 == client_pending) && ((SOCKS_CONNECTED == socks_state) || (SOCKS_SHUTDOWN == socks_state))) + { + // try to read more data from NORM into "client_buffer" and put to SOCKS destination + if (!GetClientData()) + { + PLOG(PL_ERROR, "NorpSession::OnNormEvent(RX_UPDATE) error: GetRemoteData() failure!\n"); + Shutdown(); // TBD - shut down more gracefully (informing remote) + } + } + // else there's still data in client_buffer pending relay to TCP destination + // TBD - should we go ahead and append more data to client_buffer? + // (for now, PutRemoteData() will call GetClientData() for more when relay to remote completes + } + break; + case NORM_RX_OBJECT_COMPLETED: + PLOG(PL_DETAIL, "NORM_RX_OBJECT_COMPLETED)\n"); + if (IsRemoteOriginator()) + { + if (SOCKS_CONNECTED == socks_state) + { + socks_state = SOCKS_SHUTDOWN; + // Make sure our socks_client_socket gets shut down + if (0 == remote_pending) + PutClientData(); // will shutdown our socks_client_socket + // else socks_client_socket output notification will call PutClientData() + + // TBD - if the remote obj is completed, then there's no use in pushing more data their way? + // Gracefully shut down our norm_tx_stream (requesting an ACK from the correspondent) + NormStreamClose(norm_tx_stream, true); // Note our "trick" here to do a graceful close, _then_ watermark + NormSetWatermark(norm_session, norm_tx_stream, true); // future NORM API will add "bool watermark" option to graceful close + } + else + { + ASSERT(!socks_client_socket.IsOpen()); + } + } + else + { + if (SOCKS_CONNECTED == socks_state) + { + socks_state = SOCKS_SHUTDOWN; + if (0 == client_pending) + PutRemoteData(); // will shutdown our socks_remote_socket + // else socks_remote_socket output notification will call PutClientData() + + // Gracefully shut down our norm_tx_stream (requesting an ACK from the originator) + NormStreamClose(norm_tx_stream, true); // Note our "trick" here to do a graceful close, _then_ watermark + NormSetWatermark(norm_session, norm_tx_stream, true); // future NORM API will add "bool watermark" option to graceful close + } + else + { + ASSERT(!socks_remote_socket.IsOpen()); + } + } + norm_rx_stream = NORM_OBJECT_INVALID; + norm_rx_pending = false; + if (ShutdownComplete()) Close(); + break; + case NORM_RX_OBJECT_ABORTED: + PLOG(PL_DETAIL, "NORM_RX_OBJECT_ABORTED)\n"); + break; + case NORM_GRTT_UPDATED: + PLOG(PL_DETAIL, "NORM_GRTT_UPDATED)\n"); + break; + case NORM_CC_ACTIVE: + PLOG(PL_DETAIL, "NORM_CC_ACTIVE)\n"); + break; + case NORM_CC_INACTIVE: + PLOG(PL_DETAIL, "NORM_CC_INACTIVE)\n"); + break; + case NORM_EVENT_INVALID: + PLOG(PL_DETAIL, "NORM_EVENT_INVALID)\n"); + break; + default: + PLOG(PL_DETAIL, "UNKNOWN event %d)\n", theEvent.type); + break; + } +} // end NorpSession::OnNormEvent() + +NorpPreset::NorpPreset(Norp& norpController, UINT16 tcpPort, const ProtoAddress& dstAddr, const ProtoAddress& norpAddr) + : controller(norpController), tcp_port(tcpPort), dst_addr(dstAddr), norp_addr(norpAddr), server_socket(ProtoSocket::TCP) +{ + server_socket.SetNotifier(&(controller.GetSocketNotifier())); + server_socket.SetListener(this, &NorpPreset::OnSocketEvent); + +} + +NorpPreset::~NorpPreset() +{ + if (server_socket.IsOpen()) server_socket.Close(); +} + +bool NorpPreset::Listen() +{ + if (!server_socket.Listen(tcp_port)) + { + PLOG(PL_ERROR, "NorpPreset::Listen() error: unable to establish listening TCP socket on port %hu\n", tcp_port); + return false; + } + return true; +} // end NorpPreset::Listen(); + +void NorpPreset::OnSocketEvent(ProtoSocket& theSocket, ProtoSocket::Event theEvent) +{ + // TBD - dispatch ACCEPT events up to controller to create a corresponding + // jump-started NorpSession + + PLOG(PL_DETAIL, "NorpPreset::OnSocketEvent("); + switch (theEvent) + { + case ProtoSocket::INVALID_EVENT: + PLOG(PL_DETAIL, "INVALID_EVENT) ...\n"); + break; + case ProtoSocket::CONNECT: + PLOG(PL_DETAIL, "CONNECT) ...\n"); + break; + case ProtoSocket::ACCEPT: + { + PLOG(PL_DETAIL, "ACCEPT) ...\n"); + if (!controller.AcceptPresetClientConnection(*this)) + PLOG(PL_ERROR, "Norp::OnSocketEvent(ACCEPT) error: unable to create new NorpSession\n"); + break; + } + case ProtoSocket::SEND: + PLOG(PL_DETAIL, "SEND) ...\n"); + break; + case ProtoSocket::RECV: + { + PLOG(PL_DETAIL, "RECV) ...\n"); + break; + } + case ProtoSocket::DISCONNECT: + PLOG(PL_DETAIL, "DISCONNECT) ...\n"); + break; + + case ProtoSocket::EXCEPTION: + PLOG(PL_DETAIL, "EXCEPTION) ...\n"); + break; + + case ProtoSocket::ERROR_: + PLOG(PL_DETAIL, "ERROR_) ...\n"); + break; + } + +} // end NorpPreset::OnSocketEvent() + +Norp::Norp(ProtoDispatcher& theDispatcher) + : dispatcher(theDispatcher), socks_server_socket(ProtoSocket::TCP), socks_port(DEFAULT_SOCKS_PORT), + next_session_id(1), session_count(0), norp_rx_socket(ProtoSocket::UDP), norp_local_port(DEFAULT_NORP_PORT), + norp_remote_port(DEFAULT_NORP_PORT), norp_rtt_init(NorpSession::NORP_RTT_DEFAULT), + norm_enable(true), norm_instance(NORM_INSTANCE_INVALID), norm_node_id(NORM_NODE_ANY), + norm_port(DEFAULT_NORM_PORT), norm_cc_mode(NORM_CC), norm_tx_rate(DEFAULT_TX_RATE), norm_tx_limit(-1.0), + norm_segment_size(1400), norm_block_size(64), norm_parity_count(0), norm_parity_auto(0), norm_trace(false) +// ,port_pool(9000) +{ + socks_server_socket.SetNotifier(&GetSocketNotifier()); + socks_server_socket.SetListener(this, &Norp::OnSocksServerEvent); + norp_rx_socket.SetNotifier(&GetSocketNotifier()); + norp_rx_socket.SetListener(this, &Norp::OnNorpSocketEvent); +} + +Norp::~Norp() +{ + StopServer(); +} + +bool Norp::StartServer(bool normEnable) +{ + if (!socks_server_socket.Listen(socks_port)) + { + PLOG(PL_ERROR, "Norp::StartServer() error: socks server socket listen(%d) error\n", socks_port); + return false; + } + norm_enable = normEnable; + if (normEnable) + { + norm_instance = NormCreateInstance(); + if (NORM_INSTANCE_INVALID == norm_instance) + { + PLOG(PL_ERROR, "Norp::StartServer() error: NormCreateInstance() failure!\n"); + StopServer(); + return false; + } + NormDescriptor normDescriptor = NormGetDescriptor(norm_instance); + if (!dispatcher.InstallGenericInput(normDescriptor, NormEventCallback, this)) + { + PLOG(PL_ERROR, "Norp::StartServer() error: unable to install NORM event notification!\n"); + StopServer(); + return false; + } + if (!norp_rx_socket.Open(norp_local_port)) + { + PLOG(PL_ERROR, "Norp::StartServer() error: unable to open NORP signaling socket!\n"); + StopServer(); + return false; + } + if (NORM_NODE_ANY == norm_node_id) + norm_node_id = proxy_addr.GetEndIdentifier(); // TBD - make sure proxy_addr.IsValid() ??? + } + else + { + norm_node_id = NORM_NODE_NONE; + } + PLOG(PL_INFO, "norp: created controller on NORP port %hu with local NORM node id: %08x\n", norp_local_port, norm_node_id); + return true; +} // end Norp::StartServer() + +void Norp::StopServer() +{ + norp_rx_socket.Close(); + socks_server_socket.Close(); + session_list.Destroy(); + preset_list.Destroy(); + if (NORM_INSTANCE_INVALID != norm_instance) + { + dispatcher.RemoveGenericInput(NormGetDescriptor(norm_instance)); + NormDestroyInstance(norm_instance); + norm_instance = NORM_INSTANCE_INVALID; + } +} // end Norp::StopServer() + +void Norp::OnSessionClose(NorpSession& theSession) +{ + RemoveSession(theSession); + PLOG(PL_INFO, "norp: deleting closed session %u\n", theSession.GetSessionId()); + delete &theSession; +} // end Norp::OnSessionClose(); + +bool Norp::AcceptPresetClientConnection(NorpPreset& preset) +{ + // Create a new NorpSession for this prest client connection + NorpSession* norpSession = new NorpSession(*this, next_session_id++); + if (NULL == norpSession) + { + PLOG(PL_ERROR, "Norp::AcceptPresetClientConnection() new NorpSession error: %s\n", GetErrorString()); + return false; + } + if (!norpSession->AcceptPresetClientConnection(preset, norm_enable)) + { + PLOG(PL_ERROR, "Norp::AcceptPresetClientConnection() error: unable to accept connection\n"); + delete norpSession; + return false; + } + norpSession->SetProxyAddress(proxy_addr); + AddSession(*norpSession); + PLOG(PL_INFO, "norp: session %hu accepted preset connection from %s\n", norpSession->GetSessionId(), norpSession->GetClientAddress().GetHostString()); + return true; +} // end Norp::AcceptPresetClientConnection() + +void Norp::OnSocksServerEvent(ProtoSocket& theSocket, + ProtoSocket::Event theEvent) +{ + PLOG(PL_DETAIL, "Norp::OnServerSocketEvent("); + switch (theEvent) + { + case ProtoSocket::INVALID_EVENT: + PLOG(PL_DETAIL, "INVALID_EVENT) ...\n"); + break; + case ProtoSocket::CONNECT: + PLOG(PL_DETAIL, "CONNECT) ...\n"); + break; + case ProtoSocket::ACCEPT: + { + PLOG(PL_DETAIL, "ACCEPT) ...\n"); + // Create a new NorpSession for this potential client + NorpSession* norpSession = new NorpSession(*this, next_session_id++); + if (NULL == norpSession) + { + PLOG(PL_ERROR, "Norp::OnSocksServerEvent() new NorpSession error: %s\n", GetErrorString()); + break; + } + if (!norpSession->AcceptClientConnection(socks_server_socket, norm_enable)) + // Accept the socket connection into this session's "socks_client_socket" + //if (!socks_server_socket.Accept(&norpSession->AccessSocksClientSocket())) + { + PLOG(PL_ERROR, "Norp::OnSocksServerEvent(ACCEPT) error accepting connection\n"); + delete norpSession; + break; + } + norpSession->SetProxyAddress(proxy_addr); + AddSession(*norpSession); + PLOG(PL_INFO, "norp: session %hu accepted connection from %s\n", norpSession->GetSessionId(), norpSession->GetClientAddress().GetHostString()); + break; + } + case ProtoSocket::SEND: + PLOG(PL_DETAIL, "SEND) ...\n"); + break; + case ProtoSocket::RECV: + { + PLOG(PL_DETAIL, "RECV) ...\n"); + break; + } + case ProtoSocket::DISCONNECT: + PLOG(PL_DETAIL, "DISCONNECT) ...\n"); + break; + + case ProtoSocket::EXCEPTION: + PLOG(PL_DETAIL, "EXCEPTION) ...\n"); + break; + + case ProtoSocket::ERROR_: + PLOG(PL_DETAIL, "ERROR_) ...\n"); + break; + } +} // end Norp::OnSocksServerEvent() + +void Norp::AddSession(NorpSession& session) +{ + session_list.Insert(session); + session_count++; + if (norm_tx_limit >= 0.0) + { + double lowerLimit = 0.9 * (norm_tx_limit / (double)session_count); + NorpSessionList::Iterator iterator(session_list); + NorpSession* nextSession; + while (NULL != (nextSession = iterator.GetNextItem())) + nextSession->SetNormRateBounds(lowerLimit, norm_tx_limit); + } +} // end Norp::AddSession() + +void Norp::RemoveSession(NorpSession& session) +{ + session_list.Remove(session); + session_count--; + if (norm_tx_limit >= 0.0) + { + double lowerLimit = 0.9 * (norm_tx_limit / (double)session_count); + NorpSessionList::Iterator iterator(session_list); + NorpSession* nextSession; + while (NULL != (nextSession = iterator.GetNextItem())) + nextSession->SetNormRateBounds(lowerLimit, norm_tx_limit); + } +} // end Norp::RemoveSession() + + +bool Norp::AddPreset(UINT16 tcpPort, const ProtoAddress& dstAddr, const ProtoAddress& norpAddr) +{ + NorpPreset* preset = new NorpPreset(*this, tcpPort, dstAddr, norpAddr); + if (NULL == preset) + { + PLOG(PL_ERROR, "Norp::AddPreset() new NorpPreset error: %s\n", GetErrorString()); + return false; + } + if (!preset->Listen()) + { + PLOG(PL_ERROR, "Norp::AddPreset() error: unable to create listening TCP socket\n"); + delete preset; + return false; + } + preset_list.Insert(*preset); + return true; +} // end Norp::AddPreset() + +void Norp::OnNorpSocketEvent(ProtoSocket& theSocket, + ProtoSocket::Event theEvent) +{ + if (ProtoSocket::RECV == theEvent) + { + UINT32 buffer[1024/4]; + ProtoAddress senderAddr; + for(;;) + { + unsigned int numBytes = 1024; + if (!theSocket.RecvFrom((char*)buffer, numBytes, senderAddr)) + { + NorpSession* theSession = (NorpSession*)(theSocket.GetUserData()); + if ((NULL != theSession) && (NorpSession::SOCKS_PUT_REQUEST == theSession->GetSocksState())) + { + // Assume it is an ICMP "port unreachable" error in response to NORP request + PLOG(PL_INFO, "Norp::OnNorpSocketEvent() remote NORP server port unreachable?\n"); + if (!theSession->MakeDirectConnect()) + { + PLOG(PL_ERROR, "Norp::OnNorpSocketEvent(RECV) error: unable to make direct connection\n"); + theSession->Shutdown(); + } + break; + } + } + if (0 == numBytes) break; // nothing left to read + // TBD - make sure received command is at least 20 bytes (NORP header) + NorpMsg msg(buffer, numBytes); + switch (msg.GetType()) + { + case NorpMsg::SOCKS_REQ: + { + PLOG(PL_INFO, "Norp::OnNorpSocketEvent(RECV) received SOCKS_REQ session %d:%hu\n", msg.GetNodeId(), msg.GetSessionId()); + OnRemoteRequest(msg, senderAddr); + break; + } + case NorpMsg::REQ_ACK: + { + PLOG(PL_INFO, "Norp::OnNorpSocketEvent(RECV) received REQ_ACK session %d:%hu\n", msg.GetNodeId(), msg.GetSessionId()); + NorpSession* session = session_list.FindSession(msg.GetSessionId()); + // TBD - confirm the session found is for the given node id, etc + if (NULL != session) + { + if (!session->OnRemoteRequestAcknowledgment(msg, senderAddr)) + session->Shutdown(); + } + else + { + PLOG(PL_ERROR, "Norp::OnNorpSocketEvent(RECV) warning: received remote REQ_ACK for unknown session\n"); + } + break; + } + case NorpMsg::SOCKS_REP: + { + PLOG(PL_INFO, "Norp::OnNorpSocketEvent(RECV) received SOCKS_REP session %d:%hu (length:%d)\n", msg.GetNodeId(), msg.GetSessionId(), msg.GetLength()); + NorpSession* session = session_list.FindSession(msg.GetSessionId()); + if (NULL != session) + { + // TBD - confirm the session found is for the given node id, etc + if (!session->OnRemoteReply(msg, senderAddr)) + session->Shutdown(); + } + else + { + PLOG(PL_ERROR, "Norp::OnNorpSocketEvent(RECV) warning: received remote SOCKS_REPLY for unknown session\n"); + } + break; + } + case NorpMsg::REP_ACK: + { + PLOG(PL_INFO, "Norp::OnNorpSocketEvent(RECV) received REP_ACK session %d:%hu\n", msg.GetNodeId(), msg.GetSessionId()); + NorpSession* session = session_list.FindSession(msg.GetSessionId(), msg.GetNodeId()); + if (NULL != session) + { + if (!session->OnRemoteReplyAcknowledgment(msg)) + session->Shutdown(); + } + else + { + PLOG(PL_ERROR, "Norp::OnNorpSocketEvent(RECV) warning: received remote REP_ACK for unknown session\n"); + } + break; + } + case NorpMsg::ORIG_END: + { + + PLOG(PL_INFO, "Norp::OnNorpSocketEvent(RECV) received ORIG_END session %d:%hu\n", msg.GetNodeId(), msg.GetSessionId()); + NorpSession* session = session_list.FindSession(msg.GetSessionId(), msg.GetNodeId()); + if (NULL != session) + { + // TBD - mroe gently end things (e.g. let socket shutdowns commence)??? + session->Close(); // the session will make an upcall and get itself removed/deleted + } + UINT32 buffer[20/sizeof(UINT32)]; + NorpMsg ack(buffer, 20, false); + ack.SetType(NorpMsg::ACK_END); + ack.SetSessionId(msg.GetSessionId()); + ack.SetNodeId(NORM_NODE_NONE); + if (!SendMessage(ack, senderAddr)) + PLOG(PL_ERROR, "Norp::OnNorpSocketEvent(RECV) warning: unable to send ACK_END message to originator\n"); + break; + } + case NorpMsg::CORR_END: + { + PLOG(PL_INFO, "Norp::OnNorpSocketEvent(RECV) received CORR_END session %d:%hu\n", msg.GetNodeId(), msg.GetSessionId()); + NorpSession* session = session_list.FindSession(msg.GetSessionId(), NORM_NODE_NONE); + if (NULL != session) + { + // TBD - mroe gently end things (e.g. let socket shutdowns commence)??? + session->Close(); // the session will make an upcall and get itself removed/deleted + } + UINT32 buffer[20/sizeof(UINT32)]; + NorpMsg ack(buffer, 20, false); + ack.SetType(NorpMsg::ACK_END); + ack.SetSessionId(msg.GetSessionId()); + ack.SetNodeId(GetNormNodeId()); + if (!SendMessage(ack, senderAddr)) + PLOG(PL_ERROR, "Norp::OnNorpSocketEvent(RECV) warning: unable to send ACK_END message to correspondent\n"); + break; + } + case NorpMsg::ACK_END: + { + PLOG(PL_INFO, "Norp::OnNorpSocketEvent(RECV) received ACK_END session %d:%hu\n", msg.GetNodeId(), msg.GetSessionId()); + NorpSession* session = session_list.FindSession(msg.GetSessionId(), msg.GetNodeId()); + if (NULL != session) session->Close(); // the session will make an upcall and get itself removed/deleted + break; + } + default: + { + PLOG(PL_ERROR, "Norp::OnNormEvent(NORM_RX_CMD_NEW) error: received invalid NORP command: %d\n", msg.GetType()); + break; + } + } // end switch(msg.GetType()) + numBytes = 1024; + } // end while(theSocket.RecvFrom()) + } // end if (ProtoSocket::RECV == theEvent) + else + { + PLOG(PL_DETAIL, "unhandled NORP socket event!\n"); + } +} // end Norp::OnNorpSocketEvent() + +void Norp::OnRemoteRequest(const NorpMsg& msg, const ProtoAddress& senderAddr) +{ + NorpSession* session = session_list.FindSession(msg.GetSessionId(), msg.GetNodeId()); + if (NULL == session) + { + PLOG(PL_INFO, "norp: new remote session id %hu:%08x\n", msg.GetSessionId(), msg.GetNodeId()); + // It's a new remote session + session = new NorpSession(*this, msg.GetSessionId(), msg.GetNodeId()); + if (NULL == session) + { + PLOG(PL_ERROR, "Norp::OnRemoteRequest() new NorpSession error: %s\n", GetErrorString()); + // TBD - should we somehow signal the remote of our failure? + // (just let it time out for now) + return; + } + AddSession(*session); + } + if (!session->OnRemoteRequest(msg, senderAddr)) + { + PLOG(PL_ERROR, "Norp::OnRemoteRequest() error: unable to initiate new remote NorpSession\n"); + // TBD - signal failure to remote + RemoveSession(*session); + delete session; + } +} // end Norp::OnRemoteRequest() + + +void Norp::NormEventCallback(ProtoDispatcher::Descriptor descriptor, + ProtoDispatcher::Event theEvent, + const void* userData) +{ + if (ProtoDispatcher::EVENT_INPUT == theEvent) + { + Norp* norp = (Norp*)(userData); + norp->OnNormEvent(); + } + else + { + ASSERT(0); // should never happen + } +} // end Norp::NormEventCallback() + +void Norp::OnNormEvent() +{ + NormEvent theEvent; + // We make a looping, non-blocking call to NormGetNextEvent() here + // to consume all available NORM notification events + while (NormGetNextEvent(norm_instance, &theEvent, false)) + { + PLOG(PL_DETAIL, "Norp::OnNormEvent("); + // See if this event is for a "child" NorpSession + if (NORM_SESSION_INVALID != theEvent.session) + { + NorpSession* norpSession = (NorpSession*)NormGetUserData(theEvent.session); + if (NULL != norpSession) + { + PLOG(PL_DETAIL, "session %hu event)\n", norpSession->GetSessionId()); + norpSession->OnNormEvent(theEvent); + continue; + } + } + else + { + //ASSERT(0); // should never happen + } + } +} // end Norp::OnNormEvent() + + + diff --git a/norp/src/common/norpApp.cpp b/norp/src/common/norpApp.cpp new file mode 100644 index 0000000..df6cb89 --- /dev/null +++ b/norp/src/common/norpApp.cpp @@ -0,0 +1,378 @@ +#include "protoApp.h" +#include "protoNet.h" +#include "norp.h" +#include "norpVersion.h" + +#include // for stderr +#include // for atoi() + +class NorpApp : public ProtoApp +{ + public: + NorpApp(); + ~NorpApp(); + + // Required verrides from ProtoApp base + bool OnStartup(int argc, const char*const* argv); + bool ProcessCommands(int argc, const char*const* argv); + void OnShutdown(); + + private: + enum CmdType {CMD_INVALID, CMD_ARG, CMD_NOARG}; + static CmdType GetCmdType(const char* string); + bool OnCommand(const char* cmd, const char* val); + void Usage(); + + static const char* const CMD_LIST[]; + + Norp norp; + bool norm_enable; + +}; // end class NorpApp + +// Our application instance +PROTO_INSTANTIATE_APP(NorpApp) + + +NorpApp::NorpApp() + : norp(dispatcher), norm_enable(true) +{ +} + +NorpApp::~NorpApp() +{ + norp.StopServer(); +} + +void NorpApp::Usage() +{ + fprintf(stderr, "Usage: norp [interface ][address ][sport ][port ]\n" + " [norm {on|off}][id ][nport ][cce | ccl | rate ][persist ][limit ]\n" + " [correspondent ][forward ,/[,]][version]\n" + " [debug ][trace][dlog ][lport ][rport ]\n"); +} + +const char* const NorpApp::CMD_LIST[] = +{ + "+interface", // "public facing" interface (its address is used for bindings) + "+address", // explicit "public facing" address to use for bindings + "+sport", // SOCKS server port (7000 by default) + "+port", // NORP UDP signaling port number (7001 by default) + "+norm", // "on" or "off" to enable/disable NORM proxying ("on" by default) + "+id", // set local NormNodeId (auto set from IP address by default) + "+nport", // NORM port (UDP) (7002 by default) + "-cce", // Use NORM-CCE instead of NORM-CC + "-ccl", // Use NORM-CCL instead of NORM-CC + "+rate", // set fixed transmit rate (no congestion control) + "+limit", // set _cumulaltive_ NORP transmit rate limit + "+persist", // how long to persist NORM data delivery to receiver after TCP socket closure + "+debug", // set debug level + "-trace", // enables NORM protocol packet send/recv trace in debug output + "+dlog", // specify a file for debug logging + "+lport", // "local" NORP port (for loopback debugging) + "+rport", // "remote" NORP port (for loopback debugging) + "+forward", // ,/[,] to set up a preset proxy TCP listener + "+correspondent", // to route proxied connections through remote NORP proxy at given addr + "-version", // print version number and exit + NULL +}; + +NorpApp::CmdType NorpApp::GetCmdType(const char* cmd) +{ + if (!cmd) return CMD_INVALID; + unsigned int len = strlen(cmd); + bool matched = false; + CmdType type = CMD_INVALID; + const char* const* nextCmd = CMD_LIST; + while (*nextCmd) + { + if (!strncmp(cmd, *nextCmd+1, len)) + { + if (matched) + { + // ambiguous command (command should match only once) + return CMD_INVALID; + } + else + { + matched = true; + if ('+' == *nextCmd[0]) + type = CMD_ARG; + else + type = CMD_NOARG; + } + } + nextCmd++; + } + return type; +} // end NorpApp::GetCmdType() + +bool NorpApp::OnStartup(int argc, const char*const* argv) +{ + if (!ProcessCommands(argc, argv)) + { + PLOG(PL_ERROR, "NorpApp::OnStartup() error: bad command line\n"); + Usage(); + return false; + } + // If a proxy address wasn't set, use "default" local address for host + if (!norp.GetProxyAddress().IsValid()) + { + ProtoAddress theAddr; + if (theAddr.ResolveLocalAddress()) + norp.SetProxyAddress(theAddr); + else + PLOG(PL_WARN, "NorpApp::OnStartup() warning: proxy address undetermined!\n"); + } + PLOG(PL_INFO, "norp: starting proxy with address %s\n", norp.GetProxyAddress().GetHostString()); + + if (!norp.StartServer(norm_enable)) + { + PLOG(PL_ERROR, "NorpApp::OnStartup() error: norp server startup failure!\n"); + return false; + } + return true; +} // end NorpApp::OnStartup() + +void NorpApp::OnShutdown() +{ + norp.StopServer(); + CloseDebugLog(); +} // end Norp::OnShutdown() + +bool NorpApp::ProcessCommands(int argc, const char*const* argv) +{ + // Dispatch command-line commands to our OnCommand() method + int i = 1; + while ( i < argc) + { + // Is it a class NorpApp command? + switch (GetCmdType(argv[i])) + { + case CMD_INVALID: + { + PLOG(PL_ERROR, "NorpApp::ProcessCommands() Invalid command:%s\n", argv[i]); + return false; + } + case CMD_NOARG: + if (!OnCommand(argv[i], NULL)) + { + PLOG(PL_ERROR, "NorpApp::ProcessCommands() ProcessCommand(%s) error\n", argv[i]); + return false; + } + i++; + break; + case CMD_ARG: + if (!OnCommand(argv[i], argv[i+1])) + { + PLOG(PL_ERROR, "NorpApp::ProcessCommands() ProcessCommand(%s, %s) error\n", argv[i], argv[i+1]); + return false; + } + i += 2; + break; + } + } + return true; +} // end NorpApp::ProcessCommands() + +bool NorpApp::OnCommand(const char* cmd, const char* val) +{ + // (TBD) move command processing into Mgen class ??? + CmdType type = GetCmdType(cmd); + ASSERT(CMD_INVALID != type); + unsigned int len = strlen(cmd); + if ((CMD_ARG == type) && !val) + { + PLOG(PL_ERROR, "NorpApp::ProcessCommand(%s) missing argument\n", cmd); + return false; + } + else if (!strncmp("version", cmd, len)) + { + fprintf(stdout, "norp version %s\n", NORP_VERSION); + exit(0); + } + else if (!strncmp("debug", cmd, len)) + { + SetDebugLevel(atoi(val)); + } + else if (!strncmp("trace", cmd, len)) + { + norp.SetNormTrace(true); + } + else if (!strncmp("dlog", cmd, len)) + { + OpenDebugLog(val); + } + else if (!strncmp("interface", cmd, len)) + { + ProtoAddress ifaceAddr; + if (!ProtoNet::GetInterfaceAddress(val, ProtoAddress::IPv4, ifaceAddr)) + { + PLOG(PL_ERROR, "NorpApp::OnCommand(interface) error: unable to get address for interface \"%s\"\n", val); + return false; + } + norp.SetProxyAddress(ifaceAddr); + } + else if (!strncmp("address", cmd, len)) + { + ProtoAddress theAddr; + if (!theAddr.ResolveFromString(val)) + { + PLOG(PL_ERROR, "NorpApp::OnCommand(interface) error: invalid address \"%s\"\n", val); + return false; + } + norp.SetProxyAddress(theAddr); + } + else if (!strncmp("sport", cmd, len)) + { + norp.SetSocksPort(atoi(val)); + } + else if (!strncmp("port", cmd, len)) + { + UINT16 norpPort = atoi(val); + norp.SetLocalNorpPort(norpPort); + norp.SetRemoteNorpPort(norpPort); + } + else if (!strncmp("norm", cmd, len)) + { + if (0 == strcmp("on", val)) + norm_enable = true; + else + norm_enable = false; + } + else if (!strncmp("id", cmd, len)) + { + norp.SetNormNodeId(atoi(val)); + } + else if (!strncmp("nport", cmd, len)) + { + norp.SetNormPort(atoi(val)); + } + else if (!strncmp("cce", cmd, len)) + { + norp.SetNormCC(Norp::NORM_CCE); + } + else if (!strncmp("ccl", cmd, len)) + { + norp.SetNormCC(Norp::NORM_CCL); + } + else if (!strncmp("rate", cmd, len)) + { + double txRate; + if (1 != sscanf(val, "%lf", &txRate)) + { + PLOG(PL_ERROR, "NorpApp::OnCommand(rate) error: invalid rate \"%s\" bps\n", val); + return false; + } + norp.SetNormCC(Norp::NORM_FIXED); + norp.SetNormTxRate(txRate); + } + else if (!strncmp("limit", cmd, len)) + { + double txLimit; + if (1 != sscanf(val, "%lf", &txLimit)) + { + PLOG(PL_ERROR, "NorpApp::OnCommand(limit) error: invalid limit rate \"%s\" bps\n", val); + return false; + } + norp.SetNormTxLimit(txLimit); + } + else if (!strncmp("lport", cmd, len)) + { + norp.SetLocalNorpPort(atoi(val)); + } + else if (!strncmp("rport", cmd, len)) + { + norp.SetRemoteNorpPort(atoi(val)); + } + else if (!strncmp("forward", cmd, len)) + { + // ,/[,] + char* text = new char[strlen(val) + 1]; // copy so we can slice and dice + if (NULL == text) + { + PLOG(PL_ERROR, "NorpApp::OnCommand(%s) new char[] error: %s\n", cmd, GetErrorString()); + return false; + } + strcpy(text, val); + char* dstAddrPtr = strchr(text, ','); + if (NULL == dstAddrPtr) + { + PLOG(PL_ERROR, "NorpApp::OnCommand(%s, %s) error: missing !\n", cmd, val); + delete[] text; + return false; + } + *dstAddrPtr++ = '\0'; + char* dstPortPtr = strchr(dstAddrPtr, '/'); + if (NULL == dstPortPtr) + { + PLOG(PL_ERROR, "NorpApp::OnCommand(%s, %s) error: missing !\n", cmd, val); + delete[] text; + return false; + } + UINT16 tcpPort; + int result = sscanf(text, "%hu", &tcpPort); + if ((1 != result) || (0 == tcpPort)) + { + PLOG(PL_ERROR, "NorpApp::OnCommand(%s, %s) error: invalid \"%s\"!\n", cmd, val, text); + delete[] text; + return false; + } + *dstPortPtr++ = '\0'; + char* norpAddrPtr = strchr(dstPortPtr, ','); + if (NULL != norpAddrPtr) *norpAddrPtr++ = '\0'; + ProtoAddress dstAddr; + if (!dstAddr.ResolveFromString(dstAddrPtr)) + { + PLOG(PL_ERROR, "NorpApp::OnCommand(%s, %s) error: invalid \"%s\"!\n", cmd, val, dstAddrPtr); + delete[] text; + return false; + } + UINT16 dstPort; + result = sscanf(dstPortPtr, "%hu", &dstPort); + if ((1 != result) || (0 == dstPort)) + { + PLOG(PL_ERROR, "NorpApp::OnCommand(%s, %s) error: invalid \"%s\"!\n", cmd, val, text); + delete[] text; + return false; + } + dstAddr.SetPort(dstPort); + ProtoAddress norpAddr; + if (NULL != norpAddrPtr) + { + if (!norpAddr.ResolveFromString(norpAddrPtr)) + { + PLOG(PL_ERROR, "NorpApp::OnCommand(%s, %s) error: invalid \"%s\"!\n", cmd, val, norpAddrPtr); + delete[] text; + return false; + } + } + if (!norp.AddPreset(tcpPort, dstAddr, norpAddr)) + { + PLOG(PL_ERROR, "NorpApp::OnCommand(%s, %s) error: unable to add preset!\n", cmd, val); + delete[] text; + return false; + } + delete[] text; + } + else if (!strncmp("correspondent", cmd, len)) + { + // + ProtoAddress norpAddr; + if (!norpAddr.ResolveFromString(val)) + { + PLOG(PL_ERROR, "NorpApp::OnCommand(%s, %s) error: invalid !\n", cmd, val); + return false; + } + norp.SetRemoteNorpAddress(norpAddr); + } + else + { + PLOG(PL_ERROR, "NorpApp::OnCommand(%s) error: unimplemented command!\n", cmd); + return false; + } + return true; +} // end NorpApp::OnCommand() + + + diff --git a/norp/src/common/protoPktSOCKS.cpp b/norp/src/common/protoPktSOCKS.cpp new file mode 100644 index 0000000..b9fee4c --- /dev/null +++ b/norp/src/common/protoPktSOCKS.cpp @@ -0,0 +1,388 @@ +#include "protoPktSOCKS.h" +#include "protoDebug.h" + +ProtoPktSOCKS::AuthRequest::AuthRequest(UINT32* bufferPtr, unsigned int numBytes, bool initFromBuffer, bool freeOnDestruct) + : ProtoPkt(bufferPtr, numBytes, freeOnDestruct) +{ + if (initFromBuffer) InitFromBuffer(numBytes); +} + +ProtoPktSOCKS::AuthRequest::~AuthRequest() +{ +} + +ProtoPktSOCKS::AuthReply::AuthReply(UINT32* bufferPtr, unsigned int numBytes, bool initFromBuffer, bool freeOnDestruct) + : ProtoPkt(bufferPtr, numBytes, freeOnDestruct) +{ + if (initFromBuffer) InitFromBuffer(numBytes); +} + +ProtoPktSOCKS::AuthReply::~AuthReply() +{ +} + +ProtoPktSOCKS::Request::Request(UINT32* bufferPtr, unsigned int numBytes, bool initFromBuffer, bool freeOnDestruct) + : ProtoPkt(bufferPtr, numBytes, freeOnDestruct) +{ + if (initFromBuffer) InitFromBuffer(numBytes); +} + +ProtoPktSOCKS::Request::~Request() +{ +} + +UINT8 ProtoPktSOCKS::Request::GetAddressLength() const +{ + switch (GetAddressType()) + { + case IPv4: + return 4; + case NAME: + // First byte of "name" is length value + return ((UINT8*)buffer_ptr)[OFFSET_ADDR]; + case IPv6: + return 16; + default: + return 0; + } +} // end ProtoPktSOCKS::Request::GetAddressLength() + +UINT16 ProtoPktSOCKS::Request::GetPort() const +{ + const char* portPtr = GetAddressPtr() + GetAddressLength(); + UINT16 temp16; + memcpy(&temp16, portPtr, 2); + return ntohs(temp16); +} // end ProtoPktSOCKS::Request::GetPort() + + +bool ProtoPktSOCKS::Request::GetAddress(ProtoAddress& theAddr) const +{ + switch (GetAddressType()) + { + case IPv4: + { + theAddr.SetRawHostAddress(ProtoAddress::IPv4, GetAddressPtr(), 4); + break; + } + case NAME: + { + char name[256]; + UINT8 addrLen = GetAddressLength(); + strncpy(name, GetAddressPtr(), addrLen); + name[addrLen] = '\0'; + if (!theAddr.ResolveFromString(name)) + { + PLOG(PL_ERROR, "ProtoPktSOCKS::Request::GetAddress() error: unable to resolve name \"%s\" to addr!\n", name); + return false; + } break; + } + case IPv6: + { + theAddr.SetRawHostAddress(ProtoAddress::IPv6, GetAddressPtr(), 16); + break; + } + default: + { + PLOG(PL_ERROR, "ProtoPktSOCKS::Request::GetAddress() error: invalid address type!\n"); + return false; + } + } + theAddr.SetPort(GetPort()); + return true; +} // end ProtoPktSOCKS::Request::GetAddress() + +bool ProtoPktSOCKS::Request::SetAddress(const ProtoAddress& theAddr) +{ + AddrType addrType; + UINT8 addrLength; + switch (theAddr.GetType()) + { + case ProtoAddress::IPv4: + addrType = IPv4; + addrLength = 4; + break; + case ProtoAddress::IPv6: + addrType = IPv6; + addrLength = 16; + break; + default: + PLOG(PL_ERROR, "ProtoPktSOCKS::Request::SetAddress() error: invalid address type!\n"); + return false; + } + SetAddressType(addrType); + memcpy(AccessAddressPtr(), theAddr.GetRawHostAddress(), addrLength); + pkt_length = OFFSET_ADDR + addrLength; + + SetPort(theAddr.GetPort()); + + return true; +} // end ProtoPktSOCKS::Request::SetAddress() + +bool ProtoPktSOCKS::Request::SetPort(UINT16 thePort) +{ + char* portPtr = AccessAddressPtr() + GetAddressLength(); + UINT16 temp16 = htons(thePort); + memcpy(portPtr, (char*)&temp16, 2); + pkt_length = OFFSET_ADDR + GetAddressLength() + 2; + return true; +} // end ProtoPktSOCKS::Request::SetPort() + + +bool ProtoPktSOCKS::Request::SetName(const char* theName) +{ + SetAddressType(NAME); + unsigned int nameLength = strlen(theName); + // TBD - validate there is sufficient buffer space + memcpy(AccessAddressPtr(), theName, nameLength); + pkt_length += nameLength; + return true; +} // end ProtoPktSOCKS::Request::SetAddress() + +ProtoPktSOCKS::Reply::Reply(UINT32* bufferPtr, unsigned int numBytes, bool initFromBuffer, bool freeOnDestruct) + : ProtoPkt(bufferPtr, numBytes, freeOnDestruct) +{ + if (initFromBuffer) InitFromBuffer(numBytes); +} + +ProtoPktSOCKS::Reply::~Reply() +{ +} + +UINT8 ProtoPktSOCKS::Reply::GetAddressLength() const +{ + switch (GetAddressType()) + { + case IPv4: + return 4; + case NAME: + // First byte of "name" is length value + return ((UINT8*)buffer_ptr)[OFFSET_ADDR]; + case IPv6: + return 16; + default: + return 0; + } +} // end ProtoPktSOCKS::Reply::GetAddressLength() + +UINT16 ProtoPktSOCKS::Reply::GetPort() const +{ + const char* portPtr = GetAddressPtr() + GetAddressLength(); + UINT16 temp16; + memcpy(&temp16, portPtr, 2); + return ntohs(temp16); +} // end ProtoPktSOCKS::Reply::GetPort() + +bool ProtoPktSOCKS::Reply::GetAddress(ProtoAddress& theAddr) const +{ + switch (GetAddressType()) + { + case IPv4: + { + theAddr.SetRawHostAddress(ProtoAddress::IPv4, GetAddressPtr(), 4); + break; + } + case NAME: + { + char name[256]; + UINT8 addrLen = GetAddressLength(); + strncpy(name, GetAddressPtr(), addrLen); + name[addrLen] = '\0'; + if (!theAddr.ResolveFromString(name)) + { + PLOG(PL_ERROR, "ProtoPktSOCKS::Reply::GetAddress() error: unable to resolve name \"%s\" to addr!\n", name); + return false; + } break; + } + case IPv6: + { + theAddr.SetRawHostAddress(ProtoAddress::IPv6, GetAddressPtr(), 16); + break; + } + default: + { + PLOG(PL_ERROR, "ProtoPktSOCKS::Reply::GetAddress() error: invalid address type!\n"); + return false; + } + } + theAddr.SetPort(GetPort()); + return true; +} // end ProtoPktSOCKS::Reply::GetAddress() + +void ProtoPktSOCKS::Reply::SetAddress(AddrType addrType, const char* addrPtr, UINT8 addrLen) +{ + switch (addrType) + { + case ProtoPktSOCKS::IPv4: + ((UINT8*)buffer_ptr)[OFFSET_ATYPE] = (UINT8)ProtoPktSOCKS::IPv4; + memcpy(((char*)buffer_ptr) + OFFSET_ADDR, addrPtr, 4); + pkt_length = OFFSET_ADDR + 4; + break; + case ProtoPktSOCKS::NAME: + ((UINT8*)buffer_ptr)[OFFSET_ATYPE] = (UINT8)ProtoPktSOCKS::NAME; + ((UINT8*)buffer_ptr)[OFFSET_ADDR] = (UINT8)addrLen; + memcpy(((char*)buffer_ptr) + OFFSET_ADDR + 1, addrPtr, addrLen); + pkt_length = OFFSET_ADDR + 1 + addrLen; + break; + case ProtoPktSOCKS::IPv6: + ((UINT8*)buffer_ptr)[OFFSET_ATYPE] = (UINT8)ProtoPktSOCKS::IPv6; + memcpy(((char*)buffer_ptr) + OFFSET_ADDR, addrPtr, 16); + pkt_length = OFFSET_ADDR + 16; + break; + default: + PLOG(PL_ERROR, "ProtoPktSOCKS::Reply::SetAddress() invalid SOCKS address type\n"); + break; + } +} // end ProtoPktSOCKS::Reply::SetAddress() + +void ProtoPktSOCKS::Reply::SetPort(UINT16 thePort) +{ + UINT16 temp16 = htons(thePort); + char* portPtr = AccessAddressPtr() + GetAddressLength(); + memcpy(portPtr, &temp16, 2); + pkt_length = (GetAddressPtr() + GetAddressLength() + 2 - (char*)buffer_ptr); +} // end ProtoPktSOCKS::Reply::SetPort() + +void ProtoPktSOCKS::Reply::SetAddress(const ProtoAddress& theAddr) +{ + switch (theAddr.GetType()) + { + case ProtoAddress::IPv4: + SetAddress(ProtoPktSOCKS::IPv4, theAddr.GetRawHostAddress(), 4); + SetPort(theAddr.GetPort()); + break; + case ProtoAddress::IPv6: + SetAddress(ProtoPktSOCKS::IPv6, theAddr.GetRawHostAddress(), 16); + SetPort(theAddr.GetPort()); + break; + default: + PLOG(PL_ERROR, "ProtoPktSOCKS::Reply::SetAddress() invalid ProtoAddress type\n"); + break; + } +} // end ProtoPktSOCKS::Reply::SetAddress() + +ProtoPktSOCKS::UdpRequest::UdpRequest(UINT32* bufferPtr, unsigned int numBytes, bool initFromBuffer, bool freeOnDestruct) + : ProtoPkt(bufferPtr, numBytes, freeOnDestruct) +{ + if (initFromBuffer) InitFromBuffer(numBytes); +} + +ProtoPktSOCKS::UdpRequest::~UdpRequest() +{ +} + +UINT8 ProtoPktSOCKS::UdpRequest::GetAddressLength() const +{ + switch (GetAddressType()) + { + case IPv4: + return 4; + case NAME: + // First byte of "name" is length value + return ((UINT8*)buffer_ptr)[OFFSET_ADDR]; + case IPv6: + return 16; + default: + return 0; + } +} // end ProtoPktSOCKS::UdpRequest::GetAddressLength() + +UINT16 ProtoPktSOCKS::UdpRequest::GetPort() const +{ + const char* portPtr = GetAddressPtr() + GetAddressLength(); + UINT16 temp16; + memcpy(&temp16, portPtr, 2); + return ntohs(temp16); +} // end ProtoPktSOCKS::UdpRequest::GetPort() + + +bool ProtoPktSOCKS::UdpRequest::GetAddress(ProtoAddress& theAddr) const +{ + switch (GetAddressType()) + { + case IPv4: + { + theAddr.SetRawHostAddress(ProtoAddress::IPv4, GetAddressPtr(), 4); + break; + } + case NAME: + { + char name[256]; + UINT8 addrLen = GetAddressLength(); + strncpy(name, GetAddressPtr(), addrLen); + name[addrLen] = '\0'; + if (!theAddr.ResolveFromString(name)) + { + PLOG(PL_ERROR, "ProtoPktSOCKS::UdpRequest::GetAddress() error: unable to resolve name \"%s\" to addr!\n", name); + return false; + } + break; + } + case IPv6: + { + theAddr.SetRawHostAddress(ProtoAddress::IPv6, GetAddressPtr(), 16); + break; + } + default: + { + PLOG(PL_ERROR, "ProtoPktSOCKS::UdpRequest::GetAddress() error: invalid address type!\n"); + return false; + } + } + theAddr.SetPort(GetPort()); + return true; +} // end ProtoPktSOCKS::UdpRequest::GetAddress() + + +void ProtoPktSOCKS::UdpRequest::SetAddress(AddrType addrType, const char* addrPtr, UINT8 addrLen) +{ + switch (addrType) + { + case ProtoPktSOCKS::IPv4: + ((UINT8*)buffer_ptr)[OFFSET_ATYPE] = (UINT8)ProtoPktSOCKS::IPv4; + memcpy(((char*)buffer_ptr) + OFFSET_ADDR, addrPtr, 4); + pkt_length = OFFSET_ADDR + 4; + break; + case ProtoPktSOCKS::NAME: + ((UINT8*)buffer_ptr)[OFFSET_ATYPE] = (UINT8)ProtoPktSOCKS::NAME; + ((UINT8*)buffer_ptr)[OFFSET_ADDR] = (UINT8)addrLen; + memcpy(((char*)buffer_ptr) + OFFSET_ADDR + 1, addrPtr, addrLen); + pkt_length = OFFSET_ADDR + 1 + addrLen; + break; + case ProtoPktSOCKS::IPv6: + ((UINT8*)buffer_ptr)[OFFSET_ATYPE] = (UINT8)ProtoPktSOCKS::IPv6; + memcpy(((char*)buffer_ptr) + OFFSET_ADDR, addrPtr, 16); + pkt_length = OFFSET_ADDR + 16; + break; + default: + PLOG(PL_ERROR, "ProtoPktSOCKS::UdpRequest::SetAddress() invalid SOCKS address type\n"); + break; + } +} // end ProtoPktSOCKS::UdpRequest::SetAddress() + +void ProtoPktSOCKS::UdpRequest::SetPort(UINT16 thePort) +{ + UINT16 temp16 = htons(thePort); + char* portPtr = AccessAddressPtr() + GetAddressLength(); + memcpy(portPtr, &temp16, 2); + pkt_length = (GetAddressPtr() + GetAddressLength() + 2 - (char*)buffer_ptr); +} // end ProtoPktSOCKS::UdpRequest::SetPort() + +void ProtoPktSOCKS::UdpRequest::SetAddress(const ProtoAddress& theAddr) +{ + switch (theAddr.GetType()) + { + case ProtoAddress::IPv4: + SetAddress(ProtoPktSOCKS::IPv4, theAddr.GetRawHostAddress(), 4); + SetPort(theAddr.GetPort()); + break; + case ProtoAddress::IPv6: + SetAddress(ProtoPktSOCKS::IPv6, theAddr.GetRawHostAddress(), 16); + SetPort(theAddr.GetPort()); + break; + default: + PLOG(PL_ERROR, "ProtoPktSOCKS::UdpRequest::SetAddress() invalid ProtoAddress type\n"); + break; + } +} // end ProtoPktSOCKS::UdpRequest::SetAddress() +