[tproxy] tproxy in newer 2.6 kernels

Lennert Buytenhek buytenh at wantstofly.org
Tue Jul 11 11:38:43 CEST 2006


Having to very suddenly (due to hardware failure) upgrade an old 2.4
tproxy box to a stock 2.6 distro kernel box, I was faced with the
problem of the upstream 2.6 kernel (and thus, the distro kernel) not
supporting tproxy.

REDIRECT functionality does work upstream, but TCP source address
spoofing can only be achieved with iptables SNAT.  So, I implemented
a small module that can insert SNAT rules into nat/POSTROUTING, and
our proxy (in-house program) now uses that instead of tproxy.  This
works well for us since we don't need all the other functionality
that tproxy provides, but of course, YMMV.

I've attached the code we use.  It uses libiptc, and may look kind of
weird since it was chainsawed from a (much) bigger piece of code, but
you'll get the idea.  (It flushes and inserts rules in POSTROUTING by
default, if that's not appropriate, make a new chain and add a rule to
POSTROUTING to jump to that chain.)  It's not been tested incredibly
extensively, but since the proxy has been processing 4000-5000
concurrent connections at 200-300 connections per second without a
hitch for about 12 hours now, it's at least not completely broken.

-------------- next part --------------
 * Copyright (C) 2006 Lennert Buytenhek
 * Use and distribution permitted according to the terms of the LGPL.

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <sched.h>
#include <syslog.h>
#include <libiptc/libiptc.h>
#include </usr/include/linux/netfilter_ipv4/ip_conntrack_tuple.h>
#include </usr/include/linux/netfilter_ipv4/ip_nat.h>
#include "tcp_snat.h"

struct snat_rule
	struct ipt_entry			basic_rule;
	struct {
		struct ipt_entry_match		match;
		struct ipt_tcp			tcp;
	}					tcp_match;
	struct {
		struct ipt_entry_target		target;
		struct ip_nat_multi_range	mr;
	}					nat_target;

static void build_snat_rule(struct snat_rule *rule, struct tcp_snat_request *tsr)
	memset(rule, 0, sizeof(*rule));

	rule->basic_rule.ip.src.s_addr = tsr->orig_src_ip;
	rule->basic_rule.ip.dst.s_addr = tsr->dst_ip;
	rule->basic_rule.ip.smsk.s_addr = htonl(0xffffffff);
	rule->basic_rule.ip.dmsk.s_addr = htonl(0xffffffff);
#if 0
	strcpy(rule->basic_rule.ip.iniface, "eth0");
	strcpy(rule->basic_rule.ip.outiface, "eth1");
	memcpy(rule->basic_rule.ip.iniface_mask, "\xff\xff\xff\xff", 4);
	memcpy(rule->basic_rule.ip.outiface_mask, "\xff\xff\xff\xff", 4);
	rule->basic_rule.ip.proto = IPPROTO_TCP;
	rule->basic_rule.ip.flags = 0;
	rule->basic_rule.ip.invflags = 0;
	rule->basic_rule.nfcache = NFC_UNKNOWN;
	rule->basic_rule.target_offset = sizeof(rule->basic_rule) +
	rule->basic_rule.next_offset = sizeof(rule->basic_rule) +
		sizeof(rule->tcp_match) + sizeof(rule->nat_target);
	rule->basic_rule.comefrom = 0;
	rule->basic_rule.counters = (struct ipt_counters){ 0, 0 };

	rule->tcp_match.match.u.user.match_size = sizeof(rule->tcp_match);
	strcpy(rule->tcp_match.match.u.user.name, "tcp");
	rule->tcp_match.tcp.spts[0] = tsr->src_port;
	rule->tcp_match.tcp.spts[1] = tsr->src_port;
	rule->tcp_match.tcp.dpts[0] = tsr->dst_port;
	rule->tcp_match.tcp.dpts[1] = tsr->dst_port;
	rule->tcp_match.tcp.option = 0;
	rule->tcp_match.tcp.flg_mask = 0;
	rule->tcp_match.tcp.flg_cmp = 0;
	rule->tcp_match.tcp.invflags = 0;

	rule->nat_target.target.u.user.target_size = sizeof(rule->nat_target);
	strcpy(rule->nat_target.target.u.user.name, "SNAT");
	rule->nat_target.mr.rangesize = 1;
	rule->nat_target.mr.range[0].flags = IP_NAT_RANGE_MAP_IPS;
	rule->nat_target.mr.range[0].min_ip = tsr->new_src_ip;
	rule->nat_target.mr.range[0].max_ip = tsr->new_src_ip;
	rule->nat_target.mr.range[0].min.tcp.port = 0;
	rule->nat_target.mr.range[0].max.tcp.port = 0;

static int try_to_insert_rule(ipt_chainlabel chain, struct snat_rule *rule)
	iptc_handle_t table;

	table = iptc_init("nat");
	if (table == NULL) {
		if (errno == EINVAL)
			return 0;
		syslog(LOG_CRIT, "iptc_init: %s\n", iptc_strerror(errno));

	if (!iptc_flush_entries(chain, &table)) {
		syslog(LOG_CRIT, "iptc_flush_entries: %s\n",

	if (!iptc_append_entry(chain, (struct ipt_entry *)rule, &table)) {
		syslog(LOG_CRIT, "iptc_append_entry: %s\n",

	if (!iptc_commit(&table)) {
		if (errno == EAGAIN || errno == EINVAL)
			return 0;
		syslog(LOG_ALERT, "iptc_commit: %s\n", iptc_strerror(errno));

	return 1;

int insert_snat_rule(struct tcp_snat_request *tsr)
	ipt_chainlabel chain;
	struct snat_rule rule;
	int ok;
	int i;

	memset(&chain, 0, sizeof(chain));
	strcpy(chain, "POSTROUTING");

	build_snat_rule(&rule, tsr);

	ok = 0;
	for (i = 0; i < 5; i++) {
		if (try_to_insert_rule(chain, &rule)) {
			ok = 1;

	return ok;
-------------- next part --------------
 * Copyright (C) 2006 Lennert Buytenhek
 * Use and distribution permitted according to the terms of the LGPL.

#ifndef __TCP_SNAT_H
#define __TCP_SNAT_H

#include <libiptc/libiptc.h>
#include </usr/include/linux/netfilter_ipv4/ip_conntrack_tuple.h>
#include </usr/include/linux/netfilter_ipv4/ip_nat.h>

 * iptables -t nat -A POSTROUTING -s $orig_src_ip -d $dst_ip -p tcp -m tcp
 *     --sport $src_port --dport $dst_port -j SNAT --to-source $new_src_ip
struct tcp_snat_request
	u_int32_t		dst_ip;
	u_int16_t		dst_port;
	u_int32_t		orig_src_ip;
	u_int16_t		src_port;
	u_int32_t		new_src_ip;

int insert_snat_rule(struct tcp_snat_request *tsr);


More information about the tproxy mailing list