summaryrefslogtreecommitdiff
path: root/Generate
blob: 4bab1ebcf63e2a2096f4772420bc5af3cee42a80 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
#!/usr/bin/ruby

require 'yaml'
require 'ipaddr'


THISHOST = `hostname`.chomp
RIGHTNOW = "<removed timestamp as it breaks checksumming>"
#RIGHTNOW = `822-date`.chomp
THISPROGRAM = $0
QUAGGA_DAEMONS = "# This file was automatically created by #{THISPROGRAM} on #{THISHOST} at #{RIGHTNOW}"+'
# This file tells the quagga package which daemons to start.
#
# Entries are in the format: <daemon>=(yes|no|priority)
#   0, "no"  = disabled
#   1, "yes" = highest priority
#   2 .. 10  = lower priorities
# Read /usr/share/doc/quagga/README.Debian for details.
#
# Sample configurations for these daemons can be found in
# /usr/share/doc/quagga/examples/.
#
# ATTENTION: 
#
# When activation a daemon at the first time, a config file, even if it is
# empty, has to be present *and* be owned by the user and group "quagga", else
# the daemon will not be started by /etc/init.d/quagga. The permissions should
# be u=rw,g=r,o=.
# When using "vtysh" such a config file is also needed. It should be owned by
# group "quaggavty" and set to ug=rw,o= though. Check /etc/pam.d/quagga, too.
#
zebra=yes
bgpd=yes
ospfd=no
ospf6d=no
ripd=no
ripngd=no
isisd=no
'
QUAGGA_ZEBRA = "! This file was automatically created by #{THISPROGRAM} on #{THISHOST} at #{RIGHTNOW}"+'
! -*- zebra -*-
!
hostname HOSTNAME
password zebra
enable password L5ar2fa
!
! Interface\'s description.
!
!interface lo
! description test of desc.
!
!interface sit0
! multicast

!
! Static default route sample.
!
!ip route 0.0.0.0/0 203.181.89.241
!

log syslog
'
QUAGGA_BGPD_HEAD = "! This file was automatically created by #{THISPROGRAM} on #{THISHOST} at #{RIGHTNOW}"+'
! -*- bgpd -*-
!
hostname HOSTNAME
password zebra
enable password L5ar2fa
!
log syslog
!debug bgp
!debug bgp events
!debug bgp filters
!debug bgp fsm
!!debug bgp keepalives
!debug bgp updates
!
'
DEFAULT_OPENSSLCNF = '
HOME		= CA
oid_section	= new_oids
[ new_oids ]
[ ca ]
default_ca	= CA_default
[ CA_default ]
dir		= CA/keys		# Where everything is kept
certs		= $dir			# Where the issued certs are kept
crl_dir		= $dir			# Where the issued crl are kept
database	= $dir/index.txt	# database index file.
new_certs_dir	= $dir			# default place for new certs.

certificate	= $dir/ca.crt		# The CA certificate
serial		= $dir/serial		# The current serial number
crl		= $dir/crl.pem		# The current CRL
private_key	= $dir/ca.key		# The private key
RANDFILE	= $dir/.rand		# private random number file

x509_extensions	= usr_cert		# The extentions to add to the cert

default_days	= 3650			# how long to certify for
default_crl_days= 30			# how long before next CRL
default_md	= md5			# which md to use.
preserve	= no			# keep passed DN ordering

policy		= policy_match

[ policy_match ]
organizationName	= supplied
commonName		= supplied

[ req ]
default_bits		= 2048
distinguished_name	= req_distinguished_name
attributes		= req_attributes
x509_extensions		= v3_ca		# The extentions to add to the self signed cert

string_mask		= nombstr

[ req_distinguished_name ]
0.organizationName	= Organization Name
0.organizationName_default= %s
commonName		= Common Name
commonName_default	= $ENV::CN
commonName_max		= 64

[ req_attributes ]

[ usr_cert ]
basicConstraints	= CA:FALSE
nsComment		= "Created using Peter Palfrader\'s MakeVPN and OpenSSL"
subjectKeyIdentifier	= hash
authorityKeyIdentifier	= keyid,issuer:always

[ v3_req ]
basicConstraints	= CA:FALSE
keyUsage		= nonRepudiation, digitalSignature, keyEncipherment

[ v3_ca ]
basicConstraints	= CA:true
subjectKeyIdentifier	= hash
authorityKeyIdentifier	= keyid:always,issuer:always

[ crl_ext ]
authorityKeyIdentifier	= keyid:always,issuer:always
'

UPSCRIPT='#!/bin/sh

TUN_DEV=$1
TUN_MTU=$2
LINK_MTU=$3
LOCAL_IP=$4
REMOTE_IP=$5
ACTION=$6

echo 0 > /proc/sys/net/ipv4/conf/$TUN_DEV/rp_filter

#gw=$(ip route show 0.0.0.0/0 | awk \'{print $3; exit }\');
#ip r add 141.70.64.0/20 via "$gw"
#ip r add 10.0.0.0/8 via "$gw"
#ip r add 0.0.0.0/1 dev "$TUN_DEV"
#ip r add 128.0.0.0/1 dev "$TUN_DEV"
true
'

######################################################################################
######################################################################################

$config = YAML::load( File.open( 'Hosts' ) )
$NAMESPACE=$config['namespace']

def sys(command)
	system(command) or throw "Command '#{command}' failed"
end

def check_ca
	return true if FileTest.directory?("CA")
	Dir.mkdir("CA")
	Dir.mkdir("CA/keys")
	opensslcnf = File.new('CA/openssl.cnf', "w")
	opensslcnf.puts DEFAULT_OPENSSLCNF%[$config['cadndomain']]
	opensslcnf.close

	ENV['CN'] = 'ca'+'.'+$config['cadndomain']
	sys("openssl req -batch -days 3650 -nodes -new -newkey rsa:4096 -x509 -keyout CA/keys/ca.key -out CA/keys/ca.crt -config CA/openssl.cnf ")
	File.chmod(0600, "CA/keys/ca.key") == 1 or throw "Cannot chmod CA/keys/ca.key"

	index=File.new('CA/keys/index.txt', "w")
	index.close
	serial=File.new('CA/keys/serial', "w")
	serial.puts "01"
	serial.close
end

def check_dh
	return true if FileTest.exists?("CA/keys/dh2048.pem")
	if FileTest.exists?("dh2048.pem")
		sys("cp dh2048.pem CA/keys/")
	else
		sys("openssl dhparam -out CA/keys/dh2048.pem 2048")
	end
end

def build_key(cn)
	return true if FileTest.exists?("CA/keys/#{cn}.p12")

	ENV['CN'] = cn
	sys("openssl req -batch -days 3650 -nodes -new -newkey rsa:3072 -keyout CA/keys/#{cn}.key -out CA/keys/#{cn}.csr -config CA/openssl.cnf ")
	sys("openssl ca  -batch -days 3650 -out CA/keys/#{cn}.crt -in CA/keys/#{cn}.csr -config CA/openssl.cnf ")
	File.chmod(0600, "CA/keys/#{cn}.key") == 1 or throw "Cannot chmod CA/keys/#{cn}.key"
	sys("openssl pkcs12 -passout pass: -export -in CA/keys/#{cn}.crt -inkey CA/keys/#{cn}.key -certfile CA/keys/ca.crt -out CA/keys/#{cn}.p12")
	File.chmod(0600, "CA/keys/#{cn}.p12") == 1 or throw "Cannot chmod CA/keys/#{cn}.p12"
end

def check_keys(host)
	check_ca
	check_dh

	cn = host['name'] +'.'+ $config['cadndomain']
	build_key(cn)
end


def makelist(s)
	s.delete(' ').split(',')
end

def included_in_list(haystack, needle)
	makelist(haystack).include?(needle)
end

def can_connect(client, server)
	if server['accepts'] == nil
		return false
	elsif server['accepts'] == '*'
		return true
	elsif included_in_list(server['accepts'], client['name'])
		return true
	else
		return false
	end
end


def want_connection(h1, h2)
	if h1['limit_peers'] == nil and
	   h2['limit_peers'] == nil
		return true
	end

	if h1['limit_peers'] != nil and
	   !included_in_list(h1['limit_peers'], h2['name'])
		return false
	end
	if h2['limit_peers'] != nil and
	   !included_in_list(h2['limit_peers'], h1['name'])
		return false
	end

	return true
end

def makelistname(peer, inout)
	if inout == 'import'
		return 'VPNn2'+peer+'In'
	elsif inout == 'export'
		return 'VPNn2'+peer+'Out'
	else
		throw "Unknown inout #{inout}"
	end
end


hostlist = $config['hosts'].values.sort{ |a,b| a['host_no'] <=> b['host_no'] }
$config['hosts'].each_pair{ |name, host|
	throw "Host #{name} doesn't have any groups" unless host['groups']
	host['groups'] = makelist(host['groups']).sort{|a,b| a<=>b}
	host['groups'].each{ |g|
		throw "Group #{g} in #{name} is not defined" unless $config['groups'][g]
	}

	host['name'] = name
	host['vpn_address'] = $config['prefix']+'.'+host['host_no'].to_s
	host['vpn_address6'] = $config['prefix6']+':'+host['host_no'].to_s
	host['asn'] = $config['baseasn']+host['host_no']
	host['ifacename'] = "tun-n2-#{host['name']}"[0..14]
	host['networks'] = {} unless host['networks']
	host['networks'][host['vpn_address']+"/32"] = host['groups'].join(',')
	host['pkcs12'] = true unless host.has_key?('pkcs12')
	host['ipv6'] = false unless host.has_key?('ipv6')
	host['networks6'] = {} unless host['networks6']
	if host['ipv6']
		host['networks6'][host['vpn_address6']+"/128"] = host['groups'].join(',')
	end
	host['inet_port'] = {}
}
throw "Duplicate iface names" unless hostlist.collect{ |host| host['ifacename'] }.uniq.size == $config['hosts'].size

# Setup peers and link local addresses
hostlist.each{ |host|
	host['peers'] = hostlist.find_all{ |peer|
		host['name'] != peer['name'] and
		want_connection(host, peer) and
		(can_connect(host,peer) or can_connect(peer,host))
	}

	host['link-local'] = {}
	host['peers'].each{ |peer|
		lower = host['host_no'] < peer['host_no'] ? host['host_no'] : peer['host_no']
		higher = host['host_no'] > peer['host_no'] ? host['host_no'] : peer['host_no']
		ipv6_link_local_me = "fe80:0:#{lower}:#{higher}:0:0:0:#{host['host_no']}"
		ipv6_link_local_peer = "fe80:0:#{lower}:#{higher}:0:0:0:#{peer['host_no']}"
		host['link-local'][ peer['name'] ] = {
			"me"   => ipv6_link_local_me,
			"peer" => ipv6_link_local_peer
		}
	}
}

# Setup ports for openvpn
# =======================
# First import ports from manual configuration
hostlist.each{ |host|
	next unless host['inet_port_override']
	host['inet_port_override'].each_key{ |peername|
		hostname = host['name']
		peer = $config['hosts'][ peername ]
		throw "Peer #{peername} for host #{hostname} not found" unless peer

		[peername, hostname].each{ |item|
			throw "host->#{hostname}->inet_port_override->#{peername} does not have a key #{item}" unless host['inet_port_override'][peername][item]
		}

		host['inet_port'][peername] = {}
		host['inet_port'][peername]['local'] = host['inet_port_override'][peername][hostname]
		host['inet_port'][peername]['remote'] = host['inet_port_override'][peername][peername]

		unless peer['inet_port'][hostname]
			peer['inet_port'][hostname]  = {}
			[peername, hostname].each{ |item|
				if peer['inet_port_override'] and
				   peer['inet_port_override'][hostname] and
				   peer['inet_port_override'][hostname][item] and
				   peer['inet_port_override'][hostname][item] != host['inet_port_override'][peername][item]
					throw("host->#{hostname}->inet_port_override->#{peername}->#{item} and "+
					      "host->#{peername}->inet_port_override->#{hostname}->#{item} both exist but are different")
				end
			}
			peer['inet_port'][hostname]['remote'] = host['inet_port'][peername]['local']
			peer['inet_port'][hostname]['local'] = host['inet_port'][peername]['remote']
		end
	}
}
# Then set the default values if nothing is set yet
hostlist.each{ |host|
	host['peers'].each{ |peer|
		host['inet_port'][ peer['name'] ] = {
			"remote"  => $config['baseport']+host['host_no'],
			"local" => $config['baseport']+peer['host_no']
		} unless host['inet_port'][ peer['name'] ]
	}
	throw "Duplicate local ports on host #{host['name']}" unless host['inet_port'].values.collect{ |peer| peer['local'] }.uniq.size == host['inet_port'].size
}

#####################################################################
# Create the zonefile
zonefile = File.new("#{$NAMESPACE}.zone", "w")
zonefile.puts "; zonefile snipped for #{$NAMESPACE} VPN"
zonefile.puts "; Automatically created on #{THISHOST} at #{RIGHTNOW} by #{THISPROGRAM}."
hostlist.each{ |host|
	zonefile.puts "#{host['name']}		IN	A	#{host['vpn_address']}"
	zonefile.puts "#{host['name']}		IN	AAAA	#{host['vpn_address6']}" if host['ipv6']
}
zonefile.puts
zonefile.puts "; vim:set syn=dns:"
zonefile.close

#####################################################################
hostlist.each{ |host|
	check_keys host
	cn = host['name'] +'.'+ $config['cadndomain']
	dir = "#{$NAMESPACE}-#{host['name']}"
	Dir.mkdir(dir) unless FileTest.directory?(dir)

	sys("rm -f #{dir}/#{$NAMESPACE}-#{cn}.p12")
	sys("rm -f #{dir}/#{$NAMESPACE}-#{cn}.crt")
	sys("rm -f #{dir}/#{$NAMESPACE}-#{cn}.key")
	sys("rm -f #{dir}/#{$NAMESPACE}-CA.crt")
	if host['pkcs12']
		sys("ln CA/keys/#{cn}.p12 #{dir}/#{$NAMESPACE}-#{cn}.p12")
	else
		sys("ln CA/keys/#{cn}.crt #{dir}/#{$NAMESPACE}-#{cn}.crt")
		sys("ln CA/keys/#{cn}.key #{dir}/#{$NAMESPACE}-#{cn}.key")
		sys("ln CA/keys/ca.crt #{dir}/#{$NAMESPACE}-CA.crt")
	end
	sys("rm -f #{dir}/#{$NAMESPACE}.dh2048.pem && ln CA/keys/dh2048.pem #{dir}/#{$NAMESPACE}.dh2048.pem")


	iptables = File.new("#{dir}/#{$NAMESPACE}.iptables.sh", "w")
	ip6tables = File.new("#{dir}/#{$NAMESPACE}.ip6tables.sh", "w")
	ipferm = File.new("#{dir}/#{$NAMESPACE}.iptables.ferm", "w")
	ip6ferm = File.new("#{dir}/#{$NAMESPACE}.ip6tables.ferm", "w")
	iptables.puts "# Automatically created on #{THISHOST} at #{RIGHTNOW} by #{THISPROGRAM}."
	iptables.puts "PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin"
	iptables.puts "echo 'Doing #{$NAMESPACE} VPN rules.'"
	iptables.puts "iptables --new-chain vpn-#{$NAMESPACE}"
	iptables.puts "iptables --flush vpn-#{$NAMESPACE}"

	ip6tables.puts "# Automatically created on #{THISHOST} at #{RIGHTNOW} by #{THISPROGRAM}."
	ip6tables.puts "PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin"
	ip6tables.puts "echo 'Doing #{$NAMESPACE} VPN rules.'"
	ip6tables.puts "ip6tables --new-chain vpn-#{$NAMESPACE}"
	ip6tables.puts "ip6tables --flush vpn-#{$NAMESPACE}"
	ip6ferm.puts "# Automatically created on #{THISHOST} at #{RIGHTNOW} by #{THISPROGRAM}."
	ip6ferm.puts "def &vpn_#{$NAMESPACE}6() = {"

	ipferm.puts "# Automatically created on #{THISHOST} at #{RIGHTNOW} by #{THISPROGRAM}."
	ipferm.puts "def &vpn_#{$NAMESPACE}() = {"

	daemonsfilename = "#{dir}/#{$NAMESPACE}.quagga.daemons"
	daemons = File.new(daemonsfilename, "w")
	daemons.puts QUAGGA_DAEMONS.gsub('HOSTNAME', host['name'])
	daemons.close

	zebrafilename = "#{dir}/#{$NAMESPACE}.quagga.zebra"
	zebra = File.new(zebrafilename, "w")
	zebra.puts QUAGGA_ZEBRA.gsub('HOSTNAME', host['name'])
	zebra.close
	File.chmod(0600, zebrafilename) == 1 or throw "Cannot chmod #{zebrafilename}"

	# BGP config
	bgpdfilename = "#{dir}/#{$NAMESPACE}.quagga.bgpd"
	bgpd = File.new(bgpdfilename, "w")
	bgpd.puts QUAGGA_BGPD_HEAD.gsub('HOSTNAME', host['name'])
	count = 10
	makelist($config['default_networks']).each{ |net|
		bgpd.puts "ip prefix-list VPNn2 seq #{count} permit #{net} #{net =~ /\/32/ ? '' : "le 32"}"
		count = count + 1
	}
	bgpd.puts "!"
	%w(import export).each{ |inout|
		if host.has_key?('extra_'+inout)
			host['extra_'+inout].each_pair{ |peer, extras|
				listname = makelistname(peer, inout)
				count = 10
				unless host['no_defaults_'+inout]
					makelist($config['default_networks']).each{ |net|
						bgpd.puts "ip prefix-list #{listname} seq #{count} permit #{net} #{net =~ /\/32/ ? '' : "le 32"}"
						count = count + 1
					}
				end
				makelist(extras).each{ |net|
					bgpd.puts "ip prefix-list #{listname} seq #{count} permit #{net} #{net =~ /\/32/ ? '' : "le 32"}"
					count = count + 1
				}
			}
			bgpd.puts "!"
		end
	}
	bgpd.puts "!"

	bgpd.puts "!"
	bgpd.puts "! ** route-map for incoming **"
	bgpd.puts "! filter out routes that we do not care about - we should never get sent"
	bgpd.puts "! routes like these from our peers, but hey:"
	bgpd.puts "!  make a community-list filter that matches all the communities"
	bgpd.puts "!  that we do not care about.  So start by denying those that we"
	bgpd.puts "!  do care about, then accept the rest."
	# quagga doc: ip community-list standard <name> (permit|deny) <community>
	# When community is empty it matches to any routes.
	host['groups'].each{ |group|
		bgpd.puts "!   %10s: %s"%[$config['groups'][group], group]
		bgpd.puts "ip community-list standard BoringCommunities deny #{ $config['groups'][group] }"
	}
	bgpd.puts "ip community-list standard BoringCommunities permit"
	bgpd.puts "!"

	inmaps = []
	outmaps = []

	outmaps << "! ** route-map for outgoing **"
	outmaps << "! We only announce prefixes to peers if they are in the proper group"
	outmaps << "!"
	outmaps << "! Each prefix is in one or more groups, set by the originating node"
	outmaps << "! When sending we make sure that we only announce prefixes if the"
	outmaps << "! peer is in the proper group to learn about them."
	outmaps << "! We also clear all community tags of the other groups - It's"
	outmaps << "! not a peer's business to know which of the prefixes he may know"
	outmaps << "! about are also in other groups."
	outmaps << "!   (yes, this also means that group information can get lost:"
	outmaps << "!    consider - A, B and C are in group Foo,"
	outmaps << "!             - A and C are also in group Bar."
	outmaps << "!             - connections: A to B, B to C.   (a to c is broken atm)"
	outmaps << "!             - now if there is a prefix only in group Bar, originating"
	outmaps << "!               at A, then C will never learn about it.)"
	outmaps << "!"
	outmaps << "! Nice side effect:  This allows us to drop any prefixes that come"
	outmaps << "! from a node with tags that it shouldn't know about: it must be lying."


	host['networks'].each_key{ |net|
		outmaps << "ip prefix-list pl#{ net.tr(':./', '-_') } seq 10 permit #{net} #{net =~ /\/32/ ? '' : "le 32"}"
	}
	host['networks6'].each_key{ |net|
		outmaps << "ipv6 prefix-list pl#{ net.tr(':./', '-_') } seq 10 permit #{net} #{net =~ /\/128/ ? '' : "le 128"}"
	}
	hostlist.each{ |otherhost|
		next if host['name'] == otherhost['name']
		next unless otherhost['bgp_preference']
		inmaps << "!"
		inmaps << "! Create an ACL for anything that passes through AS#{otherhost['asn']} (#{otherhost['name']})"
		inmaps << "ip as-path access-list passthruAS#{otherhost['asn']} permit _#{otherhost['asn']}_"
	}
	mapdups = {}
	host['peer_map_name'] = {}
	host['peers'].each{ |peer|
		sharedGroups = peer['groups'].find_all{ |g| host['groups'].include?(g) }
		#puts "#{host['name']} shares with #{peer['name']}: "+sharedGroups.join(' ');

		mapname = 'MAP_'+sharedGroups.join('_')
		host['peer_map_name'][peer['name']] = mapname
		next if mapdups.has_key?(mapname)
		mapdups[mapname] = true


		shared_groups_comlist     = "communities_#{ sharedGroups.join('_') }"
		shared_groups_comlist_inv = "communitiesNot_#{ sharedGroups.join('_') }"
		inmaps << "! #{shared_groups_comlist} matches the communities to send"
		inmaps << "! #{shared_groups_comlist_inv} matches the community tags to clear when sending"
		sharedGroups.each{ |g|
			inmaps << "ip community-list standard #{ shared_groups_comlist } permit #{ $config['groups'][g] }"
		};
		have_inverse_groups = false
		$config['groups'].keys.find_all{|g| not sharedGroups.include?(g) }.each{ |g|
			inmaps << "ip community-list standard #{ shared_groups_comlist_inv } permit #{ $config['groups'][g] }"
			have_inverse_groups = true
		};


		seq = 10
		inmaps << "!"
		inmaps << "! * route map from groups "+sharedGroups.join(',')+" *"
		if have_inverse_groups
			inmaps << "! It knows about stuff that it should not know about (and if it's"
			inmaps << "! just the tag on a route where it actually is allowed to know the"
			inmaps << "! route, but not that it also belongs to a group)."
			inmaps << "! It's probably lying.  Drop"
			inmaps << "route-map IN#{mapname} deny #{seq}"; seq = seq + 1
			inmaps << " match community #{shared_groups_comlist_inv}"
		end
		inmaps << "! It sends us stuff that we don't care about.  It probably"
		inmaps << "! shouldn't do that.  Drop (probably also caught already in the previous step)"
		inmaps << "route-map IN#{mapname} deny #{seq}"; seq = seq + 1
		inmaps << " match community BoringCommunities"
		hostlist.each{ |otherhost|
			next if host['name'] == otherhost['name']
			next unless otherhost['bgp_preference']
			inmaps << "!"
			inmaps << "! Anything that passes through AS#{otherhost['asn']} (#{otherhost['name']}) gets a lower preference"
			inmaps << "route-map IN#{mapname} permit #{seq}"; seq = seq + 1
			inmaps << " match as-path passthruAS#{otherhost['asn']}"
			inmaps << " set local-preference #{otherhost['bgp_preference']}"
		}
		inmaps << "route-map IN#{mapname} permit #{seq}"
		inmaps << "!"
		inmaps << "!"



		outmaps << "!"
		outmaps << "! * route map to groups "+sharedGroups.join(',')+" *"
		seq = 10
		['', '6'].each{ |type|
			outmaps << "route-map OUT#{mapname + type} permit #{seq}"; seq = seq + 1
			outmaps << " match community #{shared_groups_comlist}"
			outmaps << " set comm-list #{shared_groups_comlist_inv} delete" if have_inverse_groups
			if type == ''
				t = ['']
			else
				t = ['', '6']
			end
			t.each{ |t|
				host['networks'+t].each_pair{ |net, communities|
					communities = makelist(communities)
					setCommunities = []
					communities.each{ |c|
						throw "net #{net} declared community #{c} but host isn't even in it" unless host['groups'].include?(c)
						setCommunities << c if sharedGroups.include?(c)
					}
					next unless setCommunities.length > 0
					outmaps << "route-map OUT#{mapname + type} permit #{seq}"; seq = seq + 1
					outmaps << " match ip"+(t=="6" ? 'v6' : '')+" address prefix-list pl#{ net.tr(':./', '-_') }"
					outmaps << " set community "+setCommunities.collect{|c| $config['groups'][c] }.join(' ')+" additive"
				}
			}
			outmaps << "!"
		}
	}

	bgpd.puts inmaps.join("\n")
	bgpd.puts outmaps.join("\n")
	bgpd.puts "!"

	bgpd.puts "!ipv6 access-list all permit any"
	bgpd.puts "!route-map set-nexthop permit 10"
	bgpd.puts "!  match ipv6 address all"
	bgpd.puts "!  set ipv6 nexthop global #{host['vpn_address6']}"
	bgpd.puts "!"
	bgpd.puts "! ** BGP ROUTER **"
	bgpd.puts "router bgp #{host['asn']}"
	bgpd.puts " bgp router-id #{host['vpn_address']}"
	bgpd.puts "!"
	bgpd.puts "! ** our networks **"
	host['networks'].each_key{ |net|
		bgpd.puts " network #{net}"
	}
	if host['ipv6'] and host['networks6'].length > 0
		bgpd.puts " address-family ipv6"
		host['networks6'].each_key{ |net|
			bgpd.puts "  network #{net}"
		}
		bgpd.puts " exit-address-family"
	end

	host['peers'].each{ |peer|
		if can_connect(peer, host) and
		   can_connect(host, peer)
			host_is_server = host['host_no'] < peer['host_no']
		elsif can_connect(peer, host)
			host_is_server = true
		elsif can_connect(host, peer)
			host_is_server = false
		else
			throw "No connection is possible between #{host['name']} and #{peer['name']} but peer is in peerlist anyway?"
		end

		throw "host is server but doesn't have an address" if  host_is_server and host['host_address'] == nil
		throw "peer is server but doesn't have an address" if !host_is_server and peer['host_address'] == nil


		upscriptnamebase = "#{$NAMESPACE}-#{peer['name']}.up"
		upscriptname = "#{dir}/"+upscriptnamebase

		conffile = File.new("#{dir}/#{$NAMESPACE}-%s.conf"%[peer['name']], "w")
		conffile.puts "dev %s"%[peer['ifacename']]
		conffile.puts "tun-ipv6" if host['ipv6'] and peer['ipv6']
		#mtu = 1434
		#conffile.puts "fragment #{mtu}"
		#conffile.puts "mssfix #{mtu}"
		#conffile.puts "tun-mtu #{mtu}"
		mtu = 1200
		conffile.puts "fragment #{mtu}"
		conffile.puts "ifconfig %s %s"%[host['vpn_address'], peer['vpn_address']]
		remote_addr = []
		remote_addr << peer['host_address'] if peer['host_address']
		remote_addr += peer['host_address_extra'] if peer['host_address_extra']
		remote_port = [ host['inet_port'][ peer['name'] ]['remote'] ]
		remote_port += peer['host_port_extra'] if peer['host_port_extra']
		if not host_is_server
			if remote_addr.size > 0 then
				remote_addr.each do |raddr|
					remote_port.each do |rport|
						conffile.puts "remote #{raddr} #{rport}"
					end
				end
			else
				throw "have host_port_extra but no host_address(_extra)" if peer['host_port_extra']
				conffile.puts "rport #{host['inet_port'][ peer['name'] ]['remote']}"
			end
		end
		conffile.puts "lport #{host['inet_port'][ peer['name'] ]['local']}" unless host['accepts'].nil?
		if host['accepts'].nil?
			conffile.puts "nobind"
		else
			conffile.puts "multihome"
		end
		#if host['bind_address']
		#	conffile.puts "local %s"%[host['bind_address']]
		#elsif host['host_address']
		#	conffile.puts "local %s"%[host['host_address']]
		#end
		if host_is_server
			conffile.puts "proto udp6"
			conffile.puts "tls-server"
			conffile.puts "dh #{$NAMESPACE}.dh2048.pem"
		else
			conffile.puts "tls-client"
		end
		conffile.puts "cipher AES-256-CBC"
		if host['openvpn_ge_23']
			conffile.puts "verify-x509-name \"%s%s.%s\""%[$config['x509nameprefix'], peer['name'], $config['cadndomain']]
		else
			conffile.puts "tls-remote %s.%s"%[peer['name'], $config['cadndomain']]
		end
		if host['pkcs12']
			conffile.puts "pkcs12 #{$NAMESPACE}-%s.%s.p12"%[host['name'], $config['cadndomain']]
		else
			conffile.puts "ca   #{$NAMESPACE}-CA.crt"
			conffile.puts "cert #{$NAMESPACE}-%s.%s.crt"%[host['name'], $config['cadndomain']]
			conffile.puts "key  #{$NAMESPACE}-%s.%s.key"%[host['name'], $config['cadndomain']]
		end
		conffile.puts "up /etc/openvpn/#{upscriptnamebase}"
		conffile.puts "up-delay"
		conffile.puts "#user nobody"
		conffile.puts "#group nogroup"
		conffile.puts "#persist-tun"
		conffile.puts "persist-key"
		conffile.puts "comp-lzo"
		conffile.puts "keepalive 7 30"
		conffile.puts "verb 3"
		conffile.puts "script-security 2"
		conffile.puts "redirect-gateway def1" if host['default-via'] and host['default-via'] == peer['name']
		conffile.close

		##########
		upscript = File.new(upscriptname, "w")
		upscript.puts UPSCRIPT
		if host['ipv6'] and peer['ipv6']
			upscript.puts "ip -6 addr add #{ host['link-local'][ peer['name'] ]['me']  }/64 dev $TUN_DEV"
			upscript.puts "ip -6 addr add #{ host['vpn_address6']  }/128 dev $TUN_DEV"
		end
		upscript.close
		File.chmod(0755, upscriptname) == 1 or throw "Cannot chmod #{upscriptname}"

		##########
		# do not filter on remote port as NAT gateways may change ports
		iptables.puts  "#\n# to/from #{peer['name']}"
		ip6tables.puts "#\n# to/from #{peer['name']}"
		ipferm.puts  "#\n# to/from #{peer['name']}"
		ip6ferm.puts "#\n# to/from #{peer['name']}"

		src = [] + remote_addr
		src << '0.0.0.0/0' << '::/0' if src.size == 0
		src = src.select {|x| IPAddr.new(x) rescue nil }
		dst = []
		dst << host['host_address'] if host['host_address']
		dst += host['host_address_extra'] if host['host_address_extra']
		dst << '0.0.0.0/0' << '::/0' if dst.size == 0
		dst = dst.select {|x| IPAddr.new(x) rescue nil }
		src.each do |s|
			s6 = IPAddr.new(s).ipv6?
			dst.each do |d|
				d6 = IPAddr.new(d).ipv6?
				next if s6 != d6

				o = d6 ? ip6tables : iptables
				i = d6 ? 'ip6tables' : 'iptables'
				o.puts "#{i} --append vpn-#{$NAMESPACE} --source #{ s } --destination #{ d } \\"
				o.puts "         --protocol udp --destination-port #{ host['inet_port'][ peer['name'] ]['local'] } \\"
				o.puts "         --jump ACCEPT"

				o = d6 ? ip6ferm : ipferm
				o.puts "	saddr #{ s } daddr #{ d } " +
				       "     proto udp dport #{ host['inet_port'][ peer['name'] ]['local'] } " +
				       "     ACCEPT;"
			end
		end
		
		iptables.puts "iptables --append vpn-#{$NAMESPACE} --source #{ peer['vpn_address'] } --destination #{ host['vpn_address'] } \\"
		iptables.puts "         --protocol tcp --destination-port #{ host['bgp_port'] or '179' } \\"
		iptables.puts "         --in-interface #{ peer['ifacename'] } \\"
		iptables.puts "         --jump ACCEPT"
		if host['ipv6'] and peer['ipv6']
			ip6tables.puts "ip6tables --append vpn-#{$NAMESPACE} \\"
			ip6tables.puts "         --source #{ host['link-local'][ peer['name'] ]['peer'] } \\"
			ip6tables.puts "         --destination #{ host['link-local'][ peer['name'] ]['me'] } \\"
			ip6tables.puts "         --protocol tcp --destination-port #{ host['bgp_port'] or '179' } \\"
			ip6tables.puts "         --in-interface #{ peer['ifacename'] } \\"
			ip6tables.puts "         --jump ACCEPT"
		end

		ipferm.puts "	saddr #{ peer['vpn_address'] } daddr #{ host['vpn_address'] } " +
		            "     proto tcp dport #{ host['bgp_port'] or '179' } " +
		            "     interface #{ peer['ifacename'] } " +
		            "     ACCEPT;"

		if host['ipv6'] and peer['ipv6']
			ip6ferm.puts "	saddr #{ host['link-local'][ peer['name'] ]['peer'] } \\"
			ip6ferm.puts "         daddr #{ host['link-local'][ peer['name'] ]['me'] } \\"
			ip6ferm.puts "         proto tcp dport #{ host['bgp_port'] or '179' } \\"
			ip6ferm.puts "         interface #{ peer['ifacename'] } \\"
			ip6ferm.puts "         ACCEPT;"
		end

		##########
		bgpd.puts "!"
		bgpd.puts "! ** peer #{peer['name']} **"
		unless host['ipv6'] and peer['ipv6']
			bgpd.puts " neighbor #{peer['vpn_address']} remote-as #{peer['asn']}"
			bgpd.puts " neighbor #{peer['vpn_address']} send-community"
			bgpd.puts " neighbor #{peer['vpn_address']} interface #{peer['ifacename']}"
			bgpd.puts " neighbor #{peer['vpn_address']} description #{peer['name']}"
			bgpd.puts " neighbor #{peer['vpn_address']} port #{peer['bgp_port']}" if peer.has_key?('bgp_port')
			bgpd.puts "!"
			filtername = (host.has_key?('extra_export') and host['extra_export'].has_key?(peer['name'])) ?
					makelistname(peer['name'], 'export') :
					'VPNn2'
			bgpd.puts " neighbor #{peer['vpn_address']} prefix-list #{filtername} out"
			filtername = (host.has_key?('extra_import') and host['extra_import'].has_key?(peer['name'])) ?
					makelistname(peer['name'], 'import') :
					'VPNn2'
			bgpd.puts " neighbor #{peer['vpn_address']} prefix-list #{filtername} in"
			bgpd.puts " neighbor #{peer['vpn_address']} route-map IN#{ host['peer_map_name'][peer['name']] } in"
			bgpd.puts " neighbor #{peer['vpn_address']} route-map OUT#{ host['peer_map_name'][peer['name']] } out"
			bgpd.puts "!"
		else
		#if host['ipv6'] and peer['ipv6']
			bgpd.puts " neighbor #{ host['link-local'][ peer['name'] ]['peer'] } remote-as #{peer['asn']}"
			bgpd.puts " neighbor #{ host['link-local'][ peer['name'] ]['peer'] } send-community"
			bgpd.puts " neighbor #{ host['link-local'][ peer['name'] ]['peer'] } interface #{peer['ifacename']}"
			bgpd.puts " neighbor #{ host['link-local'][ peer['name'] ]['peer'] } description #{peer['name']}"
			bgpd.puts " neighbor #{ host['link-local'][ peer['name'] ]['peer'] } port #{peer['bgp_port']}" if peer.has_key?('bgp_port')
			bgpd.puts " neighbor #{ host['link-local'][ peer['name'] ]['peer'] } route-map IN#{ host['peer_map_name'][peer['name']] } in"
			bgpd.puts " neighbor #{ host['link-local'][ peer['name'] ]['peer'] } route-map OUT#{ host['peer_map_name'][peer['name']] }6 out"
			bgpd.puts " address-family ipv6"
			bgpd.puts "  neighbor #{ host['link-local'][ peer['name'] ]['peer'] } activate"
			bgpd.puts " exit-address-family"
		end
	}

	#ipv6stuff << " exit-address-family"
	#bgpd.puts ipv6stuff.join("\n")

	iptables.close
	ip6tables.close
	ipferm.puts "}"
	ipferm.close
	ip6ferm.puts "}"
	ip6ferm.close
	bgpd.close

	File.chmod(0600, bgpdfilename) == 1 or throw "Cannot chmod #{bgpdfilename}"
}