SSH local/dynamic/reverse port forwarding for dummies

Or “how to connect to a server that is protected by a firewall that blocks all incoming (inbound) connections”.

To implement this, I’m using a Windows laptop running two virtualized CentOS instances using VMWare player:

  • The first CentOS instance (“”) is going to connect to a web server running on…
  • The second CentOS instance (“”).
  • On centos2:
  • I will be logged on centos1 using user “laurent1” and on centos2 using user “laurent2”.

Registering the CentOS server names in the DHCP/DNS

Not really related to this post, but I always forget how to do it: I had to modify the /etc/sysconfig/network-scripts/ifcfg-eth0 file so that the DHCP correctly registers the server name in the DNS:

[laurent@centos1 network-scripts]$ cat ifcfg-eth0

For more information, check this post.

Configuring the firewall on centos2

For these series of tests, the firewall on centos2 will need to block/unblock inbound/outbound connections on port 22 (SSH), 80 (HTTP) & 443 (HTTPS) so I will use this simple iptables script:

# Reset
iptables -F

# Changing OUTBOUND policies to DROP (instead of ACCEPT)
iptables -P INPUT DROP
iptables -P OUTPUT DROP

# Allow loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Allow icmp
iptables -A INPUT -p icmp -j ACCEPT
iptables -A OUTPUT -p icmp -j ACCEPT

iptables -A INPUT -i eth0 -p tcp --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT

iptables -A INPUT -i eth0 -p tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 80 -m state --state ESTABLISHED -j ACCEPT

iptables -A INPUT -i eth0 -p tcp --dport 443 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 443 -m state --state ESTABLISHED -j ACCEPT

iptables -A OUTPUT -o eth0 -p tcp --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A INPUT -i eth0 -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT

iptables -A OUTPUT -o eth0 -p tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A INPUT -i eth0 -p tcp --sport 80 -m state --state ESTABLISHED -j ACCEPT

iptables -A OUTPUT -o eth0 -p tcp --dport 443 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A INPUT -i eth0 -p tcp --sport 443 -m state --state ESTABLISHED -j ACCEPT

# allow DNS (otherwise will never be resolved)
iptables -A OUTPUT -p udp -o eth0 --dport 53 -j ACCEPT
iptables -A INPUT -p udp -i eth0 --sport 53 -j ACCEPT

By commenting/uncommenting the needed lines and running the script, you can easily block/unblock the needed protocols (well, not exactly, since we would need an application layer firewall for this, but that’s another story). Note that these firewall rules will be lost after rebooting the centos2 server, which is what we want when testing !

Configuring the web server on centos2

As I said, there is a web server running at I’m simply using Apache that is listening on port 80 and serving this simple HTML page:

[laurent2@centos2 html]$ cat /var/www/html/index.html
Hello from centos2

Alternatively, you can use the python SimpleHTTPServer module:

[laurent2@centos2 Documents]$ pwd
[laurent2@centos2 Documents]$ cat index.html
Hello from centos2
[laurent2@centos2 Documents]$ sudo python -m SimpleHTTPServer 80
Serving HTTP on port 80 ...

Exercice #1: no port forwarding

In this exercise, the centos2 firewall allows incoming HTTP connections.

To test this, simply run the iptables script above and then… well that’s it: open your web browser on centos1 and connect to Or using curl:

[laurent1@centos1 ~]$ curl
Hello from centos2

Exercice #2: SSH local port forwarding

In this exercise, the centos2 firewall blocks inbound HTTP, but allows inbound SSH.

To test this, simply disable the “allow INBOUND HTTP” rule and run the iptables script: this should prevent the curl command used above from working (since the firewall now prevents centos1 to connect to centos2 on port 80).

To connect to our webserver, we are going to use the “local port forwarding” capacities of the SSH tool, which will allow us to:

  • use the SSH channel between the two servers to “tunnel” the HTTP traffic
  • encrypt the traffic

First, on centos2, let’s make sure that the sshd daemon is running:

[laurent2@centos2 bin]$ sudo service sshd status
openssh-daemon (pid 2095) is running...

The ssh daemon listens on port centos2:22 by default and we can connect to it from centos1:

[laurent1@centos1 ~]$ sudo ssh -L's password:
Last login: Mon Oct 22 16:32:51 2012 from
[laurent2@centos2 ~]$

We are now connected to centos2 (from centos1) and we can execute remote commands using the SSH shell: nothing fancy here.

The nice part is the “-L” command line that basically says “hey you SSH: can you please listen to all requests that arrive at centos1:9999 and forward them to centos2:80 ?”.

You can use the netstat command to check that you indeed have port centos1:9999 listening for incoming connections:

[laurent1@centos1 ~]$ sudo netstat -apn | grep 9999
tcp 0 0* LISTEN 6266/ssh
tcp 0 0 ::1:9999 :::* LISTEN 6266/ssh

And since your webserver is listening on port centos2:80, you can now access it from centos1 by connecting to http://localhost:9999 (yes, it works, even if there is not webserver on centos1: SSH silently redirects you to centos2 behind the hood !)

[laurent1@centos1 ~]$ curl http://localhost:9999
Hello from centos2

Of course, the “local port” (9999) and the “remote port” (80) do not need to be different. For example, you could use “-L” and it would work exactly the same.

Note that if you try “curl; instead of “curl http://localhost:9999&#8221;, it will not work since the netstat command above shows that centos1 only allows connections coming from locahost. To use “” you need to launch SSH with “-L″.

Exercice #3: SSH dynamic port forwarding

In this exercise, the centos2 firewall blocks inbound HTTP, but allows inbound SSH (exactly like in the previous exercise).

The “issue” with the previous exercise is that you have to explicitely say that you want to redirect port centos1:9999 to port centos2:80 (that’s the “-L” option remember ?). But what if you want to say “each local connection to port centos1:XX needs to be redirected to the same port on centos2” ?

Well, you can, thanks to the SSH -D option.

To use it, first close the SSH connection from the previous exercise and open a new one (you are still on centos1) like this:

[laurent1@centos1 ~]$ ssh -D 9999's password:
Last login: Mon Oct 22 16:57:25 2012 from
[laurent2@centos2 ~]$

You know have a SOCKS proxy running on centos1 (and listening on port 9999). From there, let’s use curl again to connect to our webserver:

[laurent1@centos1 ~]$ curl --socks5 localhost:9999 http://localhost:80

Hello from centos2

If you do not want to use curl and prefer to use Firefox, go to the “Edit > Preferences > Network > Settings” window to set the SOCKS proxy:

So what’s the difference with the previous example you may ask ? Well, with the SOCKS proxy approach, your centos2 webserver can be running on any port (80, 8080, 8081 etc…) it doesn’t matter: you can access it from centos1 without having to know the port in advance when creating the SSH connection !

Exercice #4: SSH reverse local port forwarding

In this exercise, the centos2 firewall blocks ALL inbound connections, but allows outbound SSH connections.

OK, so now the firewall doesn’t allow any connection to be established from centos1 to centos2  (run the iptables script after commenting out all INBOUND rules), so how can we access the webserver ? Well, we need to use the “hollywood principle”: “don’t call me, I’ll call you”.

Said differently, centos2 is going to open a connection to centos1 and centos1 will use this open channel to communicate with centos2 (sneaky huh ?).

So, since centos2 is going to connect to centos1, we need to first make sure that the ssh daemon is running on centos1.

[laurent1@centos1 ~]$ sudo service sshd status
openssh-daemon (pid 2077) is running...

Then, switch to centos2 and enter this command (make sure that the SSH session from the previous exercice is stopped on centos1)

[laurent2@centos2 bin]$ sudo ssh -R 9999:localhost:80's password:
Last login: Mon Oct 22 17:43:02 2012 from
[laurent1@centos1 ~]$

This time, we don’t use the “-L” option, but the “-R” option. This means “listen for connections on centos1:9999 and forward them to localhost:80 (localhost being centos2)”. Note that this does exactly the same thing that in the second exercise, except that here we are entering this command from centos2.

Now if you go back to centos1 and use the netstat command, you’ll see that the ssh(ssh daemon) process is listening for connections on port centos1:9999.

[laurent1@centos1 bin]$ sudo netstat -apn | grep 9999
tcp 0 0* LISTEN 6890/sshd
tcp 0 0 ::1:9999 :::* LISTEN 6890/sshd

You can now access the webserver from centos1:

[laurent1@centos1 ~]$ curl http://localhost:9999

Hello from centos2

The curl command line is exactly the same as the one we used in the second exercise, the only difference is that the SSH tunnel was launched from centos2, allowing the firewall to be traversed.

Exercice #5: SSH reverse dynamic port forwarding

In this exercise, the centos2 firewall blocks ALL inbound connections, but allows outbound SSH connections  (exactly like in the previous exercise).

We are going to do what we did in the “dynamic port forwarding” exercice, but this time by initiating the SSH tunnel from centos2. Also – and that’s the tricky part – the SOCKS proxy will be created on centos2, which means that we need to run two SSH commands from centos2.

First, the one to create the SOCKS proxy (yes: we are on centos2 and we use the SSH command to connect to centos2)

[laurent2@centos2 ~]$ sudo ssh -D's password:
Last login: Wed Oct 24 13:37:54 2012 from
[laurent2@centos2 ~]$

Then, the second one to create the SSH tunnel (we are still on centos2):

[laurent2@centos2 ~]$ sudo ssh -R's password:
Last login: Wed Oct 24 13:38:53 2012 from
[laurent1@centos1 ~]$

So in the end, this is what we have:

  • on centos1, we have the ssh daemon process listening on port 9998 (thanks to the “-R” command launched from centos2).
  • when connecting to this port on centos1, we will be redirected to port centos2:9999.
  • and we will be connected to the SOCKS proxy, which is listening for incoming connections on the centos2:9999.

So now we can go to centos1 and execute the exact same curl command that in the “dynamic port forwarding exercise”:

[laurent1@centos1 ~]$ curl --socks5 localhost:9998 http://localhost:80

Hello from centos2



Solving the “Permission denied: make_sock: could not bind to address” issue when starting Apache on Linux

Does this looks familiar ?

[laurent2@centos2 conf]$ sudo service httpd start
Starting httpd: (13)Permission denied: make_sock: could not bind to address [::]:8082
(13)Permission denied: make_sock: could not bind to address
no listening sockets available, shutting down
Unable to open logs

No, it’s not because I’m not running this as root (as explained here, here and there) : you can see in the command above that I’ve used the sudo command !

It’s (again) because SELinux is preventing the httpd process to listen on port 8082.

This can be checked by having a look at the SELinux log files:

  • if the auditd daemon is running, SELinux denials are in /var/log/audit/audit.log.
  • if the daemon is not running, you’ll find them in /var/log/messages.
[laurent2@centos2 conf]$ sudo tail /var/log/audit/audit.log
type=AVC msg=audit(1350478371.269:183): avc: denied { name_bind } for pid=3207 comm="httpd" src=8082 scontext=unconfined_u:system_r:httpd_t:s0 tcontext=system_u:object_r:port_t:s0 tclass=tcp_socket

To make sure that SELinux is indeed the culprit, you can simply temporarly disable it to see if that solves your issue:

[laurent2@centos2 conf]$ getenforce
[laurent2@centos2 conf]$ sudo setenforce 0
[sudo] password for laurent2:
[laurent2@centos2 conf]$ getenforce
[laurent2@centos2 conf]$ sudo service httpd start
Starting httpd: [ OK ]

Now the reason why SELinux prevents httpd to start listening on port 8082 is because only a certain number of ports are allowed:

[laurent2@centos2 conf]$ sudo semanage port -l | grep http
 http_cache_port_t tcp 3128, 8080, 8118, 8123, 10001-10010
 http_cache_port_t udp 3130
 http_port_t tcp 80, 443, 488, 8008, 8009, 8443
 pegasus_http_port_t tcp 5988
 pegasus_https_port_t tcp 5989

(by the way: if semanage is not installed, check here)

From there, you can choose to either disable SELinux, use a port that is currently allowed, or add the port you want to use to the list of authorized ports.

Connecting to a Windows Shared Drive from CentOS using smbclient

What is the quickest way to access a Windows Shared Drive from CentOS ? Use smbclient !

It’s an FTP-like client to access SMB/CIFS resources on a server and its part of the “samba-client” package which is installed by default on CentOS.

[laurent@localhost Downloads]$ rpm -qf /usr/bin/smbclient

The firewall is pre-configured so you don’t need to change anything to use it:

If you look on the internet, you’ll find lots of posts telling you to use this syntax: smbclient [SHARED_DRIVE_UNC_PATH] -U [username]. But if your target Windows server belongs to a domain, you’ll get an NT_STATUS_LOGON_FAILURE error:

[laurent@localhost Downloads]$ smbclient // -U laurent
Enter laurent's password:
session request to failed (Called name not present)
session setup failed: NT_STATUS_LOGON_FAILURE

That’s because the domain name must be provided using the -W option. Also (and I must say that I lost a couple of minutes before of this), the password that you must enter is the one from the domain you want to connect to, not the one from your local Linux account.

[laurent@localhost Downloads]$ smbclient // -U laurent -W entropysoft
Enter laurent's password:
Domain=[ENTROPYSOFT] OS=[Windows 7 Professional 7601 Service Pack 1] Server=[Windows 7 Professional 6.1]
 smb: \>

From there, you can navigate in the target system using the “ls” & “cd” commands and upload/download files using the “put” & “get” commands: see the smbclient man pages for more information.

smb: \> ls
 . DR 0 Fri Oct 5 09:27:04 2012
 .. DR 0 Fri Oct 5 09:27:04 2012
 activity_lifecycle.png A 82637 Mon Sep 3 14:01:54 2012
 AllowImperso.ps1 A 387 Mon Dec 12 16:03:37 2011
 amazon.pem 1696 Thu Aug 30 13:17:43 2012
 amazon.ppk A 1464 Wed Sep 12 19:41:54 2012

37897 blocks of size 8388608. 17669 blocks available
smb: \> get activity_lifecycle.png
getting file \activity_lifecycle.png of size 82637 as activity_lifecycle.png (2017.5 KiloBytes/sec) (average 2017.5 KiloBytes/sec)
smb: \>


Another way to manually install VMware tools on Linux

Today I tried to install the VMware tools on a fresh new Fedora 17 VM, so I made sure that the VM CD drive was linked to a physical drive in “Auto detect” mode:

Then I started the VM and chosed the “Virtual Machine > Install VMWare tools” option from VMware player:

This standard message was then displayed:

And then… nothing ! (I was expecting the Linux file manager to automatically popup, which usually happens when the “automount” feature is activated).

So I tried to follow the instructions to manually install the tools, with no success:

[root@localhost mnt]# mkdir /mnt/mycdrom
[root@localhost mnt]# mount -t iso9660 /dev/cdrom /mnt/mycdrom
mount: no medium found on /dev/sr0

Well, if you have the same problem, then this is easy to fix since it turns out that when you choose the “Virtual Machine > Install VMWare tools” option, VMware player tries to mount the [VMWARE_PLATER_HOME]\linux.iso image. On my Windows 7 laptop, this file is located in C:\Program Files (x86)\VMware\VMware Player\linux.iso.

So all you need to do is to configure your VM so that the CD drive uses this image:

Then, stop and start the VM (rebooting didn’t work for me: I had to shutdown and restart it completely) and you should be good to go:


Connecting to a Windows Shared Drive from LXDE using Gigolo

When using Ubuntu or/and GNOME, there are many ways to connect to a Windows shared drive… but how can we do this from LXDE ?

Well, you don’t need to install samba, you just need to use Gigolo:

Once Gigolo is started, it will happily sit in your taskbar: this is important to know since closing the application by clicking on the X icon will not really close it, but will put it back in the taskbar. You need to use the “File > Quit” option to correctly close it.

Using the “Help > Supported protocols” option will display the list of supported protocols. The only we are interested in is called “Windows Share (smb)”. If you don’t see it, then you need to install the gvfs-smb package using yum.

From there, click on “Connect”, choose “Windows Share” as the service type and enter your username/password/credentials:

Et voilà: just right-click on the newly established connection and choose the “Open” option to see the files in the shared drive:

If you want to re-use your connection after closing and re-opening Gigolo, you need to create a bookmark:

The next time you open Gigolo, click on the bookmark dropdown list to display the list of existing bookmarks:


Adding LXDE start menu entries and desktop shortcuts

Update: this post has been viewed more than 2,000 times, but no-one left a comment. It took me 2 hours to write it and it will only take you 2 minutes to leave a comment… what are you waiting for ?

Note: if you want to instead create a new start menu section or sub-sections (ie: “sub-menus”), check this post.

First, a screenshot to explain what I mean by “desktop shortcut”, “start menu section” and “start menu sub-section” (click on the image if you want to correctly see it):


Adding start menu entries and/or desktop shortcuts (launchers) in LXDE is certainly not as easy as under Windows.

I’ve recently installed the cool hardinfo tool which is an application that lists all your computer hardware. Unfortunately, the hardfo installer doesn’t add any menu entry in the LXDE start menu (I’m talking about the one that is displayed when you click on the button located at the bottom left of your desktop).

OK, let’s add a menu entry for hardinfo by ourselves.

1. Adding a new menu entry

All menu entries correspond to a .desktop file, and these .desktop files can be in one of the following locations:

  • /usr/share/applications
  • /usr/local/share/applications
  • ~/.local/share/applications (note that this folder may not exist on a brand new LXDE installation, but you can create it manually)
[laurent@localhost applications]$ ll /usr/share/applications
total 460
-rw-r--r--. 1 root root  9693 Feb 18 20:18 authconfig.desktop
-rw-r--r--. 1 root root   177 Apr 20 05:11 eekboard.desktop
-rw-r--r--. 1 root root  1699 Feb 27 22:31 fedora-abiword.desktop
-rw-r--r--. 1 root root  6001 May 10 15:07 fedora-abrt.desktop

Creating a new menu entry simply means creating a new .desktop file. The format is detailed here or there, but you can choose to use the existing .desktop files that you have as a template.

Since .desktop files can be located in 3 different folders, which one should you use ? I suggest that you create your .desktop file in the ~/.local/share/applications folder. This way, the shortcuts that you create will not impact other users.

LXDE ships with a .desktop file editor called LXShortcut. It’s the application that is launched when you right click on a menu entry and choose the “Properties” option:

However, I think that this tool sucks for creating new menu entries. Here is why:

  • There is no way to create a .desktop file from the LXShortcut GUI: you need to specify the filename on the command line (“-o” option)
  • It uses different command line options to create (-o) and to edit (-i) .desktop files.
  • There is no way to edit the category name (ie: “where the entry is going to be located in the menu”).

For all these reasons, it’s just easier to use a text editor to create your .desktop file.

Here is mine:

[laurent@localhost applications]$ cat ~/.local/share/applications/hardinfo.desktop

[Desktop Entry]
Name=Hardware info
Name[en_US]=Hardware info
Comment[en_US]=Hardware info

As you can see, the ‘Exec’ property contains the location of the ‘hardinfo’ application.

Now launch “lxpanelctl restart” so that LXDE picks up your changes:

[laurent@localhost applications]$ lxpanelctl restart

Et voilà:

By default, your new menu entry is part of the ‘Other’ category.

What if you want to put in another category ? That’s where the ‘Categories’ property comes into play: valid values for this property are listed here.

Note that the ‘freedesktop’ categories do not directly map to the LXDE category names, so here is the mapping between LXDE categories and freedesktop categories:

  • Accessories <-> Utility
  • Graphics <-> Graphics
  • Internet <-> Network
  • Office <-> Office
  • Sound & Video <-> AudioVideo
  • System Tools <-> System

So if you want your shortcut to be part of the LXDE ‘Accessories’ category, you need to add the ‘Categories=Utility’ property:

[laurent@localhost applications]$ cat ~/.local/share/applications/hardinfo.desktop

[Desktop Entry]
Name=Hardware info
Name[en_US]=Hardware info
Comment[en_US]=Hardware info

2. Adding an application shortcut on your desktop

To add a shortcut on your desktop, right click on an existing menu entry and choose the ‘Add to desktop’ option.

In practice, this simply puts a copy of an existing .desktop file in the $HOME/Desktop folder:

[laurent@localhost ~]$ ll $HOME/Desktop
total 4
-rw-------. 1 laurent laurent 165 Jun 28 17:53 hardinfo.desktop

This means that if you want to create a desktop shortcut for an application that is not in your start menu, you just need to create a .desktop file in $HOME/desktop, as explained in the previous chapter.

Alternatively, you can right-click anywhere on your desktop and choose the “Shortcut” option: this will launch the “LXShortcut” tool.

3. Adding a folder shortcut on your desktop

This time, right clicking on your desktop and choosing “Create new > Shortcut” will not work since this can only be used to create application shortcut.

To create a folder shortcut, you need to manually create a .desktop file in the ~/Desktop folder.

For example, a shortcut for the /home/laurent/Documents folder would look like this:

[laurent@localhost Desktop]$ cat ~/Desktop/myOtherShortcut.desktop

[Desktop Entry]
Name=My Folder Shortcut
Exec=pcmanfm /home/laurent/Documents


Basically, we are are just asking pcmanfm to open the “/home/laurent/Documents” folder. Also, I’m using the pcmanfm “system-file-manager” icon, but you can choose to use any icon that you want.

3. Changing the shorcut icon

OK, one more LXDE madness: it looks like the icons can either be located in /usr/share/pixmaps or in /usr/share/icons.

I guess I need to study this and create a new post… stay tuned 😉


Compiling glib on CentOS

Do you get this error message while compiling glib on CentOS ?

checking for LIBFFI... no
configure: error: Package requirements (libffi >= 3.0.0) were not met:

No package 'libffi' found

Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.

Alternatively, you may set the environment variables LIBFFI_CFLAGS
and LIBFFI_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.

First, let’s check if libffi is installed:

[laurent@new-host]/usr/lib/pkgconfig> rpm -qa | grep libffi

[laurent@new-host]/> sudo find . -name *libffi*

OK, it is installed, so this certainly means that pkg-config doesn’t know about it. For more information concerning pkg-config, check this link.


[laurent@new-host]/usr/lib/pkgconfig> pkg-config --print-errors libffi
Package libffi was not found in the pkg-config search path.
Perhaps you should add the directory containing `libffi.pc'
to the PKG_CONFIG_PATH environment variable
No package 'libffi' found

OK: why am I missing the /usr/lib/pkgconfig/libffi.pc file ?

That’s because it’s part of the ‘libffi-devel’ package that is not installed by default !

Install it using yum and you’ll get everything you need:

[laurent@new-host]/usr/lib/pkgconfig> rpm -ql libffi-devel-3.0.5-3.2.el6.i686