1<?php 2 3 4/** 5 * DHCP_kea class to work with isc-dhcp server 6 * 7 * It will be called form class.DHCP.php wrapper it kea is selected as DHCP type 8 * 9 * http://kea.isc.org/wiki 10 * 11 * 12 */ 13class DHCP_kea extends Common_functions { 14 15 /** 16 * Location of kea config file 17 * 18 * (default value: false) 19 * 20 * @var bool 21 * @access private 22 */ 23 private $kea_config_file = "/etc/kea/kea.conf"; 24 25 /** 26 * Settings to be provided to process kea files 27 * 28 * (default value: array()) 29 * 30 * @var array 31 * @access private 32 */ 33 private $kea_settings = array(); 34 35 /** 36 * Raw config file 37 * 38 * (default value: "") 39 * 40 * @var string 41 * @access public 42 */ 43 public $config_raw = ""; 44 45 /** 46 * Parsed config file 47 * 48 * (default value: false) 49 * 50 * @var array|bool 51 * @access public 52 */ 53 public $config = false; 54 55 /** 56 * Falg if ipv4 is used 57 * 58 * (default value: false) 59 * 60 * @var bool 61 * @access public 62 */ 63 public $ipv4_used = false; 64 65 /** 66 * Flag if ipv6 is used 67 * 68 * (default value: false) 69 * 70 * @var bool 71 * @access public 72 */ 73 public $ipv6_used = false; 74 75 /** 76 * Array to store DHCP subnets, parsed from config file 77 * 78 * Format: 79 * $subnets[] = array (pools=>array()); 80 * 81 * (default value: array()) 82 * 83 * @var array 84 * @access public 85 */ 86 public $subnets4 = array(); 87 88 /** 89 * Array to store DHCP subnets, parsed from config file 90 * 91 * Format: 92 * $subnets[] = array (pools=>array()); 93 * 94 * (default value: array()) 95 * 96 * @var array 97 * @access public 98 */ 99 public $subnets6 = array(); 100 101 /** 102 * set available lease database types 103 * 104 * (default value: array("memfile", "mysql", "postgresql")) 105 * 106 * @var string 107 * @access public 108 */ 109 public $lease_types = array("memfile", "mysql", "postgresql"); 110 111 /** 112 * List of active leases 113 * 114 * (default value: array()) 115 * 116 * @var array 117 * @access public 118 */ 119 public $leases4 = array(); 120 121 /** 122 * List of active leases 123 * 124 * (default value: array()) 125 * 126 * @var array 127 * @access public 128 */ 129 public $leases6 = array(); 130 131 /** 132 * Available reservation methods 133 * 134 * (default value: array("mysql")) 135 * 136 * @var string 137 * @access public 138 */ 139 public $reservation_types = array("file", "mysql"); 140 141 /** 142 * Definition of hosts reservations 143 * 144 * (default value: array()) 145 * 146 * @var array 147 * @access public 148 */ 149 public $reservations4 = array(); 150 public $reservations6 = array(); 151 152 /** 153 * Database object for leases and hosts 154 * 155 * (default value: false) 156 * 157 * @var bool 158 * @access protected 159 */ 160 protected $Database_kea = false; 161 162 163 164 /** 165 * __construct function. 166 * 167 * @access public 168 * @param array $kea_settings (default: array()) 169 * @return void 170 */ 171 public function __construct($kea_settings = array()) { 172 // save settings 173 if (is_array($kea_settings)) { $this->kea_settings = $kea_settings; } 174 else { throw new exception ("Invalid kea settings"); } 175 176 // set file 177 if(isset($this->kea_settings['file'])) { $this->kea_config_file = $this->kea_settings['file']; } 178 179 // parse config file on startup 180 $this->parse_config (); 181 // parse and save subnets 182 $this->parse_subnets (); 183 } 184 185 /** 186 * Opens database connection if needed for leases and hosts 187 * 188 * @access private 189 * @param mixed $username 190 * @param mixed $password 191 * @param mixed $host 192 * @param mixed $port 193 * @param mixed $dbname 194 * @param mixed $charset 195 * @return void 196 */ 197 private function init_database_conection ($username, $password, $host, $port, $dbname) { 198 // open 199 $this->Database_kea = new Database_PDO ($username, $password, $host, $port, $dbname); 200 } 201 202 203 204 205 206 207 /** 208 * This function parses config file and returns it as array. 209 * 210 * @access private 211 * @return void 212 */ 213 private function parse_config () { 214 // get file to array 215 if(file_exists($this->kea_config_file)) { 216 $config = file($this->kea_config_file); 217 // save 218 $this->config_raw = implode("\n",array_filter($config)); 219 } 220 else { 221 throw new exception ("Cannot access config file ".$this->kea_config_file); 222 } 223 224 // loop and remove comments (contains #) and replace multilpe spaces 225 $out = array(); 226 foreach ($config as $k=>$f) { 227 if (strpos($f, "#")!==false || strlen($f)==0) {} 228 else { 229 if(strlen($f)>0) { 230 $out[] = $f; 231 } 232 } 233 } 234 235 // join to line 236 $config = implode("", $out); 237 238 // validate json 239 if ($this->validate_json_string ($config)===false) { 240 throw new exception ("JSON config file error: $this->json_error"); 241 } 242 243 // save config 244 $this->config = json_decode($config, true); 245 // save IPv4 / IPv6 flags 246 if(isset($this->config['Dhcp4'])) { $this->ipv4_used = true; } 247 if(isset($this->config['Dhcp6'])) { $this->ipv6_used = true; } 248 } 249 250 /** 251 * Saves subnets definition to $subnets object 252 * 253 * @access private 254 * @return void 255 */ 256 private function parse_subnets () { 257 // save to subnets4 object 258 $this->subnets4 = @$this->config['Dhcp4']['subnet4']; 259 // save to subnets6 object 260 $this->subnets6 = @$this->config['Dhcp6']['subnet6']; 261 } 262 263 264 265 266 267 268 269 270 271 /* @leases --------------- */ 272 273 /** 274 * Saves leases to $leases object as array. 275 * 276 * @access public 277 * @param string $type (default: "IPv4") 278 * @return void 279 */ 280 public function get_leases ($type = "IPv4") { 281 // first check where they are stored - mysql, postgres or file 282 if ($type=="IPv4") { 283 $lease_database = $this->config['Dhcp4']['lease-database']; 284 } 285 else { 286 $lease_database = $this->config['Dhcp6']['lease-database']; 287 } 288 289 // set lease type 290 $lease_database_type = $lease_database['type']; 291 292 // validate database type 293 if (!in_array($lease_database_type, $this->lease_types)) { 294 throw new exception ("Invalid lease database type"); 295 } 296 297 // get leases 298 $lease_type = "get_leases_".$lease_database_type; 299 $this->{$lease_type} ($lease_database, $type); 300 } 301 302 /** 303 * Fetches leases from memfile. 304 * 305 * First line is structure 306 * address,hwaddr,client_id,valid_lifetime,expire,subnet_id,fqdn_fwd,fqdn_rev,hostname,state 307 * 308 * @access private 309 * @param mixed $lease_database 310 * @param string $type (default: "IPv4") 311 * @return void 312 */ 313 private function get_leases_memfile ($lease_database, $type) { 314 // read file to array 315 $leases_from_file = @file($lease_database['name']); 316 // first item are titles 317 unset($leases_from_file[0]); 318 // if leases are present format to array 319 if (sizeof($leases_from_file)>0 && $leases_from_file!==false) { 320 // init array 321 $leases_parsed = array(); 322 // loop and save leases 323 foreach ($leases_from_file as $l) { 324 if(strlen($l)>1) { 325 // to array 326 $l = explode(",", $l); 327 328 // set state 329 switch ($l[9]) { 330 case 0: 331 $l[9] = "default"; 332 break; 333 case 1: 334 $l[9] = "declined"; 335 break; 336 case 2: 337 $l[9] = "expired-reclaimed"; 338 break; 339 } 340 // save only active 341 if ($l[4] > time() ) { 342 $leases_parsed[] = array( 343 "address" => $l[0], 344 "hwaddr" => $l[1], 345 "client_id" => $l[2], 346 "valid_lifetime" => $l[3], 347 "expire" => date("Y-m-d H:i:s", $l[4]), 348 "subnet_id" => $l[5], 349 "fqdn_fwd" => $l[6], 350 "fqdn_rev" => $l[7], 351 "hostname" => $l[8], 352 "state" => $l[9] 353 ); 354 } 355 } 356 } 357 } 358 else { 359 throw new exception("Cannot read leases file ".$lease_database['name']); 360 } 361 362 // save result 363 if ($type=="IPv4") { $this->leases4 = $leases_parsed; } 364 else { $this->leases6 = $leases_parsed; } 365 } 366 367 /** 368 * Fetches leases from mysql database. 369 * 370 * @access private 371 * @param mixed $lease_database 372 * @param string $type (default: "IPv4") 373 * @return void 374 */ 375 private function get_leases_mysql ($lease_database, $type) { 376 // if host not specified assume localhost 377 if (strlen($lease_database['host'])==0) { $lease_database['host'] = "localhost"; } 378 // open DB connection 379 $this->init_database_conection ($lease_database['user'], $lease_database['password'], $lease_database['host'], 3306, $lease_database['name']); 380 // set query 381 if($type=="IPv4") { 382 $query = "select "; 383 $query .= "INET_NTOA(address) as `address`, hex(hwaddr) as hwaddr, hex(`client_id`) as client_id,`subnet_id`,`valid_lifetime`,`expire`,`name` as `state`,`fqdn_fwd`,`fqdn_rev`,`hostname` from `lease4` as a, "; 384 $query .= "`lease_state` as s where a.`state` = s.`state`;"; 385 } 386 else { 387 throw new Exception("IPv6 leases not yet!"); 388 } 389 // fetch leases 390 try { $leases = $this->Database_kea->getObjectsQuery($query); } 391 catch (Exception $e) { 392 throw new Exception($e->getMessage()); 393 } 394 // save leases 395 if (sizeof($leases)>0) { 396 // we need array 397 $result = array(); 398 // loop 399 foreach ($leases as $k=>$l) { 400 $result[$k] = (array) $l; 401 } 402 403 // save 404 if($type=="IPv4") { 405 $this->leases4 = $result; 406 } 407 else { 408 $this->leases6 = $result; 409 } 410 } 411 } 412 413 /** 414 * Fetches leases from postgres SQL. 415 * 416 * @access private 417 * @param mixed $lease_database 418 * @return void 419 */ 420 private function get_leases_postgresql ($lease_database) { 421 throw new exception ("PostgresSQL not supported"); 422 } 423 424 425 426 427 428 429 430 431 432 /* @reservations --------------- */ 433 434 /** 435 * Saves reservations to $reservations object as array. 436 * 437 * Note: 438 * For IPv4 reservations KEA by default uses `reservations` item under subnet4 > reservations array. 439 * It can also use hosts-database in MySQL, if hosts-database is set 440 * 441 * 442 * For KEA v 1.0 only MySQL is supported. If needed later item can be added to $reservation_types and new method created 443 * 444 * @access public 445 * @param string $type (default: "IPv4") 446 * @return void 447 */ 448 public function get_reservations ($type = "IPv4") { 449 // first check where they are stored - mysql, postgres or file 450 if($type=="IPv4") { 451 if (isset($this->config['Dhcp4']['hosts-database'])) { 452 $reservations_database = $this->config['Dhcp4']['hosts-database']; 453 } 454 else { 455 $reservations_database = false; 456 } 457 } 458 else { 459 if (isset($this->config['Dhcp4']['hosts-database'])) { 460 $reservations_database = $this->config['Dhcp6']['hosts-database']; 461 } 462 else { 463 $reservations_database = false; 464 } 465 } 466 467 468 // first check reservations under subnet > reservations, can be both 469 $this->get_reservations_config_file ($type); 470 471 // if set in config check also database 472 if ($reservations_database!==false) { 473 // set lease type 474 $reservations_database_type = $reservations_database['type']; 475 476 // id database type is set and valid check it also 477 if (!in_array($reservations_database_type, $this->reservation_types)) { 478 throw new exception ("Invalid reservations database type"); 479 } 480 else { 481 // get leases 482 $type_l = "get_reservations_".$reservations_database_type; 483 $this->{$type_l} ($reservations_database, $type); 484 } 485 } 486 } 487 488 /** 489 * Fetches leases from memfile. 490 * 491 * https://kea.isc.org/wiki/HostReservationDesign 492 * 493 * @access private 494 * @param mixed $type 495 * @return void 496 */ 497 private function get_reservations_config_file ($type) { 498 // read file 499 if($type=="IPv4") { 500 // check if set 501 if (isset($this->config['Dhcp4']['subnet4'])) { 502 foreach ($this->config['Dhcp4']['subnet4'] as $s) { 503 // set 504 if (isset($s['reservations'])) { 505 // save id 506 unset($s_id); 507 $s_id = isset($s['id']) ? $s['id'] : ""; 508 // init array 509 $this->reservations4 = array(); 510 $m=0; 511 // loop 512 foreach ($s['reservations'] as $r) { 513 $this->reservations4[$m] = array( 514 "location" => "Config file", 515 "hw-address" => $r['hw-address'], 516 "ip-address" => $r['ip-address'], 517 "hostname" => $r['hostname'], 518 "dhcp4_subnet_id"=> $s_id, 519 "subnet" => $s['subnet'] 520 ); 521 // options 522 if(isset($r['options'])) { 523 $this->reservations4[$m]['options'] = array(); 524 foreach ($r['options'] as $o) { 525 $this->reservations4[$m]['options'][$o['name']] = $o['data']; 526 } 527 } 528 // classes 529 if(isset($r['client-classes'])) { 530 $this->reservations4[$m]['classes'] = array(); 531 foreach ($r['client-classes'] as $c) { 532 $this->reservations4[$m]['classes'][] = $c; 533 } 534 } 535 536 // reformat 537 $this->reservations4[$m] = $this->reformat_empty_array_fields ($this->reservations4[$m], "/"); 538 539 // next index 540 $m++; 541 } 542 } 543 } 544 } 545 } 546 else { 547 $this->reservations6 = file($reservations_database['name']); 548 } 549 } 550 551 /** 552 * Fetches leases from mysql database. 553 * 554 * @access private 555 * @param mixed $reservations_database //database details 556 * @param mixed $type //ipv4 / ipv6 557 * @return void 558 */ 559 private function get_reservations_mysql ($reservations_database, $type) { 560 // if host not specified assume localhost 561 if (strlen($reservations_database['host'])==0) { $reservations_database['host'] = "localhost"; } 562 // open DB connection 563 $this->init_database_conection ($reservations_database['user'], $reservations_database['password'], $reservations_database['host'], 3306, $reservations_database['name']); 564 // set query 565 if($type=="IPv4") { 566 $query = "select 'MySQL' as 'location', `dhcp4_subnet_id`, `ipv4_address` as `ip-address`, HEX(`dhcp_identifier`) as `hw-address`, `hostname` from `hosts`;"; 567 } 568 else { 569 $query = "select * from `hosts`;"; 570 } 571 // fetch leases 572 try { $reservations = $this->Database_kea->getObjectsQuery($query); } 573 catch (Exception $e) { 574 throw new Exception($e->getMessage()); 575 } 576 // save leases 577 if (sizeof($reservations)>0) { 578 // we need array 579 $result = array(); 580 // loop 581 foreach ($reservations as $k=>$l) { 582 // check for subnet 583 if ($l->dhcp4_subnet_id!==0 && strlen($l->dhcp4_subnet_id)>0) { 584 if($type=="IPv4") { 585 foreach($this->subnets4 as $s) { 586 if($s['id']==$l->dhcp4_subnet_id) { 587 $l->subnet = $s['subnet']; 588 } 589 } 590 } 591 else { 592 foreach($this->subnets6 as $s) { 593 if($s['id']==$l->dhcp6_subnet_id) { 594 $l->subnet = $s['subnet']; 595 } 596 } 597 } 598 } 599 600 // save 601 if($type=="IPv4") { 602 $this->reservations4[] = (array) $l; 603 } 604 else { 605 $this->reservations6[] = (array) $l; 606 } 607 } 608 } 609 } 610 611 612 613 614 615 616 617 public function read_statistics () { 618 $sock = stream_socket_client('unix:///var/lib/kea/socket', $errno, $errstr); 619 620 $cmd = array("command"=>"list-commands"); 621 622 fwrite($sock, json_encode($cmd)."\r\n"); 623 624 echo fread($sock, 4096)."\n"; 625 626 fclose($sock); 627 } 628} 629