XCPMD Policy

Copyright 2015 by Assured Information Security, Inc. Created by Jennifer Temkin <temkinj@ainfosec.com>. This work is licensed under the Creative Commons Attribution 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/.

Overview

The power management policy consists of a set of rules, and variables that can be used as arguments in these rules. Policy can currently be loaded from a text file, the DB, or added a rule or variable at a time over DBus. All policy, regardless of origin, is ultimately made persistent by storing it in the DB. 

Rule Structure

 A rule consists of a name, a set of conditions, a set of actions, and optionally a set of undo actions. When all conditions become true, the rule becomes active, and its actions are executed in order. When any condition becomes untrue, the rule becomes inactive, and its undo actions (if any) are executed in order.

 Name

 Each rule is uniquely identified by its name. This name may be used to later modify or remove a rule. A name may consist of letters, numbers, and underscores.

 Name uniqueness is strictly enforced; attempts to add a rule with the same name as a currently loaded rule will fail.

 Conditions

A rule’s conditions are a set of Boolean functions that determine when a rule is active. A condition either checks the state of the system or listens for an event. Each condition is individually simple, but more complex rules can be built out of multiple conditions. Multiple conditions are logically ANDed together; therefore, all conditions must be true for a rule to be active. Conditions may be preceded with an exclamation point (!) to invert their return value. If a logical OR is required, consider splitting the conditions into multiple rules with the same actions.

Conditions are able to be selected from a predefined list of condition types, which are enumerated in the Available Conditions section. A condition resembles a C-like function, with a name followed by an open-close pair of parentheses that may contain arguments, according to the condition type’s prototype. Every condition type has a prototype that defines what arguments, if any, it requires. For example, whileBattLessThan() takes two integers as an argument, representing the battery number to check and the percentage to compare to, whereas whileOnBatt() takes no arguments at all. Each condition is separated with spaces, and each argument within each condition is also separated by spaces.

 See the Arguments section for specific details on arguments.

 Actions

 A rule’s actions dictate what a rule will do when it changes from inactive to active. Actions, as their name implies, perform some sort of task, such as pausing a VM or printing a message to the system log. Each action in the list is performed, one at a time, in the order that they are written in the rule’s set of actions.

 Much like conditions, each action has a defined prototype determining what arguments it takes. Unlike conditions, however, actions do not evaluate to any particular value; therefore, actions and conditions are in no way interchangeable. Actions, like conditions, also consist of a name followed by a set of parentheses that may contain arguments according to a prototype. When arguments are present, they are separated by spaces. Actions themselves are also separated by spaces.

 Undo Actions

 Undo actions are performed when a rule changes from active to inactive, but otherwise, the same conventions apply as in the action section. Undo actions may consist of any set of actions. Undo actions are optional, and may be omitted entirely.

 Arguments

 Arguments are pieces of data provided to conditions and actions. Each argument has a type, which is inferred by the parser. XCPMD currently supports four argument types: 

  • strings, which are enclosed in double-quotes (“); 
  • booleans, which are an unquoted true or false;
  • floats, which are numbers containing a decimal point; and
  • integers, which are numbers that do not contain decimal points.

There is no type-punning; an integer cannot be substituted for a float, even if their values are equivalent.

 Example Rule

The following example shows a simple rule with each section separated by pipes (|):

rule1 | whileOnBatt() whileBattLessThan(0 30) | logString(“Pausing VM!”) pauseVm(“ubuntu”) | unpauseVm(“ubuntu”)

This rule breaks down as follows:

  • The rule’s name is rule1.

  • It will become active while it is both on battery power, and battery 0 has less than 30% capacity remaining. When either of these conditions become false, the rule will be inactive.

  • When the rule changes from inactive to active, it will print the string “Pausing VM!” to the system log, then pause the VM named “ubuntu.”

  • When the rule changes from active to inactive, it will unpause the VM named “ubuntu.”

 

Variables

 

XCPMD also supports variables. Variables are, essentially, arguments whose values can arbitrarily substituted for other values of the same type, serving as an argument to one or more conditions and/or actions. Unlike constant arguments, the value of variables may change even while the daemon is running.

In a text file, variables are defined, one per line, using the syntax “varName(value).” For example, the line “batteryLow(20)” defines a variable named batteryLow, whose value is 20, and whose type is inferred as integer. Once defined, variables can be used in rules by referring to them with a dollar sign ($). Our variable could be used in a rule like so:

rule2 | whileBattLessThan(0 $batteryLow) | logString(“Battery getting low!”)

This rule simply prints the message “Battery getting low!” to the system log when battery 0’s remaining capacity drops below 20%.

Later, if we decide we’d rather have a low battery alert at 10% instead, we can overwrite $batteryLow without having to modify any rules that refer to it.

Variables have a few restrictions:

  • All variables must have unique names.

  • Names may contain only letters, numbers, and underscores (_).

  • A variable must be defined before it can be used in a rule.

  • They are strictly typed, like all arguments. A variable’s type must match the action/condition prototype in order to be used as an argument to that action/condition.

  • Once defined, their type may not change, unless they are deleted and re-added.

  • Variables may not be deleted if they are still in use by any rule.

Configuring Policy

XCPMD supports three interfaces for configuring policy: text files, DBus, and direct DB modification.

Text Files

Text files provide an easy way to encapsulate policy and deploy to many machines. Their syntax is simple and straightforward. Each policy file is broken into two sections, variables and rules, which are separated by a single line containing only an equals sign (=).

Variables are defined, one per line, with the variable’s name followed by its value within parentheses:

var_name(“value”)

Rules are defined, one per line, with the four sections described above separated by pipe characters (|):

name | conditions | actions | undo_actions

A policy file may also contain comments. Any line beginning with a pound sign (#) will be interpreted as a comment and ignored by the parser. Currently, comments at the end of a line are not supported.

To trigger loading policy from a text file, use the DBus call load_policy_from_file.

Sample Policy File
#begin variables section
ubuntu_uuid(“12345678-1234-1234-1234-123456789012”)
battery_low(20)
battery_critical(7)
=
#begin rules section
screen_off     | whileLidClosed() | runScript(“/home/user/blank_screen.sh”) | runScript(“/home/user/unblank_screen.sh”)
low_batt_alert | whileBattLessThan(0, $battery_low) | logString(“Battery low!”)
pause_ubuntu   | whileBattLessThan(0, $battery_critical) | pauseVmUuid($ubuntu_uuid) | resumeVmUuid($ubuntu_uuid)

DBus Interface

XCPMD’s DBus interface allows the user to control and query different aspects of policy. This is the interface that any UI tools would use to modify policy in XCPMD. The following calls are currently available:

RPC Name

Inputs

Outputs

Description

add_rule

string name
string conditions
string actions
string undo_actions

-

Adds a rule to the policy.

remove_rule

string rule_name

-

Deletes a rule from the policy.

add_var

string name
string value

-

Adds a variable to the policy.

remove_var

string name

-

Deletes a variable from the policy.

load_policy_from_db

-

-

Reloads the policy from the DB.

load_policy_from_file

string filename

-

Loads policy from a text file.

clear_policy

-

-

Deletes all rules and variables.

clear_rules

-

-

Deletes all rules.

clear_vars

-

-

Deletes all variables.

get_conditions

-

string conditions

Gets a list of available conditions.

get_actions

-

string actions

Gets a list of available actions.

get_rules

-

string rules

Gets the list of currently loaded rules.

get_vars

-

string vars

Gets the list of currently loaded variables.

DBus Examples

The dbus-send application provides a convenient way to issue calls on DBus:

# Get a list of available condition types and their prototypes:
dbus-send --system --print-reply --dest=com.citrix.xenclient.xcpmd / com.citrix.xenclient.xcpmd.get_conditions
 
# Add a rule that prints a message when a laptop is unplugged:
dbus-send --system --print-reply --dest=com.citrix.xenclient.xcpmd / com.citrix.xenclient.xcpmd.add_rule \
    string:’unplug_alert’ \
    string:’whileUsingBatt()’ \
    string:’logString(“Disconnected from AC power!”)’ \
    string:’’
 
# Add a variable named battery_critical whose value is 7
dbus-send --system --print-reply --dest=com.citrix.xenclient.xcpmd / com.citrix.xenclient.xcpmd.add_var \
    string:’battery_critical’ \
    string:’7’
 
# Add a variable named critical_message whose value is “battery critical!”
dbus-send --system --print-reply --dest=com.citrix.xenclient.xcpmd / com.citrix.xenclient.xcpmd.add_var \
    string:’critical_message’ \
    string:’”battery critical!”’
 
# Print out all variables currently loaded--the two we added should be there
dbus-send --system --print-reply --dest=com.citrix.xenclient.xcpmd / com.citrix.xenclient.xcpmd.get_vars
 
# Add a rule using the two variables we added
dbus-send --system --print-reply --dest=com.citrix.xenclient.xcpmd / com.citrix.xenclient.xcpmd.add_rule \
    string:’warn_critical’ \
    string:’whileBattLessThan(0, $battery_critical)’ \
    string:’logString($critical_message)’ \
    string:’’
 
# Delete the rules we added
dbus-send --system --print-reply --dest=com.citrix.xenclient.xcpmd / com.citrix.xenclient.xcpmd.remove_rule \
    string:’warn_critical’
dbus-send --system --print-reply --dest=com.citrix.xenclient.xcpmd / com.citrix.xenclient.xcpmd.remove_rule \
    string:’unplug_alert’ 
 
# Delete the variables we added 
dbus-send --system --print-reply --dest=com.citrix.xenclient.xcpmd / com.citrix.xenclient.xcpmd.remove_var \
    string:’battery_critical’
dbus-send --system --print-reply --dest=com.citrix.xenclient.xcpmd / com.citrix.xenclient.xcpmd.remove_var \
    string:’critical_message’

More information on dbus-send is available at http://dbus.freedesktop.org/doc/dbus-send.1.html.

DB Modification

XCPMD uses the DB as a backing store for all policy, and it is possible to modify the DB directly in order to effect policy changes. However, XCPMD does not receive notice of these changes directly, and it is possible for its internal knowledge of policy to become inconsistent if the policy stored in the DB is modified while it is running. Therefore, it is strongly recommended that the DB only be modified directly when XCPMD is not running.

Modifying the DB directly also bypasses the validity checking performed by XCPMD, so great care should be taken in order to avoid writing invalid policy to the DB. Whenever possible, it is strongly recommended to use the DBus or text file interfaces instead.

If the DB must be modified while XCPMD is running, it is essential to call load_db_policy on DBus after the modification to trigger a policy refresh.

Policy is stored in the following structure:

/power-management
  vars
    var_name1: “value1”
    var_name2: “value2”
  rules
    rule1
      conditions
        0
          type: “whileBattLessThan"
          is_inverted: “false”
          args
            0: “0”
            1: “70”
        1
          type: “whileUsingBatt”
          is_inverted: “true”
          args: “”
      actions
        0
          type: “logString”
          args
            0: “\“On AC power and battery is less than 70%\””
      undos
        0
          type: “logString”
          args
            0: “\“Either on battery power, or battery is greater than or equal to 70%, or both\””

All DB entries are stored and retrieved as key-value pairs, with both key and value being strings. XCPMD parses the strings retrieved from the DB in the same way as other interfaces, so the same rules apply with respect to argument and variable type inference. In particular, ensure that string-type arguments and variables are wrapped in double quotes--using backslash (\) as an escape character may be necessary, depending on your shell.

Parse Errors

If the parser is unable to fully parse a rule, it will attempt to salvage as much as it can. An unparseable or invalid condition or action will be discarded, and provided that the rule has at least one remaining condition and at least one remaining action, it will continue to be loaded.

The complete list of criteria for a valid rule are as follows:

  • The rule’s name must be unique
  • At least one valid condition:
    • Condition type is known
    • Arguments provided match prototype
    • All variables are defined
  • At least one valid action:
    • Action type is known
    • Arguments provided match prototype
    • All variables are defined

Available conditions

Condition

Description

onBacklightDownBtn(void)

True briefly when the backlight down button is pressed.

onBacklightUpBtn(void)

True briefly when the backlight up button is pressed.

onPowerBtn(void)

True briefly when the power button is pressed.

onSleepBtn(void)

True briefly when the sleep button is pressed.

onSuspendBtn(void)

True briefly when the suspend button is pressed.

whileLidClosed(void)

True as long as the laptop lid is closed.

whileLidOpen(void)

True as long as the laptop lid is open, or if the system has no lid.

whileUsingAc(void)

True as long as the system is plugged into AC power.

whileUsingBatt(void)

True as long as the system is running solely on batteries.

whileInTabletMode(void)

True as long as the system is in tablet mode.

whileBattLessThan(int battNum, int percentage)

True as long as the specified battery’s remaining capacity is less than the specified percentage.

whileBattEqualTo(int battNum, int percentage)

True as long as the specified battery’s remaining capacity is equal to the specified percentage.

whileBattPresent(int battNum)

True as long as the specified battery is present.

whileOverallBattGreaterThan(int percentage)

True when the aggregate capacity of all batteries is greater than the specified percentage.

whileOverallBattLessThan(int percentage)

True when the aggregate capacity of all batteries is less than the specified percentage.

whileOverallBattEqualTo(int percentage)

True when the aggregate capacity of all batteries is equal to the specified percentage.

whenAnyVmCreating(void)

True briefly when any VM enters the “creating” state.

whenAnyVmStopping(void)

True briefly when any VM enters the “stopping” state.

whenAnyVmRebooting(void)

True briefly when any VM enters the “rebooting” state.

whenAnyVmRunning(void)

True briefly when any VM enters the “running” state.

whenAnyVmStopped(void)

True briefly when any VM enters the “stopped” state.

whenAnyVmPaused(void)

True briefly when any VM enters the “paused” state.

whenVmUuidCreating(string uuid)

True briefly when the VM with the specified UUID enters the “creating” state.

whenVmUuidStopping(string uuid)

True briefly when the VM with the specified UUID enters the “stopping” state.

whenVmUuidRebooting(string uuid)

True briefly when the VM with the specified UUID enters the “rebooting” state.

whenVmUuidRunning(string uuid)

True briefly when the VM with the specified UUID enters the “running” state.

whenVmUuidStopped(string uuid)

True briefly when the VM with the specified UUID enters the “stopped” state.

whenVmUuidPaused(string uuid)

True briefly when the VM with the specified UUID enters the “paused” state.

whenVmCreating(string name)

True briefly when the VM with the specified name enters the “creating” state.

whenVmStopping(string name)

True briefly when the VM with the specified name enters the “stopping” state.

whenVmRebooting(string name)

True briefly when the VM with the specified name enters the “rebooting” state.

whenVmRunning(string name)

True briefly when the VM with the specified name enters the “running” state.

whenVmStopped(string name)

True briefly when the VM with the specified name enters the “stopped” state.

whenVmPaused(string name)

True briefly when the VM with the specified name enters the “paused” state.

Available actions

sleepVm(string vm_name)

Puts the named VM to sleep.

resumeVm(string vm_name)

Resumes the named VM from sleep.

pauseVm(string vm_name)

Pauses the named VM.

unpauseVm(string vm_name)

Unpauses the named VM.

rebootVm(string vm_name)

Reboots the named VM.

shutdownVm(string vm_name)

Shuts down the named VM.

startVm(string vm_name)

Starts the named VM.

suspendVmToFile(string vm_name, string filename)

Suspends the named VM to the specified file.

resumeVmFromFile(string vm_name, string filename)

Resumes the named VM from the specified file.

sleepVmUuid(string vm_uuid)

Puts the VM with the specified UUID to sleep.

resumeVmUuid(string vm_uuid)

Resumes the VM with the specified UUID from sleep.

pauseVmUuid(string vm_uuid)

Pauses the VM with the specified UUID.

unpauseVmUuid(string vm_uuid)

Unpauses the VM with the specified UUID.

rebootVmUuid(string vm_uuid)

Reboots the VM with the specified UUID.

shutdownVmUuid(string vm_uuid)

Shuts down the VM with the specified UUID.

startVmUuid(string vm_uuid)

Starts the VM with the specified UUID.

suspendVmUuidToFile(string vm_uuid, string filename)

Suspends the VM with the specified UUID to the specified file.

resumeVmUuidFromFile(string vm_uuid, string filename)

Resumes the VM with the specified UUID from the specified file.

shutdownUnusedVpnvms(void)

Shuts down all VPNVM-type VMs that are not required by any other VMs.

shutdownDepsOfVm(string vm_name)

Shuts down all dependencies of the named VM that are not required by any other VMs.

shutdownDepsOfVmUuid(string vm_uuid)

Shuts down all dependencies of the VM with the specified UUID that are not required by any other VMs.

shutdownVpnvmsForVm(string vm_name)

Shuts down all VPNVM-type dependencies of the named VM that are not required by any other VMs.

shutdownVpnvmsForVmUuid(string vm_uuid)

Shuts down all VPNVM-type dependencies of the VM with the specified UUID that are not required by any other VMs.

doNothing(void)

Does nothing.

printString(string string_to_print)

Prints a string to stdout; primarily for testing purposes.

logString(string string_to_log)

Prints a string to dom0’s system log.

runScript(string path_to_script)

Runs the script at the specified path.

screenOn(void)

Powers on the laptop display.

screenOff(void)

Powers off the laptop display.

setBacklight(int backlight_percent)

Sets the display backlight to the specified percentage.

increaseBacklight(int percent_to_increase)

Increases the backlight by the specified percentage.

decreaseBacklight(int percent_to_decrease)

Decrease the backlight by the specified percentage.

Backend Architecture

Policy Structure

XCPMD’s policy system is composed of several core pieces:

  • sources, or sensors, that react to the system state;

  • sinks, or actuators, that act upon the system; and

  • policy that determines how sources activate sinks.

Sources and sinks are provided by dynamically loaded, self-contained modules that register the condition types and action types they provide with the core policy system. The module system is designed to allow easy extension of capabilities and the ability to load platform-dependent implementations of sources/sinks while providing a unified interface to the end user.

When all modules have been loaded, policy, in the form of rules and variables, can be configured. Rules determine which conditions will trigger which actions. A rule consists of a unique ID string, a set of conditions, a set of actions, and an optional set of undo actions. When all conditions are true, the rule becomes active, and its actions are executed in order. When any condition becomes untrue, the rule becomes inactive, and its undo actions (if any) are executed in order.

Conditions are instantiated from condition types, which are provided by source modules. Certain condition types may take arguments as defined by their prototype; arguments are provided to conditions at instantiation. Similarly, actions are instantiated from action types, which are provided by sink modules, and they may take arguments as well. All arguments are strictly typed. Type is inferred from argument format; details are available in the end-user documentation.

Variables are simply arguments whose value may change over the life of the program. Variables provide flexibility in configuration and can significantly improve rule readability, both in text form and in the DB. The usual argument type checking is enforced, and safeguards are in place against removing a variable that is currently in use.

Program Flow

The core of XCPMD is a humble libevent event loop, which handles various types of events (socket, DBus, timer, etc) in an orderly queue. The policy system builds on this structure by having its source modules either register their own events or hook into existing events. Once a relevant event occurs, the system determines what conditions rely on it. If any conditions have changed, the system finds the rules that own the changed conditions and evaluates all other conditions of those rules. If the changed condition causes a rule to change from inactive to active or vice versa, that rule’s actions (or undo actions) are then executed. When all rules with changed conditions have been evaluated, the program resumes waiting for events.

Implementing New Modules

This section provides a guide for developers interested in implementing new sources and sinks for XCPMD.

Modules are shipped as self-contained shared object (.so) files, and are loaded dynamically. It is strongly recommended that new modules use a constructor, as declared with the GCC function attribute __attribute__((constructor)), to perform initialization at load time; likewise, __attribute__((destructor)) should be used to perform cleanup at unload.

Source and sink modules are similar, but sources have a few additional tasks to perform and data to track in order to handle events in the way the policy system requires. 

Source Modules

A source module should have a struct ev_wrapper for each input event that it handles. These structs are thin wrappers around input events that store information necessary for evaluating any condition_types depending on that event. Each ev_wrapper should have at least one condition_type depending on it, and each condition_type depends on one and only one ev_wrapper. At initialization, ev_wrappers should be registered using add_event() and condition_types should be registered with add_condition_type(). 

When an input event occurs, the value member of that event’s ev_wrapper should be filled with the information needed by its condition_types, and handle_events() should be called with a reference to that ev_wrapper as its argument. It is strongly recommended that all of a module’s ev_wrappers be stored in a global array (herein called an event table) for consistency.

Every condition_type provided by a module must have a condition checking function with the following prototype: bool check(struct ev_wrapper *, struct arg_node *). Arguments are passed in as a linked list of struct arg_node; the prototype member must contain a space-separated list of characters describing the number and type of these arguments. Details on the exact format of these prototype strings are available in rules.h. Populating the pretty_prototype member with a C-like, human-readable prototype string is also recommended for self-documentation purposes. Within the condition checking function, arguments should be accessed using the get_arg() function.

Sink Modules

Sink modules, on the other hand, are a bit simpler. Each action_type provided must have an action function with the following prototype: void action(struct arg_node *). Like the arguments to condition checkers, action arguments are passed in as a linked list, whose prototype string must be in the prototype member. These action_types must be registered with add_action_type().

Linking and Symbol Visibility

XCPMD is linked with -rdynamic, which exposes all symbols loaded by the main program to all modules, including symbols from other modules. This greatly simplifies keeping universal data structures consistent across all modules, but has the unfortunate side effect of cluttering the namespace. For this reason, it is essential that all functions provided by modules are either static or uniquely named, including constructors and destructors. Otherwise, shadowing may occur, resulting in one module accidentally calling functions from another.