Skip to content

Instantly share code, notes, and snippets.

@xiangchu0
Last active October 26, 2021 07:18
Show Gist options
  • Save xiangchu0/c03843fb83799a4ccd8766bfe84e859d to your computer and use it in GitHub Desktop.
Save xiangchu0/c03843fb83799a4ccd8766bfe84e859d to your computer and use it in GitHub Desktop.
tproxy example

本文是对不同平台不同 IP packet 过滤程序透明代理实现的一些思考,主要考虑过滤规则以及如何在代理程序中获取客户端网络请求的原目标 IP 地址。

参数、函数的解释

dst_addr:所需获取的目标地址;

client_addr:客户端请求的源地址,可通过 accept() 获取;

local_addr:代理程序绑定的地址,在 bind() 调用中传入的地址;

log_printf():类比 printf(),只不过用来输出信息至日志文件,这里没有给出具体的实现。

Netfilter

应用于 Linux kernel 2.4+。

过滤规则

TCP 过滤规则用了 REDIRECT 规则,UDP 过滤规则用了 TPROXY 规则,TCP 同样也可以使用 TPROXY 规则过滤。需要注意的是,使用 TPROXY 规则时,在写代理程序时需要 setsockopt(IP_TRANSPARENT),具体见下文 TPROXY 方案获取目标 IP 地址。

设置规则可参阅 iptables(8)、Netfilter 文档。

# Create TRANSPARENT_PROXY chain
iptables -t nat -N TRANSPARENT_PROXY
iptables -t mangle -N TRANSPARENT_PROXY

# Bypass remote proxy server addresses
iptables -t nat -A TRANSPARENT_PROXY -d $SERVER_IP -j RETURN
iptables -t mangle -A TRANSPARENT_PROXY -d $SERVER_IP -j RETURN

# Bypass LAN and any other addresses if desired
# Here is an example to bypass local identification ip addresses
iptables -t nat -A TRANSPARENT_PROXY -d 0.0.0.0/8 -j RETURN
iptables -t mangle -A TRANSPARENT_PROXY -d 0.0.0.0/8 -j RETURN

# The remainder should be redirect to local proxy port
# Here assumes local proxy server is running on localhost
## Port forwarding for TCP
iptables -t nat -A TRANSPARENT_PROXY -p tcp -j REDIRECT --to-ports $PROXY_PORT
## Port forwarding for UDP
ip rule add fwmark 1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100
iptables -t mangle -A TRANSPARENT_PROXY -p udp -j TPROXY --on-port $PROXY_PORT --tproxy-mark 0x1/0x1

# Add TRANSPARENT_PROXY chain to iptables PREROUTING chain
iptables -t nat -A PREROUTING -p tcp -j TRANSPARENT_PROXY
iptables -t mangle -A PREROUTING -p udp -j TRANSPARENT_PROXY

获取目标 IP 地址

使用 TPROXY 方案,获取原目标 IP 地址只需简单调用 getsockname(),与下文通过 IPFW 的实现透明代理方案一样。

#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

static int set_transparent(int fd) {
  int opt = 1;

  if (setsockopt(fd, SOL_IP, IP_TRANSPARENT, &opt, sizeof(opt))) {
    log_printf("%s - setsockopt(IP_TRANSPARENT): %s\n", __func__, strerror(errno));
    return -1;
  }
  return 0;
}

static int
get_dst_addr(int fd, struct sockaddr *dst_addr, socklen_t *dst_addrlen) {
  if (getsockname(fd, dst_addr, dst_addrlen)) {
    log_printf("%s - getsockname(dst_addr): %s\n", __func__, strerror(errno));
    return -1;
  }
  return 0;
}

使用普通方案,则可通过调用 getsockopt(SO_ORIGINAL_DST)(IPv4 地址)和 getsockopt(IP6T_SO_ORIGINAL_DST)(IPv6 地址)获取。

#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/netfilter_ipv4.h>
#include <linux/netfilter_ipv6/ip6_tables.h>

static int
get_dst_addr(int fd, struct sockaddr *dst_addr, socklen_t *dst_addrlen, sa_family_t sa_type) {
  if (sa_type == AF_INET) {
    if (getsockopt(fd, SOL_IP, SO_ORIGINAL_DST, dst_addr, dst_addrlen)) {
      log_printf("%s - getsockopt(SO_ORIGINAL_DST): %s\n", __func__, strerror(errno));
      return -1;
    }
  } else {
#ifdef IP6T_SO_ORIGINAL_DST
    if (getsockopt(fd, SOL_IPV6, IP6T_SO_ORIGINAL_DST, dst_addr, dst_addrlen)) {
      log_printf("%s - getsockopt(IP6T_SO_ORIGINAL_DST): %s\n", __func__, strerror(errno));
      return -1;
    }
#else
    log_printf("The Netfilter does not support IPv6 NAT lookup.\n");
    return -1;
#endif
  }
  return 0;
}

Packet Filter

出自 OpenBSD,应用于 OpenBSD 3.0+、FreeBSD 5.3+、NetBSD 3.0+、Solaris 11.3+、macOS 10.7+、iOS 和 QNX。

过滤规则

以下规则应用于 TCP 过滤,UDP 过滤可以用同样方式设置,PF 规则具体设置见 pf.conf(5)、FreeBSD 相关文档。FreeBSD 中使用的 PF 是 OpenBSD 4.5 的版本。OpenBSD 可以在 pass 规则增加 rdr-to 规则,可省去第一条规则,可参阅 OpenBSD 文档。macOS 中的 PF 与 FreeBSD 的更加相似。

# Here it assumes that local network interface is en0.
# rdr rules could not work on local outbound traffic directly.
# Route en0's traffic to lo0, then the original outbound traffic becomes inbound traffic, and rdr rules work.

# Redirect traffic to proxy port
rdr on lo0 proto tcp from en0 to any -> 127.0.0.1 port $PROXY_PORT

# Bypass remote proxy server addresses
pass out quick on en0 proto tcp from any to $SERVER_IP
# Route en0's traffic to lo0
pass out quick on en0 route-to lo0 proto tcp from any to any

获取目标 IP 地址

调用 open() 以只读方式打开 PF 设备文件,如果需要动态地加入过滤规则可以使用可读写方式。之后,主要通过 ioctl(DIOCNATLOOK) 实现,其它与 PF 设备相关动态的操作可参阅 pf(4)。实际上文档中的内容还是不够详细,直接在 net/pfvar.h 看结构体的声明反而更加直观。

#include <errno.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#ifdef __APPLE__
#define PRIVATE
#endif
#include <net/pfvar.h>
#ifdef __APPLE__
#undef PRIVATE
#endif

static int pffd = -1;

static int
pf_init(void) {
  pffd = open("/dev/pf", O_RDONLY);
  if (pffd) {
    log_printf("%s - open(\"/dev/pf\"): %s\n", __func__, strerror(errno));
    return -1;
  }
  if (fcntl(pffd, F_SETFD, fcntl(pffd, F_GETFD) | FD_CLOEXEC)) {
    log_printf("%s - fcntl(F_SETFD): %s\n", __func__, strerror(errno));
    return -1;
  }
  return 0;
}

static int
get_dst_addr(int fd, struct sockaddr *dst_addr, socklen_t *dst_addrlen,
             struct sockaddr *client_addr, struct sockaddr *local_addr,
             sa_family_t sa_type) {
#ifdef __APPLE__
#define sport sxport.port
#define dport dxport.port
#define rdport rdxport.port
#ifdef v4addr // XNU 4570.1.46 and later (macOS 10.13+, iOS 11+)
#define v4 v4addr
#define v6 v6addr
#endif // XNU 4570.1.46 and later (macOS 10.13+, iOS 11+)
#endif // __APPLE__
  struct pfioc_natlook pnl;

  bzero(&pnl, sizeof(pnl));
  pnl.af = sa_type;
  if (sa_type == AF_INET) {
    struct sockaddr_in *src_addr = (struct sockaddr_in *)client_addr;
    struct sockaddr_in *bind_addr = (struct sockaddr_in *)local_addr;
    bcopy(&src_addr->sin_addr, &pnl.saddr.v4, sizeof(pnl.saddr.v4));
    pnl.sport = src_addr->sin_port;
    bcopy(&bind_addr->sin_addr, &pnl.daddr.v4, sizeof(pnl.daddr.v4));
    pnl.dport = bind_addr->sin_port;
  } else if (sa_type == AF_INET6) {
    struct sockaddr_in6 *src_addr = (struct sockaddr_in6 *)client_addr;
    struct sockaddr_in6 *bind_addr = (struct sockaddr_in6 *)local_addr;
    bcopy(&src_addr->sin6_addr, &pnl.saddr.v6, sizeof(pnl.saddr.v6));
    pnl.sport = src_addr->sin6_port;
    bcopy(&bind_addr->sin6_addr, &pnl.daddr.v6, sizeof(pnl.daddr.v6));
    pnl.dport = bind_addr->sin6_port;
  }
  pnl.proto = IPPROTO_TCP;
  pnl.direction = PF_OUT;

  if (ioctl(pffd, DIOCNATLOOK, &pnl)) {
    log_printf("%s - ioctl(DIOCNATLOOK): %s\n", __func__, strerror(errno));
    return -1;
  }

  if (sa_type == AF_INET) {
    struct sockaddr_in *dst_addr_in = (struct sockaddr_in *)dst_addr;
    dst_addr_in->sin_family = sa_type;
    bcopy(&pnl.rdaddr.v4, &dst_addr_in->sin_addr, sizeof(dst_addr_in->sin_addr));
    dst_addr_in->sin_port = pnl.rdport;
  } else if (sa_type == AF_INET6) {
    struct sockaddr_in6 *dst_addr_in6 = (struct sockaddr_in6 *)dst_addr;
    dst_addr_in6->sin6_family = sa_type;
    bcopy(&pnl.rdaddr.v6, &dst_addr_in6->sin6_addr, sizeof(dst_addr_in6->sin6_addr));
    dst_addr_in6->sin6_port = pnl.rdport;
  }

  return 0;
#ifdef __APPLE__
#undef sport
#undef dport
#undef rdport
#ifdef v4addr // XNU 4570.1.46 and later (macOS 10.13+, iOS 11+)
#undef v4
#undef v6
#endif // XNU 4570.1.46 and later (macOS 10.13+, iOS 11+)
#endif // __APPLE__
}

IPFilter

应用于 FreeBSD 2.2+、NetBSD 1.2+、Solaris 10+、illumos 和 QNX。

IPFilter 过滤规则语法看起来与 PF 差不多,我还没仔细看,FreeBSD 文档也可以作为参考。

获取目标 IP 地址流程也差不多,打开设备文件,然后 ioctl(SIOCGNATL) ,参阅 ipnat(4)。

#include <errno.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/ipl.h>
#include <netinet/ip_compat.h>
#include <netinet/ip_fil.h>
#include <netinet/ip_nat.h>

static int ipfilter_fd = -1;

static int
ipfilter_init(void) {
  ipfilter_fd = open(IPNAT_NAME, O_RDONLY);
  if (ipfilter_fd) {
    log_printf("%s - open(IPNAT_NAME): %s\n", __func__, strerror(errno));
    return -1;
  }
  if (fcntl(ipfilter_fd, F_SETFD, fcntl(ipfilter_fd, F_GETFD) | FD_CLOEXEC)) {
    log_printf("%s - fcntl(F_SETFD): %s\n", __func__, strerror(errno));
    return -1;
  }
  return 0;
}

static int
get_dst_addr(int fd, struct sockaddr *dst_addr, socklen_t *dst_addrlen,
             struct sockaddr *client_addr, struct sockaddr *local_addr,
             sa_family_t sa_type) {
  struct natlookup nl;
  struct ipfobj ipfo;

  bzero(&nl, sizeof(nl));
  if (sa_type == AF_INET) {
    struct sockaddr_in *src_addr = (struct sockaddr_in *)client_addr;
    struct sockaddr_in *bind_addr = (struct sockaddr_in *)local_addr;
    nl.nl_outip = src_addr->sin_addr;
    nl.nl_outport = src_addr->sin_port;
    nl.nl_inip = bind_addr->sin_addr;
    nl.nl_inport = bind_addr->sin_port;
  } else {
    log_printf("The IPFilter does not support IPv6 NAT lookup.\n");
    return -1;
  }
  nl.nl_flags = IPN_TCP;

  bzero(&ipfo, sizeof(ipfo));
  ipfo.ipfo_rev = IPFILTER_VERSION;
  ipfo.ipfo_size = sizeof(nl);
  ipfo.ipfo_ptr = &nl;
  ipfo.ipfo_type = IPFOBJ_NATLOOKUP;

  if (ioctl(ipfilter_fd, SIOCGNATL, &ipfo)) {
    log_printf("%s - ioctl(SIOCGNATL): %s\n", __func__, strerror(errno));
    return -1;
  }

  struct sockaddr_in *dst_addr_in = (struct sockaddr_in *)dst_addr;
  dst_addr_in->sin_family = AF_INET;
  dst_addr_in->sin_addr = nl.nl_realip;
  dst_addr_in->sin_port = nl.nl_realport;

  return 0;
}

IPFW

出自 FreeBSD,应用于 FreeBSD、macOS 10.6-(macOS 10.10 完全移除),也曾被用于 Linux kernel 1.1,为 Linux 第一代防火墙。

IPFW 过滤规则暂时还没看,可参考 FreeBSD IPFW 相关文档。

原目标 IP 地址的获取与上文 Netfilter 的 TPROXY 方案一样。

#include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h>

static int get_dst_addr(int fd, struct sockaddr *dst_addr, socklen_t *dst_addrlen) { if (getsockname(fd, dst_addr, dst_addrlen)) { log_printf("%s - getsockname(dst_addr): %s\n", func, strerror(errno)); return -1; } return 0; }

NPF

出自 NetBSD,应用于 NetBSD 6.0+。

规则设置文档

提供的部分 API 可参考文档 libnpf(3),但是并不齐全,以下用到的 npf_nat_lookup() 就没有列举在其中,直接看源码——lib/libnpf/npf.h、lib/libnpf/npf.c。

以下 get_dst_addr() 中的 dst_addr 为 inout 参数,应传入 client_addr,调用函数后其值为所需的 dst_addr。

#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/pfil.h>
#include <npf.h>

static int
get_dst_addr(int fd, struct sockaddr *dst_addr, struct sockaddr *local_addr, sa_family_t sa_type) {
  npf_addr_t *addr[2];
  in_port_t port[2];

  if (sa_type == AF_INET) {
    struct sockaddr_in dst_addr_in = (struct sockaddr_in *)dst_addr;
    struct sockaddr_in local_addr_in = (struct sockaddr_in *)local_addr;
    addr[0] = (npf_addr_t *)&dst_addr_in->sin_addr;
    port[0] = dst_addr_in->sin_port;
    addr[1] = (npf_addr_t *)&local_addr_in->sin_addr;
    port[1] = local_addr_in->sin_port;

    if (npf_nat_lookup(fd, AF_INET, addr, port, IPPROTO_TCP, PFIL_OUT)) {
      log_printf("%s - npf_nat_lookup(): %s\n", __func__, strerror(errno));
      return -1;
    }
    dst_addr_in->sin_port = port[0];
    local_addr_in->sin_port = port[1];
  } else if (sa_type == AF_INET6) {
    struct sockaddr_in6 dst_addr_in6 = (struct sockaddr_in6 *)dst_addr;
    struct sockaddr_in6 local_addr_in6 = (struct sockaddr_in6 *)local_addr;
    addr[0] = (npf_addr_t *)&dst_addr_in6->sin6_addr;
    port[0] = dst_addr_in6->sin6_port;
    addr[1] = (npf_addr_t *)&local_addr_in6->sin6_addr;
    port[1] = local_addr_in6->sin6_port;

    if (npf_nat_lookup(fd, AF_INET, addr, port, IPPROTO_TCP, PFIL_OUT)) {
      log_printf("%s - npf_nat_lookup(): %s\n", __func__, strerror(errno));
      return -1;
    }
    dst_addr_in6->sin6_port = port[0];
    local_addr_in6->sin6_port = port[1];
  }
  return 0;
}
/*
* # iptables -t mangle -N DIVERT
* # iptables -t mangle -A PREROUTING -p tcp -m socket -j DIVERT
* # iptables -t mangle -A DIVERT -j MARK --set-mark 1
* # iptables -t mangle -A DIVERT -j ACCEPT
* # ip rule add fwmark 1 lookup 100
* # ip route add local 0.0.0.0/0 dev lo table 100
* # iptables -t mangle -A PREROUTING -p tcp -d 29.0.0.0/24 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 8888
* ./tcp_tproxy 8888 0
*/
#include <signal.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <limits.h>
#include <unistd.h>
#include <event.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <err.h>
#include <string.h>
int DEBUG = 0;
void debug_print(const char *fmt, ...)
{
if (DEBUG == 0)
return;
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
}
struct event_base *base;
#define CACHE_BUF_SIZE 65536
struct conn_s {
struct event *in_read_ev;
struct event *in_write_ev;
struct event *out_read_ev;
struct event *out_write_ev;
struct event_base *base;
int in_fd;
int out_fd;
int in_buf_size;
int out_buf_size;
int in_left_size;
int out_left_size;
int tr_fd;
int state;
char *in_buf;
char *out_buf;
int in_write;
int out_write;
};
typedef struct conn_s conn_t;
static inline ssize_t /* Write "n" bytes to a descriptor. */
write_nonblocking(int fd, const void *vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ((nwritten = write(fd, ptr, nleft)) <= 0) {
if (nwritten < 0 && (errno == EINTR))
nwritten = 0; /* and call write() again */
else if (nwritten < 0 && (errno == EAGAIN))
return n - nleft;
else
return (-1); /* error */
}
nleft -= nwritten;
ptr += nwritten;
}
return (n);
}
static inline ssize_t /* Read "n" bytes from a descriptor. */
read_nonblocking(int fd, void *vptr, size_t n)
{
size_t nleft;
ssize_t nread;
char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ((nread = read(fd, ptr, nleft)) < 0) {
if (errno == EINTR)
nread = 0; /* and call read() again */
else if (errno == EAGAIN)
return n - nleft;
else
return -1;
} else if (nread == 0)
break; /* EOF */
nleft -= nread;
ptr += nread;
}
return n - nleft; /* return >= 0 */
}
int set_nonblocking(int fd)
{
int flags;
flags = fcntl(fd, F_GETFL);
if (flags < 0)
return flags;
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0)
return -1;
return 0;
}
static void exchange(conn_t * conn);
void event_handler(int fd, short ev, void *arg)
{
conn_t *conn = (conn_t *) arg;
conn->tr_fd = fd;
if (ev == EV_TIMEOUT) {
}
debug_print("conn[%p] state[%d] tr_fd[%d] in_fd[%d] out_fd[%d]\n",
conn, conn->state, conn->tr_fd, conn->in_fd, conn->out_fd);
switch (conn->state) {
case 1:
exchange(conn);
break;
}
}
void on_accept(int fd, short ev, void *arg)
{
int cli_fd, len;
struct sockaddr_in clntaddr;
len = sizeof(clntaddr);
if ((cli_fd = accept(fd, (struct sockaddr *)&clntaddr, &len)) < 0) {
debug_print("error calling accept()\n");
return;
}
handle_client(cli_fd, &clntaddr);
}
int handle_client(int c, struct sockaddr_in *clntaddr);
int
tunnel_transparently(int c, struct sockaddr_in *clntaddr,
struct sockaddr_in *dstaddr)
{
int d;
int n;
int ret;
if (clntaddr == NULL || dstaddr == NULL) {
return -1;
}
d = socket(AF_INET, SOCK_STREAM, 0);
if (d == -1) {
debug_print("error creating socket (#%d %s)\n", errno,
strerror(errno));
return -2;
}
n = 1;
ret = setsockopt(d, SOL_IP, IP_TRANSPARENT, &n, sizeof(int));
if (ret != 0) {
debug_print
("error setting transparency towards destination. err (#%d %s)\n",
errno, strerror(errno));
close(d);
return -3;
}
ret = bind(d, (struct sockaddr *)clntaddr, sizeof(struct sockaddr_in));
if (ret != 0) {
debug_print("error binding to client . err (#%d %s)\n", errno,
strerror(errno));
close(d);
return -4;
}
ret = connect(d, (struct sockaddr *)dstaddr, sizeof(*dstaddr));
if (ret != 0) {
debug_print("error connecting to detination. err (#%d %s)\n",
errno, strerror(errno));
close(d);
return -5;
}
conn_t *conn = malloc(sizeof(conn_t));
memset(conn, 0, sizeof(conn_t));
conn->in_buf = malloc(CACHE_BUF_SIZE);
conn->out_buf = malloc(CACHE_BUF_SIZE);
conn->state = 0;
conn->base = base;
conn->in_fd = c;
conn->out_fd = d;
conn->state = 1;
conn->out_write = 0;
conn->in_write = 0;
set_nonblocking(conn->in_fd);
set_nonblocking(conn->out_fd);
conn->in_read_ev = event_new(conn->base, conn->in_fd,
EV_READ | EV_PERSIST, event_handler, conn);
conn->in_write_ev = event_new(conn->base, conn->in_fd,
EV_WRITE | EV_TIMEOUT, event_handler,
conn);
conn->out_read_ev = event_new(conn->base, conn->out_fd,
EV_READ | EV_PERSIST, event_handler,
conn);
conn->out_write_ev = event_new(conn->base, conn->out_fd,
EV_WRITE, event_handler, conn);
event_add(conn->in_read_ev, NULL);
// event_add(conn->in_write_ev, NULL);
event_add(conn->out_read_ev, NULL);
// event_add(conn->out_write_ev, NULL);
}
int main(int argc, char **argv)
{
int s;
int c;
short int port;
struct sockaddr_in servaddr;
int n;
int ret;
struct msghdr msg;
char cntrlbuf[64];
struct iovec iov[1];
char *endptr;
struct event ev_accept;
sigignore(SIGPIPE);
if (argc < 2) {
printf("usage: %s <port>\n", argv[0]);
return -1;
}
port = strtol(argv[1], &endptr, 0);
DEBUG = atoi(argv[2]);
if (*endptr || port <= 0) {
debug_print("invalid port number %s.\n", argv[1]);
return -2;
}
if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
debug_print("error creating listening socket.\n");
return -3;
}
n = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n));
setsockopt(s, SOL_SOCKET, SO_BROADCAST, &n, sizeof(n));
/* Enable TPROXY IP preservation */
n = 1;
ret = setsockopt(s, SOL_IP, IP_TRANSPARENT, &n, sizeof(int));
if (ret != 0) {
debug_print
("error setting transparency for listening socket. err (#%d %s)\n",
errno, strerror(errno));
close(s);
return -4;
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
if (bind(s, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
debug_print("error calling bind()\n");
return -6;
}
listen(s, 1024);
if (set_nonblocking(s) < 0) {
err(1, "failed to set server socket to non-blocking");
}
base = event_init();
event_set(&ev_accept, s, EV_READ | EV_PERSIST, on_accept, NULL);
event_base_set(base, &ev_accept);
event_add(&ev_accept, NULL);
event_base_loop(base, 0);
return 0;
}
int handle_client(int cli_fd, struct sockaddr_in *clntaddr)
{
struct sockaddr_in server_addr, clientaddr;
int ret, len;
/* get original destination address */
len = sizeof(struct sockaddr_in);
getsockname(cli_fd, (struct sockaddr *)&server_addr, &len);
getpeername(cli_fd, (struct sockaddr *)&clientaddr, &len);
server_addr.sin_family = AF_INET;
printf("original source address %X:%d\n", clientaddr.sin_addr.s_addr,
ntohs(clientaddr.sin_port));
printf("original destination address %X:%d\n",
server_addr.sin_addr.s_addr, ntohs(server_addr.sin_port));
ret = tunnel_transparently(cli_fd, clntaddr, &server_addr);
return 0;
}
void free_conn(conn_t * conn)
{
if (conn != NULL) {
debug_print("conn[%p] state[%d] in_fd[%d] out_fd[%d]\n",
conn, conn->state, conn->in_fd, conn->out_fd);
if (conn->in_read_ev != NULL) {
event_free(conn->in_read_ev);
conn->in_read_ev = NULL;
}
if (conn->in_write_ev != NULL) {
event_free(conn->in_write_ev);
conn->in_write_ev = NULL;
}
if (conn->out_read_ev != NULL) {
event_free(conn->out_read_ev);
conn->out_read_ev = NULL;
}
if (conn->out_write_ev != NULL) {
event_free(conn->out_write_ev);
conn->out_write_ev = NULL;
}
if (conn->out_fd != -1) {
close(conn->out_fd);
conn->out_fd = -1;
}
if (conn->in_fd != -1) {
close(conn->in_fd);
conn->in_fd = -1;
}
if (conn->in_buf != NULL)
free(conn->in_buf);
if (conn->out_buf != NULL)
free(conn->out_buf);
free(conn);
conn == NULL;
}
}
static void exchange(conn_t * conn)
{
int ret, wlen, fd;
int err = -1;
socklen_t len = sizeof(err);
if ((conn->tr_fd == conn->out_fd && conn->out_write == 1) ||
(conn->tr_fd == conn->in_fd && conn->in_write != 1)) {
if (conn->in_left_size > 0) {
goto out_write;
} else if (conn->tr_fd == conn->out_fd) {
fprintf(stderr,
"oddly behavior \nconn[%p] state[%d] in_fd[%d] out_fd[%d]\n",
conn, conn->state, conn->in_fd, conn->out_fd);
}
ret = read_nonblocking(conn->in_fd,
conn->in_buf + conn->in_buf_size,
CACHE_BUF_SIZE - conn->in_buf_size);
if (ret == -1) {
debug_print("conn[%p] in_fd[%d] read error\n", conn,
conn->in_fd);
goto failed;
} else if (ret == 0) {
debug_print("conn[%p] in_fd[%d] read closed\n", conn,
conn->in_fd);
goto failed;
}
conn->in_buf_size += ret;
conn->in_left_size = conn->in_buf_size;
debug_print("conn[%p] in_fd[%d] -> out_fd[%d] recv %d bytes\n",
conn, conn->in_fd, conn->out_fd, conn->in_buf_size);
out_write:
ret = write_nonblocking(conn->out_fd,
conn->in_buf + (conn->in_buf_size -
conn->in_left_size),
conn->in_left_size);
if (ret == -1) {
debug_print("conn[%p] out_fd[%d] write error\n",
conn, conn->out_fd);
goto failed;
} else if (ret < conn->in_left_size) {
conn->in_left_size -= ret;
event_add(conn->out_write_ev, NULL);
debug_print
("conn[%p] in_fd[%d] -> out_fd[%d] write p %d(%d) bytes\n",
conn, conn->in_fd, conn->out_fd, ret,
conn->in_buf_size);
if (conn->out_write == 0) {
if (event_del(conn->in_read_ev) == -1) {
debug_print
("conn[%p] in_fd[%d] out_fd[%d] disable read event failed\n",
conn, conn->in_fd, conn->out_fd);
goto failed;
}
conn->out_write = 1;
}
} else if (ret == conn->in_left_size) {
debug_print
("conn[%p] in_fd[%d] -> out_fd[%d] write %c %d(%d) bytes\n",
conn, conn->in_fd, conn->out_fd,
conn->out_write == 1 ? 'p' : 'f', ret,
conn->in_buf_size);
conn->in_buf_size = conn->in_left_size = 0;
if (conn->out_write == 1) {
getsockopt(conn->in_fd, SOL_SOCKET, SO_ERROR,
&err, &len);
if (err) {
debug_print
("conn[%p] in_fd[%d] error -> out_fd[%d]\n",
conn, conn->in_fd, conn->out_fd);
goto failed;
}
if (event_add(conn->in_read_ev, NULL) == -1) {
debug_print
("conn[%p] in_fd[%d] out_fd[%d] enable read event failed\n",
conn, conn->in_fd, conn->out_fd);
goto failed;
}
conn->out_write = 0;
}
}
return;
}
if ((conn->tr_fd == conn->in_fd && conn->in_write == 1) ||
(conn->tr_fd == conn->out_fd && conn->out_write != 1)) {
if (conn->out_left_size > 0) {
goto in_write;
} else if (conn->tr_fd == conn->in_fd) {
fprintf(stderr,
"oddly behavior \nconn[%p] state[%d] in_fd[%d] out_fd[%d]\n",
conn, conn->state, conn->in_fd, conn->out_fd);
}
ret = read_nonblocking(conn->out_fd,
conn->out_buf + conn->out_buf_size,
CACHE_BUF_SIZE - conn->out_buf_size);
if (ret == -1) {
debug_print("conn[%p] out_fd[%d] read error\n",
conn, conn->out_fd);
goto failed;
} else if (ret == 0) {
debug_print("conn[%p] out_fd[%d] read closed\n",
conn, conn->out_fd);
goto failed;
}
conn->out_buf_size += ret;
conn->out_left_size = conn->out_buf_size;
debug_print("conn[%p] out_fd[%d] -> in_fd[%d] recv %d bytes\n",
conn, conn->out_fd, conn->in_fd,
conn->out_buf_size);
in_write:
ret = write_nonblocking(conn->in_fd,
conn->out_buf + (conn->out_buf_size -
conn->out_left_size),
conn->out_left_size);
if (ret == -1) {
debug_print("conn[%p] in_fd[%d] write error\n", conn,
conn->in_fd);
goto failed;
} else if (ret < conn->out_left_size) {
conn->out_left_size -= ret;
event_add(conn->in_write_ev, NULL);
debug_print
("conn[%p] out_fd[%d] -> in_fd[%d] write p %d(%d) bytes\n",
conn, conn->out_fd, conn->in_fd, ret,
conn->out_buf_size);
if (conn->in_write == 0) {
if (event_del(conn->out_read_ev) == -1) {
debug_print
("conn[%p] in_fd[%d] out_fd[%d] disable read event failed\n",
conn, conn->in_fd, conn->out_fd);
goto failed;
}
conn->in_write = 1;
}
} else if (ret == conn->out_left_size) {
debug_print
("conn[%p] out_fd[%d] -> in_fd[%d] write %c %d(%d) bytes\n",
conn, conn->out_fd, conn->in_fd,
conn->in_write == 1 ? 'p' : 'f', ret,
conn->out_buf_size);
conn->out_buf_size = conn->out_left_size = 0;
if (conn->in_write == 1) {
getsockopt(conn->out_fd, SOL_SOCKET, SO_ERROR,
&err, &len);
if (err) {
debug_print
("conn[%p] in_fd[%d] <- out_fd[%d] error\n",
conn, conn->in_fd, conn->out_fd);
goto failed;
}
if (event_add(conn->out_read_ev, NULL) == -1) {
debug_print
("conn[%p] in_fd[%d] out_fd[%d] enable read event failed\n",
conn, conn->in_fd, conn->out_fd);
goto failed;
}
conn->in_write = 0;
}
}
return;
}
failed:
free_conn(conn);
}
/*
* # iptables -t mangle -N DIVERT
* # iptables -t mangle -A PREROUTING -p tcp -m socket -j DIVERT
* # iptables -t mangle -A DIVERT -j MARK --set-mark 1
* # iptables -t mangle -A DIVERT -j ACCEPT
* # ip rule add fwmark 1 lookup 100
* # ip route add local 0.0.0.0/0 dev lo table 100
* # iptables -t mangle -A PREROUTING -p tcp --dport 80 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 9401
*
*/
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <limits.h>
#include <linux/netfilter_ipv4.h>
#include <unistd.h>
int handle_client (int c, struct sockaddr_in *clntaddr);
int tunnel_transparently (int c, struct sockaddr_in *clntaddr, struct sockaddr_in *dstaddr);
int main (int argc, char **argv)
{
int s;
int c;
short int port;
struct sockaddr_in servaddr;
struct sockaddr_in clntaddr;
int n;
int ret;
struct msghdr msg;
char cntrlbuf[64];
struct iovec iov[1];
char *endptr;
if (argc < 2)
{
printf ("usage: %s <port>\n", argv[0]);
return -1;
}
port = strtol (argv[1], &endptr, 0);
if (*endptr || port <= 0)
{
fprintf (stderr, "invalid port number %s.\n", argv[1]);
return -2;
}
if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0)
{
fprintf (stderr, "error creating listening socket.\n");
return -3;
}
n=1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n));
setsockopt(s, SOL_SOCKET, SO_BROADCAST, &n, sizeof(n));
/* Enable TPROXY IP preservation */
n=1;
ret = setsockopt (s, SOL_IP, IP_TRANSPARENT, &n, sizeof(int));
if (ret != 0)
{
fprintf (stderr, "error setting transparency for listening socket. err (#%d %s)\n", errno, strerror(errno));
close (s);
return -4;
}
memset (&servaddr, 0, sizeof (servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
servaddr.sin_port = htons (port);
if (bind (s, (struct sockaddr *) &servaddr, sizeof (servaddr)) < 0)
{
fprintf (stderr, "error calling bind()\n");
return -6;
}
listen (s, 1024);
while (1)
{
n=sizeof(clntaddr);
if ((c = accept (s, (struct sockaddr *)&clntaddr, &n)) < 0)
{
fprintf (stderr, "error calling accept()\n");
break;
}
handle_client (c, &clntaddr);
}
close (s);
return 0;
}
int handle_client (int c, struct sockaddr_in *clntaddr)
{
struct sockaddr_in dstaddr={0,};
int ret;
int n;
/* get original destination address */
n=sizeof(struct sockaddr_in);
ret = getsockopt (c, SOL_IP, IP_ORIGDSTADDR, &dstaddr, &n); // IP_ORIGDSTADDR = 20
//ret = getsockopt (c, SOL_IP, SO_ORIGINAL_DST, &dstaddr, &n); // SO_ORIGINAL_DST = 80
if (ret != 0)
{
fprintf (stderr, "error getting original destination address. err (#%d %s)\n", errno, strerror(errno));
close (c);
return -1;
}
dstaddr.sin_family = AF_INET;
printf ("original destination address %X:%d\n", dstaddr.sin_addr.s_addr, dstaddr.sin_port);
ret = tunnel_transparently (c, clntaddr, &dstaddr);
if (ret <= 0)
{
close (c);
return -2;
}
close (c);
return 0;
}
int tunnel_transparently (int c, struct sockaddr_in *clntaddr, struct sockaddr_in *dstaddr)
{
int d;
int n;
int ret;
if (clntaddr == NULL || dstaddr == NULL)
{
return -1;
}
d = socket (AF_INET, SOCK_STREAM, 0);
if (d == -1)
{
fprintf (stderr, "error creating socket (#%d %s)\n", errno, strerror(errno));
return -2;
}
n=1;
ret = setsockopt (d, SOL_IP, IP_TRANSPARENT, &n, sizeof(int));
if (ret != 0)
{
fprintf (stderr, "error setting transparency towards destination. err (#%d %s)\n", errno, strerror(errno));
close (d);
return -3;
}
ret = bind (d, (struct sockaddr *)clntaddr, sizeof (struct sockaddr_in));
if (ret != 0)
{
fprintf (stderr, "error binding to client . err (#%d %s)\n", errno, strerror(errno));
close (d);
return -4;
}
ret = connect (d, (struct sockaddr *)dstaddr, sizeof (*dstaddr));
if (ret != 0)
{
fprintf (stderr, "error connecting to detination. err (#%d %s)\n", errno, strerror(errno));
close (d);
return -5;
}
while (1) {
sleep(600);
}
close (d);
return 0;
}
/*
* # iptables -t mangle -N DIVERT
* # iptables -t mangle -A PREROUTING -p udp -m socket -j DIVERT
* # iptables -t mangle -A DIVERT -j MARK --set-mark 1
* # iptables -t mangle -A DIVERT -j ACCEPT
* # ip rule add fwmark 1 lookup 100
* # ip route add local 0.0.0.0/0 dev lo table 100
* # iptables -t mangle -A PREROUTING -p udp --dport 9201 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 19201
*
* Tested on RHEL 6
*/
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#define MAX_RECV_BUF (1000)
int handle_msg (struct msghdr *msg);
int send_transparently (struct msghdr *msg, struct sockaddr_in *dstaddr);
int main (int argc, char **argv)
{
int s;
short int port;
struct sockaddr_in servaddr;
struct sockaddr_in clntaddr;
int n;
int ret;
struct msghdr msg;
char cntrlbuf[64];
struct iovec iov[1];
char buffer[MAX_RECV_BUF];
char *endptr;
if (argc < 2)
{
printf ("usage: %s <port>\n", argv[0]);
return -1;
}
port = strtol (argv[1], &endptr, 0);
if (*endptr || port <= 0)
{
fprintf (stderr, "invalid port number %s.\n", argv[1]);
return -2;
}
if ((s = socket (AF_INET, SOCK_DGRAM, 0)) < 0)
{
fprintf (stderr, "error creating listening socket.\n");
return -3;
}
n=1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n));
setsockopt(s, SOL_SOCKET, SO_BROADCAST, &n, sizeof(n));
n=1;
ret = setsockopt (s, SOL_IP, IP_TRANSPARENT, &n, sizeof(int));
if (ret != 0)
{
fprintf (stderr, "error setting transparency for listening socket. err (#%d %s)\n", errno, strerror(errno));
close (s);
return -4;
}
n=1;
ret = setsockopt (s, IPPROTO_IP, IP_RECVORIGDSTADDR, &n, sizeof(int));
if (ret != 0)
{
fprintf (stderr, "error setting the listening socket to IP_TRANSPARENT. err (#%d %s)\n", errno, strerror(errno));
close (s);
return -5;
}
memset (&servaddr, 0, sizeof (servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
servaddr.sin_port = htons (port);
if (bind (s, (struct sockaddr *) &servaddr, sizeof (servaddr)) < 0)
{
fprintf (stderr, "error calling bind()\n");
return -6;
}
while (1)
{
msg.msg_name = &clntaddr;
msg.msg_namelen = sizeof(clntaddr);
msg.msg_control = cntrlbuf;
msg.msg_controllen = sizeof(cntrlbuf);
iov[0].iov_base = buffer;
iov[0].iov_len = sizeof (buffer);
msg.msg_iov = iov;
msg.msg_iovlen = 1;
ret = recvmsg (s, &msg, 0);
if (ret <= 0)
{
fprintf (stderr, "error calling recvmsg(). err (#%d %s)\n", errno, strerror(errno));
break;
}
msg.msg_iov[0].iov_len = ret;
handle_msg (&msg);
}
close (s);
return 0;
}
int handle_msg (struct msghdr *msg)
{
struct sockaddr_in *clntaddr;
struct sockaddr_in dstaddr={0,};
struct cmsghdr *cmsg;
int ret;
int found=0;
clntaddr = msg->msg_name;
printf ("recvd msg from %X:%d\n", clntaddr->sin_addr.s_addr, clntaddr->sin_port);
/* get original destination address */
for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg))
{
if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_RECVORIGDSTADDR)
{
memcpy (&dstaddr, CMSG_DATA(cmsg), sizeof (struct sockaddr_in));
dstaddr.sin_family = AF_INET;
printf ("original dst address %X:%d\n", dstaddr.sin_addr.s_addr, dstaddr.sin_port);
found = 1;
}
}
if (! found)
{
return -1;
}
ret = send_transparently (msg, &dstaddr);
if (ret <= 0)
{
return -2;
}
return 0;
}
int send_transparently (struct msghdr *msg, struct sockaddr_in *dstaddr)
{
int d;
int n;
int ret;
if (msg == NULL || dstaddr == NULL)
{
return -1;
}
d = socket (AF_INET, SOCK_DGRAM, 0);
if (d == -1)
{
fprintf (stderr, "error creating socket (#%d %s)\n", errno, strerror(errno));
return -2;
}
n=1;
ret = setsockopt (d, SOL_IP, IP_TRANSPARENT, &n, sizeof(int));
if (ret != 0)
{
fprintf (stderr, "error setting transparency towards destination. err (#%d %s)\n", errno, strerror(errno));
close (d);
return -3;
}
ret = bind (d, (struct sockaddr *)msg->msg_name, sizeof (struct sockaddr_in));
if (ret != 0)
{
fprintf (stderr, "error binding to client . err (#%d %s)\n", errno, strerror(errno));
close (d);
return -4;
}
ret = sendto (d, msg->msg_iov[0].iov_base, msg->msg_iov[0].iov_len, 0, (struct sockaddr *)dstaddr, sizeof (*dstaddr));
if (ret <= 0)
{
fprintf (stderr, "error sending to detination. err (#%d %s)\n", errno, strerror(errno));
close (d);
return -5;
}
close (d);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment