So... I am not happy with Adobe right now. With the push of Flash Player 9,0,115,0 "moviestar", which included such awesome features as H.264 and AAC codec support and improvements to fullscreen mode, they kind of ambushed me with some sweeping changes to their security policy.
I'd been running pre-release nightly builds of the player since 9,0,60,x... and had noticed some strange warnings. Mysterious "Socket Security Error #2048" exceptions that were being thrown at random - even though I was serving an appropriate (for the time) crossdomain.xml file, unexplained timeouts attempting to talk to an xml socket server when I was very clearly not attempting to do any such thing, etc... My regularly repeated attempts to find documentation on what the warnings actually meant proved fruitless. I believe that is because the appropriate document was not actually released to the public until 9,0,115,0 was released.
Now, the bit where they improved the format for crossdomain.xml files doesn't really affect me one way or the other. I approve of the improvements but could really care less in this case. They don't really affect anything I'm doing.
The part that really chaps my hide is the fact that they've completely redone the way that socket security policies are handled. The important parts:
- A SWF file may no longer make a socket connection to its own domain without a socket policy file. Prior to version 9,0,115,0, a SWF file was permitted to make socket connections to ports 1024 or greater in its own domain without a policy file.
- HTTP policy files may no longer be used to authorize socket connections. Prior to version 9,0,115,0, an HTTP policy file, served from the master location of /crossdomain.xml on port 80, could be used to authorize a socket connection to any port 1024 or greater on the same host.
That's right. Your socket policy data can't live in the sitewide crossdomain.xml file that Apache serves any more.
Flash Player 9,0,115,0 introduces a concept of socket master policy files, which are served from the fixed TCP port number 843.
Socket policy files may be obtained from the same port as a main connection (the socket connection being made by ActionScript, which is authorized by a socket policy file), or from a different port, separate from the main connection. If you opt to serve a socket policy file from the same port as a main connection, the server listening on that port must understand socket policy file requests (which are indicated by a transmission of
from Flash Player), and must respond differently for policy file requests and main connection requests.
- When a SWF file attempts to make a socket connection, even to its own domain, Flash Player will first attempt to contact port 843 to see if the host is serving a socket master policy file.
So... regardless of whether you're even using a custom port 843 client, the Flash Player is going to try to hit it. What if your firewall doesn't allow/route traffic to sub-1024 ports w/o special configuration? What if you don't have the access to bind to a sub-1024 port and can't rewrite your other server process to serve the policy data on its port?
- Socket meta-policies can only be declared in a socket master policy file. The syntax is the same as for declaring a meta-policy in an URL master policy file, using the <site-control> tag. Socket meta-policies cannot be declared in HTTP response headers, as HTTP is not involved.
This implies that you can't even tell apache to listen to port 843 and serve up the data. You HAVE to either maintain a separate server process specifically for the purpose of serving this policy data, or you have to edit the process that SWF's are connecting to and make them serve the data..
As of the time of this writing (10 days after moviestar's release), they have yet to release promised help on how to deploy a solution to these new changes. Granted, the one article they did release explains what needs to be done in high level terms. It was sufficient to help me out. I wrote a server that simply listens on port 843 and spews the required xml. But... I'd have really appreciated specific examples, and I suspect plenty of people would appreciate drop-in solutions to the issue.
A 5-minute skeleton implementation (not recommended for production use by any means) written as a PHP cli script might look something like this:
#!/usr/bin/php
<?
/**
* Ugly Flash socket policy file service. This script must be run as root from
* the command line. It binds to port 843 on all interfaces and waits
* indefinitely for connections. When a connection is detected, the script spits
* out a chunk of xml and disconnects. It can only serve one request at a time,
* but that shouldn't be much of a problem.
*
* One potential problem with this script is that you can easily lock port 843
* up for an indeterminate amount of time if the script doesn't exit cleanly.
* The OS should clear the port up for you eventually, but you could be stuck
* playing the waiting game.
*
* This particular version of the script has only been tested very lightly.
* Deploy at your own peril
YMMV.
*
* - Ammon Lauritzen [12/13/07]
*/
// define the xml policy "file"
$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>';
// make sure everything launches correctly
if( posix_getuid() != 0 )
die( "You must run this script as root.\n" );
$sock = @socket_create( AF_INET, SOCK_STREAM, SOL_TCP );
if( !$sock )
die( "Unable to create socket.\n" );
$succ = @socket_bind( $sock, "0.0.0.0", 843 );
if( !$succ )
die( "Unable to bind to port 843.\n" );
$succ = @socket_listen( $sock );
if( !$succ )
die( "Unable to start listening.\n" );
// start serving policies
while( true ) {
$r = $w = $e = array( $sock );
if( @socket_select( $r, $w, $e, null ) !== false ) {
$conn = @socket_accept( $sock );
if( $conn !== false ) {
// somebody connected, just dump the xml and close
socket_write( $conn, $policy_file );
socket_close( $conn );
} else {
echo "socket_accept() failed?\n";
break;
}
} else {
echo "socket_select() failed?\n";
break;
}
}// end: listen forever
// clean up
socket_close( $sock );
?>
I'll try to make my production version of this a bit more suitable for public consumption and release it as soon as I can.
The random #2048 security errors continue, despite having deployed my port 843 policy xml server. Granted, they happen less than before... but they still happen. And even when my policy server isn't running, the errors aren't thrown 100% of the time. This just baffles me. If they were consistent, that would be one thing. But when you get a security error 1 time in 20... that's not security, that's not even a lame deterrent. It's just incentive to hammer the same port over and over again until something finally gives.
Now, I admit that I could be wrong here... but I've re-re-read the documentation on these policies a few times now, and cannot find any reason for the behaviors I'm seeing.
update
On April 22nd, 2008, I released a much better, much more reliable version of this daemon. Head over there for more details and source code.
Posted by Ammon as flash, howto, programming, rant, security at 3:14 PM EST
13 Comments »
So... it's been a while since I've written a post, and I'm feeling the withdrawals. I've got 16 half-written posts waiting in the queue and two more that I want to write but haven't even started. It's also time for another round of anime reviews; I've got two video games I want to review...
And I am having to play the "too busy with RL" card.
In stead, I present a function that I wrote this morning. It's been done countless times before, and you can probably find incredibly similar code out there already, but it's the first time I've ever actually needed something quite like this.
/**
* Send post data somewhere
*
* @param $host The domain name of the host to send the data to.
* @param $path The path of the script to receive the post.
* @param $data An array of key/value pairs to send.
* @return The HTTP response, headers and all.
*/
function http_post
( $host,
$path,
$data ) {
// build a query string from the data array
$arr =
array();
foreach( $data as $key =>
$val )
array_push( $arr,
"$key=".
urlencode($val) );
$data =
implode( "&",
$arr );
// send that post
$fh =
fsockopen( $host,
80 );
fwrite( $fh,
"POST $path HTTP/1.1\r\n" );
fwrite( $fh,
"Host: $host\r\n" );
fwrite( $fh,
"Content-type: application/x-www-form-urlencoded\r\n" );
fwrite( $fh,
"Content-length: ".
strlen($data).
"\r\n" );
fwrite( $fh,
"Connection: close\r\n\r\n" );
fwrite( $fh,
$data );
// get the response
while( !
feof($fh) )
$buf .=
fgets( $fh );
fclose( $fh );
return $buf;
}// end: http_post
This method could easily be modified to send GET data or to talk over a different port. It could also probably actually do something with the response buffer (check for 200, etc...). It could be a bit more fault tolerant, etc... but that's not what I need for the application at hand.
For other examples of how people have done similar/related things, take a look at the comments on the fsockopen() documentation page.
Posted by Ammon as confession, eat, howto, networking, php, programming, work at 11:52 AM EDT
1 Comment »
Last week week, I noticed a strange problem with a project I am working on. The SWF worked fine on XP, Linux, Vista, and OSX. It worked under Firefox, Opera, and Safari. It... loaded under IE7, and then just sort of sat there and pretended that the web services it was trying to call were broken. After poking things for a bit, I sent an email to the Flashcoders mailing list:
I have a swf that is being loaded off of an https server. As it fires up, it attempts to call a simple authentication service that lives on the same host. This works fine under Firefox, Opera, and Safari.
However, under IE, it throws an exception:
[IOErrorEvent type="ioError" bubbles=false cancelable=false eventPhase=2 text="Error #2032: Stream Error. URL: https://host/path/script.php?username=allaryin&passwd=hash"]
Obviously, if I just load the url directly into IE, it loads fine. This problem only occurs when flash tries to load the url for me.
When I monitor the query with Charles, it shows that the request is being made correctly and that the information I am expecting is successfully being returned. However, Flash is apparently ignoring the response.
This behavior has been observed on different machines, running both XP and Vista.
Thoughts?
A few days after sending this email, I'd received no response other than an IM from a friend on the list who didn't know the answer either. So... I resumed consultation of the Google. I dug through ML archives. I read IRC logs. Eventually, I stumbled across mention of a blog post back in 2005 that had addressed a similar problem under IE6. Unfortunately, the site hosting this old blog has ceased to exist/function. So, I found it on the Internet Archive:
http://web.archive.org/web/20070521185428/http://www.gmrweb.net/2005/08/18/flash-remoting-https-internet-explorer/
The post mentions a few potential solutions to the problem such as doing some http header management in Apache. I tried the suggested changes (in the PHP, I didn't have the access/desire to tweak Apache at the time):
header("Expires: " .
date("D, d M Y H:i:s",
0) .
" GMT");
header("Last-Modified: " .
gmdate("D, d M Y H:i:s") .
" GMT");
header("Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
This didn't have any effect on my particular problem. Charles showed that the requested headers were being sent correctly, but Flash + IE7 + HTTPS still failed to talk to my web service.
So, I poked around the net some more without coming up with any helpful solutions to the problem. Returning to the archived blog post, I read the comments and saw another solution proposed. Someone said that simply sending an empty pragma header seemed to help in cases where IE was having difficulty dealing with PHP sessions over HTTPS. Specifically, the pragma header needed to be flushed after the session had started.
And we were using a session variable...
And it works now.
So... yeah. Hooray for obscure comments on wayback machine archived blog posts addressing a similar problem 
Posted by Ammon as flash, howto, internet explorer, php, programming, rant, work at 10:24 AM EDT
2 Comments »
So I find myself writing a whole bunch of AS1 recently (upgrading another old game). In this case, the project was originally done in a single ginormous FLA file. No separate assets file, no external .as files. We've broken lots of timeline code out into source files, but have yet to break any of the assets out. That's my current project.
I'm actually splitting assets up into several small files and then loading them up into an invisible container clip.
When I started writing the loading code, I forgot that AS1 doesn't have the MovieClipLoader class. That's a Flash 7 invention. So, I got to do things the really old fashioned way, by listening to bytes loaded and all that. I figured since it is potentially a common problem (for those who wind up having to go back into the stone ages, that is), I may as well share the code with folks.
Please note that this code is a skeleton. It does not do a whole lot as it is. It just gives you a starting framework to edit to your individual application.
FakeMCL =
function( parent, depth
) {
if( !parent
)
parent =
_root;
this.
parent = parent;
// parent clip of container
this.
loadQueue =
[];
// queue of clips to be loaded
this.
container =
null;
// clip into which all children are loaded
this.
init( depth
);
};
// end: "constructor"
FakeMCL.prototype.init = function( depth ) {
if( !depth )
depth = this.parent.getNextHighestDepth();
// create an invisible container clip at parent._fakemcl
this.container = this.parent.createEmptyMovieClip( "_fakemcl", depth );
this.container._visible = false;
trace("<fakemcl> container "+this.container+" is at depth "+depth);
}// end: init()
FakeMCL.prototype.loadMovie = function( url ) {
this.loadQueue.push( url );
if( !this.loadInterval ) {
trace("<fakemcl> starting interval");
// try to tick once every 25ms (that's once per frame at 40fps), we're
// probably not running that fast, so it will just execute once per frame
// NOTE: you can (and might want to) throttle this back
this.loadInterval = setInterval( this.onInterval, 25, this );
}// end: start interval
}// end: loadMovie()
FakeMCL.prototype.onInterval = function( mcl ) {
// stop iterating if we're completely out of clips to load
if( !mcl.loadQueue.length && !mcl.loadingClip ) {
trace("<fakemcl> queue is empty");
clearInterval( mcl.loadInterval );
// NOTE: you might want to do something here now that the whole queue is loaded
return;
}// end: queue is empty
// start loading a clip if we aren't already
if( !mcl.loadingClip ) {
var clipURL = mcl.loadQueue.shift();
var instanceName = "_"+clipURL.substr( 0, clipURL.indexOf(".") );
trace("<fakemcl> loading "+clipURL+" ("+instanceName+")");
mcl.loadingClip = mcl.container.createEmptyMovieClip( instanceName, mcl.container.getNextHighestDepth() );
mcl.loadingClip.loadMovie( clipURL );
// NOTE: clip init goes here
}// end: not loading anything
else {
// check status of currently loading movie
var percentLoaded = mcl.loadingClip.getBytesLoaded() / mcl.loadingClip.getBytesTotal();
trace("<fakemcl> loaded "+mcl.loadingClip.getBytesLoaded()+"/"+mcl.loadingClip.getBytesTotal());
if( percentLoaded>= 1.0 && mcl.loadingClip.getBytesTotal() ) {
// we're done loading
trace("<fakemcl> done, "+mcl.loadingClip);
mcl.loadingClip = null;
// NOTE: callback for individual clip loading goes here
}
}// end: currently loading
}// end: onInterval()
Again, this is skeleton code and isn't terribly useful as is. I've put some notes in the code where you might want to do more interesting things to adapt the durned thing to your application. Also, feel free to yoink the trace statements, they can get awful spammy
This skeleton supports queuing up a whole list of swf's for loading, but in actual use, it is probably going to be more useful for you to just load one swf at a time. Shrug.
example
Put the following code on a frame of your timeline:
#include "FakeMCL.as"
var mcl = new FakeMCL( this, 10 );
mcl.loadMovie("fish.swf");
mcl.loadMovie("bird.swf");
And hopefully, you'll get some output like this:
<fakemcl> container _level0._fakemcl is at depth 10
<fakemcl> starting interval
<fakemcl> loading fish.swf (_fish)
<fakemcl> loaded 0/-1
<fakemcl> loaded 1465744/1465744
<fakemcl> done, _level0._fakemcl._fish
<fakemcl> loading bird.swf (_bird)
<fakemcl> loaded 1427856/1427856
<fakemcl> done, _level0._fakemcl._bird
<fakemcl> queue is empty
When all is done running, you've got those clips at _fakemcl._bird and _fakemcl._fish that you can toy with however you need. Chances are, you probably want to know when the loading is actually done, however. That'll take some editing of this code. Put a stop() on the frame you call FakeMCL.loadMovie() from and then put "mcl.parent.play()" in the load complete block, etc...
Posted by Ammon as as1, howto, programming, work at 1:33 PM EDT
No Comments »
Ever since we moved into our new house, I've had dreams about setting up a killer network. Every room in the building wired with gigabit ethernet, etc... but... we have no practical application for it, and the budget is insufficient to allow me to do that kind of stuff just for fun.
Our current situation has computers in 3 rooms of the house: my office, wife's computer in our bedroom, and the PS3 in the living room. Oh, and the laptop, whenever it's turned on.
Well, we've been using wireless for maybe two years now - and through two wireless routers. We initially got an old D-Link router to go with our first cable modem, but it got fried during a power outage in our apartment (I'd neglected to put it on any sort of power conditioner). Much of the networking hardware died in that. Actually, that was my second wholesale networking hardware failure - the first one happened at our first apartment when (I am guessing) a spike came over the phone line of all things (fried dsl router and the switch it was plugged into).
Anyhow.
The cheap old D-Link 802.11g router was replaced by a Linksys model, since it matched my new modem. They stacked together, which I thought was neat... but it turned out to be a terrible problem. We've been suffering with the easily overheated router and modem for a while now without many problems, but then we moved into the new house.
Heat no longer was a problem, my office is huge. I have plenty of room to put the modem and router on opposite ends of their little shelf and not feel bad about it.
The problem in the new place is the relative position of my bedroom with respect to the office. Or, more appropriately, the position of the master bathroom and all of its glorious plumbing (2-head shower, hot tub :P) in between the two
Tarma, my wife's machine, was using a little usb Linksys wifi adaptor, but it just couldn't get strong enough of a signal through all of the plumbing to be useful. So we bought her a new PCI card and plugged a ginormous antenna into it. And that worked, for a while, until the strain of a heavy antenna combined with an extended period of moving the machine around and fiddling with things broke the connector loose. It still works fine, but you have to jam it in there. I can probably just solder it in place, but I've been kind of lazy - and don't remember where my soldering stuff is packed anyway
And, we could have lived with that. The computer was stable and wasn't being moved around any more. Even with the connector loose, the big antenna meant that it could get 40-60% signal through all of the plumbing, except when water was running. Then she gets booted.
No, the last straw was our wireless router (the one that liked to overheat) finally giving up on talking to the modem. It... just refused to acquire an IP address any more. Modem's fine. ISP is fine. All of the computers in the house are fine. The LAN ports and wireless on the router are fine... but it simply ceased to be useful at actually talking to the internet. And that's no good.
So, thusly convinced that something needed to be done to feed her addiction, my wife approved the acquisition of some new networking hardware.
And when I arrived at the local emporium of all things expensive and electronic, I decided that 802.11n didn't sound like all that bad of an idea. It's backwards compatible with G (and B, for whatever that matters any more), and promises something like 6x the throughput and substantially higher signal range. Better signal means better chance of blasting through the hot tub...
So I bought a D-Link DIR-655 router and DWA-552 nic for Penny's machine. Set the router up, it worked flawlessly. It's got a decent admin interface, and all of my old 802.11g devices talk to it wonderfully. It also doesn't hurt that the wired ports are gigabit, which means the machines in my office are now talking to each other at the speed their ethernet adaptors intended
One thing I do like about the router is that it is capable of running a network in both WPA and WPA2 modes at once. This means that most of my machines are WPA2 encrypted, but those devices that don't quite support it (like the Playstation...) are at least able to fall back and use WPA. Say what you will about possibly questionable security practices, but I think that this is a fabulous solution - it gives me the highest security realistically available on every device on my network w/o having to spend gobs of money.
So, network upgraded, and all legacy clients confirmed to be operating with better signal quality and security options, it was time to tackle Penny's machine. This computer has been nothing but trouble since the day we got it. It has heat issues, it has power supply issues, it has WinXP MCE issues, etc... but, it's still a generally functional system and it still works like a TV+TiVo in our bedroom.
I popped the new NIC into the box and fired it up. Drivers were installed from the disk and networks were identified. Signal strength meter comes up - full bars. Connection speed identified itself at 300Mbit. Penny was filling the tub for Alanna at the time. I considered that a success at the time and called it a night.
Well, the following day, Penny started having stability problems with the machine. Nothing unusual about that. I look at Tarma funny and the audio decides to start stuttering for a month w/o any explanation and then go back to normal. The box is certainly near the top of my list of problem children over the years.
When I got a chance, I fiddled with the box a bit more, streamlined a few things, ran the obligatory spyware/av/adware battery of scans. Nothing came up, machine was clean. It... just lagged beyond reason. I took a look at the task manager and the cpu load was reported to never really drop below 90%, yet the box wasn't actually doing anything. 'System idle processes' were reported to be reliably occupying >80% of the processor's time.
My first thought was that it was the new wireless card, but it worked fine... it never lost signal, file transfers and net access worked fine. The machine itself was just so slow that we couldn't ever see anything like a decent transfer rate (so no streaming video from the file server). It couldn't even play the videos off of the hard drive any more. Heaven forbid you tried to watch television.
After a bit more pondering and discussing the problem with my father and with Danny, I decided to disable to network card and see if it was just the drivers pegging the cpu.
I finally did that tonight.
The instant the interface was disabled, the system load dropped to nil. I turned the device on, and load skyrocketed back to well over 90%.
A bit of very lazy searching didn't turn up anyone else reporting this problem. And I'd sat on the problem for too long to expect a return on the device from the store... so I decided to check if there were any new drivers.
Sure enough, D-Link had released a new driver last week. (Actually, the version number was several up from what came on the disc with my card, so I'll assume they'd released a few versions in the interim as well). I installed the new driver, and not only did it upgrade things to 802.11n draft 2.0 (from the draft 1 advertised on the box), it fixed the system load problem. I successfully streamed a 1.3gb video w/o a hitch.
I also upgraded the router's firmware at the same time, and haven't noticed any changes in its behavior, but the firmware upgrade process was painless and upgraded it to using draft 2 as well.
I'm happy. Penny's happy. Alanna'd prolly be happy too if she knew what the whole fuss had been about
I'm kind of annoyed at myself that I didn't check for new drivers and firmware the minute I installed the devices, but oh well. It works now, and I'm not out $100 for an unusable network card after all. (The Linksys card with the loose antenna connector will probably be put into service elsewhere, I'm just not sure where yet).
Posted by Ammon as hacker culture, howto, networking, play at 1:49 AM EDT
No Comments »