xref: /freebsd/share/examples/netgraph/virtual.lan (revision 2e3507c25e42292b45a5482e116d278f5515d04d)
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 adds virtual nodes to one of the physical interfaces
17# visible on your local area network (LAN). Virtual nodes seems real
18# to external observers.
19# If traceroute is executed to one of virtual nodes, the IP
20# address of the physical interface will not be seen in the output.
21# Virtual nodes are generated via jails and network connections are
22# established using ng_bridge(4) and ng_eiface(4) node types.
23#
24# To use this script:
25#
26# 0. Make your own copy of this example script.
27#
28# 1. Edit the definition of ${ETHER_INTF} as described below
29#    to define your real interface connected to the LAN. Virtual nodes
30#    will placed on the same physical network as this interface.
31#
32# 2. Edit the definition of ${TARGET_TOPOLOGY} to define your virtual
33#    nodes. Virtual topology definition includes node names and their
34#    IP address. Target top. syntax: ( node1|ip1/24 node2|ip2/24 ... )
35#    Example 1: ( n1|122.122.122.12/24, n2|122.122.122.13/24 ...)
36#    Example 2: ( n1|2001:b90::14a/125, n1|2001:b90::14b/125 ...)
37#
38# 3. Run this script with "start" as the command line argument.
39#
40# 4. Stop bridging by running this script with "stop" as the
41#    command line argument.
42#
43# 5. This script uses a template file in order to carry information
44#    between start and stop calls.
45#      In the start call, the netgraph interfaces and jails are created.
46#      At the stop phase, all created objects should be removed.
47#    DO NOT delete the temporary file between the start and stop phases.
48#
49# To add virtual nodes for multiple independent LANs, create multiple
50# copies of this script with different variable definitions.
51#
52# Target Topology:
53#
54#
55#                 +---------------+ +---------------+ +---------------+
56#                 |  n0 (vimage)  | |  n1 (vimage)  | |  nk (vimage)  |
57#                 |               | |               | |               |
58#                 | +-----------+ | | +-----------+ | | +-----------+ |
59#                 | |  ngeth0   | | | |  ngeth1   | | | |  ngethk   | |
60#                 | |(ng_eiface)| | | |(ng_eiface)| | | |(ng_eiface)| |
61#                 | +--+-----+--+ | | +--+-----+--+ | | +--+-----+--+ |
62#                 |    |ether|    | |    |ether|    | |    |ether|    |
63#                 |    +--X--+    | |    +--X--+    | |    +---X-+    |
64#   +-----+       +--------\------+ +--------\------+ +-------/-------+
65#   |upper|----\            \ip_addr          \ip_addr       /ip_addr
66# +-+-----+--+  \            \                 \             \
67# |   em0    |   \            +--------+        +-+           \
68# |(ng_ether)|    +-----------+         \          \           \
69# +-+-----+--+                 \         \          /           \
70#   |lower|    +---------\      \         \        /            /
71#   +--X--+   /        O--X--O O-X---O O---X-O O--X--O     O---X---O
72#      \      |        |link0| |link1| |link2| |link3|     |linkk+2|
73#       \     /      +-O-----O-O-----O-O-----O-O-----O-----O-------O-+
74#        +---+       |                                               |
75#                    |          bridge (ng_bridge)                   |
76#                    +-----------------------------------------------+
77#
78#
79
80# Give the name of ethernet interface. Virtual nodes will be seen as
81# local neighbours of this interface.
82
83ETHER_INTF="em0"
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="c1|10.0.2.20/24 c2|10.0.2.21/24 c3|10.0.2.22/24"
89
90# MAC manufacturer prefix. This can be modified according to needs.
91MAC_PREFIX="00:1d:92"
92
93# Temporary file is important for proper execution of script.
94TEMP_FILE="/var/tmp/.virtual.lan.tmp"
95
96# Set root directory for jails to be created.
97JAIL_PATH="/usr/jails/node"
98
99
100####################################################################
101####    Nothing below this point should need to be modified.    ####
102####################################################################
103
104
105# Start/restart routine.
106virtual_lan_start() {
107
108	# Load netgraph KLD's as necessary.
109
110	for KLD in ng_ether ng_bridge ng_eiface; do
111		if ! kldstat -v | grep -qw ${KLD}; then
112			echo -n "Loading ${KLD}.ko... "
113			kldload ${KLD} || exit 1
114			echo "done"
115		fi
116	done
117
118	# Reset all interfaces and jails. If temporary file can not be found
119	# script assumes that there is no previous configuration.
120
121	if [ ! -e ${TEMP_FILE} ]; then
122		echo "No previous configuration(${TEMP_FILE}) found to clean-up."
123	else
124		echo -n "Cleaning previous configuration..."
125		virtual_lan_stop
126		echo "done"
127	fi
128
129	# Create temporary file for usage. This file includes generated
130	# interface names and jail names. All bridges, interfaces and jails
131	# are written to file while created. In clean-up process written
132	# objects are cleaned (i.e. removed) from system.
133
134	if [ -e ${TEMP_FILE} ]; then
135		touch ${TEMP_FILE}
136	fi
137
138	echo -n "Verifying ethernet interface existence..."
139	# Verify ethernet interface exist.
140	if ! ngctl info ${ETHER_INTF}: >/dev/null 2>&1; then
141		echo "Error: interface ${ETHER_INTF} does not exist"
142		exit 1
143	fi
144	ifconfig ${ETHER_INTF} up || exit 1
145	echo "done"
146
147	# Get current number of bridge interfaces in the system. This number
148	# is used to create a name for new bridge.
149	BRIDGE_COUNT=`ngctl l | grep bridge | wc -l | sed -e "s/ //g"`
150	BRIDGE_NAME="bridge${BRIDGE_COUNT}"
151
152	# Create new ng_bridge(4) node and attach it to the ethernet interface.
153	# Connect ng_ether:lower hook to bridge:link0 when creating bridge and
154	# connect ng_ether:upper hook to bridge:link1 after bridge name is set.
155
156	echo "Creating bridge interface: ${BRIDGE_NAME}..."
157	ngctl mkpeer ${ETHER_INTF}: bridge lower link0 || exit 1
158	ngctl name ${ETHER_INTF}:lower ${BRIDGE_NAME} || exit 1
159	ngctl connect ${ETHER_INTF}: ${BRIDGE_NAME}: upper link1 || exit 1
160	echo "Bridge ${BRIDGE_NAME} is created and ${ETHER_INTF} is connected."
161
162	# In the above code block two hooks are connected to bridge interface,
163	# therefore LINKNUM is set to 2 indicating total number of connected
164	# hooks on the bridge interface.
165	LINKNUM=2
166
167	# Write name of the bridge to temp file. Clean-up procedure will use
168	# this name to shutdown bridge interface.
169	echo "bridge ${BRIDGE_NAME}" > ${TEMP_FILE}
170
171
172	# Attach other interfaces as well.
173	for NODE in ${TARGET_TOPOLOGY}; do
174
175		# Virtual nodes are defined in TARGET_TOPOLOGY variable. They
176		# have the form of 'nodeName|IPaddr'. Below two lines split
177		# node definition to get node name and node IP.
178
179		NODE_NAME=`echo ${NODE} | awk -F"|" '{print $1}'`
180		NODE_IP=`echo ${NODE} | awk -F"|" '{print $2}'`
181
182		# Create virtual node (jail) with given name and using
183		# JAIL_PATH as root directory for jail.
184
185		echo -n "Creating virtual node (jail) ${NODE_NAME}..."
186		jail -c vnet name=${NODE_NAME} host.hostname=${NODE_NAME} \
187			path=${JAIL_PATH} persist
188		echo "done"
189
190		# Write name of the jail to temp file. Clean-up procedure will
191		# use this name to remove jail.
192
193		echo "node ${NODE_NAME}" >> ${TEMP_FILE}
194
195		# Create a ng_eiface object for virtual node. ng_eiface
196		# object has a hook that can be connected to one of bridge
197		# links. After creating interface get its automatically
198		# generated name for further usage.
199
200		echo "Creating eiface interface for virtual node ${NODE_NAME}."
201		ngctl mkpeer eiface ether ether
202		EIFACE=`ngctl l | grep ngeth | tail -n 1| awk '{print $2}'`
203		echo "Interface ${EIFACE} is created."
204
205		# Write name of the interface to temp file. Clean-up procedure
206		# will use this name to shutdown interface.
207
208		echo "interface ${EIFACE}" >> ${TEMP_FILE}
209
210		# Move virtual interface to virtual node. Note that Interface
211		# name will not be changed at the end of this movement. Moved
212		# interface can be seen at the output of ifconfig command in
213		# jail: 'jexec jailname ifconfig'
214
215		echo "Moving ${EIFACE} to ${NODE_NAME}"
216		ifconfig ${EIFACE} vnet ${NODE_NAME}
217
218		# Make lo0 interface localhost.
219		jexec ${NODE_NAME} ifconfig lo0 localhost
220
221		# Generate a random mac address for virtual interface. First
222		# three octets can be changed by user. Last three octets are
223		# generated randomly.
224		M4=`od -An -N2 -i /dev/random | sed -e 's/ //g' | \
225				awk '{ print $1 % 256 }'`
226		M5=`od -An -N2 -i /dev/random | sed -e 's/ //g' | \
227				awk '{ print $1 % 256 }'`
228		M6=`od -An -N2 -i /dev/random | sed -e 's/ //g' | \
229				awk '{ print $1 % 256 }'`
230
231		MAC=`printf ${MAC_PREFIX}:%02x:%02x:%02x ${M4} ${M5} ${M6}`
232
233		# Set the link address (mac address) of virtual interface in
234		# virtual node to randomly generated MAC.
235		echo "Setting MAC address of ${EIFACE} to '${MAC}'"
236		jexec ${NODE_NAME} ifconfig ${EIFACE} link $MAC
237
238		# Either IPv4 or IPv6 can be used in this script. Ifconfig
239		# IP setting syntax differs slightly for two IP versions.
240		# For version 4 'inet' keyword is used whereas for version 6
241		# 'inet6' is used. Below line tries to decide which IP version
242		# is given and sets IPVER to 'inet' or 'inet6'.
243
244		IPVER=`echo ${NODE_IP} | awk -F"." '{ split($4,last,"/"); \
245			if( NF==4 && $1>0 && $1<256 && $2<256 && $3<256 && \
246			last[1]<256) print "inet"; else print "inet6"}'`
247
248		# Set IP address of virtual interface in virtual node.
249		echo "Setting IP address of ${EIFACE} to '${NODE_IP}'"
250		jexec ${NODE_NAME} ifconfig ${EIFACE} ${IPVER} ${NODE_IP}
251
252		# Connect virtual interface to bridge interface. Syntax is :
253		# ngctl connect INTERFACE: BRIDGE: INTERFACE_HOOK EMPTY_LINK.
254		# Interface has one hook named 'ether' and below line connects
255		# ether hook to bridge's first unconnected link.
256
257		echo -n "Connecting ${EIFACE}:ether to ${BRIDGE_NAME}:link${LINKNUM}..."
258		ngctl connect ${EIFACE}: ${BRIDGE_NAME}: ether link${LINKNUM} \
259			|| exit 1
260		echo "done"
261
262		# Now, bridge has one more connected link thus link count is
263		# incremented.
264		LINKNUM=`expr ${LINKNUM} + 1`
265	done
266	echo "Virtual LAN established successfully!"
267
268}
269
270# Stop routine.
271virtual_lan_stop() {
272
273	if [ ! -e ${TEMP_FILE} ]; then
274		echo "Nothing to stop! ${TEMP_FILE}: temp file not found"
275	else
276
277		echo -n "Shutdown bridge interface.."
278		OBJECTS=`cat ${TEMP_FILE} | grep bridge | awk '{print $2}'`
279		for BRIDGE in ${OBJECTS}; do
280			ngctl shutdown ${BRIDGE}: >/dev/null 2>&1
281		done
282		echo "done"
283
284		echo -n "Shutdown all eiface interfaces..."
285		OBJECTS=`cat ${TEMP_FILE} | grep interface | awk '{print $2}'`
286		for INTERFACE in ${OBJECTS}; do
287			ngctl shutdown ${INTERFACE}: >/dev/null 2>&1
288		done
289		echo "done"
290
291		echo -n "Removing all jails..."
292		OBJECTS=`cat ${TEMP_FILE} | grep node | awk '{print $2}'`
293		for NODE in ${OBJECTS}; do
294			jail -r ${NODE}
295		done
296		echo "done"
297
298		echo "Removing tempfile ${TEMP_FILE}"
299		rm ${TEMP_FILE}
300	fi
301	echo "Virtual LAN objects removed successfully!"
302
303}
304
305virtual_lan_usage() {
306	echo "usage: $0 start [target_topology]"
307	echo "     : $0 [ stop | help ]"
308}
309
310
311# Main entry point.
312
313case $# in
314	1)
315		case $1 in
316                        start)
317                                echo -n "Creating default target topology:"
318				echo " ${TARGET_TOPOLOGY}"
319                                virtual_lan_start
320                                ;;
321                        stop)
322
323				if [ ! -e ${TEMP_FILE} ]; then
324					echo -n "Noting to stop! ${TEMP_FILE}:"
325					echo " temp file not found"
326				else
327					virtual_lan_stop
328				fi
329                                ;;
330                        help)
331                                virtual_lan_usage
332				exit 1
333                                ;;
334                        *)
335                                virtual_lan_usage
336                                exit 1
337
338                esac
339		;;
340	2)
341	        case $1 in
342			start)
343                        	TARGET_TOPOLOGY=$2
344                                echo -n "Creating target topology:"
345				echo "${TARGET_TOPOLOGY}"
346                                virtual_lan_start
347                                ;;
348                        *)
349                        	virtual_lan_usage
350                                exit 1
351                esac
352		;;
353
354	*)
355                virtual_lan_usage
356                exit 1
357esac
358
359