xref: /freebsd/share/examples/netgraph/virtual.chain (revision a03411e84728e9b267056fd31c7d1d9d1dc1b01e)
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