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