#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/socket.h>
#include <net/if_arp.h>
#include <netinet/in.h>
#include <sys/systeminfo.h>
#include <netinet/inetutil.h>
#include <netinet/dhcp.h>
#include <dhcpmsg.h>
#include <libdevinfo.h>
#include "agent.h"
#include "async.h"
#include "util.h"
#include "packet.h"
#include "interface.h"
#include "states.h"
typedef struct {
char dk_if_name[IFNAMSIZ];
char dk_ack[1];
} dhcp_kcache_t;
static int get_dhcp_kcache(dhcp_kcache_t **, size_t *);
static boolean_t get_prom_prop(const char *, const char *, uchar_t **,
uint_t *);
boolean_t
dhcp_adopt(void)
{
int retval;
dhcp_kcache_t *kcache = NULL;
size_t kcache_size;
PKT_LIST *plp = NULL;
dhcp_lif_t *lif;
dhcp_smach_t *dsmp = NULL;
uint_t client_id_len;
retval = get_dhcp_kcache(&kcache, &kcache_size);
if (retval == 0 || kcache_size < sizeof (dhcp_kcache_t)) {
dhcpmsg(MSG_CRIT, "dhcp_adopt: cannot fetch kernel cache");
goto failure;
}
dhcpmsg(MSG_DEBUG, "dhcp_adopt: fetched %s kcache", kcache->dk_if_name);
plp = alloc_pkt_entry(strlen(kcache->dk_ack) / 2, B_FALSE);
if (plp == NULL)
goto failure;
dhcpmsg(MSG_DEBUG, "dhcp_adopt: allocated ACK of %d bytes", plp->len);
if (hexascii_to_octet(kcache->dk_ack, plp->len * 2, plp->pkt,
&plp->len) != 0) {
dhcpmsg(MSG_CRIT, "dhcp_adopt: cannot convert kernel ACK");
goto failure;
}
if (dhcp_options_scan(plp, B_TRUE) != 0) {
dhcpmsg(MSG_CRIT, "dhcp_adopt: cannot parse kernel ACK");
goto failure;
}
if ((lif = attach_lif(kcache->dk_if_name, B_FALSE, &retval)) == NULL) {
dhcpmsg(MSG_ERROR, "dhcp_adopt: unable to attach %s: %d",
kcache->dk_if_name, retval);
goto failure;
}
if ((dsmp = insert_smach(lif, &retval)) == NULL) {
dhcpmsg(MSG_ERROR, "dhcp_adopt: unable to create state "
"machine for %s: %d", kcache->dk_if_name, retval);
goto failure;
}
dhcpmsg(MSG_DEBUG, "dhcp_adopt: getting /chosen:clientid property");
client_id_len = 0;
if (!get_prom_prop("chosen", "client-id", &dsmp->dsm_cid,
&client_id_len)) {
dhcpmsg(MSG_DEBUG,
"dhcp_adopt: cannot allocate client id for %s",
dsmp->dsm_name);
goto failure;
} else if (dsmp->dsm_hwtype == ARPHRD_IB && dsmp->dsm_cid == NULL) {
dhcpmsg(MSG_DEBUG, "dhcp_adopt: no /chosen:clientid id for %s",
dsmp->dsm_name);
goto failure;
}
dsmp->dsm_cidlen = client_id_len;
if (set_lif_dhcp(lif) != DHCP_IPC_SUCCESS)
goto failure;
if (!set_smach_state(dsmp, ADOPTING))
goto failure;
dsmp->dsm_dflags = DHCP_IF_PRIMARY;
if (!dhcp_bound(dsmp, plp)) {
dhcpmsg(MSG_CRIT, "dhcp_adopt: cannot use cached packet");
goto failure;
}
free(kcache);
return (B_TRUE);
failure:
if (dsmp != NULL)
remove_smach(dsmp);
free(kcache);
free_pkt_entry(plp);
return (B_FALSE);
}
void
dhcp_adopt_complete(dhcp_smach_t *dsmp)
{
dhcpmsg(MSG_DEBUG, "dhcp_adopt_complete: completing adoption");
if (async_start(dsmp, DHCP_EXTEND, B_FALSE) == 0) {
dhcpmsg(MSG_CRIT, "dhcp_adopt_complete: async_start failed");
return;
}
if (dhcp_extending(dsmp) == 0) {
dhcpmsg(MSG_CRIT,
"dhcp_adopt_complete: cannot send renew request");
return;
}
if (grandparent != (pid_t)0) {
dhcpmsg(MSG_DEBUG, "adoption complete, signalling parent (%ld)"
" to exit.", grandparent);
(void) kill(grandparent, SIGALRM);
}
}
static int
get_dhcp_kcache(dhcp_kcache_t **kernel_cachep, size_t *kcache_size)
{
char dummy;
long size;
size = sysinfo(SI_DHCP_CACHE, &dummy, sizeof (dummy));
if (size == -1)
return (0);
*kcache_size = size;
*kernel_cachep = malloc(*kcache_size);
if (*kernel_cachep == NULL)
return (0);
(void) sysinfo(SI_DHCP_CACHE, (caddr_t)*kernel_cachep, size);
return (1);
}
static boolean_t
get_prom_prop(const char *nodename, const char *propname, uchar_t **propvaluep,
uint_t *lenp)
{
di_node_t root_node;
di_node_t node;
di_prom_handle_t phdl = DI_PROM_HANDLE_NIL;
di_prom_prop_t pp;
uchar_t *value = NULL;
unsigned int len = 0;
boolean_t success = B_TRUE;
if ((root_node = di_init("/", DINFOCPYALL)) == DI_NODE_NIL ||
(phdl = di_prom_init()) == DI_PROM_HANDLE_NIL) {
dhcpmsg(MSG_DEBUG, "get_prom_prop: property root node "
"not found");
goto get_prom_prop_cleanup;
}
for (node = di_child_node(root_node);
node != DI_NODE_NIL;
node = di_sibling_node(node)) {
if (strcmp(di_node_name(node), nodename) == 0) {
break;
}
}
if (node == DI_NODE_NIL) {
dhcpmsg(MSG_DEBUG, "get_prom_prop: node not found");
goto get_prom_prop_cleanup;
}
for (pp = di_prom_prop_next(phdl, node, DI_PROM_PROP_NIL);
pp != DI_PROM_PROP_NIL;
pp = di_prom_prop_next(phdl, node, pp)) {
dhcpmsg(MSG_DEBUG, "get_prom_prop: property = %s",
di_prom_prop_name(pp));
if (strcmp(propname, di_prom_prop_name(pp)) == 0) {
break;
}
}
if (pp == DI_PROM_PROP_NIL) {
dhcpmsg(MSG_DEBUG, "get_prom_prop: property not found");
goto get_prom_prop_cleanup;
}
len = di_prom_prop_data(pp, (uchar_t **)&value);
if (value == NULL) {
success = B_FALSE;
dhcpmsg(MSG_ERR, "get_prom_prop: cannot read property data");
goto get_prom_prop_cleanup;
}
if (propvaluep != NULL) {
*propvaluep = calloc(len, sizeof (uchar_t));
if (*propvaluep == NULL) {
success = B_FALSE;
dhcpmsg(MSG_ERR, "get_prom_prop: cannot allocate "
"memory for property value");
goto get_prom_prop_cleanup;
}
(void) memcpy(*propvaluep, value, len);
if (lenp != NULL) {
*lenp = len;
}
dhcpmsg(MSG_DEBUG, "get_prom_prop: property value "
"length = %d", len);
}
get_prom_prop_cleanup:
if (phdl != DI_PROM_HANDLE_NIL) {
di_prom_fini(phdl);
}
if (root_node != DI_NODE_NIL) {
di_fini(root_node);
}
return (success);
}