简介
policy-file是flash socket安全机制的重要部分,而本文详细介绍提供policy-file的policyserver的实现过程。事实上,policyserver本身也是一个socket服务器端的简单原型。了解本文也将为教程中后续的sessionserver的讨论有所铺垫。
什么是policy-file
policy-file是一个flash的安全控制机制,它的设置决定了是否允许flash程序进行跨域通讯。这是对ajax,dom无法跨域的一个拓展。
一个典型的policy-file可能是这样的:
<?xml version="1.0" encoding="UTF-8"?> <cross-domain-policy xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.adobe.com/xml/schemas/PolicyFileSocket.xsd"> <allow-access-from domain="*" to-ports="*" secure="false" /> <site-control permitted-cross-domain-policies="master-only" /> </cross-domain-policy>
其中你可以在policy-file指定允许开放的域名/ip,或者是端口。
policy-file的请求过程
1 flash socket在真正执行socket.connect,以连接到指定的ip和端口前,会先尝试连接该ip的843端口
2如果843端口被监听,并且在连接上后接收到一个policy-file.xml的话,才会断开与843端口的连接
3 flash会分析policy-file的内容,并确认是否访问指定的ip和端口是否是被允许的
4 如果指定的ip和端口是否是被允许的,flash才会真正的连接到指定的ip和端口
policy-file的请求过程的异常情况
1 如果843端口连接失败或者超时,flash依然会连接指定端口,并尝试从指定端口获取policy-file
2如果policy-file获取失败,或者指定的ip和端口不在policy-file允许范围内,flash socket将抛出flash.events.SecurityErrorEvent.SECURITY_ERROR这个异常
policyserver的实现
监听端口
IPAddress ipAddress = IPAddress.Parse(ip); IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 843); Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { listener.Bind(localEndPoint); listener.Listen(100); while (true) { allDone.Reset(); listener.BeginAccept( new AsyncCallback(AcceptCallback), listener); allDone.WaitOne(); } } catch (Exception e) { Console.WriteLine(e.ToString()); }
其中allDone是一个信号控制变量。在接收到一个新的连接请求前,policyserver会阻塞在allDone.WaitOne()这一句。
处理接入请求
public static void AcceptCallback(IAsyncResult ar) { allDone.Set(); Socket listener = (Socket) ar.AsyncState; Socket handler = listener.EndAccept(ar); Console.WriteLine("conneceted : " + handler.RemoteEndPoint.ToString()); StateObject state = new StateObject(); state.workSocket = handler; handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state); }
在上面的代码里,我还把客户端的ip写入控制台,并将异步接受flash的第一个请求。
读入请求,写回policy-file
当接入一个连接请求以后,allDone.Set()释放信号量,在等待接入的循环体里,将完成一次过程,进入下一次循环。
public static void ReadCallback(IAsyncResult ar) { String content = String.Empty; StateObject state = (StateObject) ar.AsyncState; Socket handler = state.workSocket; int bytesRead = handler.EndReceive(ar); if (bytesRead > 0) { state.sb.Append(Encoding.UTF8.GetString(state.buffer, 0, bytesRead)); content = state.sb.ToString(); if ("<policy-file-request/>/0" == content) { Send(handler, @" <?xml version=""1.0"" encoding=""UTF-8""?> <cross-domain-policy xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xsi:noNamespaceSchemaLocation=""http://www.adobe.com/xml/schemas/PolicyFileSocket.xsd""> <allow-access-from domain=""*"" to-ports=""*"" secure=""false"" /> <site-control permitted-cross-domain-policies=""master-only"" /> </cross-domain-policy> "); } else { handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state); } } }
写完策略文件后,在控制台写入作为提示信息。并关闭socket。
配置
考虑到这个policyserver可能部署在不同的环境,为了方便部署,我从文件名中读取配置。
Match m = Regex.Match(Environment.CommandLine, @"/d+/./d+/./d+/./d+");
string ip = string.IsNullOrEmpty(m.Value) ? "127.0.0.1" : m.Value;
Console.WriteLine("binding to " + ip);
比如编译结果文件为SimplePolicyServer.exe,我把它文件名修改为SimplePolicyServer.192.168.1.10.exe ,那么它在执行的时候,便会监听192.168.1.10这个ip的端口。如果没有指定正确的ip的话,默认绑定到本机,即127.0.0.1。
完整代码
using System; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Net.NetworkInformation; using System.Text.RegularExpressions; public class StateObject { public Socket workSocket = null; public const int BufferSize = 1024; public byte[] buffer = new byte[BufferSize]; public StringBuilder sb = new StringBuilder(); } namespace SimplePolicyServer { class Program { // Thread signal. public static ManualResetEvent allDone = new ManualResetEvent(false); public static void StartListening() { byte[] bytes = new Byte[1024]; Match m = Regex.Match(Environment.CommandLine, @"/d+/./d+/./d+/./d+"); string ip = string.IsNullOrEmpty(m.Value) ? "127.0.0.1" : m.Value; Console.WriteLine("binding to " + ip); IPAddress ipAddress = IPAddress.Parse(ip); IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 843); Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { listener.Bind(localEndPoint); listener.Listen(100); while (true) { allDone.Reset(); listener.BeginAccept( new AsyncCallback(AcceptCallback), listener); allDone.WaitOne(); } } catch (Exception e) { Console.WriteLine(e.ToString()); } Console.Read(); } public static void AcceptCallback(IAsyncResult ar) { allDone.Set(); Socket listener = (Socket) ar.AsyncState; Socket handler = listener.EndAccept(ar); Console.WriteLine("conneceted : " + handler.RemoteEndPoint.ToString()); StateObject state = new StateObject(); state.workSocket = handler; handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state); } public static void ReadCallback(IAsyncResult ar) { String content = String.Empty; StateObject state = (StateObject) ar.AsyncState; Socket handler = state.workSocket; int bytesRead = handler.EndReceive(ar); if (bytesRead > 0) { state.sb.Append(Encoding.UTF8.GetString(state.buffer, 0, bytesRead)); content = state.sb.ToString(); if ("<policy-file-request/>/0" == content) { Send(handler, @" <?xml version=""1.0"" encoding=""UTF-8""?> <cross-domain-policy xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance"" xsi:noNamespaceSchemaLocation=""http://www.adobe.com/xml/schemas/PolicyFileSocket.xsd""> <allow-access-from domain=""*"" to-ports=""*"" secure=""false"" /> <site-control permitted-cross-domain-policies=""master-only"" /> </cross-domain-policy> "); } else { handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state); } } } private static void Send(Socket handler, String data) { byte[] byteData = Encoding.ASCII.GetBytes(data); handler.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), handler); } private static void SendCallback(IAsyncResult ar) { try { Socket handler = (Socket) ar.AsyncState; int bytesSent = handler.EndSend(ar); Console.WriteLine("policy file sent"); handler.Shutdown(SocketShutdown.Both); handler.Close(); } catch (Exception e) { Console.WriteLine(e.ToString()); } } public static int Main(String[] args) { StartListening(); return 0; } } }