Key Pages: [ Rope Home Page | Basics (tutorial) | Language Reference | Download ]
ROPE is a scriptable IpTables match module, used to descide whether IP packets passed to it match a particular set of criteria or not. It started life as a project to make the "string" match module of IpTables stronger (Rope is strong string) and evolved fairly quickly into the open-ended matching module we have here today.
The match modules of iptables allow rules to take actions depending on whether packets match certain criteria or not. The standard distribution of netfilter / IpTables provides a range of useful modules of this type. These typically allow protocol types (TCP or UDP), source and destination addresses and ports etc to be checked. There is also a set of interesting "extras" than can be compiled into the kernel to provide some extended packet matching features. One such example is the "string" module that allows packets to be matched on the basis of the existance (or otherwise) of specified strings anywhere in the data payload portion of the packets. There are a number of other hidden treasures that can be used to significantly extend the features of the system.
These modules are included into an IpTables configuration using a command such as..
iptables -A FORWARD \
-i eth0 \
-p tcp \
-s 192.168.0.10 \
--sport 80 \
-m string --string "application/mpeg" \
-j DROP
The example above uses the "tcp" and "string" modules to match packets that come from port 80 on host 192.168.0.10 and contain the string "application/mpeg" anywhere in the data payload.
ROPE is a match module that allows more complex criteria to be specified without resorting to writing code in the C language or recompiling the linux kernel (once the Rope module itself has been compiled).
In order to use ROPE to build a match rule, you first need to write the ROPE scriptlet that encodes your match criteria. As an example, we could look for the "Content-length" header of an HTTP download and check that the length does not exceed 1000000 bytes using the following script..
expecti_to( "Content-Length: " )
expect_while({isdigit}) put($n)
if( gt( atoi($n) 1000000 ) { yes } )
no
(There is a much more complete version of this script at HttpContentLength)
This script takes the following steps..
The language in which scripts like this are written is based on the idea of ReversePolish notation but extended to handle the concept of AnchorBrackets. The language is documented in detail in LanguageReference.
Rope scripts are compiled using the ropec command, like this..
ropec -o compiled.rp source.rope
In order to allow the script to be loaded into the kernel by the IpTables utility, the compiled file must be placed in the directory /etc/rope.d/scripts and be given the extension ".rp".
Comments in ROPE scripts start with a hash character and end at the end of the line they started in - like "perl".
Here's some examples..
# This comment fills an entire line print( "Hello" ) # this one comes after some code
A ROPE script finishes its execution by returning a "no" status to indicate that the packet does not match the criteria, or "yes" to indicate that it does match. These statuses are returned using the "yes" and "no" action words. For example: this script matches all packets..
yes
and this script never matches a single packet.
no
In addition to these two keywords, there are a number of actions that use the idea of "expecting" a particular content in the packet. These check the packet at the location specified in the $offset register for specified content. If the content is found, the script continues to execute. If the content is not found, then the expect-style action returns the "no" status just as if the "no" action had been invoked, and the script execution stops.
For example, this script
expect_str( "HTTP" ) println( "Got it" ) yes
The ROPE programming language uses reverse polish notation, but has a few tricks up it's sleeves that plug some of the "holes" with that syntax.
So what is "reverse polish" notation? Well: simply put, it means that the operators (like "add") are put after the arguments they operate on, and that extensive and explicit use of a stack is made.
In well known languages like C, perl, Java etc, the act of adding two nunmbers and printing out the result looks something like this..
print( 10 + 139 );
In reverse polish notation, this would be written..
10 139 + print
This works like this...
ROPE uses this style of syntax except that it doesnt use "+" to denote addition, but the word "add". So the ROPE version of this script looks like this..
10 130 add print
So that's "reverse polish", but what about the word "anchored" in the title of this section. Well - rope adds the ability to use brackets in the script to do two things..
We'll explain this trick by using an example. The add-and-print script above can be written in any of these ways..
10 130 add print add(10 130) print print( 10 130 add ) print( add( 10 130 ) )
These are all valid. The way to understand this syntax extension is to understand that the "add(" or "print(" part of the script drops an anchor onto the stack which is pulled up again when the matching ")" is encountered, and the action is taken at that point. Lets take this version to show how this works..
add( 10 130 ) print
This does the following...
The fact that "add" knows how many arguments it was passed with this syntax means that we can add multiple numbers, like this..
add( 10 130 58 982 ) print
Where as, if we had said..
10 130 58 982 add print
then the "add" function has no ability to count it's arguments and simply takes its default number (which is 2) off the stack and adds those.
The full set of IP, UDP and TCP/IP packet header fields are available as '$' registers, using names like $ip_saddr or $tcp_dest - etc. Rules can therefore combine tests against the fields as well as the data payload. For example, to test for an HTTP request, try..
$tcp_dest 80 eq assert expect_str( "GET " ) yes
The header fields $ip_saddr and $ip_daddr contain the packet's source and destination IPv4 addresses (we don't handle V6 yet - that will come). Rope represents these as 4-byte strings in the original in-packet format which can be converted to a printable string using ipv4_ntoa, like this..
$ip_saddr ipv4_ntoa println
In addition, ROPE allows IP addresses to be included in the script source code. When this is done, they are treated as 4 byte strings, where each octet of the address is mapped to one byte of the string. You can use this fact to do simple address arithmetic. For example, to check for address in the 192.168.0.0/16 subnet, you could use something like..
$ip_saddr 255.255.0.0 and 192.168.0.0 eq assert
Note that and, or and xor are capable of operating on strings, provided that their lengths are the same.
(see: IpAddress for more details).
A ROPE script has the task of inspecting an IP packet in order to determine whether it matches a set of user-defined criteria. In order to do this, it is clearly important that the script has access to the packet's contents. ROPE provides this access in two ways.
The "lift" functions look like ...
lift(10) -- lifts a string of 10 characters
lift_while({isidigit}) -- lifts characters until a non-digit is seen
lift_to( "\n" ) -- lifts characters up to a new line
lifti_to( "End" ) -- lifts characters up to the string "End", ignoring case.
All of these functions lift (take) bytes from the packet, starting at the position pointed to by the register $offset, and advance $offset by the number of bytes lifted. In this sense, "lift" is like C's "read" and $offset is like a combination of "ltell" and "lseek". For detailed documentation on these actions, see lift, lift_while, lift_to, lifti_to.
A common required for ROPE scripts is to be able to "lift" some portion of the IP packet and compare the lifted string with some pre-defined string, exiting the script with a "no match" status if the two are not the same. In order to facilitate the scripting of this very common requirement, a number of "expect" actions are available.
This script line..
if( lift(4) "HTTP" ne { no } )
Lifts four characters and returns "no" to Iptables if they don't contain the string "HTTP".
The following alternative syntax does exactly the same, but uses slightly less code..
lift(4) "HTTP" eq assert
In this example, we have introduced the assert action - this checks for a TRUE (not zero) value on the stack and terminates the script with a "no" status if it is not found (ie: the value is FALSE (zero)). If a TRUE value is on the stack, then the script continues. This is similar to the "assert" function of the C language.
An even more compact script line that provides the same logic is..
expect_str( "HTTP" )
This combines the "lift", the test and the assert into a single action.
There a variety of other "expect-style" actions that mirror the "lift" syntaxes but terminate the script with a "no" status if a match is not found. These actions are expect_str, expect_while, expect_to and expecti_to.
Lowth.com: [ Home | Rope | P2PWall | LinWiz | cutter | Protector - Free Antivirus software | TapeIO ]