How to forward port in user space using socat

I was asked to help setting up a kind of traffic redirection through VPN. In particular, the VPN server is Cisco VPN so the client should be Cisco VPN client too. Since the client is Ubuntu Edgy, it is very easy to install and run cvpnd. Then I tried to forward a TCP port to another host on the same port using IPTables and DNAT. Unfortunately, it failed. Incoming TCP connection through this port always get "connection refused". I guess that the problem might involve the module cisco_ipsec. This module may hook packet routing somewhere in the kernel so DNAT will not work correctly through its network interface. However, I don't have enough to investigate deeper for more detail since the service must be available tonight. In other words, I have 15 minutes left. So I decided to use port forwarding tool running on user space instead. After searching for a while, I found socat. socat looks like an extension of netcat which provides extremely flexibility to all of us for forwarding anything and then redirect to anything.

First of all, we have to install socat.

sudo apt-get install socat

Then the command for forwarding is as simple as follow.

 socat -d -d -lmlocal2 \
     TCP4-LISTEN:81,su=nobody,fork,reuseaddr \
     TCP4:192.168.16.55:81

It means all TCP connection to port 81 on local host will be redirected to 192.168.16.55 on port 81. The process will be degraded to run as nobody and multiple connections will be served by forking. It works! In addition to make it run smoothly as a system service, I adapted initscript from squid as follow.

#! /bin/sh
#
# socat         Startup script for the socat
#
#
 
NAME=socat
DESC=socat
DAEMON=/usr/bin/socat
LIB=/usr/lib/socat
SOCAT_ARGS="-d -d -lmlocal2"
 
[ ! -f /etc/default/socat ] || . /etc/default/socat
 
. /lib/lsb/init-functions
 
PATH=/bin:/usr/bin:/sbin:/usr/sbin
 
[ -x $DAEMON ] || exit 0
 
#
#       Try to increase the # of filedescriptors we can open.
#
maxfds () {
        [ -n "$SOCAT_MAXFD" ] || return
        [ -f /proc/sys/fs/file-max ] || return 0
        [ $SOCAT_MAXFD -le 4096 ] || SQUID_MAXFD=4096
        global_file_max=`cat /proc/sys/fs/file-max`
        minimal_file_max=$(($SOCAT_MAXFD + 4096))
        if [ "$global_file_max" -lt $minimal_file_max ]
        then
                echo $minimal_file_max > /proc/sys/fs/file-max
        fi
        ulimit -n $SOCAT_MAXFD
}
 
start_socat() {
        start-stop-daemon --quiet --start \
                --pidfile /var/run/socat.$NAME.pid \
                --background --make-pidfile \
                --exec $DAEMON -- $SOCAT_ARGS $ARGS < /dev/null
}
 
stop_socat() {
        start-stop-daemon --stop --quiet --pidfile /var/run/socat.$NAME.pid --exec $DAEMON
        rm -f /var/run/socat.$NAME.pid
}
 
start () {
        echo "Starting $DESC:"
 
        maxfds
        umask 027
        cd /tmp
        if test "x$AUTOSTART" = "xnone" -o -z "x$AUTOSTART" ; then
                echo "Autostart disabled."
                exit 0
        fi
        for NAME in $AUTOSTART ; do
                ARGS=`eval echo \\\$SOCAT_$NAME`
                echo $ARGS
                start_socat
                echo " $NAME $ARGS"
        done
        return $?
}
 
stop () {
        echo -n "Stopping $DESC:"
 
        for PIDFILE in `ls /var/run/socat.*.pid 2> /dev/null`; do
                NAME=`echo $PIDFILE | cut -c16-`
                NAME=${NAME%%.pid}
                stop_socat
                echo -n " $NAME"
        done
}
 
case "$1" in
    start)
        log_daemon_msg "Starting socat" "socat"
        if start ; then
                log_end_msg $?
        else
                log_end_msg $?
        fi
        ;;
    stop)
        log_daemon_msg "Stopping socat" "socat"
        if stop ; then
                log_end_msg $?
        else
                log_end_msg $?
        fi
        ;;
    reload|force-reload|restart)
        log_daemon_msg "Restarting socat" "socat"
        stop
        if start ; then
                log_end_msg $?
        else
                log_end_msg $?
        fi
        ;;
    *)
        echo "Usage: /etc/init.d/$NAME {start|stop|reload|force-reload|restart}"
        exit 3
        ;;
esac
 
exit 0

I saved this file in /etc/init.d/socat and then registered to system service.

update-rc.d socat defaults

Lastly, configuration file must be created at /etc/default/socat as follow.

AUTOSTART=default
 
SOCAT_default="TCP4-LISTEN:81,su=nobody,fork,reuseaddr TCP4:192.168.16.55:81"

Now I can start/stop socat through initscript.

sudo /etc/init.d/socat start
sudo /etc/init.d/socat stop

For more examples, check them out at socat.

Tags: , , ,

Post new comment