1#!/bin/sh 2# 3# Copyright (c) 2010, Yavuz Gokirmak 4# 5# All rights reserved. 6# 7# This source code may be used, modified, copied, distributed, and 8# sold, in both source and binary form provided that the above 9# copyright and these terms are retained, verbatim, as the first 10# lines of this file. Under no circumstances is the author 11# responsible for the proper functioning of the software nor does 12# the author assume any responsibility for damages incurred with 13# its use. 14# 15# 16# This script creates and connects n router like nodes. Complex wide 17# area topologies can be created with the help of script. 18# 19# Virtual nodes are generated via jails and network connections are 20# established using ng_eiface(4) node types. 21# 22# To use this script: 23# 24# 0. Make your own copy of this example script. 25# 26# 1. Edit the definition of ${TARGET_TOPOLOGY} to define your virtual 27# nodes. Virtual topology definition includes node names and their 28# IP address. Target top. syntax: ( name|ip<->name|ip ... ) 29# Example 1: ( n1|10.0.2.1/30<->n2|10.0.2.2/30 ...) 30# Example 2: ( n1|2001:b90::14a/125<->n1|2001:b90::14b/125 ...) 31# 32# 2. Run this script with "start" as the command line argument. 33# 34# 3. Add necessary static route commands for each virtual node. For 35# example assume you have three virtual nodes connected each other 36# like a chain (n1 is connected to n2, n2 is connected to n3). 37# In order to establish connectivity among these virtual nodes, 38# you have to add default routes to node n1 and node n3. Example 39# static route command is: 40# STATIC_ROUTE0="jexec n1 route add -inet default 10.0.2.2" 41# STATIC_ROUTE1="jexec n3 route add -inet default 10.0.2.5" 42# After defining default routes with above format you have to set 43# the total number of static route commands as: 44# STATIC_ROUTE_CNT=2 45# 46# 4. Stop bridging by running this script with "stop" as the 47# command line argument. 48# 49# 5. This script uses a template file in order to carry information 50# between start and stop calls. 51# In the start call, the netgraph interfaces and jails are created. 52# At the stop phase, all created objects should be removed. 53# DO NOT delete the temporary file between the start and stop phases. 54# 55# Target Topology: 56# 57# +---------------+ +---------------------------------------------+ 58# | n1 (vimage) | | n2 (vimage) | 59# | | | | 60# | +-----------+ | | +-----------+ +-----------+ +-----------+ | 61# | | ngeth0 | | | | ngeth1 | | ngeth2 | | ngeth4 | | 62# | |(ng_eiface)| | | |(ng_eiface)| |(ng_eiface)| |(ng_eiface)| | 63# | +--+-----+--+ | | +--+-----+--+ +--+-----+--+ +--+-----+--+ | 64# | |ether| | | |ether| |ether| |ether| | 65# | +-X---+ | | +--X--+ +--X--+ +--X--+ | 66# +-------X-------+ +------X--------------X---------------X-------+ 67# X X X X 68# X X X X 69# XXXXXXXXXXXXXXX X X 70# X X 71# +--------X------+ +--------X------+ 72# | -+--X--+- | | -+--X--+- | 73# | |ether| | | |ether| | 74# | +--+-----+--+ | | +--+-----+--+ | 75# | | ngeth3 | | | | ngeth5 | | 76# | |(ng_eiface)| | | |(ng_eiface)| | 77# | +-----------+ | | +-----------+ | 78# | | | | 79# | n3 (vimage) | | n4 (vimage) | 80# +---------------+ +---------------+ 81# 82# 83# 84 85# List the names of virtual nodes and their IP addresses. Use ':' 86# character to separate node name from node IP address and netmask. 87 88TARGET_TOPOLOGY="n1|10.0.2.1/30<->n2|10.0.2.2/30 n2|10.0.2.5/30<->n3|10.0.2.6/30 n2|10.0.2.9/30<->n4|10.0.2.10/30" 89STATIC_ROUTE0="jexec n1 route add -inet default 10.0.2.2" 90STATIC_ROUTE1="jexec n3 route add -inet default 10.0.2.5" 91STATIC_ROUTE2="jexec n4 route add -inet default 10.0.2.9" 92STATIC_ROUTE_CNT=3 93 94# MAC manufacturer prefix. This can be modified according to needs. 95MAC_PREFIX="00:1d:92" 96 97# Temporary file is important for proper execution of script. 98TEMP_FILE="/var/tmp/.virtual.chain.tmp" 99 100# Set root directory for jails to be created. 101JAIL_PATH="/usr/jails/router" 102 103 104#################################################################### 105#### Nothing below this point should need to be modified. #### 106#################################################################### 107 108 109# Start/restart routine. 110virtual_chain_start() { 111 112 # Load netgraph KLD's as necessary. 113 114 for KLD in ng_ether ng_bridge ng_eiface; do 115 if ! kldstat -v | grep -qw ${KLD}; then 116 echo -n "Loading ${KLD}.ko... " 117 kldload ${KLD} || exit 1 118 echo "done" 119 fi 120 done 121 122 # Reset all interfaces and jails. If temporary file can not be found 123 # script assumes that there is no previous configuration. 124 125 if [ ! -e ${TEMP_FILE} ]; then 126 echo "No previous configuration(${TEMP_FILE}) found to clean-up." 127 else 128 echo -n "Cleaning previous configuration..." 129 virtual_chain_stop 130 echo "done" 131 fi 132 133 # Create temporary file for usage. This file includes generated 134 # interface names and jail names. All bridges, interfaces and jails 135 # are written to file while created. In clean-up process written 136 # objects are cleaned (i.e. removed) from system. 137 138 if [ -e ${TEMP_FILE} ]; then 139 touch ${TEMP_FILE} 140 fi 141 142 143 # Attach other interfaces as well. 144 for CONNECTION in ${TARGET_TOPOLOGY}; do 145 146 # Virtual connections are defined in TARGET_TOPOLOGY variable. 147 # They have the form of 'nodeName|IPaddr'. Below two lines split 148 149 PEER1=`echo ${CONNECTION} | awk -F"<->" '{print $1}'` 150 PEER1_NAME=`echo ${PEER1} | awk -F"|" '{print $1}'` 151 PEER1_IP=`echo ${PEER1} | awk -F"|" '{print $2}'` 152 153 PEER2=`echo ${CONNECTION} | awk -F"<->" '{print $2}'` 154 PEER2_NAME=`echo ${PEER2} | awk -F"|" '{print $1}'` 155 PEER2_IP=`echo ${PEER2} | awk -F"|" '{print $2}'` 156 157 # !!! if not created already.. 158 # Create virtual node (jail) with given name and using 159 # JAIL_PATH as root directory for jail. 160 161 virtual_chain_create_peer_if_necessary ${PEER1_NAME} 162 virtual_chain_create_peer_if_necessary ${PEER2_NAME} 163 164 # create an interface for peer with the given peer IP. Get interface 165 # for future use; you will connect this interface to the other 166 # peers' (PEER2) interface. 167 virtual_chain_create_interface_with_ip ${PEER1_NAME} ${PEER1_IP} 168 PEER1_INTERFACE=${RET_INTERFACE} 169 170 # create an interface for peer with the given peer IP. Get interface 171 # for future use; you will connect this interface to the other 172 # peers' (PEER2) interface. 173 virtual_chain_create_interface_with_ip ${PEER2_NAME} ${PEER2_IP} 174 PEER2_INTERFACE=${RET_INTERFACE} 175 176 # Connect virtual interface to other interface. Syntax is : 177 # ngctl connect INTERFACE1: INTERFACE2: ether ether. 178 179 echo -n "Connecting ${PEER1_INTERFACE}:ether to ${PEER2_INTERFACE}:ether..." 180 ngctl connect ${PEER1_INTERFACE}: ${PEER2_INTERFACE}: ether ether \ 181 || exit 1 182 echo "done" 183 184 done 185 186 # Executes static route add commands. 187 i=0 188 while [ $i != $STATIC_ROUTE_CNT ]; do 189 eval ROUTE=\${STATIC_ROUTE${i}} 190 ret=`${ROUTE}` 191 i=`expr $i + 1` 192 done 193 194 echo "Virtual WAN established successfully!" 195} 196 197virtual_chain_create_interface_with_ip() { 198 199 NODE_NAME=$1 200 NODE_IP=$2 201 202 # Create a ng_eiface object for virtual node. ng_eiface 203 # object has a hook that can be connected to one of bridge 204 # links. After creating interface get its automatically 205 # generated name for further usage. 206 207 echo "Creating eiface interface for virtual node ${NODE_NAME}." 208 ngctl mkpeer eiface ether ether 209 EIFACE=`ngctl l | grep ngeth | tail -n 1| awk '{print $2}'` 210 echo "Interface ${EIFACE} is created." 211 212 # Write name of the interface to temp file. Clean-up procedure 213 # will use this name to shutdown interface. 214 215 echo "interface ${EIFACE}" >> ${TEMP_FILE} 216 217 # Move virtual interface to virtual node. Note that Interface 218 # name will not be changed at the end of this movement. Moved 219 # interface can be seen at the output of ifconfig command in 220 # jail: 'jexec jailname ifconfig' 221 222 echo "Moving ${EIFACE} to ${NODE_NAME}" 223 ifconfig ${EIFACE} vnet ${NODE_NAME} 224 225 # Make lo0 interface localhost. 226 jexec ${NODE_NAME} ifconfig lo0 localhost 227 228 # Generate a random mac address for virtual interface. First 229 # three octets can be changed by user. Last three octets are 230 # generated randomly. 231 M4=`od -An -N2 -i /dev/random | sed -e 's/ //g' | \ 232 awk '{ print $1 % 256 }'` 233 M5=`od -An -N2 -i /dev/random | sed -e 's/ //g' | \ 234 awk '{ print $1 % 256 }'` 235 M6=`od -An -N2 -i /dev/random | sed -e 's/ //g' | \ 236 awk '{ print $1 % 256 }'` 237 238 MAC=`printf ${MAC_PREFIX}:%02x:%02x:%02x ${M4} ${M5} ${M6}` 239 240 # Set the link address (mac address) of virtual interface in 241 # virtual node to randomly generated MAC. 242 echo "Setting MAC address of ${EIFACE} to '${MAC}'" 243 jexec ${NODE_NAME} ifconfig ${EIFACE} link $MAC 244 245 # Either IPv4 or IPv6 can be used in this script. Ifconfig 246 # IP setting syntax differs slightly for two IP versions. 247 # For version 4 'inet' keyword is used whereas for version 6 248 # 'inet6' is used. Below line tries to decide which IP version 249 # is given and sets IPVER to 'inet' or 'inet6'. 250 251 IPVER=`echo ${NODE_IP} | awk -F"." '{ split($4,last,"/"); \ 252 if( NF==4 && $1>0 && $1<256 && $2<256 && $3<256 && \ 253 last[1]<256) print "inet"; else print "inet6"}'` 254 255 # Set IP address of virtual interface in virtual node. 256 echo "Setting IP address of ${EIFACE} to '${NODE_IP}'" 257 jexec ${NODE_NAME} ifconfig ${EIFACE} ${IPVER} ${NODE_IP} 258 259 RET_INTERFACE=${EIFACE} 260} 261 262virtual_chain_create_peer_if_necessary() { 263 264 if ! grep -q $1 ${TEMP_FILE} ; then 265 266 echo -n "Creating virtual node (jail) ${1}..." 267 jail -c vnet name=${1} host.hostname=${1} \ 268 path=${JAIL_PATH} persist 269 jexec ${1} sysctl -w net.inet.ip.forwarding=1 270 jexec ${1} sysctl -w net.inet6.ip6.forwarding=1 271 echo "done" 272 273 # Write name of the jail to temp file. Clean-up 274 # procedure will use this name to remove jail. 275 276 echo "node ${1}" >> ${TEMP_FILE} 277 fi 278 279} 280 281# Stop routine. 282virtual_chain_stop() { 283 284 if [ ! -e ${TEMP_FILE} ]; then 285 echo "Nothing to stop! ${TEMP_FILE}: temp file not found" 286 else 287 288 echo -n "Shutdown bridge interface.." 289 OBJECTS=`cat ${TEMP_FILE} | grep bridge | awk '{print $2}'` 290 for BRIDGE in ${OBJECTS}; do 291 ngctl shutdown ${BRIDGE}: >/dev/null 2>&1 292 done 293 echo "done" 294 295 echo -n "Shutdown all eiface interfaces..." 296 OBJECTS=`cat ${TEMP_FILE} | grep interface | awk '{print $2}'` 297 for INTERFACE in ${OBJECTS}; do 298 ngctl shutdown ${INTERFACE}: >/dev/null 2>&1 299 done 300 echo "done" 301 302 echo -n "Removing all jails..." 303 OBJECTS=`cat ${TEMP_FILE} | grep node | awk '{print $2}'` 304 for NODE in ${OBJECTS}; do 305 jail -r ${NODE} 306 done 307 echo "done" 308 309 echo "Removing tempfile ${TEMP_FILE}" 310 rm ${TEMP_FILE} 311 fi 312 echo "Virtual LAN objects removed successfully!" 313 314} 315 316virtual_chain_usage() { 317 echo "usage: $0 start [target_topology]" 318 echo " : $0 [ stop | help ]" 319} 320 321 322# Main entry point. 323 324case $# in 325 1) 326 case $1 in 327 start) 328 echo -n "Creating default target topology:" 329 echo " ${TARGET_TOPOLOGY}" 330 virtual_chain_start 331 ;; 332 stop) 333 334 if [ ! -e ${TEMP_FILE} ]; then 335 echo -n "Noting to stop! ${TEMP_FILE}:" 336 echo " temp file not found" 337 else 338 virtual_chain_stop 339 fi 340 ;; 341 help) 342 virtual_chain_usage 343 exit 1 344 ;; 345 *) 346 virtual_chain_usage 347 exit 1 348 349 esac 350 ;; 351 2) 352 case $1 in 353 start) 354 TARGET_TOPOLOGY=$2 355 echo -n "Creating target topology:" 356 echo "${TARGET_TOPOLOGY}" 357 virtual_chain_start 358 ;; 359 *) 360 virtual_chain_usage 361 exit 1 362 esac 363 ;; 364 365 *) 366 virtual_chain_usage 367 exit 1 368esac 369 370