///
///
import UdpLib = freedom_UdpSocket;
import logging = require('../../../third_party/uproxy-lib/logging/logging');
var log :logging.Log = new logging.Log('udprelay');
/**
* A UDP-based "relay" server intended for use as part of a SOCKS5 proxy:
* http://www.ietf.org/rfc/rfc1928.txt
*
* Briefly, here's how to use this:
* - create an instance of this class
* - call bind (you probably want to specify port zero to have the system
* pick a free port)
* - call getInfo, to discover on which port the relay is listening
* - (the caller can now return the address and port back to the SOCKS
* client)
* - call setDataReceivedHandler
* - this class will invoke the setDataReceivedHandler each time a datagram
* is received on the socket; the full message is sent, including SOCKS
* UDP headers
* - (the caller can relay the message across the datachannel)
* - call sendRemoteReply for each datagram received from remote hosts; this
* will be sent to the client, and should include the same SOCKS UDP
* header received in the original request
* - call destroy to clean up, typically when the TCP connection on which
* the UDP_ASSOCIATE was negotiated is terminated
*
* One relay should be created in response to each UDP_ASSOCIATE command.
*
* Other notes:
* - while the RFC states that the relay MUST drop any message originating
* from an IP other than that which requested the association, this
* implementation makes no effort to do so (this isn't urgent because we
* typically only listen locally)
* - similarly, we make no effort to respect the DST.PORT and DST.ADDR fields
* specified by the client during the handshake: having run various proxy
* clients it seems that these are rarely specified anyway (which is fine
* according to section 6 of the RFC)
* and, in any case, we are typically only listening locally
* - we make no attempt to implement fragmentation (see section 7 of the
* RFC)
*
* TODO: this is so similar to udprelay.ts that they can almost certainly
* be merged into one
*/
class UdpRelay {
// The Socks client sends datagrams to this socket.
// Eventually, it will also receive replies on this socket.
private socket_:UdpLib.Socket;
// Address and port to which the "client-side" socket is bound.
private address_:string;
private port_:number;
// Address and port from which the client is sending us packets.
// We store this so that we can relay responses from the server
// back to the client.
private clientAddress_:string;
private clientPort_:number;
/**
* Function to be called when data is received.
*/
private dataReceivedHandler:(data:ArrayBuffer) => void;
constructor () {
this.socket_ = freedom['core.udpsocket']();
}
/**
* Returns a promise to create a socket, bind to the specified address and
* port, and start relaying events. Specify port zero to have the system
* choose a free port.
*/
public bind(address:string, port:number) {
return this.socket_.bind(address, port)
.then((resultCode:number) => {
// Ensure the listen was successful.
if (resultCode != 0) {
return Promise.reject(new Error('listen failed on ' +
this.address_ + ':' + this.port_ +
' with result code ' + resultCode));
}
return Promise.resolve(resultCode);
})
.then(this.socket_.getInfo)
.then((socketInfo:UdpLib.SocketInfo) => {
// Record the address and port on which our socket is listening.
this.address_ = socketInfo.localAddress;
this.port_ = socketInfo.localPort;
log.info('listening on ' + this.address_ + ':' + this.port_);
})
.then(this.attachSocketHandler_);
}
/**
* Listens for onData events.
* The socket must be bound.
*/
private attachSocketHandler_ = () => {
this.socket_.on('onData', this.onSocksClientData_);
}
private onSocksClientData_ = (recvFromInfo:UdpLib.RecvFromInfo) => {
// Record the host:port from which the client is sending us datagrams.
// This is where we'll relay any replies from remote servers.
// TODO: check if these change over the liftime of the relay
this.clientAddress_ = recvFromInfo.address;
this.clientPort_ = recvFromInfo.port;
if (this.dataReceivedHandler) {
this.dataReceivedHandler(recvFromInfo.data);
}
}
/**
* Sets the function to be called when data is received from the client.
* This is intended for relaying datagrams from the client across the
* datachannel from a remote server. The full datagram as received from the
* client is sent, complete with SOCKS headers.
*/
public setDataReceivedHandler(callback:(buffer:ArrayBuffer) => void) {
this.dataReceivedHandler = callback;
}
/**
* Returns a promise to send data to the client.
* This is intended for relaying responses from remote servers back to
* the client.
*/
public sendRemoteReply(buffer:ArrayBuffer) : Promise {
if (!this.clientAddress_) {
throw new Error('cannot send data to client before it sends data');
}
return this.socket_.sendTo(buffer, this.clientAddress_, this.clientPort_);
}
// TODO(yangoon): add destroy() method
/**
* Returns the address on which the local socket associated with this
* relay is listening.
*/
public getAddress = () => {
return this.address_;
}
/**
* Returns the port on which the local socket associated with this
* relay is listening.
*/
public getPort = () => {
return this.port_;
}
}
export = UdpRelay;