2257 lines
92 KiB
C#
2257 lines
92 KiB
C#
/*
|
|
* 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.co 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.Net.Http;
|
|
using System.Text.RegularExpressions;
|
|
using OpenMetaverse;
|
|
using OpenMetaverse.Http;
|
|
using OpenMetaverse.Packets;
|
|
using OpenMetaverse.StructuredData;
|
|
using log4net;
|
|
using XmlRpcCore;
|
|
using Logger = XmlRpcCore.Logger;
|
|
|
|
namespace GridProxy
|
|
{
|
|
/// <summary>
|
|
/// Proxy Configuration Class
|
|
/// </summary>
|
|
public class ProxyConfig
|
|
{
|
|
/// <summary>
|
|
/// The user agent reported to the remote server
|
|
/// </summary>
|
|
public string userAgent;
|
|
/// <summary>
|
|
/// Email address of the proxy application's author
|
|
/// </summary>
|
|
public string author;
|
|
/// <summary>
|
|
/// The port the proxy server will listen on
|
|
/// </summary>
|
|
public ushort loginPort = 8080;
|
|
/// <summary>
|
|
/// The IP Address the proxy server will communication with the client on
|
|
/// </summary>
|
|
public IPAddress clientFacingAddress = IPAddress.Loopback;
|
|
/// <summary>
|
|
/// The IP Address the proxy server will communicate with the server on
|
|
/// </summary>
|
|
public IPAddress remoteFacingAddress = IPAddress.Any;
|
|
/// <summary>
|
|
/// The URI of the login server
|
|
/// </summary>
|
|
public Uri remoteLoginUri = new Uri("https://login.agni.lindenlab.com/cgi-bin/login.cgi");
|
|
|
|
/// <summary>
|
|
/// construct a default proxy configuration with the specified userAgent and author
|
|
/// </summary>
|
|
/// <param name="userAgent">The user agent reported to the remote server</param>
|
|
/// <param name="author">Email address of the proxy application's author</param>
|
|
public ProxyConfig(string userAgent, string author)
|
|
{
|
|
this.userAgent = userAgent;
|
|
this.author = author;
|
|
}
|
|
|
|
/// <summary>
|
|
/// construct a default proxy configuration, parsing command line arguments (try --help)
|
|
/// </summary>
|
|
/// <param name="userAgent">The user agent reported to the remote server</param>
|
|
/// <param name="author">Email address of the proxy application's author</param>
|
|
/// <param name="args">An array containing the parameters to use to override the proxy
|
|
/// servers default settings</param>
|
|
public ProxyConfig(string userAgent, string author, string[] args, bool exitOnError)
|
|
: this(userAgent, author)
|
|
{
|
|
Dictionary<string, ArgumentParser> argumentParsers = new Dictionary<string, ArgumentParser>();
|
|
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=<port> listen for logins on <port>");
|
|
Console.WriteLine(" --proxy-client-facing-address=<IP> communicate with client via <IP>");
|
|
Console.WriteLine(" --proxy-remote-facing-address=<IP> communicate with server via <IP>");
|
|
Console.WriteLine(" --proxy-remote-login-uri=<URI> use SL login server at <URI>");
|
|
Console.WriteLine(" --log-all log all packets by default in Analyst");
|
|
Console.WriteLine(" --log-whitelist=<file> log packets listed in file, one name per line");
|
|
Console.WriteLine(" --no-log-blacklist=<file> don't log packets in file, one name per line");
|
|
Console.WriteLine(" --output=<logfile> 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;
|
|
|
|
private static readonly HttpClient HttpClient = new HttpClient();
|
|
|
|
static List<string> BinaryResponseCaps = new List<string>()
|
|
{
|
|
"GetTexture",
|
|
"GetMesh",
|
|
"GetMesh2"
|
|
};
|
|
|
|
/*
|
|
* Proxy Management
|
|
*/
|
|
|
|
// Proxy: construct a proxy server with the given configuration
|
|
public Proxy(ProxyConfig proxyConfig)
|
|
{
|
|
this.proxyConfig = proxyConfig;
|
|
|
|
ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
|
|
ServicePointManager.Expect100Continue = false;
|
|
|
|
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("<T< KeepAlive", Helpers.LogLevel.Debug);
|
|
}
|
|
|
|
// AddDelegate: add callback packetDelegate for packets of type packetName going direction
|
|
public void AddDelegate(PacketType packetType, Direction direction, PacketDelegate packetDelegate)
|
|
{
|
|
lock (this)
|
|
{
|
|
Dictionary<PacketType, List<PacketDelegate>> delegates = (direction == Direction.Incoming ? incomingDelegates : outgoingDelegates);
|
|
if (!delegates.ContainsKey(packetType))
|
|
{
|
|
delegates[packetType] = new List<PacketDelegate>();
|
|
}
|
|
List<PacketDelegate> 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<PacketType, List<PacketDelegate>> delegates = (direction == Direction.Incoming ? incomingDelegates : outgoingDelegates);
|
|
if (!delegates.ContainsKey(packetType))
|
|
{
|
|
return;
|
|
}
|
|
List<PacketDelegate> delegateArray = delegates[packetType];
|
|
if (delegateArray.Contains(packetDelegate))
|
|
{
|
|
delegateArray.Remove(packetDelegate);
|
|
}
|
|
}
|
|
}
|
|
|
|
private Packet callDelegates(Dictionary<PacketType, List<PacketDelegate>> 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<Packet> 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("<T< LoginProxy", Helpers.LogLevel.Debug);
|
|
});
|
|
|
|
connThread.IsBackground = true;
|
|
connThread.Name = "LoginProxy";
|
|
connThread.Start();
|
|
}
|
|
catch (SocketException e)
|
|
{
|
|
// indicates we've told the listener to shutdown
|
|
if (e.SocketErrorCode == SocketError.Interrupted)
|
|
break;
|
|
|
|
OpenMetaverse.Logger.Log("Login Failed", Helpers.LogLevel.Error, e);
|
|
break;
|
|
}
|
|
catch (ObjectDisposedException)
|
|
{
|
|
break;
|
|
}
|
|
// send any packets queued for injection
|
|
if (activeCircuit != null)
|
|
{
|
|
lock (this)
|
|
{
|
|
SimProxy activeProxy = (SimProxy)simProxies[activeCircuit];
|
|
foreach (Packet packet in queuedOutgoingInjections)
|
|
activeProxy.Inject(packet, Direction.Outgoing);
|
|
queuedOutgoingInjections = new List<Packet>();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
OpenMetaverse.Logger.Log("Exception in RunLoginProxy", Helpers.LogLevel.Error, e);
|
|
}
|
|
|
|
OpenMetaverse.Logger.Log("<T< RunLoginProxy", Helpers.LogLevel.Debug);
|
|
}
|
|
|
|
private class HandyNetReader
|
|
{
|
|
private NetworkStream netStream;
|
|
private const int BUF_SIZE = 8192;
|
|
private byte[] buf = new byte[BUF_SIZE];
|
|
private int bufFill = 0;
|
|
|
|
public HandyNetReader(NetworkStream s)
|
|
{
|
|
netStream = s;
|
|
}
|
|
|
|
public byte[] ReadLine()
|
|
{
|
|
int i = -1;
|
|
while (true)
|
|
{
|
|
i = Array.IndexOf(buf, (byte)'\n', 0, bufFill);
|
|
if (i >= 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<string, string> headers = new Dictionary<string, string>();
|
|
|
|
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<string, CapInfo> KnownCaps = new ObservableDictionary<string, CapInfo>();
|
|
//private Dictionary<string, bool> SubHack = new Dictionary<string, bool>();
|
|
|
|
private void ProxyCaps(NetworkStream netStream, string meth, string uri, Dictionary<string, string> 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 = "<data>";
|
|
}
|
|
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<string, List<CapsDelegate>> KnownCapsDelegates = new Dictionary<string, List<CapsDelegate>>();
|
|
|
|
|
|
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<CapsDelegate>();
|
|
}
|
|
List<CapsDelegate> 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<CapsDelegate> 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<CapsDelegate> 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
|
|
var cts = new CancellationTokenSource();
|
|
cts.CancelAfter(TimeSpan.FromSeconds(30)); // 30 second timeout
|
|
response = HttpClient.PostAsXmlRpcAsync(proxyConfig.remoteLoginUri, request, cts.Token).Result;
|
|
cts.Dispose();
|
|
}
|
|
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
|
|
using (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");
|
|
|
|
using (XmlTextWriter responseWriter = new XmlTextWriter(writer))
|
|
{
|
|
XmlRpcResponseSerializer.Singleton.Serialize(responseWriter, response);
|
|
}
|
|
}
|
|
|
|
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<IPEndPoint, IPEndPoint> proxyEndPoints = new Dictionary<IPEndPoint, IPEndPoint>();
|
|
private Dictionary<IPEndPoint, SimProxy> simProxies = new Dictionary<IPEndPoint, SimProxy>();
|
|
private Dictionary<EndPoint, SimProxy> proxyHandlers = new Dictionary<EndPoint, SimProxy>();
|
|
//private XmlRpcRequestDelegate loginRequestDelegate = null;
|
|
//private XmlRpcResponseDelegate loginResponseDelegate = null;
|
|
|
|
public List<XmlRpcRequestDelegate> loginRequestDelegates = new List<XmlRpcRequestDelegate>();
|
|
public List<XmlRpcResponseDelegate> loginResponseDelegates = new List<XmlRpcResponseDelegate>();
|
|
|
|
private Dictionary<PacketType, List<PacketDelegate>> incomingDelegates = new Dictionary<PacketType, List<PacketDelegate>>();
|
|
private Dictionary<PacketType, List<PacketDelegate>> outgoingDelegates = new Dictionary<PacketType, List<PacketDelegate>>();
|
|
private List<Packet> queuedIncomingInjections = new List<Packet>();
|
|
private List<Packet> queuedOutgoingInjections = new List<Packet>();
|
|
|
|
// 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<uint> incomingInjections;
|
|
private List<uint> outgoingInjections;
|
|
private uint incomingOffset = 0;
|
|
private uint outgoingOffset = 0;
|
|
private Dictionary<uint, Packet> incomingAcks;
|
|
private Dictionary<uint, Packet> outgoingAcks;
|
|
private List<uint> incomingSeenAcks;
|
|
private List<uint> 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<uint>();
|
|
outgoingInjections = new List<uint>();
|
|
incomingAcks = new Dictionary<uint, Packet>();
|
|
outgoingAcks = new Dictionary<uint, Packet>();
|
|
incomingSeenAcks = new List<uint>();
|
|
outgoingSeenAcks = new List<uint>();
|
|
}
|
|
|
|
// 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<Packet>();
|
|
}
|
|
}
|
|
}
|
|
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<uint, Packet> 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<uint, Packet> acks = direction == Direction.Incoming ? outgoingAcks : incomingAcks;
|
|
List<uint> 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<PacketAckPacket.PacketsBlock> newPacketBlocks = new List<PacketAckPacket.PacketsBlock>();
|
|
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<uint> ourInjections = direction == Direction.Outgoing ? outgoingInjections : incomingInjections;
|
|
List<uint> 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<PacketType, AddressChecker> incomingCheckers = new Dictionary<PacketType, AddressChecker>();
|
|
Dictionary<PacketType, AddressChecker> outgoingCheckers = new Dictionary<PacketType, AddressChecker>();
|
|
|
|
// 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 (!string.IsNullOrEmpty(simCaps))
|
|
{
|
|
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<CapsDelegate> Delegates = new List<CapsDelegate>();
|
|
|
|
|
|
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<CapsDelegate> GetDelegates()
|
|
{
|
|
lock (this)
|
|
{
|
|
return new List<CapsDelegate>(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;
|
|
}
|
|
}
|
|
}
|
|
|
|
|