geneva

package module
v0.0.0-...-c04e0fa Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 1, 2026 License: GPL-3.0 Imports: 2 Imported by: 1

README

geneva, an implementation of Geneva rules for Go

Go Actions Status

This is a reimplementation of the client- and server-side rule processing mechanisms of the Geneva project.

Geneva is both a method to describe ways of manipulating packets to attempt to circumvent censorship, and a genetic algorithm (GENetic EVAsion) that one can deploy to discover new circumventions. This package does not implement the population manager or evaluator; the dependency-free mutate package provides the strategy mutation and crossover primitives those systems need. More broadly, though, one can encode arbitrary instructions for packet manipulation using Geneva rules as a sort of "standard syntax", although the use case outside of censorship circumvention may be somewhat tenuous.

This package aims to implement the same triggers and actions that the Geneva project's canonical Python package does. Please note: this package is a work-in-progress, and there are still things left to implement.

Quick Background

Geneva rules are called strategies. A strategy consists of zero or more action trees that can be applied to inbound or outbound packets. The actions trees define both a trigger and a tree of actions to take on a packet if the trigger matches. The result of an action tree will be zero or more packets that should replace the original packet, which then can be reinjected into the host OS' network stack.

Strategies, Forests, and Action Trees

Let's work from the top down. A strategy, conceptually, looks like this:

outbound-forest \/ inbound-forest

outbound-forest and inbound-forest are ordered lists of (trigger, action tree) pairs. The Geneva paper calls these ordered lists forests. The outbound and inbound forests are separated by the \/ characters (that is a backslash followed by a forward-slash); if the strategy omits one or the other, then that side of the \/ is left empty. For example, a strategy that only includes an outbound forest would take the form outbound \/, whereas an inbound-only strategy would be \/ inbound.

The original Geneva paper does not have a name for these (trigger, action tree) pairs. In practice, however, the Python code actually defines an action tree as a (trigger, action) pair, where the "action" is the root of a tree of actions. This package follows this nomenclature as well.

A real example, taken from the original paper (pg 2202), would look like this:

[TCP:flags:S]-
   duplicate(
      tamper{TCP:flags:replace:SA}(
         send),
       send)-| \/
[TCP:flags:R]-drop-|

In this example, the outbound forest would trigger on TCP packets that have just the SYN flag set, and would perform a few different actions on those packets. The inbound forest would only apply to TCP packets with the RST flag set, and would simply drop them. Each of the forests in the example are made up of a single (trigger, action tree) pair.

The outbound forest for the above action in graph form looks like this:

Inbound Forest Graph

In a forest, each action tree must adhere to the syntax [trigger]-action-|. The action is optional: canonical Geneva allows trigger-only passthrough trees such as [TCP:flags:A]-|, and parses strategies wrapped in a single pair of hanging double quotes (e.g. "\/ [TCP:flags:A]-drop-|"). The parser rejects branching actions (duplicate and fragment) in inbound trees because inbound evaluation is single-in/single-out.

Triggers

A trigger defines a way to match packets so that an action tree can be applied to them. In the example above, the first trigger is [TCP:flags:S]. This is a trigger that matches on the TCP segment's flags field, and requires that only the SYN flag be set. (Note that this trigger will not fire for packets that have, i.e., both SYN and ACK set.) If the packet is not a TCP packet, or the flags do not match exactly, then this trigger will not fire. As a compatibility note (matching canonical Geneva), earlier versions of this library treated a bare flag set such as [TCP:flags:A] as a subset match that fired whenever at least those bits were set; matching is now exact. To opt back into subset matching, append a *: [TCP:flags:A*] fires for any segment with ACK set.

Triggers may include a fourth, integer gas field. Positive gas bounds how many matching packets can fire the tree, zero disables it, and negative gas is a bomb that starts firing after that many matching packets. For example,

An empty trigger value is only valid where it denotes "no data": [TCP:load:] matches packets with no payload, and data-less options such as [TCP:options-sackok:] match their option whenever present. Empty values on all other fields are rejected when parsing or validating, since they could only ever produce a trigger that never fires. [TCP:flags:S:2] fires twice, while [TCP:flags:S:-2] suppresses two matches and fires from the third onward.

Actions

An action simply encodes steps to manipulate a packet. There are a number of actions described in the Geneva paper:

send

The "send" action simply yields the given packet. (A quirk—what the paper calls canonical syntax—is to elide any "send" actions in the action tree. For instance, the action "duplicate(,)" is equivalent to "duplicate(send,send)". Bear this in mind when reading Geneva strategies!)

drop

The "drop" action discards the given packet.

duplicate(a1, a2)

The "duplicate" action copies the original packet, then applies action a1 to the original and a2 to the copy. For example, if a1 and a2 are both "send" actions, then the action will yield two packets identical to the first.

fragment{protocol:offset:inOrder}(a1, a2)

The "fragment" action takes the original packet and fragments it, applying a1 to one of the fragments and a2 to the other. Since both the IP and TCP layers support fragmentation, the rule must specify which layer's payload to fragment. The first fragment will include up to offset bytes of the layer's payload; the second fragment will contain the rest. As an example, given an IPv4 packet with a 60-byte payload and an 8-byte offset, the first fragment will have the same IP header as the original packet (aside from the fields that must be fixed) and then the first eight bytes of the payload. The second fragment will contain the other 52 bytes. (You can also indicate that the fragments be returned out-of-order; i.e., reversed, by specifying "False" for the inOrder argument.) TCP fragmentation also supports an optional fourth overlap field, e.g., fragment{TCP:8:True:4}, which repeats that many bytes of payload in both fragments.

tamper{protocol:field:mode[:newValue]}(a1)

The "tamper" action takes the original packet and modifies it in some fashion, depending on the protocol, field, and mode given. There are three modes: replace, corrupt, and add. The "replace" mode will replace the value of the given field with newValue; the "corrupt" mode will replace the value with random data; and the "add" mode adds newValue to the field's current value, wrapping at the field's bit size. Add mode is only valid for numeric scalar fields (e.g., seq, ack, ttl); it is rejected for the flags bitmap, payload (load), options, and address fields. (Note that add is one of the modes the Python code supports that are not defined in the original Geneva paper.)

sleep{seconds}(a1)

The "sleep" action pauses for the given duration — expressed in (fractional) seconds, e.g., sleep{0.5} — before applying its child action, and therefore before the resulting packets are emitted. The pause is synchronous, so in a per-packet processing model it delays only the packet being processed. As in canonical Geneva, the child action is optional: sleep{1} is shorthand for sleep{1}(send).

Additionally, note that not all actions are valid for both inbound and outbound directions. The Python code mentions that "branching actions are not supported on inbound trees". Practically, this means that the duplicate and fragment actions can only be applied to outbound packets, while the drop, tamper, and sleep actions can apply to packets of either direction. The parser and Validate enforce this constraint.

Disclaimer

Currently only IPv4 and TCP are supported. There are plans to add support for UDP in the future (although pull requests are welcome! Look at TCPTamperAction and IPv4TamperAction in actions/tamper_action.go as examples. UDPTamperAction must implement the actions.Action interface). There are no plans at the moment to add support for IPv6.

Credits

See https://censorship.ai for more information about Geneva itself.

Documentation

Overview

Package geneva is a reimplementation of the client- and server-side rule processing mechanisms of the Geneva project.

Geneva is both a method to describe ways of manipulating packets to attempt to circumvent censorship, and a genetic algorithm (GENetic EVAsion) that one can deploy to discover new circumventions. The building blocks of that genetic algorithm—the strategy mutation and crossover operators—are implemented by the adjacent "mutate" package; the full evolution loop (population management, fitness evaluation, and selection) is left to callers. More broadly, one can encode arbitrary instructions for packet manipulation using Geneva rules as a sort of "standard syntax", although the use case outside of censorship circumvention may be somewhat tenuous.

This package aims to implement the same triggers and actions that the Geneva project's canonical Python package does.

Quick Background

Geneva rules are called "strategies". A strategy consists of zero or more "action trees" that can be applied to inbound or outbound packets. The actions trees define both a "trigger" and a tree of actions to take on a packet if the trigger matches. The result of an action tree will be zero or more packets that should replace the original packet, which then can be reinjected into the host OS' network stack.

Strategies, Forests, and Action Trees

Let's work from the top down. A strategy, conceptually, looks like this:

outbound-forest \/ inbound-forest

"outbound-forest" and "inbound-forest" are ordered lists of (trigger, action tree) pairs. The Geneva paper calls these ordered lists "forests". The outbound and inbound forests are separated by the "\/" characters (that is a backslash followed by a forward-slash); if the strategy omits one or the other, then that side of the "\/" is left empty. For example, a strategy that only includes an outbound forest would take the form "outbound \/", whereas an inbound-only strategy would be "\/ inbound".

The original Geneva paper does not have a name for these (trigger, action tree) pairs. In practice, however, the Python code actually defines an action tree as a (trigger, action) pair, where the "action" is the root of a tree of actions. This package follows this nomenclature as well.

A real example, taken from https://geneva.cs.umd.edu/papers/geneva_ccs19.pdf (pg 2202), would look like this:

[TCP:flags:S]-
    duplicate(
      tamper{TCP:flags:replace:SA}(send),
      send)-| \/
[TCP:flags:R]-drop-|

In this example, the outbound forest would trigger on TCP packets that have just the SYN flag set, and would perform a few different actions on those packets. The inbound forest would only apply to TCP packets with the RST flag set, and would simply drop them. Each of the forests in the example are made up of a single (trigger, action tree) pair.

In a forest, each action tree must adhere to the syntax "[trigger]-action-|". The action is optional: canonical Geneva allows trigger-only passthrough trees such as "[TCP:flags:A]-|", and parses strategies wrapped in a single pair of hanging double quotes.

Triggers

A trigger defines a way to match packets so that an action tree can be applied to them. In the example above, the first trigger is "[TCP:flags:S]". This is a trigger that matches on the TCP protocol's "flags" field, and requires that only the SYN flag be set. (Note that this trigger will not fire for packets that have, i.e., both SYN and ACK set.) If the packet is not a TCP packet, or the flags do not match exactly, then this trigger will not fire. As a compatibility note (matching canonical Geneva), earlier versions of this library treated a bare flag set such as "[TCP:flags:A]" as a subset match that fired whenever at least those bits were set; matching is now exact. To opt back into subset matching, append a "*": "[TCP:flags:A*]" fires for any segment with ACK set.

Triggers optionally accept a fourth "gas" field that limits how many matching packets the trigger can fire for: "[TCP:flags:S:3]" fires three times and then stops matching, while "[TCP:flags:S:0]" never fires. Negative gas is a "bomb": "[TCP:flags:S:-2]" suppresses its first two matches and then matches every subsequent packet indefinitely.

An empty trigger value is only valid where it denotes "no data": "[TCP:load:]" matches packets with no payload, and data-less options such as "[TCP:options-sackok:]" match their option whenever it is present. Empty values on all other fields are rejected when parsing or validating, since they could only ever produce a trigger that never fires.

Actions

An action simply encodes steps to manipulate a packet. There are a number of actions described in the Geneva paper:

send

The "send" action simply yields the given packet. (A quirk—or what the paper deems to be canonical syntax—is to elide any "send" actions in the action tree. For instance, the action "duplicate(,)" is equivalent to "duplicate(send,send)". Bear this in mind when reading Geneva strategies!)

drop

The "drop" action discards the given packet.

duplicate(a1, a2)

The "duplicate" action copies the original packet, then applies action a1 to the original and a2 to the copy. For example, if a1 and a2 are both "send" actions, then the action will yield two packets identical to the first.

fragment{protocol:offset:inOrder}(a1, a2)

The "fragment" action takes the original packet and fragments it, applying a1 to one of the fragments and a2 to the other. Since both the IP and TCP layers support fragmentation, the rule must specify which layer's payload to fragment. The first fragment will include up to "offset" bytes of the layer's payload; the second fragment will contain the rest. As an example, given an IPv4 packet with a 60-byte payload and an 8-byte offset, the first fragment will have the same IP header as the original packet (aside from the fields that must be fixed) and then the first eight bytes of the payload. The second fragment will contain the other 52 bytes. (You can also indicate that the fragments be returned out-of-order; i.e., reversed, by specifying "False" for the "inOrder" argument.) TCP fragmentation additionally supports an optional fourth "overlap" field, e.g., "fragment{TCP:8:True:4}", which repeats that many bytes of payload in both fragments.

tamper{protocol:field:mode[:newValue]}(a1)

The "tamper" action takes the original packet and modifies it in some fashion, depending on the protocol, field, and mode given. There are two modes: replace and corrupt. The "replace" mode will replace the value of the given field with newValue, while the "corrupt" mode will replace the value with random data. (Note that there are other modes that the Python code supports that are not defined in the original Geneva paper.)

Additionally, note that not all actions are valid for both inbound and outbound directions. The Python code mentions that "branching actions are not supported on inbound trees". Practically, this means that the duplicate and fragment actions can only be applied to outbound packets, while the drop and tamper actions can apply to packets of either direction.

See https://censorship.ai for more information about Geneva itself.

Index

Constants

This section is empty.

Variables

View Source
var Strategies = []string{
	"[TCP:flags:PA]-duplicate(tamper{TCP:dataofs:replace:10}(tamper{TCP:chksum:corrupt},),)-| \\/",
	"[TCP:flags:PA]-duplicate(tamper{TCP:dataofs:replace:10}(tamper{IP:ttl:replace:10},),)-| \\/",
	"[TCP:flags:PA]-duplicate(tamper{TCP:dataofs:replace:10}(tamper{TCP:ack:corrupt},),)-| \\/",
	"[TCP:flags:PA]-duplicate(tamper{TCP:options-wscale:corrupt}(tamper{TCP:dataofs:replace:8},),)-| \\/",
	"[TCP:flags:PA]-duplicate(tamper{TCP:load:corrupt}(tamper{TCP:chksum:corrupt},),)-| \\/",
	"[TCP:flags:PA]-duplicate(tamper{TCP:load:corrupt}(tamper{IP:ttl:replace:8},),)-| \\/",
	"[TCP:flags:PA]-duplicate(tamper{TCP:load:corrupt}(tamper{TCP:ack:corrupt},),)-| \\/",
	"[TCP:flags:S]-duplicate(,tamper{TCP:load:corrupt})-| \\/",
	"[TCP:flags:PA]-duplicate(tamper{IP:len:replace:64},)-| \\/",
	"[TCP:flags:A]-duplicate(,tamper{TCP:flags:replace:R}(tamper{TCP:chksum:corrupt},))-| \\/",
	"[TCP:flags:A]-duplicate(,tamper{TCP:flags:replace:R}(tamper{IP:ttl:replace:10},))-| \\/",
	"[TCP:flags:A]-duplicate(,tamper{TCP:options-md5header:corrupt}(tamper{TCP:flags:replace:R},))-| \\/",
	"[TCP:flags:A]-duplicate(,tamper{TCP:flags:replace:RA}(tamper{TCP:chksum:corrupt},))-| \\/",
	"[TCP:flags:A]-duplicate(,tamper{TCP:flags:replace:RA}(tamper{IP:ttl:replace:10},))-| \\/",
	"[TCP:flags:A]-duplicate(,tamper{TCP:options-md5header:corrupt}(tamper{TCP:flags:replace:R},))-| \\/",
	"[TCP:flags:A]-duplicate(,tamper{TCP:flags:replace:FRAPUEN}(tamper{TCP:chksum:corrupt},))-| \\/",
	"[TCP:flags:A]-duplicate(,tamper{TCP:flags:replace:FREACN}(tamper{IP:ttl:replace:10},))-| \\/",
	"[TCP:flags:A]-duplicate(,tamper{TCP:flags:replace:FRAPUN}(tamper{TCP:options-md5header:corrupt},))-| \\/",
	"[TCP:flags:PA]-fragment{tcp:8:False}-| [TCP:flags:A]-tamper{TCP:seq:corrupt}-| \\/",
	"[TCP:flags:PA]-fragment{tcp:8:True}(,fragment{tcp:4:True})-| \\/",
	"[TCP:flags:PA]-fragment{tcp:-1:True}-|  \\/",
	"[TCP:flags:PA]-duplicate(tamper{TCP:flags:replace:F}(tamper{IP:len:replace:78},),)-|  \\/",
	"[TCP:flags:S]-duplicate(tamper{TCP:flags:replace:SA},)-| \\/",
	"[TCP:flags:PA]-tamper{TCP:options-uto:corrupt}-|  \\/",

	"[TCP:options-sackok:]-tamper{TCP:dataofs:replace:7}-| \\/",
	"[TCP:options-sack::4]-fragment{tcp:-1:False}-| \\/",
	"[TCP:options-nop:]-tamper{TCP:urgptr:corrupt}(tamper{TCP:options-eol:corrupt},)-| \\/",
	"[TCP:options-nop:]-fragment{ip:-1:True:9}(drop,)-| \\/",
	"[TCP:urgptr:0]-duplicate-| \\/",
	"[TCP:dataofs:10:3]-tamper{TCP:options-mss:replace:17484}(fragment{tcp:-1:False},)-| \\/",
	"[TCP:options-sack:]-tamper{TCP:window:corrupt}(tamper{TCP:options-eol:corrupt},)-| \\/",
	"[TCP:options-altchksum:]-duplicate(fragment{ip:-1:False},)-| \\/",
	"[TCP:options-altchksumopt:]-fragment{tcp:-1:True}-| \\/",
	"[TCP:dataofs:8]-duplicate(duplicate,)-| \\/",
	"[TCP:options-md5header:]-duplicate-| \\/",
	"[TCP:options-md5header:]-fragment{tcp:-1:False}-| [TCP:options-wscale:7]-drop-| \\/",
	"[TCP:options-uto:]-duplicate(,tamper{TCP:load:replace:y0qgai1woz})-| \\/",
	"[TCP:options-sackok::1]-tamper{TCP:window:replace:120}(tamper{TCP:ack:corrupt},)-| \\/",
	"[TCP:load:]-tamper{TCP:options-uto:corrupt}(fragment{tcp:-1:False},)-| [TCP:options-uto:]-tamper{TCP:options-mss:replace:}-| \\/",
	"[TCP:options-wscale:]-tamper{TCP:options-nop:corrupt}(tamper{TCP:options-altchksum:replace:},)-| \\/",
	"[TCP:options-sack::1]-tamper{TCP:chksum:replace:22170}-| \\/",
	"[TCP:load:]-fragment{tcp:-1:False}(tamper{TCP:urgptr:replace:29},)-| \\/",
	"[TCP:urgptr:0]-fragment{tcp:-1:False}(duplicate,tamper{TCP:options-sackok:replace:})-| \\/",
	"[TCP:options-eol:]-tamper{TCP:window:corrupt}-| \\/",
	"[TCP:options-uto:]-tamper{TCP:options-altchksum:replace:90}(duplicate(tamper{TCP:options-sack:replace:},),)-| \\/",
	"[TCP:options-altchksumopt:]-tamper{TCP:options-uto:replace:}(tamper{TCP:load:corrupt}(fragment{tcp:-1:True}(,drop),),)-| \\/",
	"[TCP:options-altchksumopt:]-fragment{tcp:-1:False}(drop,duplicate)-| \\/",
	"[TCP:options-eol:]-tamper{TCP:urgptr:corrupt}(duplicate,)-| \\/",
	"[TCP:options-sack:]-duplicate(tamper{TCP:urgptr:corrupt}(fragment{tcp:-1:False},),)-| \\/",
	"[TCP:options-nop::2]-fragment{tcp:46:True}(fragment{tcp:-1:True},)-| \\/",
	"[TCP:chksum:26741]-duplicate(tamper{TCP:options-timestamp:replace:},)-| \\/",
	"[TCP:options-uto:]-duplicate(,tamper{TCP:options-nop:replace:})-| \\/",
	"[TCP:options-altchksum:]-tamper{TCP:options-wscale:replace:248}(tamper{TCP:options-sackok:replace:},)-| \\/",
	"[TCP:options-sackok:]-duplicate(duplicate(,tamper{TCP:load:replace:GET%20/%3Fq%3Dultrasurf%20HTTP/1.1%0D%0AHost%3A%2023.88.46.143%3A4228%0D%0AUser-Agent%3A%20python-requests/2.23.0%0D%0AAccept-Encoding%3A%20gzip%2C%20deflate%0D%0AAccept%3A%20%2A/%2A%0D%0AConnection%3A%20keep-alive%0D%0A%0D%0A}),duplicate)-| \\/",
	"[TCP:options-sackok::1]-fragment{tcp:29:False:14}(tamper{TCP:options-timestamp:replace:4263716593},)-| \\/",
	"[TCP:options-altchksumopt:]-tamper{TCP:options-nop:corrupt}(duplicate,)-| \\/",
}

Functions

func NewStrategy

func NewStrategy(st string) (*strategy.Strategy, error)

NewStrategy parses st into a Geneva strategy.

This is a convenience wrapper for strategy.ParseStrategy().

func Validate

func Validate(s *strategy.Strategy) error

Validate checks strategy DNA before it is proposed or deployed.

Types

This section is empty.

Directories

Path Synopsis
Package actions describes the actions that can be applied to a given packet.
Package actions describes the actions that can be applied to a given packet.
Package censorsim provides in-process simulations of the censors from the canonical Geneva project (its censors/ directory), for use as an end-to-end test harness.
Package censorsim provides in-process simulations of the censors from the canonical Geneva project (its censors/ directory), for use as an end-to-end test harness.
Package common provides common functions for Geneva.
Package common provides common functions for Geneva.
Package internal provides internal Geneva types and functions.
Package internal provides internal Geneva types and functions.
scanner
Package scanner is a token scanner tailored to this library.
Package scanner is a token scanner tailored to this library.
Package mutate provides dependency-free genetic operations for Geneva strategy ASTs.
Package mutate provides dependency-free genetic operations for Geneva strategy ASTs.
Package strategy provides types and functions for creating Geneva strategies.
Package strategy provides types and functions for creating Geneva strategies.
Package triggers enumerates all of the various triggers that can be used to match packets.
Package triggers enumerates all of the various triggers that can be used to match packets.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL