Flash Media Server: URLs tokenization

This post is also available in: Russian

This post continues a series of in-depth overviews on the development of Flash Media Server C + + plugins implementing a custom logic to handle user content delivery requests. Here we will discuss the RTMP URL based content tokenization. It uses an authorization plugin enabling content delivery with dynamically rather than statically generated links. This authorization plugin is often requested by video content copyright holders.

Contents

  • 1.Introduction
  • 2.General Recommendations
  • 3.RTMP URL Tokenization
  • 4.Origin only scheme
  • 5.Edge/Origin scheme
  • 6.Summary

Introduction
Authorization plugins are designed to authenticate client application access to the internal FMIS event model. Such plugins are run within the core process after the connection to the client application is established, but not yet accepted by the core process.
 
A server can host one or more authorization plugins. For example, one plugin can be used to authorize content playback, and another one — to check a permission to publish content. The core process loads plugins in the alphabetical order. So if plugin A is subscribed to the E_PLAY event, plugin B will not receive this event; if plugin A blocks it.

For more detail on event types and fields available for each authorization plugin event, please refer to the Adobe’s Web page Developing an Authorization plug-in.

General Recommendations
Events of the authorization plugin are classified into two types:

  • notify events are those that do not block the thread inside the core process, i.e. the core thread simply informs the plugin (s) of a certain event and continues
  • authorize events are those blocking the thread. In this case, a thread waits for immediate plugin’s response to the event, and then continues depending on the response.

When processing both types of events, please make sure to call the specific service methods that will inform the core process that the event was accepted by the plugin and hence it is no longer needed. For notify events, it is the onNotify () method. If you do not call this method for each accepted event, the event will remain idle in the internal core process structures and cause a memory leak. For authorize events, the onAuthorize () method should be evoked. Otherwise the situation will be even worse than with the notify events. As the authorize events are all blocking, then sooner or later, FMS will no longer accept incoming connections, while current, already connected, clients will hang, as all the running core threads will have to wait for event authorization.

It is also worth noting that the authorization plugin applies globally to all SSAS applications run by FMS. If you want the plugin to accept events only for certain application, you can do it as follows. The plugin should be aware of SSAS application name (e.g., from a configuration file). Then you have to pass to the event handler the name of the SSAS application to which the event was fired, and compare it with the target application. Here is an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void MyFmsAuthorizeEvent:: authorize () {
              / / Target SSAS application for which the plugin is run
              std:: string our_app_name = "vod";
              bool allowed = true;
              switch (m_pAev-> getType ()) {
                            case IFmsAuthEvent:: E_PLAY: {
                                          / / Get the name of the SSAS application to which the event was fired
                                          / / It may also contain names of instances, etc.
                                          std:: string app_name = getStringField (m_pEvent, IFmsAuthEvent:: F_APP_NAME);
                                          / / Get the "clean" name of the SSAS application
                                          size_t dash_pos = app_name.find ('/');
                                          if (dash_p os!= Std:: string:: npos)
                                                        app_name = app_name.substr (0, dash_pos);
                                          /  Is this event targeted to our application?
                                          if (app_name!= Our_app_name)
                                                        break;
                                          / / Otherwise handle the event
                                          .........
                                          allowed = ...;
                            }
              }
              pServerCtx-> onAuthorize (m_pEvent, allowed, NULL);
}

If an event takes a long time to process, for example, it requires making some calculations or sending a request to an external system, it is recommended to allocate a special thread pool and delegate event handling to threads in this pool.

RTMP URL Tokenization
To improve safety of access to content, tokenization and obfuscation of the path to content located at the server, is used. This may be required, for various reasons, by copyright holders, for instance, to prevent intruders from deriving the full path to content on the server or to limit access to content by using tokens.

The token can be a random string that uniquely identifies the content unit or an encrypted path to content or access permissions of a certain group of users, etc.

Let’s discuss the implementation of this functionality for two popular FMS deployment models. The first configuration consists of the Origin servers only, and the second configuration — of Edge and Origin servers.

In this case, the plugin just converts the logical path to the content to the physical path, usually derived from content name (a token in this case), provided by the client application.

To change the physical path to the content, you need to change the F_STREAM_PATH field, which is read-only for several events, but is read-write for the E_FILENAME_TRANSFORM event only. This event is generated immediately after the E_PLAY event, when the core process attempts to map a logical path to the content to the physical path. This event will be fired until the content is found at the logical path, or all possible content sources are looped through.

From now on we are going to discuss only the authorize events, which block the thread in the core process to completely control the FMS operation.

Origin only scheme
In this configuration, the tokenization plugin is quite simple: it subscribed to E_FILENAME_TRANSFORM event and changes the logical path to the physical path.


Figure 1 Configuration based on Origin servers only.

Let’s now consider a step by step operation of this plugin, with examples of code.

1. It is necessary to subscribe to the E_FILENAME_TRANSFORM event only:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void FmsAuthAdaptor:: getEvents (I32 aevBitAuth [], I32 aevBitNotf [], unsigned int count) {
              ....
              / / Unsubscribe from all events except the one you need             
              IFmsAuthEvent:: EventType authExcludeEvent [] = {
                            IFmsAuthEvent:: E_APPSTART,
                            IFmsAuthEvent:: E_APPSTOP,
                            IFmsAuthEvent:: E_CONNECT,
                            IFmsAuthEvent:: E_DISCONNECT,
                            IFmsAuthEvent:: E_PLAY, 
                            / / IFmsAuthEvent:: E_FILENAME_TRANSFORM, 
                            IFmsAuthEvent:: E_STOP,
                            IFmsAuthEvent:: E_SEEK,
                            IFmsAuthEvent:: E_PAUSE,
                            IFmsAuthEvent:: E_PUBLISH,
                            IFmsAuthEvent:: E_UNPUBLISH,
                            IFmsAuthEvent:: E_LOADSEGMENT,
                            IFmsAuthEvent:: E_ACTION,
                            IFmsAuthEvent:: E_CODEC_CHANGE,
                            IFmsAuthEvent:: E_RECORD,
                            IFmsAuthEvent:: E_RECORD_STOP
              };
              m_pFmsAuthServerContext-> excludeEvents (aevBitAuth, count, authExcludeEvent, sizeof (authExcludeEvent) / sizeof (authExcludeEvent [0]));
              ....
}

2. Then get the content token name from the event handler, derive the physical path and change the logical path to the physical path:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void MyFmsAuthorizeEvent:: authorize () {
              bool allowed = false;
              switch (m_pAev-> getType ()) {
                            case IFmsAuthEvent:: E_FILENAME_TRANSFORM: {
                                          / / Get the content name
                                          std:: string stream_name = getStringField (pEvent, IFmsAuthEvent:: F_STREAM_NAME);
                                          / / Derive the physical path based on the token received
                                          const char * real_path = ...............
                                          / / Have you found a token and derived the physical path?
                                          if (!real_path) {
                                                        / / Write something to the log ...
                                                        break;
                                          }
                                          / / Change the physical path
                                          if (!setStringField (pEvent, IFmsAuthEvent:: F_STREAM_PATH, real_path)) {
                                                        / / Write something to the log
                                                        break;
                                          }
                                          allowed = true;              
                            }
              }
              pServerCtx-> onAuthorize (pEvent, allowed, NULL);
}

There is a small nuisance with the E_FILENAME_TRANSFORM event. If you block the first event in the chain of events fired for every possible content source, all the other events will still fire, although we’ve already figured out that there is no physical content at the token received.

Basically, this is not critical if deriving of the physical path is not resource-intensive, involving no request to an external system. Otherwise, this problem can be circumvented by additionally subscribing to the E_PLAY event in order to make a primary validation of the token and authorize or block the event. Yet another way is, after the first failure of the physical path derival from the token, to forcibly substitute the physical path with the special (alternative) content.

So, in current FMS versions, blocking of the E_FILENAME_TRANSFORM event does not make much sense.

Edge/Origin scheme
It is well-known that, in this configuration, the Edge server usually runs as a caching proxy. In this case, the Edge server first attempts to find content in the file cache, and if the content is not found (e.g., the needed fragment is missing), the Edge server requests it from the Origin server. Further, we’ll assume that the file cache is enabled on the Edge server.

Basically, for this configuration the functionality described in the previous step is quite sufficient, and the plugin can be installed on the Origin server only. Well, why only there? In this configuration, no E_FILENAME_TRANSFORM events will be fired for the Edge server core processes. This is quite logical, since the SSAS applications (and, accordingly, the configuration files setting virtual directories), are run on the Origin server.

When you receive a request to play back the content, the Edge server will fail to find proper content fragment in the file cache, because the content is no more than a token. Then the Edge server will send a request to the Origin server which will convert the physical path to the content based on the token (content name). The reply, along with a content fragment, will be sent back to the Edge server, which will be informed of the changes in the physical path to the content. This information is sent from the Origin server, as it is used to enable file cache on the Edge server. Then, either the Edge server will continue to request content fragments from the Origin server or use the earlier received content from the file cache.


Figure 2 Messaging between the Edge and Origin servers.

So that the task might not seem trivial, in addition to content name tokenization, let’s also implement the geographic IP based filtering of incoming content requests. This is required by almost all copyright holders to meet the video content licensing conditions.

It would be better to make such geographic filtering at the access plugin level, but as we have the already operable authorization plugin, let’s add it here. Also, this will ensure that any requests for content playback never avoid geo filtering!

Thus we have the following model:


Figure 3. Plugins to enable geographic filtering on Edge and tokenization on Origin.

The Edge server plugin will perform:

  • Initial validation of tokens
  • IP-based geographic filtering of requests for each unit of content

The Origin server plug-in will perform:

  • conversion of the physical path to the content based on the token received (the name of the content received from the client application)

The Edge server plugin must:

1. Subscribe to the E_PLAY event. The related code has already been given above.

2. Validate the token and apply geographic filtering of requests based on IP address, using the event handler.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void MyFmsAuthorizeEvent:: authorize () {
              bool allowed = false;
              switch (m_pAev-> getType ()) {
                            case IFmsAuthEvent:: E_PLAY: {
                                          / / Validate the token
                                          / / Get the content name
                                          std:: string stream_name = getStringField (pEvent, IFmsAuthEvent:: F_STREAM_NAME);
                                          if (!validate_token (stream_name)) {
                                                        / / Write something to the log ...
                                                        break;
                                          }
                                          / / Get the IP address
                                          std:: string cip = getStringField (pEvent, IFmsAuthEvent:: F_CLEINT_IP);
                                          / / Verify IP address permission to access the content
                                          if (!content_available_for_ip (stream_name, cip)) {
                                                        / / Write something to the log
                                                        break;
                                          }
                                          / / Authorize the event
                                          allowed = true;
                            }
              }
              pServerCtx-> onAuthorize (pEvent, allowed, NULL);
}

It is clear that token validation and access to content can be combined into one operation, depending on your system architecture.

The plugin for Origin server fully repeats the code specified in the “Configuration of Origin Servers” section.

Summary
While developing authorization plugins, particular attention must be given to your source code, which is responsible for event processing performance. Also, please do not forget to notify the core process that an event requires authorization or is no longer needed. Also, heavy transactions with external systems should be delegated to special threads with pooling, timeouts, etc.

Wishing you full-scale video content delivery!

Leave a Reply