PageRenderTime 51ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/easypxe

https://github.com/aastaneh/easypxe
Perl | 284 lines | 251 code | 12 blank | 21 comment | 2 complexity | 0d64e6175f6e7fd6528853e33858f255 MD5 | raw file
  1. #!/usr/bin/perl -w
  2. use strict;
  3. use warnings;
  4. use File::Basename;
  5. use Fcntl qw(LOCK_EX LOCK_NB);
  6. use Socket;
  7. use Net::DHCP::Packet;
  8. use Net::DHCP::Constants;
  9. use POSIX qw(setsid strftime);
  10. use IO::Socket::INET;
  11. use threads;
  12. use Sys::Syslog;
  13. use Net::TFTPd;
  14. use Time::HiRes qw(usleep);
  15. sub dhcp_acknowledge;
  16. sub dhcp_discover;
  17. ##################################################################
  18. # easypxe - Perl-Based Simple PXE Solution, in place of installing
  19. # and configuring dhcpd and tftpd just to run a linux
  20. # installer on one machine. Adapted from sysprod,
  21. # a beowulf cluster provisioning script written by the
  22. # same author.
  23. #
  24. # Amin Astaneh, amin@aminastaneh.net
  25. # GPLv2, Sucka. Open Source Rocks- Evolve or Die.
  26. ##################################################################
  27. ##################################################################
  28. # Configuration options for the script
  29. ##################################################################
  30. my $pxe_client_ip = "192.168.1.2";
  31. my $listen_address = "192.168.1.1";
  32. my $name_server = "0.0.0.0";
  33. my $gateway = "0.0.0.0";
  34. my $tftp_root = "./tftp_root";
  35. my $pxe_boot_file = "pxelinux.0";
  36. my $netmask = "255.255.255.0";
  37. my $domain_name = "localdomain";
  38. my $daemonize_process = 0;
  39. ##################################################################
  40. # DO NOT EDIT BELOW THIS LINE!!!
  41. ##################################################################
  42. ##################################################################
  43. # Startup Stuff (spin off threads, bind sockets, daemonize)
  44. ##################################################################
  45. my $proc_name = basename($0);
  46. openlog $proc_name, 0, "user" or die "No logging is available\n" if $daemonize_process;
  47. logger("Starting");
  48. # Need to detach and go off on our own...
  49. if ($daemonize_process){
  50. open SELFLOCK, "<$0" or die "Couldn't open $0: $!\n";
  51. flock SELFLOCK, LOCK_EX | LOCK_NB or die "Aborting: another $proc_name is already running\n";
  52. close STDERR; close STDOUT; close STDIN;
  53. $| = 1;
  54. chdir('/');
  55. # Double-fork to avoid leaving a zombie process behind:
  56. exit if fork;
  57. exit if fork;
  58. sleep 1 until getppid == 1;
  59. }
  60. # Socket for receiving DHCP requests
  61. my $sock_in = IO::Socket::INET->new(
  62. LocalPort => 67,
  63. PeerPort => 68,
  64. Broadcast => 1,
  65. Proto => 'udp')
  66. or die "DHCP server socket creation error: $@\n";
  67. # Socket for sending DHCP responses
  68. my $sock_out = IO::Socket::INET->new(
  69. PeerPort => 68,
  70. PeerAddr => '255.255.255.255',
  71. LocalAddr => $listen_address,
  72. Proto => 'udp',
  73. Broadcast => 1 )
  74. or die "DHCP client socket creation error: $@\n";
  75. $sock_out->sockopt(SO_BROADCAST, 1);
  76. # Let there be threads!
  77. # Single Thread for the TFTP server
  78. my $tftp = threads->create(\&tftpd, $tftp_root);
  79. # Transaction Counter
  80. my $transaction = 0;
  81. ##################################################################
  82. # Main Routine
  83. ##################################################################
  84. for (;;) {
  85. my $buf = undef;
  86. my $fromaddr;
  87. my $dhcpreq;
  88. my @config;
  89. my @configpxe;
  90. my %machines;
  91. eval {
  92. logger("DHCP: Listening for request.");
  93. $fromaddr = $sock_in->recv( $buf,4096 ) || logger("recv:$!");
  94. next if ($!);
  95. $transaction++;
  96. {
  97. use bytes;
  98. my ( $port,$addr ) = unpack_sockaddr_in($fromaddr);
  99. my $ipaddr = inet_ntoa($addr);
  100. }
  101. my $dhcpreq = new Net::DHCP::Packet($buf);
  102. $dhcpreq->comment($transaction);
  103. my $messagetype = $dhcpreq->getOptionValue(DHO_DHCP_MESSAGE_TYPE());
  104. my $mac = substr($dhcpreq->chaddr(),0,12);
  105. logger("DHCP: Received request from " . $mac);
  106. if ( $messagetype eq DHCPDISCOVER() ) {
  107. dhcp_discover ($dhcpreq, $pxe_client_ip, $netmask, $domain_name, $pxe_boot_file, $listen_address, $name_server );
  108. } elsif ( $messagetype eq DHCPREQUEST() ) {
  109. dhcp_acknowledge( $dhcpreq, $pxe_client_ip, $netmask, $domain_name, $pxe_boot_file, $listen_address, $name_server );
  110. logger("PXE: Host $pxe_client_ip beginning boot process.");
  111. } else {
  112. logger("DHCP: request from $mac ignored.");
  113. }
  114. };
  115. if ($@) {
  116. logger("Daemon Error: $@\n");
  117. }
  118. }
  119. # ____ _ _ ___ ____ ____ _ _ ___ _ _ _ ____ ____
  120. # [__ | | |__] |__/ | | | | | | |\ | |___ [__
  121. # ___] |__| |__] | \ |__| |__| | | | \| |___ ___]
  122. #
  123. # A simple loggin routine. Meh.
  124. sub logger {
  125. my $string = shift;
  126. if ($daemonize_process){
  127. syslog 'info', $string;
  128. } else {
  129. my $ts = localtime;
  130. print STDERR "[$ts] " . $string . "\n";
  131. }
  132. }
  133. # Send off your DHCPOFFER packet
  134. sub dhcp_discover($$) {
  135. my ($dhcpreq, $calc_ip, $calc_mask, $domain_name, $pxe_boot_file, $tftp_server, $name_server) = @_;
  136. my $calc_router;
  137. my $dhcpresp = new Net::DHCP::Packet(
  138. Comment => $dhcpreq->comment(),
  139. Op => BOOTREPLY(),
  140. Hops => $dhcpreq->hops(),
  141. Xid => $dhcpreq->xid(),
  142. Flags => $dhcpreq->flags(),
  143. Ciaddr => $dhcpreq->ciaddr(),
  144. File => $pxe_boot_file,
  145. Yiaddr => $calc_ip,
  146. Siaddr => $tftp_server,
  147. Giaddr => $dhcpreq->giaddr(),
  148. Chaddr => $dhcpreq->chaddr(),
  149. DHO_DHCP_MESSAGE_TYPE() => DHCPOFFER(),
  150. DHO_SUBNET_MASK() => $calc_mask,
  151. DHO_DOMAIN_NAME() => $domain_name,
  152. DHO_ROUTERS() => $tftp_server,
  153. DHO_NAME_SERVERS() => $name_server,
  154. DHO_DHCP_SERVER_IDENTIFIER() => $tftp_server
  155. );
  156. logger("DHCP: Sending OFFER tr=".$dhcpresp->comment());
  157. $sock_out->send($dhcpresp->serialize()) or die "Error sending OFFER:$!\n";
  158. }
  159. # Send off your DHCPACK/NAK packet
  160. sub dhcp_acknowledge {
  161. my ($dhcpreq, $calc_ip, $calc_mask, $domain_name, $pxe_boot_file, $tftp_server, $name_server) = @_;
  162. # compare calculated address with requested address
  163. if ($calc_ip eq $dhcpreq->getOptionValue(DHO_DHCP_REQUESTED_ADDRESS())) {
  164. # address is correct, we send an ACK
  165. my $dhcpresp = new Net::DHCP::Packet(
  166. Comment => $dhcpreq->comment(),
  167. Op => BOOTREPLY(),
  168. Hops => $dhcpreq->hops(),
  169. Xid => $dhcpreq->xid(),
  170. File => $pxe_boot_file,
  171. Flags => $dhcpreq->flags(),
  172. Ciaddr => $dhcpreq->ciaddr(),
  173. Yiaddr => $calc_ip,
  174. Siaddr => $tftp_server,
  175. Giaddr => $dhcpreq->giaddr(),
  176. Chaddr => $dhcpreq->chaddr(),
  177. DHO_DHCP_MESSAGE_TYPE() => DHCPACK(),
  178. DHO_DHCP_LEASE_TIME() => "36000",
  179. DHO_SUBNET_MASK() => $calc_mask,
  180. DHO_DOMAIN_NAME() => $domain_name,
  181. DHO_ROUTERS() => $tftp_server,
  182. DHO_NAME_SERVERS() => $name_server,
  183. DHO_DHCP_SERVER_IDENTIFIER() => $tftp_server
  184. );
  185. logger("DHCP: Sending ACK tr=".$dhcpresp->comment());
  186. $sock_out->send($dhcpresp->serialize()) || die "Error sending ACK:$!\n";
  187. return 0;
  188. } else {
  189. # bad request, we send a NAK
  190. my $dhcpresp = new Net::DHCP::Packet(
  191. Comment => $dhcpreq->comment(),
  192. Op => BOOTREPLY(),
  193. Hops => $dhcpreq->hops(),
  194. Xid => $dhcpreq->xid(),
  195. Flags => $dhcpreq->flags(),
  196. Ciaddr => $dhcpreq->ciaddr(),
  197. Yiaddr => "0.0.0.0",
  198. Siaddr => $dhcpreq->siaddr(),
  199. Giaddr => $dhcpreq->giaddr(),
  200. Chaddr => $dhcpreq->chaddr(),
  201. DHO_DHCP_MESSAGE_TYPE() => DHCPNAK(),
  202. DHO_DHCP_MESSAGE(), "Bad request...",
  203. );
  204. logger("DHCP: Sending NAK tr=".$dhcpresp->comment());
  205. $sock_out->send($dhcpresp->serialize()) || die "Error sending NAK:$!\n";
  206. return 1;
  207. }
  208. }
  209. # A TFTPd service, ran as a thread. Used for PXEbooting.
  210. sub tftpd {
  211. my $rootdir = shift;
  212. my $listener = Net::TFTPd->new(
  213. 'RootDir' => $rootdir,
  214. 'Timeout' => 10
  215. ) or die Net::TFTPd->error;
  216. logger("TFTP_SERVER: TFTP server listening");
  217. for(;;) {
  218. if ( my $request = $listener->waitRQ() ){
  219. my $file_name = $request->{'_REQUEST_'}{'FileName'};
  220. # um, for some reason this tftp module returns a bunch
  221. # of crap around the file name... lets get rid of it
  222. # add any characters to the class that you need
  223. $file_name =~ s/[^a-zA-Z0-9\/\.\_\-]*//g;
  224. $request->{'_REQUEST_'}{'FileName'} = $file_name;
  225. logger( "TFTP_SERVER: Received TFTP request for file \"$file_name\"" );
  226. my $pid = fork();
  227. if($pid == 0) {
  228. my $ret = $request->processRQ();
  229. if ( defined($ret) ){
  230. logger( "TFTP_SERVER: Transfer of \"$file_name\" completed successfully.");
  231. } else {
  232. logger( "TFTP_SERVER_ERROR: File \"$file_name\": " . $request->error );
  233. }
  234. exit 0;
  235. }
  236. }
  237. }
  238. }