/*
* GridProxy.cs: implementation of OpenMetaverse proxy library
*
* Copyright (c) 2006 Austin Jennings
* Pregen modifications made by Andrew Ortman on Dec 10, 2006 -> Dec 20, 2006
*
*
* All rights reserved.
*
* - Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Neither the name of the openmetaverse.org nor the names
* of its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.IO;
using System.Net;
using System.Xml;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using OpenMetaverse;
using OpenMetaverse.Http;
using OpenMetaverse.Packets;
using OpenMetaverse.StructuredData;
using log4net;
using Nwc.XmlRpc;
using Logger = Nwc.XmlRpc.Logger;
namespace GridProxy
{
///
/// Proxy Configuration Class
///
public class ProxyConfig
{
///
/// The user agent reported to the remote server
///
public string userAgent;
///
/// Email address of the proxy application's author
///
public string author;
///
/// The port the proxy server will listen on
///
public ushort loginPort = 8080;
///
/// The IP Address the proxy server will communication with the client on
///
public IPAddress clientFacingAddress = IPAddress.Loopback;
///
/// The IP Address the proxy server will communicate with the server on
///
public IPAddress remoteFacingAddress = IPAddress.Any;
///
/// The URI of the login server
///
public Uri remoteLoginUri = new Uri("https://login.agni.lindenlab.com/cgi-bin/login.cgi");
///
/// construct a default proxy configuration with the specified userAgent and author
///
/// The user agent reported to the remote server
/// Email address of the proxy application's author
public ProxyConfig(string userAgent, string author)
{
this.userAgent = userAgent;
this.author = author;
}
///
/// construct a default proxy configuration, parsing command line arguments (try --help)
///
/// The user agent reported to the remote server
/// Email address of the proxy application's author
/// An array containing the parameters to use to override the proxy
/// servers default settings
public ProxyConfig(string userAgent, string author, string[] args, bool exitOnError)
: this(userAgent, author)
{
Dictionary argumentParsers = new Dictionary();
argumentParsers["help"] = new ArgumentParser(ParseHelp);
argumentParsers["proxy-help"] = new ArgumentParser(ParseHelp);
argumentParsers["proxy-login-port"] = new ArgumentParser(ParseLoginPort);
argumentParsers["proxy-client-facing-address"] = new ArgumentParser(ParseClientFacingAddress);
argumentParsers["proxy-remote-facing-address"] = new ArgumentParser(ParseRemoteFacingAddress);
argumentParsers["proxy-remote-login-uri"] = new ArgumentParser(ParseRemoteLoginUri);
foreach (string arg in args)
{
foreach (string argument in argumentParsers.Keys)
{
Match match = (new Regex("^--" + argument + "(?:=(.*))?$")).Match(arg);
if (match.Success)
{
string value;
if (match.Groups[1].Captures.Count == 1)
value = match.Groups[1].Captures[0].ToString();
else
value = null;
try
{
((ArgumentParser)argumentParsers[argument])(value);
}
catch
{
Console.WriteLine("invalid value for --" + argument);
if (exitOnError)
{
ParseHelp(null);
}
else
{
throw;
}
}
}
}
}
}
private delegate void ArgumentParser(string value);
private void ParseHelp(string value)
{
Console.WriteLine("Proxy command-line arguments:");
Console.WriteLine(" --help display this help");
Console.WriteLine(" --proxy-login-port= listen for logins on ");
Console.WriteLine(" --proxy-client-facing-address= communicate with client via ");
Console.WriteLine(" --proxy-remote-facing-address= communicate with server via ");
Console.WriteLine(" --proxy-remote-login-uri= use SL login server at ");
Console.WriteLine(" --log-all log all packets by default in Analyst");
Console.WriteLine(" --log-whitelist= log packets listed in file, one name per line");
Console.WriteLine(" --no-log-blacklist= don't log packets in file, one name per line");
Console.WriteLine(" --output= log Analyst output to a file");
Environment.Exit(1);
}
private void ParseLoginPort(string value)
{
loginPort = Convert.ToUInt16(value);
}
private void ParseClientFacingAddress(string value)
{
clientFacingAddress = IPAddress.Parse(value);
}
private void ParseRemoteFacingAddress(string value)
{
remoteFacingAddress = IPAddress.Parse(value);
}
private void ParseRemoteLoginUri(string value)
{
remoteLoginUri = new Uri(value);
}
}
// Proxy: OpenMetaverse proxy server
// A Proxy instance is only prepared to deal with one client at a time.
public class Proxy
{
public ProxyConfig proxyConfig;
private string loginURI;
static List BinaryResponseCaps = new List()
{
"GetTexture",
"GetMesh",
"GetMesh2"
};
/*
* Proxy Management
*/
// Proxy: construct a proxy server with the given configuration
public Proxy(ProxyConfig proxyConfig)
{
this.proxyConfig = proxyConfig;
ServicePointManager.CertificatePolicy = new TrustAllCertificatePolicy();
ServicePointManager.Expect100Continue = false;
// Even though this will compile on Mono 2.4, it throws a runtime exception
//ServicePointManager.ServerCertificateValidationCallback = TrustAllCertificatePolicy.TrustAllCertificateHandler;
InitializeLoginProxy();
InitializeSimProxy();
InitializeCaps();
}
object keepAliveLock = new object();
// Start: begin accepting clients
public void Start()
{
lock (this)
{
System.Threading.Monitor.Enter(keepAliveLock);
(new Thread(new ThreadStart(KeepAlive))).Start();
RunSimProxy();
Thread runLoginProxy = new Thread(new ThreadStart(RunLoginProxy));
runLoginProxy.IsBackground = true;
runLoginProxy.Name = "Login Proxy";
runLoginProxy.Start();
IPEndPoint endPoint = (IPEndPoint)loginServer.LocalEndPoint;
IPAddress displayAddress;
if (endPoint.Address == IPAddress.Any)
displayAddress = IPAddress.Loopback;
else
displayAddress = endPoint.Address;
loginURI = "http://" + displayAddress + ":" + endPoint.Port + "/";
OpenMetaverse.Logger.Log("Proxy ready at " + loginURI, Helpers.LogLevel.Info);
}
}
// Stop: allow foreground threads to die
public void Stop()
{
lock (this)
{
System.Threading.Monitor.Exit(keepAliveLock);
}
}
// KeepAlive: blocks until the proxy is free to shut down
public void KeepAlive()
{
OpenMetaverse.Logger.Log(">T> KeepAlive", Helpers.LogLevel.Debug);
lock (keepAliveLock) { };
if (loginServer.Connected)
{
loginServer.Disconnect(false);
loginServer.Shutdown(SocketShutdown.Both);
}
loginServer.Close();
OpenMetaverse.Logger.Log("> delegates = (direction == Direction.Incoming ? incomingDelegates : outgoingDelegates);
if (!delegates.ContainsKey(packetType))
{
delegates[packetType] = new List();
}
List delegateArray = delegates[packetType];
if (!delegateArray.Contains(packetDelegate))
{
delegateArray.Add(packetDelegate);
}
}
}
// RemoveDelegate: remove callback for packets of type packetName going direction
public void RemoveDelegate(PacketType packetType, Direction direction, PacketDelegate packetDelegate)
{
lock (this)
{
Dictionary> delegates = (direction == Direction.Incoming ? incomingDelegates : outgoingDelegates);
if (!delegates.ContainsKey(packetType))
{
return;
}
List delegateArray = delegates[packetType];
if (delegateArray.Contains(packetDelegate))
{
delegateArray.Remove(packetDelegate);
}
}
}
private Packet callDelegates(Dictionary> delegates, Packet packet, IPEndPoint remoteEndPoint)
{
PacketType origType = packet.Type;
foreach (PacketDelegate del in delegates[origType])
{
try { packet = del(packet, remoteEndPoint); }
catch (Exception ex) { OpenMetaverse.Logger.Log("Error in packet delegate", Helpers.LogLevel.Warning, ex); }
// FIXME: how should we handle the packet type changing?
if (packet == null || packet.Type != origType) break;
}
return packet;
}
// InjectPacket: send packet to the client or server when direction is Incoming or Outgoing, respectively
public void InjectPacket(Packet packet, Direction direction)
{
lock (this)
{
if (activeCircuit == null)
{
// no active circuit; queue the packet for injection once we have one
List queue = direction == Direction.Incoming ? queuedIncomingInjections : queuedOutgoingInjections;
queue.Add(packet);
}
else
// tell the active sim proxy to inject the packet
((SimProxy)simProxies[activeCircuit]).Inject(packet, direction);
}
}
/*
* Login Proxy
*/
private Socket loginServer;
private int capsReqCount = 0;
// InitializeLoginProxy: initialize the login proxy
private void InitializeLoginProxy()
{
try
{
loginServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
loginServer.Bind(new IPEndPoint(proxyConfig.clientFacingAddress, proxyConfig.loginPort));
loginServer.Listen(1);
}
catch (SocketException e)
{
OpenMetaverse.Logger.Log("Socket Exception", Helpers.LogLevel.Error, e);
}
catch (ObjectDisposedException e)
{
OpenMetaverse.Logger.Log("Socket Object is disposed Exception", Helpers.LogLevel.Error, e);
}
}
// RunLoginProxy: process login requests from clients
private void RunLoginProxy()
{
OpenMetaverse.Logger.Log(">T> RunLoginProxy", Helpers.LogLevel.Debug);
try
{
for (; ; )
{
try
{
Socket client = loginServer.Accept();
Thread connThread = new Thread((ThreadStart)delegate
{
OpenMetaverse.Logger.Log(">T> LoginProxy", Helpers.LogLevel.Debug);
ProxyHTTP(client);
OpenMetaverse.Logger.Log("();
}
}
}
}
catch (Exception e)
{
OpenMetaverse.Logger.Log("Exception in RunLoginProxy", Helpers.LogLevel.Error, e);
}
OpenMetaverse.Logger.Log("= 0) break;
if (bufFill >= BUF_SIZE) return null;
if (!ReadMore()) return null;
}
if (bufFill < (i + 1)) return null;
byte[] ret = new byte[i];
Array.Copy(buf, ret, i);
Array.Copy(buf, i + 1, buf, 0, bufFill - (i + 1));
bufFill -= i + 1;
return ret;
}
private bool ReadMore()
{
try
{
int n = netStream.Read(buf, bufFill, BUF_SIZE - bufFill);
bufFill += n;
return n > 0;
}
catch
{
return false;
}
}
public int Read(byte[] rbuf, int start, int len)
{
int read = 0;
while (len > bufFill)
{
Array.Copy(buf, 0, rbuf, start, bufFill);
start += bufFill; len -= bufFill;
read += bufFill; bufFill = 0;
if (!ReadMore()) break;
}
if (bufFill < len) return 0;
Array.Copy(buf, 0, rbuf, start, len);
Array.Copy(buf, len, buf, 0, bufFill - len);
bufFill -= len; read += len;
return read;
}
}
// ProxyHTTP: proxy a HTTP request
private void ProxyHTTP(Socket client)
{
NetworkStream netStream = new NetworkStream(client);
HandyNetReader reader = new HandyNetReader(netStream);
string line = null;
int reqNo;
int contentLength = 0;
string contentType = "";
Match match;
string uri;
string meth;
Dictionary headers = new Dictionary();
lock (this)
{
capsReqCount++; reqNo = capsReqCount;
}
byte[] byteLine = reader.ReadLine();
if (byteLine == null)
{
//This dirty hack is part of the LIBOMV-457 workaround
//The connecting libomv client being proxied can manage to trigger a null from the ReadLine()
//The happens just after the seed request and is not seen again. TODO find this bug in the library.
netStream.Close(); client.Close();
return;
}
if (byteLine != null) line = Encoding.UTF8.GetString(byteLine).Replace("\r", "");
if (line == null)
throw new Exception("EOF in client HTTP header");
match = new Regex(@"^(\S+)\s+(\S+)\s+(HTTP/\d\.\d)$").Match(line);
if (!match.Success)
{
OpenMetaverse.Logger.Log("[" + reqNo + "] Bad request!", Helpers.LogLevel.Warning);
byte[] wr = Encoding.ASCII.GetBytes("HTTP/1.0 400 Bad Request\r\nContent-Length: 0\r\n\r\n");
netStream.Write(wr, 0, wr.Length);
netStream.Close(); client.Close();
return;
}
meth = match.Groups[1].Captures[0].ToString();
uri = match.Groups[2].Captures[0].ToString();
OpenMetaverse.Logger.Log(String.Format("[{0}] {1}:{2}", reqNo, meth, uri), Helpers.LogLevel.Debug);
// read HTTP header
do
{
// read one line of the header
line = Encoding.UTF8.GetString(reader.ReadLine()).Replace("\r", "");
// check for premature EOF
if (line == null)
throw new Exception("EOF in client HTTP header");
if (line == "") break;
match = new Regex(@"^([^:]+):\s*(.*)$").Match(line);
if (!match.Success)
{
OpenMetaverse.Logger.Log(String.Format("[{0}] Bad Header: '{1}'", reqNo, line), Helpers.LogLevel.Warning);
byte[] wr = Encoding.ASCII.GetBytes("HTTP/1.0 400 Bad Request\r\nContent-Length: 0\r\n\r\n");
netStream.Write(wr, 0, wr.Length);
netStream.Close(); client.Close();
return;
}
string key = match.Groups[1].Captures[0].ToString();
string val = match.Groups[2].Captures[0].ToString();
headers[key.ToLower()] = val;
} while (line != "");
if (headers.ContainsKey("content-length"))
{
contentLength = Convert.ToInt32(headers["content-length"]);
}
if (headers.ContainsKey("content-type"))
{
contentType = headers["content-type"];
}
// read the HTTP body into a buffer
byte[] content = new byte[contentLength];
reader.Read(content, 0, contentLength);
if (contentLength < 8192)
OpenMetaverse.Logger.Log(String.Format("[{0}] request length={1}:\n{2}", reqNo, contentLength, Utils.BytesToString(content)), Helpers.LogLevel.Debug);
if (uri == "/")
{
if (contentType == "application/xml+llsd" || contentType == "application/xml")
{
ProxyLoginSD(netStream, content);
}
else
{
ProxyLogin(netStream, content);
}
}
else if (new Regex(@"^/https?://.*$").Match(uri).Success)
{
ProxyCaps(netStream, meth, uri.Substring(1), headers, content, reqNo);
}
else if (new Regex(@"^/https?:/.*$").Match(uri).Success)
{
//This is a libomv client and the proxy CAPS URI has been munged by the C# URI class
//Part of the LIBOMV-457 work around, TODO make this much nicer.
uri = uri.Replace(":/", "://");
ProxyCaps(netStream, meth, uri.Substring(1), headers, content, reqNo);
}
else
{
OpenMetaverse.Logger.Log("404 not found: " + uri, Helpers.LogLevel.Error);
byte[] wr = Encoding.ASCII.GetBytes("HTTP/1.0 404 Not Found\r\nContent-Length: 0\r\n\r\n");
netStream.Write(wr, 0, wr.Length);
netStream.Close(); client.Close();
return;
}
netStream.Close();
client.Close();
}
public ObservableDictionary KnownCaps = new ObservableDictionary();
//private Dictionary SubHack = new Dictionary();
private void ProxyCaps(NetworkStream netStream, string meth, string uri, Dictionary headers, byte[] content, int reqNo)
{
Match match = new Regex(@"^(https?)://([^:/]+)(:\d+)?(/.*)$").Match(uri);
if (!match.Success)
{
OpenMetaverse.Logger.Log("[" + reqNo + "] Malformed proxy URI: " + uri, Helpers.LogLevel.Error);
byte[] wr = Encoding.ASCII.GetBytes("HTTP/1.0 404 Not Found\r\nContent-Length: 0\r\n\r\n");
netStream.Write(wr, 0, wr.Length);
return;
}
CapInfo cap = null;
lock (this)
{
string capuri = Regex.Replace(uri, @"/?\?.*$", string.Empty);
if (KnownCaps.ContainsKey(capuri))
{
cap = KnownCaps[capuri];
}
}
CapsRequest capReq = null; bool shortCircuit = false; bool requestFailed = false;
if (cap != null)
{
capReq = new CapsRequest(cap);
if (cap.ReqFmt == CapsDataFormat.OSD)
{
capReq.Request = OSDParser.DeserializeLLSDXml(content);
}
else
{
capReq.Request = OSDParser.DeserializeLLSDXml(content);
}
capReq.RawRequest = content;
capReq.FullUri = uri;
foreach (CapsDelegate d in cap.GetDelegates())
{
if (d(capReq, CapsStage.Request)) { shortCircuit = true; break; }
}
}
byte[] respBuf = null;
string consoleMsg = String.Empty;
if (shortCircuit)
{
byte[] wr = Encoding.UTF8.GetBytes("HTTP/1.0 200 OK\r\n");
netStream.Write(wr, 0, wr.Length);
}
else
{
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(uri);
req.KeepAlive = false;
foreach (string header in headers.Keys)
{
if (header == "connection" ||
header == "content-length" || header == "date" || header == "expect" ||
header == "host" || header == "if-modified-since" || header == "referer" ||
header == "transfer-encoding" || header == "user-agent" ||
header == "proxy-connection" || header == "accept-encoding")
{
// can't touch these!
}
else if (header == "accept")
{
req.Accept = headers["accept"];
}
else if (header == "content-type")
{
req.ContentType = headers["content-type"];
}
else if (header == "range")
{
string rangeHeader = headers[header];
string[] parts = rangeHeader.Split('=');
if (parts.Length == 2)
{
string[] range = parts[1].Split('-');
int from;
int to;
if (range.Length == 2)
{
if (int.TryParse(range[0], out from)
&& int.TryParse(range[1], out to))
{
req.AddRange(parts[0], from, to);
}
}
else if (range.Length == 1 && int.TryParse(range[0], out to))
{
req.AddRange(parts[0], to);
}
}
}
else
{
req.Headers[header] = headers[header];
}
}
if (capReq != null)
{
capReq.RequestHeaders = req.Headers;
}
req.Method = meth;
// can't do gets on requests with a content body
// without throwing a protocol exception. So force it to post
// incase our parser stupidly set it to GET due to the viewer
// doing something stupid like sending an empty request
if (content.Length > 0)
req.Method = "POST";
req.ContentLength = content.Length;
HttpWebResponse resp;
try
{
if (content.Length > 0)
{
Stream reqStream = req.GetRequestStream();
reqStream.Write(content, 0, content.Length);
reqStream.Close();
}
else if (cap == null)
{
OpenMetaverse.Logger.Log(string.Format("{0} {1}", req.Method, req.Address.ToString()), Helpers.LogLevel.Info);
}
resp = (HttpWebResponse)req.GetResponse();
}
catch (WebException e)
{
if (e.Status == WebExceptionStatus.Timeout || e.Status == WebExceptionStatus.SendFailure)
{
OpenMetaverse.Logger.Log("Request timeout", Helpers.LogLevel.Warning, e);
byte[] wr = Encoding.ASCII.GetBytes("HTTP/1.0 504 Proxy Request Timeout\r\nContent-Length: 0\r\n\r\n");
netStream.Write(wr, 0, wr.Length);
return;
}
else if (e.Status == WebExceptionStatus.ProtocolError && e.Response != null)
{
resp = (HttpWebResponse)e.Response; requestFailed = true;
}
else
{
OpenMetaverse.Logger.Log("Request error", Helpers.LogLevel.Error, e);
byte[] wr = Encoding.ASCII.GetBytes("HTTP/1.0 502 Gateway Error\r\nContent-Length: 0\r\n\r\n"); // FIXME
netStream.Write(wr, 0, wr.Length);
return;
}
}
try
{
Stream respStream = resp.GetResponseStream();
int read;
int length = 0;
respBuf = new byte[256];
do
{
read = respStream.Read(respBuf, length, 256);
if (read > 0)
{
length += read;
Array.Resize(ref respBuf, length + 256);
}
} while (read > 0);
Array.Resize(ref respBuf, length);
if (capReq != null && !requestFailed)
{
if (cap.RespFmt == CapsDataFormat.OSD)
{
capReq.Response = OSDParser.DeserializeLLSDXml(respBuf);
}
else
{
capReq.Response = OSDParser.DeserializeLLSDXml(respBuf);
}
capReq.RawResponse = respBuf;
}
consoleMsg += "[" + reqNo + "] Response from " + uri + "\nStatus: " + (int)resp.StatusCode + " " + resp.StatusDescription + "\n";
{
byte[] wr = Encoding.UTF8.GetBytes("HTTP/1.0 " + (int)resp.StatusCode + " " + resp.StatusDescription + "\r\n");
netStream.Write(wr, 0, wr.Length);
}
if (capReq != null)
capReq.ResponseHeaders = resp.Headers;
for (int i = 0; i < resp.Headers.Count; i++)
{
string key = resp.Headers.Keys[i];
string val = resp.Headers[i];
string lkey = key.ToLower();
if (lkey != "content-length" && lkey != "transfer-encoding" && lkey != "connection")
{
consoleMsg += key + ": " + val + "\n";
byte[] wr = Encoding.UTF8.GetBytes(key + ": " + val + "\r\n");
netStream.Write(wr, 0, wr.Length);
}
}
}
catch (Exception ex)
{
// TODO: Should we handle this somehow?
OpenMetaverse.Logger.DebugLog("Failed writing output: " + ex.Message);
}
}
if (cap != null && !requestFailed && !capReq.Response.ToString().Equals("undef"))
{
foreach (CapsDelegate d in cap.GetDelegates())
{
try
{
if (d(capReq, CapsStage.Response)) { break; }
}
catch (InvalidCastException ex)
{
OpenMetaverse.Logger.Log("Invalid Cast thrown trying to cast OSD to OSDMap: \n'" + capReq.Response.AsString() + "' Length=" + capReq.RawResponse.Length.ToString() + "\n",
Helpers.LogLevel.Error, ex);
}
catch (Exception ex)
{
OpenMetaverse.Logger.Log("Error firing delegate", Helpers.LogLevel.Error, ex);
}
}
if (cap.RespFmt == CapsDataFormat.OSD)
{
respBuf = OSDParser.SerializeLLSDXmlBytes((OSD)capReq.Response);
}
else
{
respBuf = OSDParser.SerializeLLSDXmlBytes(capReq.Response);
}
}
string respString;
if (cap == null || cap.RespFmt == CapsDataFormat.Binary)
{
respString = "";
}
else
{
respString = Encoding.UTF8.GetString(respBuf);
}
consoleMsg += "\n" + respString + "\n--------";
OpenMetaverse.Logger.Log(consoleMsg, Helpers.LogLevel.Debug);
OpenMetaverse.Logger.Log("[" + reqNo + "] Fixed-up response:\n" + respString + "\n--------", Helpers.LogLevel.Debug);
try
{
byte[] wr2 = Encoding.UTF8.GetBytes("Content-Length: " + respBuf.Length + "\r\n\r\n");
netStream.Write(wr2, 0, wr2.Length);
netStream.Write(respBuf, 0, respBuf.Length);
}
catch (SocketException) { }
catch (IOException) { }
catch (Exception e)
{
OpenMetaverse.Logger.Log("Exception: Error writing to stream " + e, Helpers.LogLevel.Error, e);
}
return;
}
private bool FixupSeedCapsResponse(CapsRequest capReq, CapsStage stage)
{
if (stage != CapsStage.Response) return false;
OSDMap nm = new OSDMap();
if (capReq.Response.Type == OSDType.Map)
{
OSDMap m = (OSDMap)capReq.Response;
foreach (string key in m.Keys)
{
string val = m[key].AsString();
if (!String.IsNullOrEmpty(val))
{
if (!KnownCaps.ContainsKey(val))
{
CapsDataFormat resFmt = BinaryResponseCaps.Contains(key) ? CapsDataFormat.Binary : CapsDataFormat.OSD;
CapsDataFormat reqFmt = CapsDataFormat.OSD;
CapInfo newCap = new CapInfo(val, capReq.Info.Sim, key, reqFmt, resFmt);
newCap.AddDelegate(new CapsDelegate(KnownCapDelegate));
lock (this) { KnownCaps[val] = newCap; }
}
nm[key] = OSD.FromString(loginURI + val);
}
else
{
nm[key] = OSD.FromString(val);
}
}
}
capReq.Response = nm;
return false;
}
private Dictionary> KnownCapsDelegates = new Dictionary>();
private void InitializeCaps()
{
AddCapsDelegate("EventQueueGet", new CapsDelegate(FixupEventQueueGet));
}
public void AddCapsDelegate(string CapName, CapsDelegate capsDelegate)
{
lock (this)
{
if (!KnownCapsDelegates.ContainsKey(CapName))
{
KnownCapsDelegates[CapName] = new List();
}
List delegateArray = KnownCapsDelegates[CapName];
if (!delegateArray.Contains(capsDelegate))
{
delegateArray.Add(capsDelegate);
}
}
}
public void RemoveCapRequestDelegate(string CapName, CapsDelegate capsDelegate)
{
lock (this)
{
if (!KnownCapsDelegates.ContainsKey(CapName))
{
return;
}
List delegateArray = KnownCapsDelegates[CapName];
if (delegateArray.Contains(capsDelegate))
{
delegateArray.Remove(capsDelegate);
}
}
}
private bool KnownCapDelegate(CapsRequest capReq, CapsStage stage)
{
lock (this)
{
if (!KnownCapsDelegates.ContainsKey(capReq.Info.CapType))
return false;
if (stage == CapsStage.Response)
{
if (capReq.Response != null && capReq.Response is OSDMap)
{
OSDMap map = (OSDMap)capReq.Response;
if (map.ContainsKey("uploader"))
{
string val = map["uploader"].AsString();
if (!KnownCaps.ContainsKey(val))
{
CapInfo newCap = new CapInfo(val, capReq.Info.Sim, capReq.Info.CapType, CapsDataFormat.Binary, CapsDataFormat.OSD);
newCap.AddDelegate(new CapsDelegate(KnownCapDelegate));
lock (this) { KnownCaps[val] = newCap; }
}
map["uploader"] = OSD.FromString(loginURI + val);
}
}
}
List delegates = KnownCapsDelegates[capReq.Info.CapType];
foreach (CapsDelegate d in delegates)
{
if (d(capReq, stage)) { return true; }
}
}
return false;
}
private bool FixupEventQueueGet(CapsRequest capReq, CapsStage stage)
{
if (stage != CapsStage.Response) return false;
OSDMap map = null;
if (capReq.Response is OSDMap)
map = (OSDMap)capReq.Response;
else return false;
OSDArray array = null;
if (map.ContainsKey("events") && map["events"] is OSDArray)
array = (OSDArray)map["events"];
else
return false;
for (int i = 0; i < array.Count; i++)
{
OSDMap evt = (OSDMap)array[i];
string message = evt["message"].AsString();
OSDMap body = (OSDMap)evt["body"];
if (message == "TeleportFinish" || message == "CrossedRegion")
{
OSDMap info = null;
if (message == "TeleportFinish")
info = (OSDMap)(((OSDArray)body["Info"])[0]);
else
info = (OSDMap)(((OSDArray)body["RegionData"])[0]);
byte[] bytes = info["SimIP"].AsBinary();
uint simIP = Utils.BytesToUInt(bytes);
ushort simPort = (ushort)info["SimPort"].AsInteger();
string capsURL = info["SeedCapability"].AsString();
GenericCheck(ref simIP, ref simPort, ref capsURL, capReq.Info.Sim == activeCircuit);
info["SeedCapability"] = OSD.FromString(capsURL);
bytes[0] = (byte)(simIP % 256);
bytes[1] = (byte)((simIP >> 8) % 256);
bytes[2] = (byte)((simIP >> 16) % 256);
bytes[3] = (byte)((simIP >> 24) % 256);
info["SimIP"] = OSD.FromBinary(bytes);
info["SimPort"] = OSD.FromInteger(simPort);
}
else if (message == "EnableSimulator")
{
OSDMap info = null;
info = (OSDMap)(((OSDArray)body["SimulatorInfo"])[0]);
byte[] bytes = info["IP"].AsBinary();
uint IP = Utils.BytesToUInt(bytes);
ushort Port = (ushort)info["Port"].AsInteger();
string capsURL = null;
GenericCheck(ref IP, ref Port, ref capsURL, capReq.Info.Sim == activeCircuit);
bytes[0] = (byte)(IP % 256);
bytes[1] = (byte)((IP >> 8) % 256);
bytes[2] = (byte)((IP >> 16) % 256);
bytes[3] = (byte)((IP >> 24) % 256);
info["IP"] = OSD.FromBinary(bytes);
info["Port"] = OSD.FromInteger(Port);
}
else if (message == "EstablishAgentCommunication")
{
string ipAndPort = body["sim-ip-and-port"].AsString();
string[] pieces = ipAndPort.Split(':');
byte[] bytes = IPAddress.Parse(pieces[0]).GetAddressBytes();
uint simIP = Utils.BytesToUInt(bytes);
ushort simPort = (ushort)Convert.ToInt32(pieces[1]);
string capsURL = body["seed-capability"].AsString();
OpenMetaverse.Logger.Log("DEBUG: Got EstablishAgentCommunication for " + ipAndPort + " with seed cap " + capsURL, Helpers.LogLevel.Debug);
GenericCheck(ref simIP, ref simPort, ref capsURL, false);
body["seed-capability"] = OSD.FromString(capsURL);
string ipport = String.Format("{0}:{1}", new IPAddress(simIP), simPort);
body["sim-ip-and-port"] = OSD.FromString(ipport);
OpenMetaverse.Logger.Log("DEBUG: Modified EstablishAgentCommunication to " + body["sim-ip-and-port"].AsString() + " with seed cap " + capsURL, Helpers.LogLevel.Debug);
}
}
return false;
}
private void ProxyLogin(NetworkStream netStream, byte[] content)
{
lock (this)
{
// incase some silly person tries to access with their web browser
if (content.Length <= 0)
return;
// convert the body into an XML-RPC request
XmlRpcRequest request = (XmlRpcRequest)(new XmlRpcRequestDeserializer()).Deserialize(Encoding.UTF8.GetString(content));
// call the loginRequestDelegate
lock (loginRequestDelegates)
{
foreach (XmlRpcRequestDelegate d in loginRequestDelegates)
{
try { d(this, new XmlRpcRequestEventArgs(request)); }
//try { d(request); }
catch (Exception e) { OpenMetaverse.Logger.Log("Exception in login request delegate" + e, Helpers.LogLevel.Error, e); }
}
}
XmlRpcResponse response;
try
{
// forward the XML-RPC request to the server
response = (XmlRpcResponse)request.Send(proxyConfig.remoteLoginUri.ToString(),
30 * 1000); // 30 second timeout
}
catch (Exception e)
{
OpenMetaverse.Logger.Log("Error during login response", Helpers.LogLevel.Error, e);
return;
}
System.Collections.Hashtable responseData;
try
{
responseData = (System.Collections.Hashtable)response.Value;
}
catch (Exception e)
{
OpenMetaverse.Logger.Log(e.Message, Helpers.LogLevel.Error);
return;
}
// proxy any simulator address given in the XML-RPC response
if (responseData.Contains("sim_ip") && responseData.Contains("sim_port"))
{
IPEndPoint realSim = new IPEndPoint(IPAddress.Parse((string)responseData["sim_ip"]), Convert.ToUInt16(responseData["sim_port"]));
IPEndPoint fakeSim = ProxySim(realSim);
responseData["sim_ip"] = fakeSim.Address.ToString();
responseData["sim_port"] = fakeSim.Port;
activeCircuit = realSim;
}
// start a new proxy session
Reset();
if (responseData.Contains("seed_capability"))
{
CapInfo info = new CapInfo((string)responseData["seed_capability"], activeCircuit, "SeedCapability");
info.AddDelegate(new CapsDelegate(FixupSeedCapsResponse));
KnownCaps[(string)responseData["seed_capability"]] = info;
responseData["seed_capability"] = loginURI + responseData["seed_capability"];
}
// forward the XML-RPC response to the client
StreamWriter writer = new StreamWriter(netStream);
writer.Write("HTTP/1.0 200 OK\r\n");
writer.Write("Content-type: text/xml\r\n");
writer.Write("\r\n");
XmlTextWriter responseWriter = new XmlTextWriter(writer);
XmlRpcResponseSerializer.Singleton.Serialize(responseWriter, response);
responseWriter.Close(); writer.Close();
lock (loginResponseDelegates)
{
foreach (XmlRpcResponseDelegate d in loginResponseDelegates)
{
try { d(response); }
catch (Exception e) { OpenMetaverse.Logger.Log("Exception in login response delegate" + e, Helpers.LogLevel.Error, e); }
}
}
}
}
private void ProxyLoginSD(NetworkStream netStream, byte[] content)
{
lock (this)
{
AutoResetEvent remoteComplete = new AutoResetEvent(false);
CapsClient loginRequest = new CapsClient(proxyConfig.remoteLoginUri);
OSD response = null;
loginRequest.OnComplete += new CapsClient.CompleteCallback(
delegate(CapsClient client, OSD result, Exception error)
{
if (error == null)
{
if (result != null && result.Type == OSDType.Map)
{
response = result;
}
}
remoteComplete.Set();
}
);
loginRequest.BeginGetResponse(content, "application/llsd+xml", 1000 * 100);
remoteComplete.WaitOne(1000 * 100, false);
if (response == null)
{
byte[] wr = Encoding.ASCII.GetBytes("HTTP/1.0 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n");
netStream.Write(wr, 0, wr.Length);
return;
}
OSDMap map = (OSDMap)response;
OSD llsd;
string sim_port = null, sim_ip = null, seed_capability = null;
map.TryGetValue("sim_port", out llsd);
if (llsd != null) sim_port = llsd.AsString();
map.TryGetValue("sim_ip", out llsd);
if (llsd != null) sim_ip = llsd.AsString();
map.TryGetValue("seed_capability", out llsd);
if (llsd != null) seed_capability = llsd.AsString();
if (sim_port == null || sim_ip == null || seed_capability == null)
{
if (map != null)
{
OpenMetaverse.Logger.Log("Connection to server failed, returned LLSD error follows:\n" + map.ToString(), Helpers.LogLevel.Error);
}
byte[] wr = Encoding.ASCII.GetBytes("HTTP/1.0 500 Internal Server Error\r\nContent-Length: 0\r\n\r\n");
netStream.Write(wr, 0, wr.Length);
return;
}
IPEndPoint realSim = new IPEndPoint(IPAddress.Parse(sim_ip), Convert.ToUInt16(sim_port));
IPEndPoint fakeSim = ProxySim(realSim);
map["sim_ip"] = OSD.FromString(fakeSim.Address.ToString());
map["sim_port"] = OSD.FromInteger(fakeSim.Port);
activeCircuit = realSim;
// start a new proxy session
Reset();
CapInfo info = new CapInfo(seed_capability, activeCircuit, "SeedCapability");
info.AddDelegate(new CapsDelegate(FixupSeedCapsResponse));
KnownCaps[seed_capability] = info;
map["seed_capability"] = OSD.FromString(loginURI + seed_capability);
StreamWriter writer = new StreamWriter(netStream);
writer.Write("HTTP/1.0 200 OK\r\n");
writer.Write("Content-type: application/xml+llsd\r\n");
writer.Write("\r\n");
writer.Write(OSDParser.SerializeLLSDXmlString(response));
writer.Close();
}
}
/*
* Sim Proxy
*/
private Socket simFacingSocket;
public IPEndPoint activeCircuit = null;
private Dictionary proxyEndPoints = new Dictionary();
private Dictionary simProxies = new Dictionary();
private Dictionary proxyHandlers = new Dictionary();
//private XmlRpcRequestDelegate loginRequestDelegate = null;
//private XmlRpcResponseDelegate loginResponseDelegate = null;
public List loginRequestDelegates = new List();
public List loginResponseDelegates = new List();
private Dictionary> incomingDelegates = new Dictionary>();
private Dictionary> outgoingDelegates = new Dictionary>();
private List queuedIncomingInjections = new List();
private List queuedOutgoingInjections = new List();
// InitializeSimProxy: initialize the sim proxy
private void InitializeSimProxy()
{
InitializeAddressCheckers();
simFacingSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
simFacingSocket.Bind(new IPEndPoint(proxyConfig.remoteFacingAddress, 0));
Reset();
}
// Reset: start a new session
private void Reset()
{
foreach (SimProxy simProxy in simProxies.Values)
simProxy.Reset();
KnownCaps.Clear();
}
private byte[] receiveBuffer = new byte[8192];
private byte[] zeroBuffer = new byte[8192];
private EndPoint remoteEndPoint = (EndPoint)new IPEndPoint(IPAddress.Any, 0);
// RunSimProxy: start listening for packets from remote sims
private void RunSimProxy()
{
simFacingSocket.BeginReceiveFrom(receiveBuffer, 0, receiveBuffer.Length, SocketFlags.None, ref remoteEndPoint, new AsyncCallback(ReceiveFromSim), null);
}
// ReceiveFromSim: packet received from a remote sim
private void ReceiveFromSim(IAsyncResult ar)
{
lock (this)
try
{
//if (!simFacingSocket.Connected) return;
// pause listening and fetch the packet
bool needsZero = false;
bool needsCopy = true;
int length;
length = simFacingSocket.EndReceiveFrom(ar, ref remoteEndPoint);
if (proxyHandlers.ContainsKey(remoteEndPoint))
{
// find the proxy responsible for forwarding this packet
SimProxy simProxy = (SimProxy)proxyHandlers[remoteEndPoint];
// interpret the packet according to the SL protocol
Packet packet;
int end = length - 1;
packet = Packet.BuildPacket(receiveBuffer, ref end, zeroBuffer);
// check for ACKs we're waiting for
packet = simProxy.CheckAcks(packet, Direction.Incoming, ref length, ref needsCopy);
// modify sequence numbers to account for injections
uint oldSequence = packet.Header.Sequence;
packet = simProxy.ModifySequence(packet, Direction.Incoming, ref length, ref needsCopy);
// keep track of sequence numbers
if (packet.Header.Sequence > simProxy.incomingSequence)
simProxy.incomingSequence = packet.Header.Sequence;
// check the packet for addresses that need proxying
if (incomingCheckers.ContainsKey(packet.Type))
{
/* if (needsZero) {
length = Helpers.ZeroDecode(packet.Header.Data, length, zeroBuffer);
packet.Header.Data = zeroBuffer;
needsZero = false;
} */
Packet newPacket = ((AddressChecker)incomingCheckers[packet.Type])(packet);
SwapPacket(packet, newPacket);
packet = newPacket;
needsCopy = false;
}
// pass the packet to any callback delegates
if (incomingDelegates.ContainsKey(packet.Type))
{
/* if (needsZero) {
length = Helpers.ZeroDecode(packet.Header.Data, length, zeroBuffer);
packet.Header.Data = zeroBuffer;
needsCopy = true;
} */
if (packet.Header.AckList != null && needsCopy)
{
uint[] newAcks = new uint[packet.Header.AckList.Length];
Array.Copy(packet.Header.AckList, 0, newAcks, 0, newAcks.Length);
packet.Header.AckList = newAcks; // FIXME
}
try
{
Packet newPacket = callDelegates(incomingDelegates, packet, (IPEndPoint)remoteEndPoint);
if (newPacket == null)
{
if (packet.Header.Reliable)
simProxy.Inject(SpoofAck(oldSequence), Direction.Outgoing);
if (packet.Header.AppendedAcks)
packet = SeparateAck(packet);
else
packet = null;
}
else
{
bool oldReliable = packet.Header.Reliable;
bool newReliable = newPacket.Header.Reliable;
if (oldReliable && !newReliable)
simProxy.Inject(SpoofAck(oldSequence), Direction.Outgoing);
else if (!oldReliable && newReliable)
simProxy.WaitForAck(packet, Direction.Incoming);
SwapPacket(packet, newPacket);
packet = newPacket;
}
}
catch (Exception e)
{
OpenMetaverse.Logger.Log("Exception in incoming delegate", Helpers.LogLevel.Error, e);
}
if (packet != null)
simProxy.SendPacket(packet, false);
}
else
simProxy.SendPacket(packet, needsZero);
}
else
// ignore packets from unknown peers
OpenMetaverse.Logger.Log("Dropping packet from unknown peer " + remoteEndPoint, Helpers.LogLevel.Warning);
}
catch (Exception e)
{
OpenMetaverse.Logger.Log("Error processing incoming packet from simulator", Helpers.LogLevel.Error, e);
}
finally
{
// resume listening
try
{
simFacingSocket.BeginReceiveFrom(receiveBuffer, 0, receiveBuffer.Length, SocketFlags.None,
ref remoteEndPoint, new AsyncCallback(ReceiveFromSim), null);
}
catch (Exception e)
{
OpenMetaverse.Logger.Log("Listener Socket Exception", Helpers.LogLevel.Error, e);
}
}
}
// SendPacket: send a packet to a sim from our fake client endpoint
public void SendPacket(Packet packet, IPEndPoint endPoint, bool skipZero)
{
byte[] buffer = packet.ToBytes();
if (skipZero || !packet.Header.Zerocoded)
simFacingSocket.SendTo(buffer, buffer.Length, SocketFlags.None, endPoint);
else
{
int zeroLength = Helpers.ZeroEncode(buffer, buffer.Length, zeroBuffer);
simFacingSocket.SendTo(zeroBuffer, zeroLength, SocketFlags.None, endPoint);
}
}
// SpoofAck: create an ACK for the given packet
public Packet SpoofAck(uint sequence)
{
PacketAckPacket spoof = new PacketAckPacket();
spoof.Packets = new PacketAckPacket.PacketsBlock[1];
spoof.Packets[0] = new PacketAckPacket.PacketsBlock();
spoof.Packets[0].ID = sequence;
return (Packet)spoof;
}
// SeparateAck: create a standalone PacketAck for packet's appended ACKs
public Packet SeparateAck(Packet packet)
{
PacketAckPacket seperate = new PacketAckPacket();
seperate.Packets = new PacketAckPacket.PacketsBlock[packet.Header.AckList.Length];
for (int i = 0; i < packet.Header.AckList.Length; ++i)
{
seperate.Packets[i] = new PacketAckPacket.PacketsBlock();
seperate.Packets[i].ID = packet.Header.AckList[i];
}
Packet ack = seperate;
ack.Header.Sequence = packet.Header.Sequence;
return ack;
}
// SwapPacket: copy the sequence number and appended ACKs from one packet to another
public static void SwapPacket(Packet oldPacket, Packet newPacket)
{
newPacket.Header.Sequence = oldPacket.Header.Sequence;
int oldAcks = oldPacket.Header.AppendedAcks ? oldPacket.Header.AckList.Length : 0;
int newAcks = newPacket.Header.AppendedAcks ? newPacket.Header.AckList.Length : 0;
if (oldAcks != 0 || newAcks != 0)
{
uint[] newAckList = new uint[oldAcks];
Array.Copy(oldPacket.Header.AckList, 0, newAckList, 0, oldAcks);
newPacket.Header.AckList = newAckList;
newPacket.Header.AppendedAcks = oldPacket.Header.AppendedAcks;
}
}
// ProxySim: return the proxy for the specified sim, creating it if it doesn't exist
private IPEndPoint ProxySim(IPEndPoint simEndPoint)
{
if (proxyEndPoints.ContainsKey(simEndPoint))
// return the existing proxy
return (IPEndPoint)proxyEndPoints[simEndPoint];
else
{
// return a new proxy
SimProxy simProxy = new SimProxy(proxyConfig, simEndPoint, this);
IPEndPoint fakeSim = simProxy.LocalEndPoint();
OpenMetaverse.Logger.Log("Creating proxy for " + simEndPoint + " at " + fakeSim, Helpers.LogLevel.Info);
simProxy.Run();
proxyEndPoints.Add(simEndPoint, fakeSim);
simProxies.Add(simEndPoint, simProxy);
return fakeSim;
}
}
// AddHandler: remember which sim proxy corresponds to a given sim
private void AddHandler(EndPoint endPoint, SimProxy proxy)
{
proxyHandlers.Add(endPoint, proxy);
}
// SimProxy: proxy for a single simulator
private class SimProxy
{
//private ProxyConfig proxyConfig;
private IPEndPoint remoteEndPoint;
private Proxy proxy;
private Socket socket;
public uint incomingSequence;
public uint outgoingSequence;
private List incomingInjections;
private List outgoingInjections;
private uint incomingOffset = 0;
private uint outgoingOffset = 0;
private Dictionary incomingAcks;
private Dictionary outgoingAcks;
private List incomingSeenAcks;
private List outgoingSeenAcks;
// SimProxy: construct a proxy for a single simulator
public SimProxy(ProxyConfig proxyConfig, IPEndPoint simEndPoint, Proxy proxy)
{
//this.proxyConfig = proxyConfig;
remoteEndPoint = new IPEndPoint(simEndPoint.Address, simEndPoint.Port);
this.proxy = proxy;
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.Bind(new IPEndPoint(proxyConfig.clientFacingAddress, 0));
proxy.AddHandler(remoteEndPoint, this);
Reset();
}
// Reset: start a new session
public void Reset()
{
incomingSequence = 0;
outgoingSequence = 0;
incomingInjections = new List();
outgoingInjections = new List();
incomingAcks = new Dictionary();
outgoingAcks = new Dictionary();
incomingSeenAcks = new List();
outgoingSeenAcks = new List();
}
// BackgroundTasks: resend unacknowledged packets and keep data structures clean
private void BackgroundTasks()
{
try
{
int tick = 1;
int incomingInjectionsPoint = 0;
int outgoingInjectionsPoint = 0;
int incomingSeenAcksPoint = 0;
int outgoingSeenAcksPoint = 0;
for (; ; Thread.Sleep(1000)) lock (proxy)
{
if ((tick = (tick + 1) % 60) == 0)
{
for (int i = 0; i < incomingInjectionsPoint; ++i)
{
incomingInjections.RemoveAt(0);
++incomingOffset;
}
incomingInjectionsPoint = incomingInjections.Count;
for (int i = 0; i < outgoingInjectionsPoint; ++i)
{
outgoingInjections.RemoveAt(0);
++outgoingOffset;
}
outgoingInjectionsPoint = outgoingInjections.Count;
for (int i = 0; i < incomingSeenAcksPoint; ++i)
{
incomingAcks.Remove(incomingSeenAcks[0]);
incomingSeenAcks.RemoveAt(0);
}
incomingSeenAcksPoint = incomingSeenAcks.Count;
for (int i = 0; i < outgoingSeenAcksPoint; ++i)
{
outgoingAcks.Remove(outgoingSeenAcks[0]);
outgoingSeenAcks.RemoveAt(0);
}
outgoingSeenAcksPoint = outgoingSeenAcks.Count;
}
foreach (uint id in incomingAcks.Keys)
if (!incomingSeenAcks.Contains(id))
{
Packet packet = (Packet)incomingAcks[id];
packet.Header.Resent = true;
SendPacket(packet, false);
}
foreach (uint id in outgoingAcks.Keys)
if (!outgoingSeenAcks.Contains(id))
{
Packet packet = (Packet)outgoingAcks[id];
packet.Header.Resent = true;
proxy.SendPacket(packet, remoteEndPoint, false);
}
}
}
catch (Exception e)
{
OpenMetaverse.Logger.Log("Exception running BackgroundTasks", Helpers.LogLevel.Error, e);
}
}
// LocalEndPoint: return the endpoint that the client should communicate with
public IPEndPoint LocalEndPoint()
{
return (IPEndPoint)socket.LocalEndPoint;
}
private byte[] receiveBuffer = new byte[8192];
private byte[] zeroBuffer = new byte[8192];
private EndPoint clientEndPoint = new IPEndPoint(IPAddress.Any, 0);
bool firstReceive = true;
// Run: forward packets from the client to the sim
public void Run()
{
Thread backgroundTasks = new Thread(new ThreadStart(BackgroundTasks));
backgroundTasks.IsBackground = true;
backgroundTasks.Start();
socket.BeginReceiveFrom(receiveBuffer, 0, receiveBuffer.Length, SocketFlags.None, ref clientEndPoint, new AsyncCallback(ReceiveFromClient), null);
}
// ReceiveFromClient: packet received from the client
private void ReceiveFromClient(IAsyncResult ar)
{
lock (proxy)
{
try
{
// pause listening and fetch the packet
bool needsZero = false;
bool needsCopy = true;
int length = 0;
try { length = socket.EndReceiveFrom(ar, ref clientEndPoint); }
catch (SocketException) { }
if (length != 0)
{
// interpret the packet according to the SL protocol
int end = length - 1;
Packet packet = OpenMetaverse.Packets.Packet.BuildPacket(receiveBuffer, ref end, zeroBuffer);
//OpenMetaverse.Logger.Log("-> " + packet.Type + " #" + packet.Header.Sequence, Helpers.LogLevel.Debug);
// check for ACKs we're waiting for
packet = CheckAcks(packet, Direction.Outgoing, ref length, ref needsCopy);
// modify sequence numbers to account for injections
uint oldSequence = packet.Header.Sequence;
packet = ModifySequence(packet, Direction.Outgoing, ref length, ref needsCopy);
// keep track of sequence numbers
if (packet.Header.Sequence > outgoingSequence)
outgoingSequence = packet.Header.Sequence;
// check the packet for addresses that need proxying
if (proxy.outgoingCheckers.ContainsKey(packet.Type))
{
/* if (packet.Header.Zerocoded) {
length = Helpers.ZeroDecode(packet.Header.Data, length, zeroBuffer);
packet.Header.Data = zeroBuffer;
needsZero = false;
} */
Packet newPacket = ((AddressChecker)proxy.outgoingCheckers[packet.Type])(packet);
SwapPacket(packet, newPacket);
packet = newPacket;
needsCopy = false;
}
// pass the packet to any callback delegates
if (proxy.outgoingDelegates.ContainsKey(packet.Type))
{
if (packet.Header.AckList != null && needsCopy)
{
uint[] newAcks = new uint[packet.Header.AckList.Length];
Array.Copy(packet.Header.AckList, 0, newAcks, 0, newAcks.Length);
packet.Header.AckList = newAcks; // FIXME
}
try
{
Packet newPacket = proxy.callDelegates(proxy.outgoingDelegates, packet, remoteEndPoint);
if (newPacket == null)
{
if (packet.Header.Reliable)
Inject(proxy.SpoofAck(oldSequence), Direction.Incoming);
if (packet.Header.AppendedAcks)
packet = proxy.SeparateAck(packet);
else
packet = null;
}
else
{
bool oldReliable = packet.Header.Reliable;
bool newReliable = newPacket.Header.Reliable;
if (oldReliable && !newReliable)
Inject(proxy.SpoofAck(oldSequence), Direction.Incoming);
else if (!oldReliable && newReliable)
WaitForAck(packet, Direction.Outgoing);
SwapPacket(packet, newPacket);
packet = newPacket;
}
}
catch (Exception e)
{
OpenMetaverse.Logger.Log("exception in outgoing delegate", Helpers.LogLevel.Error, e);
}
if (packet != null)
proxy.SendPacket(packet, remoteEndPoint, false);
}
else
proxy.SendPacket(packet, remoteEndPoint, needsZero);
// send any packets queued for injection
if (firstReceive)
{
firstReceive = false;
foreach (Packet queuedPacket in proxy.queuedIncomingInjections)
Inject(queuedPacket, Direction.Incoming);
proxy.queuedIncomingInjections = new List();
}
}
}
catch (Exception e)
{
OpenMetaverse.Logger.Log("Proxy error sending packet", Helpers.LogLevel.Error, e);
}
finally
{
// resume listening
try
{
socket.BeginReceiveFrom(receiveBuffer, 0, receiveBuffer.Length, SocketFlags.None,
ref clientEndPoint, new AsyncCallback(ReceiveFromClient), null);
}
catch (SocketException e)
{
OpenMetaverse.Logger.Log("Socket Shutdown: " + e.SocketErrorCode, Helpers.LogLevel.Warning);
}
}
}
}
// SendPacket: send a packet from the sim to the client via our fake sim endpoint
public void SendPacket(Packet packet, bool skipZero)
{
byte[] buffer = packet.ToBytes();
if (skipZero || !packet.Header.Zerocoded)
socket.SendTo(buffer, buffer.Length, SocketFlags.None, clientEndPoint);
else
{
int zeroLength = Helpers.ZeroEncode(buffer, buffer.Length, zeroBuffer);
socket.SendTo(zeroBuffer, zeroLength, SocketFlags.None, clientEndPoint);
}
}
// Inject: inject a packet
public void Inject(Packet packet, Direction direction)
{
if (direction == Direction.Incoming)
{
if (firstReceive)
{
proxy.queuedIncomingInjections.Add(packet);
return;
}
incomingInjections.Add(++incomingSequence);
packet.Header.Sequence = incomingSequence;
}
else
{
outgoingInjections.Add(++outgoingSequence);
packet.Header.Sequence = outgoingSequence;
}
if (packet.Header.Reliable)
WaitForAck(packet, direction);
if (direction == Direction.Incoming)
{
byte[] buffer = packet.ToBytes();
if (!packet.Header.Zerocoded)
socket.SendTo(buffer, buffer.Length, SocketFlags.None, clientEndPoint);
else
{
int zeroLength = Helpers.ZeroEncode(buffer, buffer.Length, zeroBuffer);
socket.SendTo(zeroBuffer, zeroLength, SocketFlags.None, clientEndPoint);
}
}
else
proxy.SendPacket(packet, remoteEndPoint, false);
}
// WaitForAck: take care of resending a packet until it's ACKed
public void WaitForAck(Packet packet, Direction direction)
{
Dictionary table = direction == Direction.Incoming ? incomingAcks : outgoingAcks;
table.Add(packet.Header.Sequence, packet);
}
// CheckAcks: check for and remove ACKs of packets we've injected
public Packet CheckAcks(Packet packet, Direction direction, ref int length, ref bool needsCopy)
{
Dictionary acks = direction == Direction.Incoming ? outgoingAcks : incomingAcks;
List seenAcks = direction == Direction.Incoming ? outgoingSeenAcks : incomingSeenAcks;
if (acks.Count == 0)
return packet;
// check for embedded ACKs
if (packet.Type == PacketType.PacketAck)
{
bool changed = false;
List newPacketBlocks = new List();
foreach (PacketAckPacket.PacketsBlock pb in ((PacketAckPacket)packet).Packets)
{
uint id = pb.ID;
if (acks.ContainsKey(id))
{
acks.Remove(id);
seenAcks.Add(id);
changed = true;
}
else
{
newPacketBlocks.Add(pb);
}
}
if (changed)
{
PacketAckPacket newPacket = new PacketAckPacket();
newPacket.Packets = new PacketAckPacket.PacketsBlock[newPacketBlocks.Count];
int a = 0;
foreach (PacketAckPacket.PacketsBlock pb in newPacketBlocks)
{
newPacket.Packets[a++] = pb;
}
SwapPacket(packet, (Packet)newPacket);
packet = newPacket;
needsCopy = false;
}
}
// check for appended ACKs
if (packet.Header.AppendedAcks)
{
int ackCount = packet.Header.AckList.Length;
for (int i = 0; i < ackCount; )
{
uint ackID = packet.Header.AckList[i]; // FIXME FIXME FIXME
if (acks.ContainsKey(ackID))
{
uint[] newAcks = new uint[ackCount - 1];
Array.Copy(packet.Header.AckList, 0, newAcks, 0, i);
Array.Copy(packet.Header.AckList, i + 1, newAcks, i, ackCount - i - 1);
packet.Header.AckList = newAcks;
--ackCount;
acks.Remove(ackID);
seenAcks.Add(ackID);
needsCopy = false;
}
else
++i;
}
if (ackCount == 0)
{
packet.Header.AppendedAcks = false;
packet.Header.AckList = new uint[0];
}
}
return packet;
}
// ModifySequence: modify a packet's sequence number and ACK IDs to account for injections
public Packet ModifySequence(Packet packet, Direction direction, ref int length, ref bool needsCopy)
{
List ourInjections = direction == Direction.Outgoing ? outgoingInjections : incomingInjections;
List theirInjections = direction == Direction.Incoming ? outgoingInjections : incomingInjections;
uint ourOffset = direction == Direction.Outgoing ? outgoingOffset : incomingOffset;
uint theirOffset = direction == Direction.Incoming ? outgoingOffset : incomingOffset;
uint newSequence = (uint)(packet.Header.Sequence + ourOffset);
foreach (uint injection in ourInjections)
if (newSequence >= injection)
++newSequence;
packet.Header.Sequence = newSequence;
if (packet.Header.AppendedAcks)
{
int ackCount = packet.Header.AckList.Length;
for (int i = 0; i < ackCount; ++i)
{
//int offset = length - (ackCount - i) * 4 - 1;
uint ackID = packet.Header.AckList[i] - theirOffset;
for (int j = theirInjections.Count - 1; j >= 0; --j)
if (ackID >= (uint)theirInjections[j])
--ackID;
packet.Header.AckList[i] = ackID;
}
}
if (packet.Type == PacketType.PacketAck)
{
PacketAckPacket pap = (PacketAckPacket)packet;
foreach (PacketAckPacket.PacketsBlock pb in pap.Packets)
{
uint ackID = (uint)pb.ID - theirOffset;
for (int i = theirInjections.Count - 1; i >= 0; --i)
if (ackID >= (uint)theirInjections[i])
--ackID;
pb.ID = ackID;
}
switch (packet.Header.Frequency)
{
case PacketFrequency.High: length = 7; break;
case PacketFrequency.Medium: length = 8; break;
case PacketFrequency.Low: length = 10; break;
}
needsCopy = false;
}
return packet;
}
}
// Checkers swap proxy addresses in for real addresses. A few constraints:
// - Checkers must not alter the incoming packet.
// - Checkers must return a freshly built packet, even if nothing's changed.
// - The incoming packet's buffer may be longer than the length of the data it contains.
// - The incoming packet's buffer must not be used after the checker returns.
// This is all because checkers may be operating on data that's still in a scratch buffer.
delegate Packet AddressChecker(Packet packet);
Dictionary incomingCheckers = new Dictionary();
Dictionary outgoingCheckers = new Dictionary();
// InitializeAddressCheckers: initialize delegates that check packets for addresses that need proxying
private void InitializeAddressCheckers()
{
// TODO: what do we do with mysteries and empty IPs?
AddMystery(PacketType.OpenCircuit);
//AddMystery(PacketType.AgentPresenceResponse);
incomingCheckers.Add(PacketType.TeleportFinish, new AddressChecker(CheckTeleportFinish));
incomingCheckers.Add(PacketType.CrossedRegion, new AddressChecker(CheckCrossedRegion));
incomingCheckers.Add(PacketType.EnableSimulator, new AddressChecker(CheckEnableSimulator));
//incomingCheckers.Add("UserLoginLocationReply", new AddressChecker(CheckUserLoginLocationReply));
}
// AddMystery: add a checker delegate that logs packets we're watching for development purposes
private void AddMystery(PacketType type)
{
incomingCheckers.Add(type, new AddressChecker(LogIncomingMysteryPacket));
outgoingCheckers.Add(type, new AddressChecker(LogOutgoingMysteryPacket));
}
// GenericCheck: replace the sim address in a packet with our proxy address
private void GenericCheck(ref uint simIP, ref ushort simPort, ref string simCaps, bool active)
{
IPAddress sim_ip = new IPAddress((long)simIP);
IPEndPoint realSim = new IPEndPoint(sim_ip, Convert.ToInt32(simPort));
IPEndPoint fakeSim = ProxySim(realSim);
simPort = (ushort)fakeSim.Port;
byte[] bytes = fakeSim.Address.GetAddressBytes();
simIP = Utils.BytesToUInt(bytes);
if (simCaps != null && simCaps.Length > 0)
{
CapInfo info = new CapInfo(simCaps, realSim, "SeedCapability");
info.AddDelegate(new CapsDelegate(FixupSeedCapsResponse));
lock (this)
{
KnownCaps[simCaps] = info;
}
simCaps = loginURI + simCaps;
}
if (active)
activeCircuit = realSim;
}
// CheckTeleportFinish: check TeleportFinish packets
private Packet CheckTeleportFinish(Packet packet)
{
TeleportFinishPacket tfp = (TeleportFinishPacket)packet;
string simCaps = Encoding.UTF8.GetString(tfp.Info.SeedCapability).Replace("\0", "");
GenericCheck(ref tfp.Info.SimIP, ref tfp.Info.SimPort, ref simCaps, true);
tfp.Info.SeedCapability = Utils.StringToBytes(simCaps);
return (Packet)tfp;
}
// CheckEnableSimulator: check EnableSimulator packets
private Packet CheckEnableSimulator(Packet packet)
{
EnableSimulatorPacket esp = (EnableSimulatorPacket)packet;
string simCaps = null;
GenericCheck(ref esp.SimulatorInfo.IP, ref esp.SimulatorInfo.Port, ref simCaps, false);
return (Packet)esp;
}
// CheckCrossedRegion: check CrossedRegion packets
private Packet CheckCrossedRegion(Packet packet)
{
CrossedRegionPacket crp = (CrossedRegionPacket)packet;
string simCaps = Encoding.UTF8.GetString(crp.RegionData.SeedCapability).Replace("\0", "");
GenericCheck(ref crp.RegionData.SimIP, ref crp.RegionData.SimPort, ref simCaps, true);
crp.RegionData.SeedCapability = Utils.StringToBytes(simCaps);
return (Packet)crp;
}
// LogPacket: log a packet dump
private Packet LogPacket(Packet packet, string type)
{
OpenMetaverse.Logger.Log(type + " packet:\n" + packet, Helpers.LogLevel.Info);
return packet;
}
// LogIncomingMysteryPacket: log an incoming packet we're watching for development purposes
private Packet LogIncomingMysteryPacket(Packet packet)
{
return LogPacket(packet, "incoming mystery");
}
// LogOutgoingMysteryPacket: log an outgoing packet we're watching for development purposes
private Packet LogOutgoingMysteryPacket(Packet packet)
{
return LogPacket(packet, "outgoing mystery");
}
public void AddLoginRequestDelegate(XmlRpcRequestDelegate xmlRpcRequestDelegate)
{
lock (loginRequestDelegates)
if (!loginRequestDelegates.Contains(xmlRpcRequestDelegate))
loginRequestDelegates.Add(xmlRpcRequestDelegate);
}
public void AddLoginResponseDelegate(XmlRpcResponseDelegate xmlRpcResponseDelegate)
{
lock (loginResponseDelegates)
if (!loginResponseDelegates.Contains(xmlRpcResponseDelegate))
loginResponseDelegates.Add(xmlRpcResponseDelegate);
}
}
// Describes the data format of a capability
public enum CapsDataFormat
{
Binary = 0,
OSD = 1
}
// Describes a caps URI
public class CapInfo
{
private string uri;
private IPEndPoint sim;
private string type;
private CapsDataFormat reqFmt;
private CapsDataFormat respFmt;
private List Delegates = new List();
public CapInfo(string URI, IPEndPoint Sim, string CapType)
:
this(URI, Sim, CapType, CapsDataFormat.OSD, CapsDataFormat.OSD) { }
public CapInfo(string URI, IPEndPoint Sim, string CapType, CapsDataFormat ReqFmt, CapsDataFormat RespFmt)
{
uri = URI; sim = Sim; type = CapType; reqFmt = ReqFmt; respFmt = RespFmt;
}
public string URI
{
get { return uri; }
}
public string CapType
{
get { return type; } /* EventQueueGet, etc */
}
public IPEndPoint Sim
{
get { return sim; }
}
public CapsDataFormat ReqFmt
{
get { return reqFmt; } /* expected request format */
}
public CapsDataFormat RespFmt
{
get { return respFmt; } /* expected response format */
}
public void AddDelegate(CapsDelegate deleg)
{
lock (this)
{
if (!Delegates.Contains(deleg))
{
Delegates.Add(deleg);
}
}
}
public void RemoveDelegate(CapsDelegate deleg)
{
lock (this)
{
if (Delegates.Contains(deleg))
{
Delegates.Remove(deleg);
}
}
}
// inefficient, but avoids potential deadlocks.
public List GetDelegates()
{
lock (this)
{
return new List(Delegates);
}
}
}
// Information associated with a caps request/response
public class CapsRequest
{
public CapsRequest(CapInfo info)
{
Info = info;
}
public readonly CapInfo Info;
// The request
public OSD Request = null;
// The corresponding response
public OSD Response = null;
public byte[] RawRequest = null;
public byte[] RawResponse = null;
public WebHeaderCollection RequestHeaders = new WebHeaderCollection();
public WebHeaderCollection ResponseHeaders = new WebHeaderCollection();
public string FullUri = string.Empty;
}
// XmlRpcRequestDelegate: specifies a delegate to be called for XML-RPC requests
public delegate void XmlRpcRequestDelegate(object sender, XmlRpcRequestEventArgs e);
// XmlRpcResponseDelegate: specifies a delegate to be called for XML-RPC responses
public delegate void XmlRpcResponseDelegate(XmlRpcResponse response);
// PacketDelegate: specifies a delegate to be called when a packet passes through the proxy
public delegate Packet PacketDelegate(Packet packet, IPEndPoint endPoint);
// Delegate for a caps request. Generally called twice - first with stage = CapsStage.Request
// before the request is sent, then with stage = CapsStage.Response when the response is
// received. Returning true causes all the subsequent delegates in that stage to be skipped,
// and in the case of CapsStage.Request also prevents the request being forwarded. In this
// case, you should set req.Response to the response you want to return.
// Can modify req.Request and req.Response, with the expected effects.
public delegate bool CapsDelegate(CapsRequest req, CapsStage stage);
// Direction: specifies whether a packet is going to the client (Incoming) or to a sim (Outgoing)
public enum Direction
{
Incoming,
Outgoing
}
public enum CapsStage
{
Request,
Response
}
public class XmlRpcRequestEventArgs : EventArgs
{
public XmlRpcRequest m_Request;
public XmlRpcRequestEventArgs(XmlRpcRequest request)
{
this.m_Request = request;
}
}
}