Openswan tunnel to Juniper SSG

Just a small gathering of information on how I’ve setup a tunnel between a Centos 6.3, with openswan and NETKEY ipsec stack, and a Juniper SSG. Before we start configuring, lets define IP’s nets and address (by the way, those are not the real IP’s). We are link two networks with this tunnel, not a network-to-client configuration.

On the Centos side we have:

  • Name: Office City A
  • External Ip: 200.201.202.203
  • Internal Network: 10.20.20.0/24
  • Internal Gateway Ip: 10.20.20.254

On the Juniper SSG we have:

  • Name: Office City B
  • External Ip: 100.101.102.103
  • Internal Network: 10.20.10.0/24
  • Internal Gateway Ip: 10.20.10.254

Pre-shared Key: my-long-and-secret-key

Centos Side

First we need to install and configure the centos box. That should be fairly simple, start by installing openswan:

yum install openswan

Now we have to edit /etc/ipsec.conf. The default config should be fine for us, but we have to make sure that the line which includes the configs “.conf” stored under /etc/ipsec.d/ is uncommented. Your config file should look something like this:

# /etc/ipsec.conf - Openswan IPsec configuration file
#
# Manual:     ipsec.conf.5
#
# Please place your own config files in /etc/ipsec.d/ ending in .conf

version	2.0	# conforms to second version of ipsec.conf specification

# basic configuration
config setup
	# Debug-logging controls: "none" for (almost) none, "all" for lots.
	# klipsdebug=none
	# plutodebug="control parsing"
	# For Red Hat Enterprise Linux and Fedora, leave protostack=netkey
	protostack=netkey
	nat_traversal=yes
	virtual_private=
	oe=off
	# Enable this if you see "failed to find any available worker"
	# nhelpers=0

#You may put your configuration (.conf) file in the "/etc/ipsec.d/" and uncomment this.
include /etc/ipsec.d/*.conf

You also need to make sure that file /etc/ipsec.secrets includes all “.secret” files under /etc/ipsec.d/. It should read like:

include /etc/ipsec.d/*.secrets

We have to create the config file for our tunnel, let’s name it “office_b_tun”. The new config will be stored under /etc/ipsec.d/office_b_tun.conf. The content of the file should be:

conn office_b_tun
	ike=3des-md5
	esp=3des-md5
	authby=secret
	keyingtries=0
	left=100.101.102.103
	leftsubnet=10.20.10.0/24
	leftnexthop=%defaultroute
	right=200.201.202.203
	rightsubnet=10.20.20.0/24
	rightnexthop=%defaultroute
	compress=no
	auto=start

We need to set the PSK for the tunnel, so edit the file /etc/ipsec.d/office_b_tun.secrets.

100.101.102.103 200.201.202.203: PSK "my-long-and-secret-key"

As I don’t have two NIC’s on my server, I’ve setup an alias for eth0. This is not needed if you have two NIC’s. Edit /etc/sysconfig/network-scripts/ifcfg-eth0:0:

DEVICE=eth0:0
ONBOOT=yes
NETWORK=10.20.0.0
NETMASK=255.255.0.0
IPADDR=10.20.20.254

Restart your network, and start ipsec.

/etc/init.d/ipsec start

Finish configuring the Juniper, and then check the output of ipsec auto --status, it should read something like “IPsec SA established” and “ISAKMP SA established”. Verify your routes and test the tunnel.

Juniper SSG

We can configure the junipers using either the WebUI or the CLI, so I’ll describe first how to configure using the WebUi, and latter I’ll show the CLI config lines. I’m doing a Route Based VPN config as it adds more flexibility to my setup, you can use a Policy Based VPN if you wish, but I’m not covering that here (see a sample config here).

Some extra info we need to know on the Juniper side, is that I have a VPN Zone bound to trust-vr. I recommend that you create a zone for your VPN’s tunnels, as it makes easier to add trafic policies to it later.

Tunnel Interface

Go to Network -> Interface, select “Tunnel IF” and click the New button. Select a not used tunnel number, mine is 1. Also, make sure you select the Zone (VR) as “vpn” and that it’s an unnumbered interface. Click Ok. That’s it for the Tunnel Interface.

VPN AutoKey Gateway

Now we need to setup the VPN Gateway, for that go to VPN -> AutoKey Advanced -> Gateway. Click on the New button. Name the gateway as “gw_to_office_a”. Make sure “Static IP Address” is selected, and fill in the IPv4/v6 Address/Hostname field. The remote IP address is 200.201.202.203.

Click on Advanced button. On that page, enter the Pre-shared Key “my-long-and-secret-key”. Select the correct outgoing interface, mine is “Ethernet0/0”.

On the Security Level field, select “pre-g2-3des-md5“. It’s really important that you get this right!

Make sure the Mode (Initiator) is set to Main. That’s it, just click Ok to save the gateway configuration.

VPN AutoKey IKE

Time to setup the AutoKey IKE VPN, so go to VPN -> AutoKey IKE. Click on New button. I’ll name this vpn as “vpn_to_office_a”. Make sure you selected “gw_to_office_a” as the predefined gateway. Click on Advanced.

On the advanced configuration page, set the security level as “g2-esp-3des-md5“. That’s really import, otherwise the tunnel will not work.

Bind the VPN to tunnel interface “tunnel.1”. Check “Proxy-ID Check”, “VPN Monitor”, “Optimize”, “Rekey”. Select as source interface, your external port, mine is “Ethernet0/0”. Fill in the destination IP, the remote internal gateway ip address, 10.20.20.254.

Click Ok to save the tunnel.

Proxy-ID

We need to setup the Proxy-ID for the tunnel, go to the AutoKey IKE listing, click on Proxy ID for the “vpn_to_office_a” tunnel. Add the following:

Local: 10.20.10.0/24
Remote: 10.20.20.0/24
Service: ANY

Click on New, and that’s it.

Route

We need to set a static route to Centos network, as it’s not running a dynamic routing daemon (such as RIP, OSPF, BGP, …). Go to Network -> Routing -> Destination. Select “trust-vr” and click New.

The route we want to add is 10.20.20.0/24, using as gateway the interface “tunnel.1” with the address 200.201.202.203. Make the route permanent, set the preference to 20, and add a description “office A network”.

Click Ok to save it.

Policy

As I’m connecting two Trusted networks, I’ll allow any trafic incoming from VPN to Trusted and from Trusted to VPN. You can, and should, set tighter policies as you see fit.

CLI

You can configure the VPN using the CLI, use the following commands, adapt as need.

set zone id 100 "vpn"
set interface "tunnel.1" zone "vpn"
set interface tunnel.1 ip unnumbered interface ethernet0/0
set ike gateway "gw_to_office_a" address 200.201.202.203 Main outgoing-interface "ethernet0/0" preshare "my-long-and-secret-key" proposal "pre-g2-3des-md5"
set ike respond-bad-spi 1
set ike ikev2 ike-sa-soft-lifetime 60
unset ike ikeid-enumeration
unset ike dos-protection
unset ipsec access-session enable
set ipsec access-session maximum 5000
set ipsec access-session upper-threshold 0
set ipsec access-session lower-threshold 0
set ipsec access-session dead-p2-sa-timeout 0
unset ipsec access-session log-error
unset ipsec access-session info-exch-connected
unset ipsec access-session use-error-log
set vpn "vpn_to_office_a" gateway "gw_to_office_a" no-replay tunnel idletime 0 proposal "g2-esp-3des-md5" 
set vpn "vpn_to_office_a" monitor source-interface ethernet0/0 destination-ip 10.20.20.254 optimized rekey
set vpn "vpn_to_office_a" id 0xa bind interface tunnel.1
unset interface tunnel.1 acvpn-dynamic-routing
set url protocol websense
exit
set vpn "vpn_to_office_a" proxy-id check
set vpn "vpn_to_office_a" proxy-id local-ip 10.20.10.0/24 remote-ip 10.20.20.0/24 "ANY" 
set route 10.20.20.0/24 interface tunnel.1 gateway 200.201.202.203 preference 20 permanent description "office A network"

Testing

On the Office A network, try to ping a machine on B Office network, something like:

ping 10.20.10.254

On the Office B network, try to ping a machine on A Office network, something like:

ping 10.20.20.254

If you got ping’s, everything is up and running! Have fun!

Asterisk, OpenVPN and QoS

Installing a VoIP system is nowadays an easy task, just install Asterisk, have a few SIP clients and you have an ‘instant’ telephone system. But your system will not be as reliable as the one offered by any telecom company. Why? Quality of Service, or for short, QoS.

Telecom companies use sophisticated hierarchies of systems to deliver the needed QoS. Backbones uses SDH systems, where one can guarantee the bandwidth and throughput for any kind of data. So if you specify that a voice packet should be delivered in 10ms, it will get delivered in that time span. Now when it comes to IP networks, you have no guarantee that your packet will be delivered in that time frame, which is a good thing when you’re downloading files, opening web pages, and so on. But when it comes to voice and video streaming, it’s a real mess. So you must create some QoS rules for your packets.

Asterisk has this real nice feature for aggregating multiple servers so that it works as a single phone network. The only problem is that this feature is not really secure, so as to mitigate that, one can always create VPN’s (Virtual Private Networks). But how does that impact your QoS solution? Well, depends on what kind, or how you configure your VPN, with OpenVPN it’s quite simple.

Just as a reminder, for the rest of the article when I say QoS, I really mean the QoS of the gateway of your network. The gateway is the one place that will enforce the needed quality of service (okay, on bigger networks you will have multiple routers which will need to be configured for QoS too).

Don’t get too excited with QoS, even though you did everything by the book that doesn’t mean that your ISP will use TOS field the same way you did. By that I mean, you won’t solve any problem with QoS if the problem is not on how you route packets to the internet. If you have full control of your link and all the router in between your networks, you’re a lucky guy!

The Network

We have some computers, servers, and IP phones on each network. The OpenVPN tunnel server doesn’t need to be the same as the gateway, as long as you export the correct ports to that server. Make sure you also add the correct gateway for the packets that should be tunneled (ie. packets for the network 10.2.1.0 that originates on the 10.1.1.0 network). On the image, the tunnel is represented by the red lines.

A sample network using Asterisk and OpenVPN

OpenVPN

I don’t intend to give a full how-to on OpenVPN, just a basic configuration, with a highlight on how to get QoS for the tunneled packets. Besides that, configuring OpenVPN is really simple.

First you have to create your own Certificate Authority (CA). You can use something like tinyca or minica, or the command line version, described here. Remember that you will need one certificate per client. After that is just a matter of writing a really simple text file. Below are a sample configuration, known to work well integrating two Asterisk servers.

Server

# OpenVPN server
# Listen to local ip address only
local 10.1.1.2

# Should be exported on the router
port 1194
proto udp
dev tun

# SSL/TLS CA and keys
ca ca.crt
cert server.network.crt
key server.network.key

# Diffie Hellman Parameters
dh dh1024.pem

# Server tunnel
server 10.3.1.0 255.255.255.0
ifconfig-pool-persist ipp.txt

push "route 10.1.1.0 255.255.255.0"
route 10.2.1.0 255.255.255.0
client-config-dir client-configs
keepalive 10 120

# Drop privileges
user nobody
group nogroup

# Persist
persist-key
persist-tun

# Logs
verb 5
status /var/log/openvpn.log

# Fork to the background
daemon

Client

The highlighted line is the one which will make the QoS work for the encrypted packets. If you think that passing the TOS (Type of service) is a security fault, don’t panic, just create another tunnel for passing your sensitive data – and that’s really easy to do with OpenVPN.

# OpenVPN client
client

# Interface for tunnel
# Protocol and Port
dev tun0
proto udp
port 1194

# SSL/TLS CA and keys
ca /etc/openvpn/certs/ca.crt
cert /etc/openvpn/certs/remote1.mynetwork.crt
key /etc/openvpn/keys/remote1.mynetwork.key

# Symmetric cipher
cipher BF-CBC

# Remote server to connect to. Can be domain name or IP address.
remote remote1.mynetwork.com

# Check if the tunnel went down and restart it. 
# 10 is the ping interval number and 120 is the timeout to restart.
keepalive 10 120
route 10.1.1.0 255.255.255.0

# This is need so we can apply QoS to the tunnel
passtos

# Drop privileges
user nobody
group nogroup

# Use a persistent key and tunnel interface.
persist-tun
persist-key

# Log to file instead of syslog
log-append /var/log/openvpn.log
verb 4

# Fork to the background
daemon

If you can ping the remote server, using the internal IP address, then your tunnel is up and running.

Asterisk

I suppose that you already know how to configure an Asterisk server, if you don’t you can follow my guide (it’s a bit outdated, I might update it soon).

Getting IAX2 working is really simple too, so I won’t describe it. If you’re using FreePBX, you can follow this guide. Remember to use the internal IP’s from your network.

Make sure your asterisk installation is tagging the correct TOS for the packets. On my FreePBX install it already had the correct configuration set on /etc/asterisk/sip_general_additional.conf. Check your asterisk configuration for the following lines:

tos_sip=cs3
tos_audio=ef
tos_video=af41

This tags your voice data as Expedited Forwarding, normal SIP packets get Class Selector 3 and video data gets Assured Forwarding, Class 4, with drop precedence 1. More on what all this means shortly.

QoS

Getting the right choice of tools for your specific QoS application is a hard problem. You can have some traffic shaping algorithms, congestion avoidance mechanisms and quite a few packet scheduling algorithms. I’m not an expert on how all these different types of algorithms work, or what is the best solution for your case. I’m just putting together some information that I think is relevant. One can always read all the RFC’s about QoS.

First things first, the mentioned TOS field is now called DSCP (Differentiated Services Code Point), it replaces the TOS field and is specified for IPv4 and IPv6 (for reference RFC2474 is the specification). It tries to maintain backward compatibility with the TOS field. Most networks use the following traffic classes:

  • Default PHB — which is typically best-effort traffic
  • Expedited Forwarding (EF) PHB — dedicated to low-loss, low-latency traffic
  • Assured Forwarding (AF) PHB — gives assurance of delivery under prescribed conditions
  • Class Selector PHBs — which maintain backward compatibility with the IP Precedence field.

That is what EF, CS3 and AF41 means, just a common way of signalling that your packet is important, or not that much. But just tagging your packets won’t get you far. For now, you’ve got your Asterisk correctly tagging the packets, and your tunnel to preserve them. Time to add the magic to classify and prioritise the packets!

Linux Traffic Control

Linux has the tc tool for configuring and setting up a QoS policy. With it you can configure different kinds of queueing disciplines and classes. This queues acts directly on net devices, so you have to configure it per device. In the example below we have an ADSL modem on ppp0 device.

TC allows you to configure classful and classless disciples, each one supporting different scheduling algorithms. We will use Hierarchy Token Bucket (HTB) for the classful packets (the ones that got tagged by Asterisk), and Stochastic Fairness Queueing (SFQ) for the classless packets. After getting your queues configured you have to inform iptables that it should use the queue, that’s basically setting up some CLASSIFY targets. You definitely can add some MARK rules to tag your packets, but we don’t need it, Asterisk is doing that job for us.

First we will configure what is the maximum bandwidth allowed, in this case we have an 1000kbps uplink that we want to add a QoS policy. The following table illustrates the QoS policy required for the network. As we are using an asymmetric connection, we will limit the upload bandwidth to 95% of the nominal speed.

Class Nominal rate Maximum rate Priority Packets
Real time 47.5kbps 95kbps 0 ICMP, SYN, RST, ACK
High 522.5kbps 950kbps 1 EF and CS3 packets
Regular 190kbps 950kbps 2 Regular traffic, HTTP, SSH, etc
Bulk 190kbps 950kbps 3  
QoS Policy

With the queues in place you just have to add the necessary iptable rules. The rules will classify the packets that have the DSCP tag using the same classes that you defined using tc. That’s it, your QoS is now in place. Just make sure you add and remove the rules according to the status of your link (in this case ppp0). The script bellow is called by /etc/ppp/ip-up.d and /etc/ppp/ip-down.d, with the start and stop targets respectively.

# !/bin/bash
# 20110916 - Leonardo Santos <leonardo at aligera dot com dot br>
# Initial version. It only uses the iptables target CLASSIFY.
# For the QoS to work, Asterisk has to tag the packets with the right DSCP.
# The OpenVPN tunnel must be passing along the DSCP field, and not blanking it out.
#
PATH=$PATH:/usr/local/sbin:/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin

# uplink in kbps
UPLINK=1000
DEV=ppp0

CEIL=$(($UPLINK*95/100))

CLASS_RT="10"
CLASS_HIGH="11"
CLASS_REG="12"
CLASS_BULK="13"

do_iptables() {
        iptables -$1 POSTROUTING -t mangle -p icmp -j CLASSIFY --set-class 1:$CLASS_RT
        iptables -$1 POSTROUTING -t mangle -p tcp -m tcp --tcp-flags SYN,RST,ACK SYN -j CLASSIFY --set-class 1:$CLASS_RT
        iptables -$1 POSTROUTING -t mangle -p udp -m dscp --dscp-class cs3 -j CLASSIFY --set-class 1:$CLASS_HIGH
        iptables -$1 POSTROUTING -t mangle -p udp -m dscp --dscp-class ef -j CLASSIFY --set-class 1:$CLASS_HIGH
}
add_rules() {
        tc qdisc add dev $DEV root handle 1: htb default $CLASS_BULK
        tc class add dev $DEV parent 1: classid 1:1 htb rate ${CEIL}kbit ceil ${CEIL}kbit
        tc class add dev $DEV parent 1:1 classid 1:$CLASS_RT   htb rate $((1*$CEIL/20))kbit  ceil $(($CEIL/10))kbit prio 0
        tc class add dev $DEV parent 1:1 classid 1:$CLASS_HIGH htb rate $((11*$CEIL/20))kbit ceil ${CEIL}kbit       prio 1
        tc class add dev $DEV parent 1:1 classid 1:$CLASS_REG  htb rate $((4*$CEIL/20))kbit  ceil ${CEIL}kbit       prio 2
        tc class add dev $DEV parent 1:1 classid 1:$CLASS_BULK htb rate $((4*$CEIL/20))kbit  ceil ${CEIL}kbit       prio 3
        tc qdisc add dev $DEV parent 1:$CLASS_HIGH handle 120: sfq perturb 10
        tc qdisc add dev $DEV parent 1:$CLASS_BULK handle 130: sfq perturb 10
        do_iptables A
}
del_rules() {
        tc qdisc del dev $DEV root
        do_iptables D
}
show_status() {
        tc -s -d class show dev $DEV
        tc -s -d qdisc show dev $DEV
}
case $1 in
        start)
                add_rules
        ;;
        stop)
                del_rules
        ;;
        status)
                show_status
        ;;
        restart)
                del_rules
                add_rules
        ;;
        *)
                echo "Usage: $0 {start|stop|restart|status}"
                exit 1
        ;;
esac

I would like to thank Leonardo Santos for putting the script together and letting me publish it, and for being a good friend.

From CVS to Git to Gitorious!

Migrating from CVS to Git

Last week I’ve offered myself to migrate some ~300 repositories to git. Not an easy task at first, but with the right tools at hand the task becomes manageable. Installing cvs2git, and following its documentation will get you started. In Ubuntu that is as simples as:

sudo apt-get install cvs2svn

I know it’s weird, but cvs2git is bundled in cvs2svn… go figure.

But migrating hundreds of repositories isn’t a task to do manually, so I created a script for automating the process. As I had access to the server files, migrating was easier then I expected. My directory structure was something like:

  • cvs_project_1
    • repo_1
    • repo_2
    • repo_3
  • cvs_project_2

I’ve decided to migrate one project at a time, making it straightforward to verify each repo. My script is the following, bare in mind that it my have some flaws, it worked for me. Test it before erasing your old CVS data.

#!/bin/bash
# Copyright (C) Pedro Kiefer

for f in `cat repo_list`;
do
	FOP=${f/\//\-}
	echo "===== Creating git repository for ${f/\//\-/}/";
	sed -e "s/__REPO__/${f/\//\\/}/g" my-default.options > $FOP.options;
	cvs2git --options=$FOP.options
	rm $FOP.options
	mkdir $FOP.git
	cd $FOP.git
	git init --bare
	cat ../cvs2svn-tmp/git-blob.dat ../cvs2svn-tmp/git-dump.dat | git fast-import
	cd ..
done

The script takes a repo_list file with a list of paths to the CVS repositories. Creating this list is quite easy, something like this should work. Be sure to remove CVSROOT and the root directory.

find cvs_project_1/ -maxdepth 1 -type d | sort > repo_list
vim repo_list

The other file the script need is my-default.options, which is the configuration file used by cvs2git. Most of the default values are good, but you really want to add a list of cvs commiters – so you can map the cvs login to a name + email. The other change need is on the line that sets the repository path. For the script to work you need to have it set as __REPO__. Like this:

run_options.set_project(
    # The filesystem path to the part of the CVS repository (*not* a
    # CVS working copy) that should be converted.  This may be a
    # subdirectory (i.e., a module) within a larger CVS repository.
    r'__REPO__',

That’s it, just run the script, and voilà, git repositories for all your cvs modules.

From Git to Gitorious

The second part of my task was importing all of those git repositories to my local Gitorious install. Again, doing it manually is not the right way to do it. After asking about it on gitorious mailing list and learning some ruby, I’ve created this little script. It creates all the repositories for a given project. The projects were created manually on gitorious, as I had only 6 projects – extending the tool to support the creating of projects should be easy.

After using the script above, I had the following directory structure:

  • project_1/
    • repo_1.git
    • repo_2.git
    • repo_3.git

      The scripts takes as argument the project name, which should be equal to the one you created on gitorious web interface. The script scan the project directory and creates the matching gitorious repositories, copying the data to the newly created repository. Some magic regexp was added to remove version numbers and set uniform names to the new repositories. You might want to edit this to your taste.

      By the way, this is my very first ruby programming, don’t expect it to be pretty!

      #!/usr/bin/env ruby
      # encoding: utf-8
      #--
      # Copyright (C) Pedro Kiefer
      #
      # Mass migrate git repositories to gitorious
      #
      #++
      
      require "/path/to/gitorious/config/environment.rb"
      require "optparse"
      
      def new_repos(opts={})
        Repository.new({
          :name => "foo"
          }.merge(opts))
      end
      
      current_proj = ARGV[0]
      
      @project = Project.find_by_slug(current_proj)
      
      Dir.chdir(current_proj)
      puts Dir.pwd
      files = Dir.glob("*.git")
      
      files.each do |f|
        orig_repo = f
        f = f.gsub(/\.git$/, "")
        f = f.gsub(/_/,"-")
        
        # has version?
        version = f.match(/-([0-9](.[0-9][0-9]*)+)(-)?/)
        f = f.gsub(/-([0-9](.[0-9][0-9]*)+)(-)?/, "")
        
        desc = "Repository for package #{f.downcase}\n"
        desc << "Package version #{version&#91;1&#93;}\n" if version
        
        print "Creating repository for package #{f} ... " 
        
        @repo = new_repos(:name => f.downcase, :project => @project, :owner => @project.owner, :user => @project.user, :description => desc)
        @repo.save
        path = @repo.full_repository_path
        Repository.git_backend.create(path)
        Repository.create_git_repository(@repo.real_gitdir)
        @repo.ready = true
        @repo.save 
      
        FileUtils.cp_r(["#{orig_repo}/branches", "#{orig_repo}/info", "#{orig_repo}/objects", "#{orig_repo}/refs"], @repo.full_repository_path)
        puts "Ok!"
      end