| 12
 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
 
 | #!/usr/bin/perl
# TODO
# * use syslog
# * runit - as also invalid DHCP packets cause library to croak()
# * error checking
use strict;
use warnings;
use Sys::Hostname;
use IO::Socket::INET;
use Net::DHCP::Constants qw/:dho_codes :dhcp_message :bootp_codes :htype_codes :dhcp_other/;
use Net::DHCP::Packet;
# populated in BEGIN
my %addOption;
my $domainnameservers = '192.0.1.2 192.0.1.50';
# Mac OS X 10.7 seems to accept space seperated entries here (no idea if others do too)
my $domainname = 'domain.foo';
my @subnets;
for (<DATA>) {
	next if (/^(#|$)/);
	chomp;
	push @subnets, $_;
}
my $sock = IO::Socket::INET->new(
	Proto		=> 'udp',
	LocalPort	=> 67,
);
unless ($sock) {
	print STDERR "unable to open socket: $@\n";
	exit 1;
}
my $buf;
while (my $src = $sock->recv($buf, DHCP_MAX_MTU - DHCP_UDP_OVERHEAD)) {
	my $sport = $sock->peerport;
	next unless ($sport == 68);
	# add padding to stop the library complaining
	# as the packets are typically 262 bytes long
	$buf .= "\x00" x (BOOTP_MIN_LEN() - length($buf))
		if (length($buf) < BOOTP_MIN_LEN());
	my $pac = Net::DHCP::Packet->new($buf);
	next unless ($pac->isDhcp
		&& $pac->op == BOOTREQUEST()
		&& $pac->htype == HTYPE_FDDI()
		&& $pac->getOptionValue(DHO_DHCP_MESSAGE_TYPE()) == DHCPINFORM());
	my $res = Net::DHCP::Packet->new(
		Op			=> BOOTREPLY(),
		Htype			=> $pac->htype,
		Xid			=> $pac->xid,
		Ciaddr			=> $sock->peerhost,
		Chaddr			=> $pac->chaddr,
		Sname			=> hostname,
		DHO_DHCP_MESSAGE_TYPE()	=> DHCPACK(),
	);
	foreach (split / /, $pac->getOptionValue(DHO_DHCP_PARAMETER_REQUEST_LIST())) {
		&{$addOption{$_}}($res, $_)
			if (defined($addOption{$_}));
	}
	$sock->send($res->serialize);
}
exit 0;
BEGIN {
	sub OptionSubnetMask {
		my ($res, $opt) = @_;
		$res->addOptionValue(DHO_SUBNET_MASK(), '255.255.255.255');
	}
	$addOption{DHO_SUBNET_MASK()} = \&OptionSubnetMask;
	sub OptionDomainName {
		my ($res, $opt) = @_;
		$res->addOptionValue(DHO_DOMAIN_NAME(), $domainname);
	}
	$addOption{DHO_DOMAIN_NAME()} = \&OptionDomainName;
	sub OptionDomainNameServers {
		my ($res, $opt) = @_;
		$res->addOptionValue(DHO_DOMAIN_NAME_SERVERS(), $domainnameservers);
	}
	$addOption{DHO_DOMAIN_NAME_SERVERS()} = \&OptionDomainNameServers;
	sub OptionClasslessStaticRoutes {
		my ($res, $opt) = @_;
		$res->addOptionValue($opt, [ map { [$_, '0.0.0.0'] } @subnets ]);
	}
	$addOption{121} = \&OptionClasslessStaticRoutes;
	$addOption{249} = \&OptionClasslessStaticRoutes;
}
__DATA__
10.0.0.0/8
192.168.0.0/16
 |