don't deliver gPXE to systems which allready have that user class option
[pxelator] / bin / dhcpd.pl
1 #!/usr/bin/perl
2
3 # based on http://www.perlmonks.org/index.pl?node_id=325248
4
5 use strict;
6 use warnings;
7
8 use IO::Socket::INET;
9 use Net::DHCP::Packet;
10 use Net::DHCP::Constants;
11 use Data::Dump qw/dump/;
12
13 die "need to run $0 as root like this\nsudo $0\n" unless $< == 0;
14
15 my $debug = shift @ARGV;
16
17 our ( $server_ip, $next_file );
18 require "config.pl";
19
20 my $sock = IO::Socket::INET->new(
21         LocalPort       => 67,
22 #       LocalAddr       => 'localhost',
23 #       LocalAddr       => '10.0.0.100',
24         LocalAddr       => '0.0.0.0',
25         Proto           => 'udp',
26         ReuseAddr       => 1,
27 #       PeerPort        => getservbyname('bootpc', 'udp'),
28         Broadcast       => 1,
29         Type            => SOCK_DGRAM,
30 ) or die "Failed to bind to socket: $@";
31
32 my $_ip = 10;
33 my $_mac2ip;
34
35 sub client_ip {
36         my ( $mac ) = @_;
37
38         my $ip = $_mac2ip->{$mac};
39         return $ip if $ip;
40
41         $ip = "10.0.0.$_ip";
42         $_mac2ip->{$mac} = $ip;
43
44         $_ip++;
45         if ( $_ip == 100 ) {
46                 warn "IP roll-over to 10\n";
47                 $_ip = 10;
48         }
49
50         return $ip;
51 }
52
53 while (1) {
54
55         print "waiting for DHCP requests on ",$sock->sockhost,":",$sock->sockport,"\n";
56
57         my $buf;
58         $sock->recv($buf, 1024);
59         print "<< peer:",$sock->peerhost,":",$sock->peerport,"\n";
60
61         if (defined $buf) {
62
63                 my $dhcp;
64
65                 eval { $dhcp = Net::DHCP::Packet->new($buf); };
66                 die "can't use request", dump( $buf ) if $@;
67
68                 if ( $debug ) {
69                         warn "recv: ", $dhcp->toString, "\n\n";
70                 }
71
72                 my $mac = substr($dhcp->chaddr(),0,$dhcp->hlen()*2);
73                 my $ip = client_ip($mac);
74
75                 my $file =  $next_file;
76                 $file = 'undionly.kpxe' if ! $file || $dhcp->getOptionValue(DHO_USER_CLASS()) ne 'gPXE';
77
78                 my $packet = new Net::DHCP::Packet(
79                         Op              => BOOTREPLY(),
80                         Hops    => $dhcp->hops(),
81                         Xid             => $dhcp->xid(),
82                         Flags   => $dhcp->flags(),
83                         Ciaddr  => $dhcp->ciaddr(),
84                         Yiaddr  => $ip,
85                         Siaddr  => $server_ip,
86                         Giaddr  => $dhcp->giaddr(),
87                         Chaddr  => $dhcp->chaddr(),
88                         File    => $file,
89 #                       DHO_DHCP_MESSAGE_TYPE() => DHCPACK(),
90                 DHO_SUBNET_MASK() => '255.0.0.0',
91                 );
92
93                 warn ">> $mac == $ip server $server_ip\n";
94                 
95                 warn "## ",$packet->toString(),"\n" if $debug;
96
97                 my $reply = IO::Socket::INET->new(
98                         LocalAddr => $server_ip,
99                         LocalPort => 67,
100                         Proto => "udp",
101                         Broadcast => 1,
102                         PeerAddr => '255.255.255.255',
103                         PeerPort => 68,
104                         Reuse => 1,
105                 ) or die "socket: $@";
106
107                 my $buff = $packet->serialize();
108                 $reply->send( $buff, 0 ) or die "Error sending: $!\n";
109
110 #               system("arp -s $ip $mac"),
111
112         } else {
113                 print "No bootp request.\n";
114         }
115
116 }