1.0.174 • Published 6 years ago

xudpmorpher v1.0.174

Weekly downloads
1
License
BSD-3-Clause
Repository
github
Last release
6 years ago

WindRibbon UDP Morpher (XUDPMorpher)

A UDP relay with datagram morpher functionality.

Introduction

In C/S system based on UDP (like OpenVPN), datagrams are sent through internet directly like following:

Figure 1 - UDP C/S dataflow

A DPI-box (or firewall) on-line may detects the protocol by capturing and analyzing the traffic and then interrupts the communication between the client and the server.

To get rid of this condition, we designed this system which works as a relay and has some other functions (like encryption, morphing, confusing and segmentation). See following dataflow diagram:

Figure 2 - Morphed UDP C/S dataflow

Requirements

PackageVersion
NodeJS>= 8.7.0

Installation

To install XUDPMorpher, type following NPM command:

npm install xudpmorpher -g

After installation, you can run xudpmorpher in your terminal, like (replace sample.json with the path of your own configuration file):

xudpmorpher --log-level=info --configuration sample.json

Components

NAT

The core of our system is a user-space NAT engine. In our system, a connection must be initiated by the client.

Imagine such a scenario, we a client (10.8.0.1), a server (10.8.0.3:1194) and our relay (10.8.0.2:1194). The client want to send a datagram to the server, so it creates a datagram socket on its local port (for example 1000), writes data to the datagram and then send it to our relay (10.8.0.2:1194). The relay assign a new port (for example 2000) for the client (10.8.0.1:1000) and then send the datagram with source address (10.8.0.2:2000). When the reply datagram comes, the relay changes the source address back to the relay address (10.8.0.2:1194) and send it back to the client (10.8.0.1:1000).

In our system, we maintain an internal NAT table, each NAT rule has a timeout. If related port keeps idle for a long time, we will close it.

Processors

Before sending or after receiving a datagram, the system will deliver the datagram to a series of processor. The processors will process the datagram one-by-one and then deliver it to another side.

In our system, we use JSON to configure the processors. Before using any processor, you must configure it properly.

Filter

This kind of processors can filter datagrams that matches specified rules (such as datagram size limit).

Threshold

This processor is used to filter out datagrams that are too small or too large. Only datagrams within specified size range can bypass this filter processor.

To configure the processor, use following configuration:

{
    "type": "filter/threshold",
    "min": [The minimum datagram size],
    "max": [The maximum datagram size]
}
Bandwidth

This processor can shape the UDP datagram traffic under specific bandwidth. You can always use this filter to limit the speed of the UDP datagram traffic.

To configure the processor, use following configuration:

{
    "type": "filter/bandwidth",
    "bandwidth": [The bandwidth limit (B/s)]
}

Permutate

This processor can be used in changing the position of critical fields of specified protocol. So that the firewall can not identify these fields and then we would bypass the firewall.

The "permutate" processor generates a permutation by using a seed (or you can think of it as a key) and then use the permutation to rearrange the order of the data.

For example, we have a seed received a datagram:

iINi
0'H'
1'e'
2'l'
3'l'
4'o'

And then we will use the seed to generate a permutation (with the same length as the datagram). Note that when the seed and length are unchanged, the resulting permutation also unchanged.

Here is our permutation:

iPi
04
11
20
33
42

In non-inverse mode, we use following rules to generate the output datagram (OUT0..4):

OUT[i] = IN[P[i]]

In inverse mode, we use following rules instead:

OUT[P[i]] = IN[i]

Note that if you send a datagram to a non-inverse mode permutate processor and then send the output again to a inverse mode permutate processor with the same key, you will get a datagram with the same content as the original datagram.

For example, in above example, we will get following output "oeHll" if we pass the original datagram to a non-inverse mode permutate processor. Then pass "oeHll" to a inverse mode permutate processor, you will get "Hello" which is the content of the original datagram.

In real world, generating permutation is very time-expensive. So it is better of you to use cache to speed your application up.

To configure the processor, use following configuration:

{
    "type": "permutate",
    "seed": "[Your seed]",
    "inverse": false (or true to enable inverse mode),
    "rounds": 1 (how many rounds to generate a permutation, default = 1),
    "cache": {
        "enable": true (or false to disable),
        "timeout": 60000 (expiry of each cache record),
        "size": 65536 (max count of cached records)
    }
}

Segmenter

In some conditions, you may want to split a datagram into several datagrams with some defined rules in one side and reassemble them in another side. For example, you may want to split a large datagram to some smaller ones to get rid of IP fragmentation. In some authorization states, this processor can also be used to bypass firewall (by changing the size of some critical datagrams).

The segmenter contains two parts – the disassembler and the assembler.

Disassembler

Here is the data flow diagram of the disassembler processor:

Figure 3 - Disassembler processor

The input datagram will be delivered to both the disassembler and the fragment length generator. The fragment length generator will generates a series of fragment length numbers and pass it to the disassembler. Then the disassembler will split the input datagram, add the header and send it out.

Here is the splitting algorithm:

  1. Pass the count of remaining bytes (X) to the fragment length generator.
  2. The fragment length generator will return an integer Y.
  3. If X is less than or equal to Y, add some random padding (with length Y-X) to the tail of remaining bytes, send them and quit.
  4. If X is larger than Y, split the first Y bytes off from the remaining bytes, send them and repeat step (1).

The fragment length generator use an algorithm chain to generate fragment length numbers. Currently we have following algorithms.

Fixed

This algorithm has two modes – "set" and "increase".

In "set" mode, we will return a fixed integer. In "increase" mode, we will add a fixed incremental to the input and then return it.

For example, in "set" mode, if the input is 1000 and the fixed value is 500, you will get the result 500. In "increase" mode, you will get the result 1500.

Use following JSON to configure this algorithm:

{
    "type": "fixed",
    "mode": "set" (or "increase"),
    "value": [The value]
}
Random

Use a pseudo-random number generator (PRNG) to generate an integer as the incremental of the input. For example, if the PRNG generates 1, 2, 3, 4 and the inputs are 10, 20, 30, 40, the output will be 11, 22, 33, 44.

The algorithm accepts 4 parameters, includes the min/max value of generated integers, the seed and the probability distribution segment count. Generally, the segment count and the entropy are inversely related.

Use following JSON to configure this algorithm:

{
    "type": "random",
    "min": [Min value],
    "max": [Max value],
    "segments": [Segment count (1 <= K <= max - min + 1)]
}
Round-robin

The algorithm adds an integer X to the input. X was drawn from an array cyclically.

Use following JSON to configure this algorithm:

{
    "type": "round-robin",
    "values": [The array]
}
Threshold

The algorithm give thresholds to the input value. If the min threshold was set and the input value is lower than that, the output value will be set to the min threshold. Similarly, if the max threshold was set and the input value is larget that than, the output value will be set to the max threshold. Otherwise, the output will be the same as the input value.

Use following JSON to configure this algorithm:

{
    "type": "threshold",
    "min": [Min threshold] (Optional),
    "max": [Max threshold] (Optional)
}

To chain algorithms, you should use an array to wrap all selected algorithm configurations. For example:

[
    {
        "type": "fixed",
        "mode": "increase",
        "value": 64
    },
    {
        "type": "random",
        "seed": "Lovely Seed",
        "min": -64,
        "max": 64
    },
    {
        "type": "round-robin",
        "values": [0, 59, 7, 38, 62, 19]
    },
    {
        "type": "threshold",
        "max": 1400
    }
][
    {
        "type": "fixed",
        "mode": "increase",
        "value": 64
    },
    {
        "type": "random",
        "seed": "Lovely Seed",
        "min": -64,
        "max": 64
    },
    {
        "type": "round-robin",
        "values": [0, 59, 7, 38, 62, 19]
    },
    {
        "type": "threshold",
        "max": 1400
    }
]

After configuring the algorithm chain of the fragment length generator, you can configure the disassembler with following configuration:

{
    "type": "segmenter/disassembler",
    "max-datagram": [Max size of the output datagram] (Optional, 65507 by default), 
    "pipeline": [The algorithm chain]
}

Here is a full example:

{
    "type": "segmenter/disassembler",
    "max-datagram": 1420,
    "pipeline": [
        {
            "type": "fixed",
            "mode": "increase",
            "value": 64
        },
        {
            "type": "random",
            "seed": "Lovely Seed",
            "min": -64,
            "max": 64
        },
        {
            "type": "round-robin",
            "values": [0, 59, 7, 38, 62, 19]
        },
        {
            "type": "threshold",
            "max": 1400
        }
    ]
}
Assembler

This processor will reassemble fragments sent by the disassembler processor.

To configure the processor, use following configuration:

{
    "type": "segmenter/assembler",
    "timeout": [How long does a assembler record expire] (In milliseconds),
    "max-datagram": [Max size of the output datagram] (Optional, 65507 by default),
}

Cryptography

Cipher

This processor will encrypt input datagram with specified cryptography algorithm and password. Currently, we supports following algorithm(s):

  • RC4
  • AES-128-CBC
  • AES-192-CBC
  • AES-256-CBC

To configure the processor, use following configuration (use lower-case algorithm name only, e.g. "rc4"):

{
    "type": "cryptography/cipher",
    "algorithm": "[The algorithm]",
    "password": "[The password]"
}
Decipher

This processor will decrypt input datagrams that encrypted by the cipher.

{
    "type": "cryptography/decipher",
    "algorithm": "[The algorithm]",
    "password": "[The password]"
}

Bypass

This processor will bypass all input datagrams.

Since it does nothing, it also doesn't need much configuring. If you want to use this processor, just use following configuration with no change:

{
    "type": "bypass"
}

Configuration

Here is the structure of a configuration file:

{
    "local": {
        "type": "udp4" (or "udp6" for IPv6),
        "address": "[Local address]",
        "port": [Local port],
        "buffer": {
            "send": [Value for SO_SNDBUF option],
            "receive": [Value for SO_RCVBUF option]
        },
        "ttl": [TTL of output datagrams],
        "reuse": true (or false to disable SO_REUSEADDR option)
    },
    "remote": {
        "type": "udp4" (or "udp6" for IPv6),
        "address": "[Remote address]",
        "port": [Remote port],
        "buffer": {
            "send": [Value for SO_SNDBUF option],
            "receive": [Value for SO_RCVBUF option]
        },
        "ttl": [TTL of output datagrams],
        "timeout": 360000, (NAT rule timeout, in milliseconds)
        "reuse": true (or false to disable SO_REUSEADDR option)
    },
    "buffer": {
        "upstream": {
            "max-size": [Max size of buffered upstream datagrams], (Optional)
            "max-count": [Max count of buffered upstream datagrams] (Optional)
        },
        "downstream": {
            "max-size": [Max size of buffered downstream datagrams], (Optional)
            "max-count": [Max count of buffered downstream datagrams] (Optional)
        }
    },
    "processor": {
        "upstream": [Processors for upstream],
        "downstream": [Processors for downstream]
    }
}

See sample configuration files in "examples" directory for more details.