1
0
Fork 0
mirror of https://github.com/albfan/miraclecast.git synced 2025-03-09 23:38:56 +00:00

Initial MiracleCast Implementation

This initial commit contains the main "miracled" daemon that does
link-management and peer-discovery/control. The "miraclectl" tool can be
used to control this daemon during runtime.

Note that this implementation is still missing a lot of stuff. All it
currently does is provide link-management and basic peer-discovery.
Following commits will hook everything else up.

The actual Miracast/Wifi-Display related runtime control is not being
worked on, yet. Feel free to use the proof-of-concept from the OpenWFD
repository. The MiracleCast implementation will not get any such
functionality unless the basic link-management is properly working.

Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
This commit is contained in:
David Herrmann 2014-02-06 16:43:11 +01:00
commit 051b584746
35 changed files with 13147 additions and 0 deletions

3154
src/gdhcp/client.c Normal file

File diff suppressed because it is too large Load diff

759
src/gdhcp/common.c Normal file
View file

@ -0,0 +1,759 @@
/*
* DHCP library with GLib integration
*
* Copyright (C) 2007-2012 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdint.h>
#include <string.h>
#include <endian.h>
#include <net/if_arp.h>
#include <linux/if.h>
#include <netpacket/packet.h>
#include <net/ethernet.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "gdhcp.h"
#include "common.h"
static const DHCPOption client_options[] = {
{ OPTION_IP, 0x01 }, /* subnet-mask */
{ OPTION_IP | OPTION_LIST, 0x03 }, /* routers */
{ OPTION_IP | OPTION_LIST, 0x06 }, /* domain-name-servers */
{ OPTION_STRING, 0x0c }, /* hostname */
{ OPTION_STRING, 0x0f }, /* domain-name */
{ OPTION_IP | OPTION_LIST, 0x2a }, /* ntp-servers */
{ OPTION_U32, 0x33 }, /* dhcp-lease-time */
/* Options below will not be exposed to user */
{ OPTION_IP, 0x32 }, /* requested-ip */
{ OPTION_U8, 0x35 }, /* message-type */
{ OPTION_U32, 0x36 }, /* server-id */
{ OPTION_U16, 0x39 }, /* max-size */
{ OPTION_STRING, 0x3c }, /* vendor */
{ OPTION_STRING, 0x3d }, /* client-id */
{ OPTION_STRING, 0xfc }, /* UNOFFICIAL proxy-pac */
{ OPTION_UNKNOWN, 0x00 },
};
GDHCPOptionType dhcp_get_code_type(uint8_t code)
{
int i;
for (i = 0; client_options[i].code; i++) {
if (client_options[i].code == code)
return client_options[i].type;
}
return OPTION_UNKNOWN;
}
uint8_t *dhcp_get_option(struct dhcp_packet *packet, int code)
{
int len, rem;
uint8_t *optionptr;
uint8_t overload = 0;
/* option bytes: [code][len][data1][data2]..[dataLEN] */
optionptr = packet->options;
rem = sizeof(packet->options);
while (1) {
if (rem <= 0)
/* Bad packet, malformed option field */
return NULL;
if (optionptr[OPT_CODE] == DHCP_PADDING) {
rem--;
optionptr++;
continue;
}
if (optionptr[OPT_CODE] == DHCP_END) {
if (overload & FILE_FIELD) {
overload &= ~FILE_FIELD;
optionptr = packet->file;
rem = sizeof(packet->file);
continue;
} else if (overload & SNAME_FIELD) {
overload &= ~SNAME_FIELD;
optionptr = packet->sname;
rem = sizeof(packet->sname);
continue;
}
break;
}
len = 2 + optionptr[OPT_LEN];
rem -= len;
if (rem < 0)
continue; /* complain and return NULL */
if (optionptr[OPT_CODE] == code)
return optionptr + OPT_DATA;
if (optionptr[OPT_CODE] == DHCP_OPTION_OVERLOAD)
overload |= optionptr[OPT_DATA];
optionptr += len;
}
return NULL;
}
int dhcp_end_option(uint8_t *optionptr)
{
int i = 0;
while (optionptr[i] != DHCP_END) {
if (optionptr[i] != DHCP_PADDING)
i += optionptr[i + OPT_LEN] + OPT_DATA - 1;
i++;
}
return i;
}
uint8_t *dhcpv6_get_option(struct dhcpv6_packet *packet, uint16_t pkt_len,
int code, uint16_t *option_len, int *option_count)
{
int rem, count = 0;
uint8_t *optionptr, *found = NULL;
uint16_t opt_code, opt_len, len;
optionptr = packet->options;
rem = pkt_len - 1 - 3;
if (rem <= 0)
goto bad_packet;
while (1) {
opt_code = optionptr[0] << 8 | optionptr[1];
opt_len = len = optionptr[2] << 8 | optionptr[3];
len += 2 + 2; /* skip code and len */
if (len < 4)
goto bad_packet;
rem -= len;
if (rem < 0)
break;
if (opt_code == code) {
if (option_len)
*option_len = opt_len;
if (rem < 0)
goto bad_packet;
else
found = optionptr + 2 + 2;
count++;
}
if (rem == 0)
break;
optionptr += len;
}
if (option_count)
*option_count = count;
return found;
bad_packet:
if (option_len)
*option_len = 0;
if (option_count)
*option_count = 0;
return NULL;
}
uint8_t *dhcpv6_get_sub_option(unsigned char *option, uint16_t max_len,
uint16_t *option_code, uint16_t *option_len)
{
int rem;
uint16_t code, len;
rem = max_len - 2 - 2;
if (rem <= 0)
/* Bad option */
return NULL;
code = option[0] << 8 | option[1];
len = option[2] << 8 | option[3];
rem -= len;
if (rem < 0)
return NULL;
*option_code = code;
*option_len = len;
return &option[4];
}
/*
* Add an option (supplied in binary form) to the options.
* Option format: [code][len][data1][data2]..[dataLEN]
*/
void dhcp_add_binary_option(struct dhcp_packet *packet, uint8_t *addopt)
{
unsigned len;
uint8_t *optionptr = packet->options;
unsigned end = dhcp_end_option(optionptr);
len = OPT_DATA + addopt[OPT_LEN];
/* end position + (option code/length + addopt length) + end option */
if (end + len + 1 >= DHCP_OPTIONS_BUFSIZE)
/* option did not fit into the packet */
return;
memcpy(optionptr + end, addopt, len);
optionptr[end + len] = DHCP_END;
}
/*
* Add an option (supplied in binary form) to the options.
* Option format: [code][len][data1][data2]..[dataLEN]
*/
void dhcpv6_add_binary_option(struct dhcpv6_packet *packet, uint16_t max_len,
uint16_t *pkt_len, uint8_t *addopt)
{
unsigned len;
uint8_t *optionptr = packet->options;
len = 2 + 2 + (addopt[2] << 8 | addopt[3]);
/* end position + (option code/length + addopt length) */
if (*pkt_len + len >= max_len)
/* option did not fit into the packet */
return;
memcpy(optionptr + *pkt_len, addopt, len);
*pkt_len += len;
}
static GDHCPOptionType check_option(uint8_t code, uint8_t data_len)
{
GDHCPOptionType type = dhcp_get_code_type(code);
uint8_t len;
if (type == OPTION_UNKNOWN)
return type;
len = dhcp_option_lengths[type & OPTION_TYPE_MASK];
if (len != data_len) {
printf("Invalid option len %d (expecting %d) for code 0x%x\n",
data_len, len, code);
return OPTION_UNKNOWN;
}
return type;
}
void dhcp_add_option_uint32(struct dhcp_packet *packet, uint8_t code,
uint32_t data)
{
uint8_t option[6];
if (check_option(code, sizeof(data)) == OPTION_UNKNOWN)
return;
option[OPT_CODE] = code;
option[OPT_LEN] = sizeof(data);
put_be32(data, option + OPT_DATA);
dhcp_add_binary_option(packet, option);
return;
}
void dhcp_add_option_uint16(struct dhcp_packet *packet, uint8_t code,
uint16_t data)
{
uint8_t option[6];
if (check_option(code, sizeof(data)) == OPTION_UNKNOWN)
return;
option[OPT_CODE] = code;
option[OPT_LEN] = sizeof(data);
put_be16(data, option + OPT_DATA);
dhcp_add_binary_option(packet, option);
return;
}
void dhcp_add_option_uint8(struct dhcp_packet *packet, uint8_t code,
uint8_t data)
{
uint8_t option[6];
if (check_option(code, sizeof(data)) == OPTION_UNKNOWN)
return;
option[OPT_CODE] = code;
option[OPT_LEN] = sizeof(data);
option[OPT_DATA] = data;
dhcp_add_binary_option(packet, option);
return;
}
void dhcp_init_header(struct dhcp_packet *packet, char type)
{
memset(packet, 0, sizeof(*packet));
packet->op = BOOTREQUEST;
switch (type) {
case DHCPOFFER:
case DHCPACK:
case DHCPNAK:
packet->op = BOOTREPLY;
}
packet->htype = 1;
packet->hlen = 6;
packet->cookie = htonl(DHCP_MAGIC);
packet->options[0] = DHCP_END;
dhcp_add_option_uint8(packet, DHCP_MESSAGE_TYPE, type);
}
void dhcpv6_init_header(struct dhcpv6_packet *packet, uint8_t type)
{
int id;
memset(packet, 0, sizeof(*packet));
packet->message = type;
id = random();
packet->transaction_id[0] = (id >> 16) & 0xff;
packet->transaction_id[1] = (id >> 8) & 0xff;
packet->transaction_id[2] = id & 0xff;
}
static bool check_vendor(uint8_t *option_vendor, const char *vendor)
{
uint8_t vendor_length = sizeof(vendor) - 1;
if (option_vendor[OPT_LEN - OPT_DATA] != vendor_length)
return false;
if (memcmp(option_vendor, vendor, vendor_length) != 0)
return false;
return true;
}
static void check_broken_vendor(struct dhcp_packet *packet)
{
uint8_t *vendor;
if (packet->op != BOOTREQUEST)
return;
vendor = dhcp_get_option(packet, DHCP_VENDOR);
if (!vendor)
return;
if (check_vendor(vendor, "MSFT 98"))
packet->flags |= htons(BROADCAST_FLAG);
}
int dhcp_recv_l3_packet(struct dhcp_packet *packet, int fd)
{
int n;
memset(packet, 0, sizeof(*packet));
n = read(fd, packet, sizeof(*packet));
if (n < 0)
return -errno;
if (packet->cookie != htonl(DHCP_MAGIC))
return -EPROTO;
check_broken_vendor(packet);
return n;
}
int dhcpv6_recv_l3_packet(struct dhcpv6_packet **packet, unsigned char *buf,
int buf_len, int fd)
{
int n;
n = read(fd, buf, buf_len);
if (n < 0)
return -errno;
*packet = (struct dhcpv6_packet *)buf;
return n;
}
/* TODO: Use glib checksum */
uint16_t dhcp_checksum(void *addr, int count)
{
/*
* Compute Internet Checksum for "count" bytes
* beginning at location "addr".
*/
int32_t sum = 0;
uint16_t *source = (uint16_t *) addr;
while (count > 1) {
/* This is the inner loop */
sum += *source++;
count -= 2;
}
/* Add left-over byte, if any */
if (count > 0) {
/* Make sure that the left-over byte is added correctly both
* with little and big endian hosts */
uint16_t tmp = 0;
*(uint8_t *) &tmp = *(uint8_t *) source;
sum += tmp;
}
/* Fold 32-bit sum to 16 bits */
while (sum >> 16)
sum = (sum & 0xffff) + (sum >> 16);
return ~sum;
}
#define IN6ADDR_ALL_DHCP_RELAY_AGENTS_AND_SERVERS_MC_INIT \
{ { { 0xff,0x02,0,0,0,0,0,0,0,0,0,0,0,0x1,0,0x2 } } } /* ff02::1:2 */
static const struct in6_addr in6addr_all_dhcp_relay_agents_and_servers_mc =
IN6ADDR_ALL_DHCP_RELAY_AGENTS_AND_SERVERS_MC_INIT;
int dhcpv6_send_packet(int index, struct dhcpv6_packet *dhcp_pkt, int len)
{
struct msghdr m;
struct iovec v;
struct in6_pktinfo *pktinfo;
struct cmsghdr *cmsg;
int fd, ret;
struct sockaddr_in6 dst;
void *control_buf;
size_t control_buf_len;
fd = socket(PF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
if (fd < 0)
return -errno;
memset(&dst, 0, sizeof(dst));
dst.sin6_family = AF_INET6;
dst.sin6_port = htons(DHCPV6_SERVER_PORT);
dst.sin6_addr = in6addr_all_dhcp_relay_agents_and_servers_mc;
control_buf_len = CMSG_SPACE(sizeof(struct in6_pktinfo));
control_buf = g_try_malloc0(control_buf_len);
if (!control_buf) {
close(fd);
return -ENOMEM;
}
memset(&m, 0, sizeof(m));
memset(&v, 0, sizeof(v));
m.msg_name = &dst;
m.msg_namelen = sizeof(dst);
v.iov_base = (char *)dhcp_pkt;
v.iov_len = len;
m.msg_iov = &v;
m.msg_iovlen = 1;
m.msg_control = control_buf;
m.msg_controllen = control_buf_len;
cmsg = CMSG_FIRSTHDR(&m);
cmsg->cmsg_level = IPPROTO_IPV6;
cmsg->cmsg_type = IPV6_PKTINFO;
cmsg->cmsg_len = CMSG_LEN(sizeof(*pktinfo));
pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg);
memset(pktinfo, 0, sizeof(*pktinfo));
pktinfo->ipi6_ifindex = index;
m.msg_controllen = cmsg->cmsg_len;
ret = sendmsg(fd, &m, 0);
if (ret < 0) {
char *msg = "DHCPv6 msg send failed";
if (errno == EADDRNOTAVAIL) {
char *str = g_strdup_printf("%s (index %d)",
msg, index);
perror(str);
g_free(str);
} else
perror(msg);
}
g_free(control_buf);
close(fd);
return ret;
}
int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt,
uint32_t source_ip, int source_port, uint32_t dest_ip,
int dest_port, const uint8_t *dest_arp, int ifindex)
{
struct sockaddr_ll dest;
struct ip_udp_dhcp_packet packet;
int fd, n;
enum {
IP_UPD_DHCP_SIZE = sizeof(struct ip_udp_dhcp_packet) -
EXTEND_FOR_BUGGY_SERVERS,
UPD_DHCP_SIZE = IP_UPD_DHCP_SIZE -
offsetof(struct ip_udp_dhcp_packet, udp),
};
fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IP));
if (fd < 0)
return -errno;
memset(&dest, 0, sizeof(dest));
memset(&packet, 0, sizeof(packet));
packet.data = *dhcp_pkt;
dest.sll_family = AF_PACKET;
dest.sll_protocol = htons(ETH_P_IP);
dest.sll_ifindex = ifindex;
dest.sll_halen = 6;
memcpy(dest.sll_addr, dest_arp, 6);
if (bind(fd, (struct sockaddr *)&dest, sizeof(dest)) < 0) {
close(fd);
return -errno;
}
packet.ip.protocol = IPPROTO_UDP;
packet.ip.saddr = source_ip;
packet.ip.daddr = dest_ip;
packet.udp.source = htons(source_port);
packet.udp.dest = htons(dest_port);
/* size, excluding IP header: */
packet.udp.len = htons(UPD_DHCP_SIZE);
/* for UDP checksumming, ip.len is set to UDP packet len */
packet.ip.tot_len = packet.udp.len;
packet.udp.check = dhcp_checksum(&packet, IP_UPD_DHCP_SIZE);
/* but for sending, it is set to IP packet len */
packet.ip.tot_len = htons(IP_UPD_DHCP_SIZE);
packet.ip.ihl = sizeof(packet.ip) >> 2;
packet.ip.version = IPVERSION;
packet.ip.ttl = IPDEFTTL;
packet.ip.check = dhcp_checksum(&packet.ip, sizeof(packet.ip));
/*
* Currently we send full-sized DHCP packets (zero padded).
* If you need to change this: last byte of the packet is
* packet.data.options[dhcp_end_option(packet.data.options)]
*/
n = sendto(fd, &packet, IP_UPD_DHCP_SIZE, 0,
(struct sockaddr *) &dest, sizeof(dest));
close(fd);
if (n < 0)
return -errno;
return n;
}
int dhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt,
uint32_t source_ip, int source_port,
uint32_t dest_ip, int dest_port)
{
struct sockaddr_in client;
int fd, n, opt = 1;
enum {
DHCP_SIZE = sizeof(struct dhcp_packet) -
EXTEND_FOR_BUGGY_SERVERS,
};
fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
if (fd < 0)
return -errno;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
memset(&client, 0, sizeof(client));
client.sin_family = AF_INET;
client.sin_port = htons(source_port);
client.sin_addr.s_addr = htonl(source_ip);
if (bind(fd, (struct sockaddr *) &client, sizeof(client)) < 0) {
close(fd);
return -errno;
}
memset(&client, 0, sizeof(client));
client.sin_family = AF_INET;
client.sin_port = htons(dest_port);
client.sin_addr.s_addr = htonl(dest_ip);
if (connect(fd, (struct sockaddr *) &client, sizeof(client)) < 0) {
close(fd);
return -errno;
}
n = write(fd, dhcp_pkt, DHCP_SIZE);
close(fd);
if (n < 0)
return -errno;
return n;
}
int dhcp_l3_socket(int port, const char *interface, int family)
{
int fd, opt = 1, len;
struct sockaddr_in addr4;
struct sockaddr_in6 addr6;
struct sockaddr *addr;
fd = socket(family, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
if (fd < 0)
return -errno;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,
interface, strlen(interface) + 1) < 0) {
close(fd);
return -1;
}
if (family == AF_INET) {
memset(&addr4, 0, sizeof(addr4));
addr4.sin_family = family;
addr4.sin_port = htons(port);
addr = (struct sockaddr *)&addr4;
len = sizeof(addr4);
} else if (family == AF_INET6) {
memset(&addr6, 0, sizeof(addr6));
addr6.sin6_family = family;
addr6.sin6_port = htons(port);
addr = (struct sockaddr *)&addr6;
len = sizeof(addr6);
} else {
close(fd);
return -EINVAL;
}
if (bind(fd, addr, len) != 0) {
close(fd);
return -1;
}
return fd;
}
char *get_interface_name(int index)
{
struct ifreq ifr;
int sk, err;
if (index < 0)
return NULL;
sk = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
if (sk < 0) {
perror("Open socket error");
return NULL;
}
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_ifindex = index;
err = ioctl(sk, SIOCGIFNAME, &ifr);
if (err < 0) {
perror("Get interface name error");
close(sk);
return NULL;
}
close(sk);
return g_strdup(ifr.ifr_name);
}
bool interface_is_up(int index)
{
int sk, err;
struct ifreq ifr;
bool ret = false;
sk = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
if (sk < 0) {
perror("Open socket error");
return false;
}
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_ifindex = index;
err = ioctl(sk, SIOCGIFNAME, &ifr);
if (err < 0) {
perror("Get interface name error");
goto done;
}
err = ioctl(sk, SIOCGIFFLAGS, &ifr);
if (err < 0) {
perror("Get interface flags error");
goto done;
}
if (ifr.ifr_flags & IFF_UP)
ret = true;
done:
close(sk);
return ret;
}

211
src/gdhcp/common.h Normal file
View file

@ -0,0 +1,211 @@
/*
*
* DHCP client library with GLib integration
*
* Copyright (C) 2009-2012 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <netinet/udp.h>
#include <netinet/ip.h>
#include <glib.h>
#include "unaligned.h"
#include "gdhcp.h"
#define CLIENT_PORT 68
#define SERVER_PORT 67
#define DHCPV6_CLIENT_PORT 546
#define DHCPV6_SERVER_PORT 547
#define MAX_DHCPV6_PKT_SIZE 1500
#define EXTEND_FOR_BUGGY_SERVERS 80
static const uint8_t MAC_BCAST_ADDR[ETH_ALEN] __attribute__((aligned(2))) = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
static const uint8_t MAC_ANY_ADDR[ETH_ALEN] __attribute__((aligned(2))) = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
/* DHCP packet */
#define DHCP_MAGIC 0x63825363
#define DHCP_OPTIONS_BUFSIZE 308
#define BOOTREQUEST 1
#define BOOTREPLY 2
#define BROADCAST_FLAG 0x8000
/* See RFC 2131 */
struct dhcp_packet {
uint8_t op;
uint8_t htype;
uint8_t hlen;
uint8_t hops;
uint32_t xid;
uint16_t secs;
uint16_t flags;
uint32_t ciaddr;
uint32_t yiaddr;
uint32_t siaddr_nip;
uint32_t gateway_nip;
uint8_t chaddr[16];
uint8_t sname[64];
uint8_t file[128];
uint32_t cookie;
uint8_t options[DHCP_OPTIONS_BUFSIZE + EXTEND_FOR_BUGGY_SERVERS];
} __attribute__((packed));
struct ip_udp_dhcp_packet {
struct iphdr ip;
struct udphdr udp;
struct dhcp_packet data;
} __attribute__((packed));
/* See RFC 3315 */
struct dhcpv6_packet {
uint8_t message;
uint8_t transaction_id[3];
uint8_t options[];
} __attribute__((packed));
/* See RFC 2132 */
#define DHCP_PADDING 0x00
#define DHCP_SUBNET 0x01
#define DHCP_ROUTER 0x03
#define DHCP_TIME_SERVER 0x04
#define DHCP_NAME_SERVER 0x05
#define DHCP_DNS_SERVER 0x06
#define DHCP_HOST_NAME 0x0c
#define DHCP_DOMAIN_NAME 0x0f
#define DHCP_NTP_SERVER 0x2a
#define DHCP_REQUESTED_IP 0x32
#define DHCP_LEASE_TIME 0x33
#define DHCP_OPTION_OVERLOAD 0x34
#define DHCP_MESSAGE_TYPE 0x35
#define DHCP_SERVER_ID 0x36
#define DHCP_PARAM_REQ 0x37
#define DHCP_ERR_MESSAGE 0x38
#define DHCP_MAX_SIZE 0x39
#define DHCP_VENDOR 0x3c
#define DHCP_CLIENT_ID 0x3d
#define DHCP_END 0xff
#define OPT_CODE 0
#define OPT_LEN 1
#define OPT_DATA 2
#define OPTION_FIELD 0
#define FILE_FIELD 1
#define SNAME_FIELD 2
/* DHCP_MESSAGE_TYPE values */
#define DHCPDISCOVER 1
#define DHCPOFFER 2
#define DHCPREQUEST 3
#define DHCPDECLINE 4
#define DHCPACK 5
#define DHCPNAK 6
#define DHCPRELEASE 7
#define DHCPINFORM 8
#define DHCP_MINTYPE DHCPDISCOVER
#define DHCP_MAXTYPE DHCPINFORM
/* Message types for DHCPv6, RFC 3315 sec 5.3 */
#define DHCPV6_SOLICIT 1
#define DHCPV6_ADVERTISE 2
#define DHCPV6_REQUEST 3
#define DHCPV6_CONFIRM 4
#define DHCPV6_RENEW 5
#define DHCPV6_REBIND 6
#define DHCPV6_REPLY 7
#define DHCPV6_RELEASE 8
#define DHCPV6_DECLINE 9
#define DHCPV6_RECONFIGURE 10
#define DHCPV6_INFORMATION_REQ 11
/*
* DUID time starts 2000-01-01.
*/
#define DUID_TIME_EPOCH 946684800
typedef enum {
OPTION_UNKNOWN,
OPTION_IP,
OPTION_STRING,
OPTION_U8,
OPTION_U16,
OPTION_U32,
OPTION_TYPE_MASK = 0x0f,
OPTION_LIST = 0x10,
} GDHCPOptionType;
typedef struct dhcp_option {
GDHCPOptionType type;
uint8_t code;
} DHCPOption;
/* Length of the option types in binary form */
static const uint8_t dhcp_option_lengths[] = {
[OPTION_IP] = 4,
[OPTION_STRING] = 1,
[OPTION_U8] = 1,
[OPTION_U16] = 2,
[OPTION_U32] = 4,
};
uint8_t *dhcp_get_option(struct dhcp_packet *packet, int code);
uint8_t *dhcpv6_get_option(struct dhcpv6_packet *packet, uint16_t pkt_len,
int code, uint16_t *option_len, int *option_count);
uint8_t *dhcpv6_get_sub_option(unsigned char *option, uint16_t max_len,
uint16_t *code, uint16_t *option_len);
int dhcp_end_option(uint8_t *optionptr);
void dhcp_add_binary_option(struct dhcp_packet *packet, uint8_t *addopt);
void dhcpv6_add_binary_option(struct dhcpv6_packet *packet, uint16_t max_len,
uint16_t *pkt_len, uint8_t *addopt);
void dhcp_add_option_uint8(struct dhcp_packet *packet,
uint8_t code, uint8_t data);
void dhcp_add_option_uint16(struct dhcp_packet *packet,
uint8_t code, uint16_t data);
void dhcp_add_option_uint32(struct dhcp_packet *packet,
uint8_t code, uint32_t data);
GDHCPOptionType dhcp_get_code_type(uint8_t code);
GDHCPOptionType dhcpv6_get_code_type(uint16_t code);
uint16_t dhcp_checksum(void *addr, int count);
void dhcp_init_header(struct dhcp_packet *packet, char type);
void dhcpv6_init_header(struct dhcpv6_packet *packet, uint8_t type);
int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt,
uint32_t source_ip, int source_port,
uint32_t dest_ip, int dest_port,
const uint8_t *dest_arp, int ifindex);
int dhcpv6_send_packet(int index, struct dhcpv6_packet *dhcp_pkt, int len);
int dhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt,
uint32_t source_ip, int source_port,
uint32_t dest_ip, int dest_port);
int dhcp_l3_socket(int port, const char *interface, int family);
int dhcp_recv_l3_packet(struct dhcp_packet *packet, int fd);
int dhcpv6_recv_l3_packet(struct dhcpv6_packet **packet, unsigned char *buf,
int buf_len, int fd);
int dhcp_l3_socket_send(int index, int port, int family);
char *get_interface_name(int index);
bool interface_is_up(int index);

230
src/gdhcp/gdhcp.h Normal file
View file

@ -0,0 +1,230 @@
/*
*
* DHCP library with GLib integration
*
* Copyright (C) 2009-2012 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef __G_DHCP_H
#define __G_DHCP_H
#include <stdbool.h>
#include <stdint.h>
#include <arpa/inet.h>
#include <glib.h>
#ifdef __cplusplus
extern "C" {
#endif
/* DHCP Client part*/
struct _GDHCPClient;
typedef struct _GDHCPClient GDHCPClient;
typedef enum {
G_DHCP_CLIENT_ERROR_NONE,
G_DHCP_CLIENT_ERROR_INTERFACE_UNAVAILABLE,
G_DHCP_CLIENT_ERROR_INTERFACE_IN_USE,
G_DHCP_CLIENT_ERROR_INTERFACE_DOWN,
G_DHCP_CLIENT_ERROR_NOMEM,
G_DHCP_CLIENT_ERROR_INVALID_INDEX,
G_DHCP_CLIENT_ERROR_INVALID_OPTION
} GDHCPClientError;
typedef enum {
G_DHCP_CLIENT_EVENT_LEASE_AVAILABLE,
G_DHCP_CLIENT_EVENT_IPV4LL_AVAILABLE,
G_DHCP_CLIENT_EVENT_NO_LEASE,
G_DHCP_CLIENT_EVENT_LEASE_LOST,
G_DHCP_CLIENT_EVENT_IPV4LL_LOST,
G_DHCP_CLIENT_EVENT_ADDRESS_CONFLICT,
G_DHCP_CLIENT_EVENT_INFORMATION_REQ,
G_DHCP_CLIENT_EVENT_SOLICITATION,
G_DHCP_CLIENT_EVENT_ADVERTISE,
G_DHCP_CLIENT_EVENT_REQUEST,
G_DHCP_CLIENT_EVENT_RENEW,
G_DHCP_CLIENT_EVENT_REBIND,
G_DHCP_CLIENT_EVENT_RELEASE,
G_DHCP_CLIENT_EVENT_CONFIRM,
G_DHCP_CLIENT_EVENT_DECLINE,
} GDHCPClientEvent;
typedef enum {
G_DHCP_IPV4,
G_DHCP_IPV6,
G_DHCP_IPV4LL,
} GDHCPType;
#define G_DHCP_SUBNET 0x01
#define G_DHCP_ROUTER 0x03
#define G_DHCP_TIME_SERVER 0x04
#define G_DHCP_DNS_SERVER 0x06
#define G_DHCP_DOMAIN_NAME 0x0f
#define G_DHCP_HOST_NAME 0x0c
#define G_DHCP_NTP_SERVER 0x2a
#define G_DHCP_CLIENT_ID 0x3d
#define G_DHCPV6_CLIENTID 1
#define G_DHCPV6_SERVERID 2
#define G_DHCPV6_IA_NA 3
#define G_DHCPV6_IA_TA 4
#define G_DHCPV6_IAADDR 5
#define G_DHCPV6_ORO 6
#define G_DHCPV6_PREFERENCE 7
#define G_DHCPV6_ELAPSED_TIME 8
#define G_DHCPV6_STATUS_CODE 13
#define G_DHCPV6_RAPID_COMMIT 14
#define G_DHCPV6_DNS_SERVERS 23
#define G_DHCPV6_DOMAIN_LIST 24
#define G_DHCPV6_IA_PD 25
#define G_DHCPV6_IA_PREFIX 26
#define G_DHCPV6_SNTP_SERVERS 31
#define G_DHCPV6_ERROR_SUCCESS 0
#define G_DHCPV6_ERROR_FAILURE 1
#define G_DHCPV6_ERROR_NO_ADDR 2
#define G_DHCPV6_ERROR_BINDING 3
#define G_DHCPV6_ERROR_LINK 4
#define G_DHCPV6_ERROR_MCAST 5
#define G_DHCPV6_ERROR_NO_PREFIX 6
typedef enum {
G_DHCPV6_DUID_LLT = 1,
G_DHCPV6_DUID_EN = 2,
G_DHCPV6_DUID_LL = 3,
} GDHCPDuidType;
typedef struct {
/*
* Note that no field in this struct can be allocated
* from heap or there will be a memory leak when the
* struct is freed by client.c:remove_option_value()
*/
struct in6_addr prefix;
unsigned char prefixlen;
uint32_t preferred;
uint32_t valid;
time_t expire;
} GDHCPIAPrefix;
typedef void (*GDHCPClientEventFunc) (GDHCPClient *client, gpointer user_data);
typedef void (*GDHCPDebugFunc)(const char *str, gpointer user_data);
GDHCPClient *g_dhcp_client_new(GDHCPType type, int index,
GDHCPClientError *error);
int g_dhcp_client_start(GDHCPClient *client, const char *last_address);
void g_dhcp_client_stop(GDHCPClient *client);
GDHCPClient *g_dhcp_client_ref(GDHCPClient *client);
void g_dhcp_client_unref(GDHCPClient *client);
void g_dhcp_client_register_event(GDHCPClient *client,
GDHCPClientEvent event,
GDHCPClientEventFunc func,
gpointer user_data);
GDHCPClientError g_dhcp_client_set_request(GDHCPClient *client,
unsigned int option_code);
void g_dhcp_client_clear_requests(GDHCPClient *dhcp_client);
void g_dhcp_client_clear_values(GDHCPClient *dhcp_client);
GDHCPClientError g_dhcp_client_set_id(GDHCPClient *client);
GDHCPClientError g_dhcp_client_set_send(GDHCPClient *client,
unsigned char option_code,
const char *option_value);
char *g_dhcp_client_get_address(GDHCPClient *client);
char *g_dhcp_client_get_netmask(GDHCPClient *client);
GList *g_dhcp_client_get_option(GDHCPClient *client,
unsigned char option_code);
int g_dhcp_client_get_index(GDHCPClient *client);
void g_dhcp_client_set_debug(GDHCPClient *client,
GDHCPDebugFunc func, gpointer user_data);
int g_dhcpv6_create_duid(GDHCPDuidType duid_type, int index, int type,
unsigned char **duid, int *duid_len);
int g_dhcpv6_client_set_duid(GDHCPClient *dhcp_client, unsigned char *duid,
int duid_len);
int g_dhcpv6_client_set_pd(GDHCPClient *dhcp_client, uint32_t *T1, uint32_t *T2,
GSList *prefixes);
GSList *g_dhcpv6_copy_prefixes(GSList *prefixes);
gboolean g_dhcpv6_client_clear_send(GDHCPClient *dhcp_client, uint16_t code);
void g_dhcpv6_client_set_send(GDHCPClient *dhcp_client, uint16_t option_code,
uint8_t *option_value, uint16_t option_len);
uint16_t g_dhcpv6_client_get_status(GDHCPClient *dhcp_client);
int g_dhcpv6_client_set_oro(GDHCPClient *dhcp_client, int args, ...);
void g_dhcpv6_client_create_iaid(GDHCPClient *dhcp_client, int index,
unsigned char *iaid);
int g_dhcpv6_client_get_timeouts(GDHCPClient *dhcp_client,
uint32_t *T1, uint32_t *T2,
time_t *started, time_t *expire);
uint32_t g_dhcpv6_client_get_iaid(GDHCPClient *dhcp_client);
void g_dhcpv6_client_set_iaid(GDHCPClient *dhcp_client, uint32_t iaid);
int g_dhcpv6_client_set_ia(GDHCPClient *dhcp_client, int index,
int code, uint32_t *T1, uint32_t *T2,
bool add_addresses, const char *address);
int g_dhcpv6_client_set_ias(GDHCPClient *dhcp_client, int index,
int code, uint32_t *T1, uint32_t *T2,
GSList *addresses);
void g_dhcpv6_client_reset_request(GDHCPClient *dhcp_client);
void g_dhcpv6_client_set_retransmit(GDHCPClient *dhcp_client);
void g_dhcpv6_client_clear_retransmit(GDHCPClient *dhcp_client);
/* DHCP Server */
typedef enum {
G_DHCP_SERVER_ERROR_NONE,
G_DHCP_SERVER_ERROR_INTERFACE_UNAVAILABLE,
G_DHCP_SERVER_ERROR_INTERFACE_IN_USE,
G_DHCP_SERVER_ERROR_INTERFACE_DOWN,
G_DHCP_SERVER_ERROR_NOMEM,
G_DHCP_SERVER_ERROR_INVALID_INDEX,
G_DHCP_SERVER_ERROR_INVALID_OPTION,
G_DHCP_SERVER_ERROR_IP_ADDRESS_INVALID
} GDHCPServerError;
typedef void (*GDHCPSaveLeaseFunc) (unsigned char *mac,
unsigned int nip, unsigned int expire);
struct _GDHCPServer;
typedef struct _GDHCPServer GDHCPServer;
GDHCPServer *g_dhcp_server_new(GDHCPType type,
int ifindex, GDHCPServerError *error);
int g_dhcp_server_start(GDHCPServer *server);
void g_dhcp_server_stop(GDHCPServer *server);
GDHCPServer *g_dhcp_server_ref(GDHCPServer *server);
void g_dhcp_server_unref(GDHCPServer *server);
int g_dhcp_server_set_option(GDHCPServer *server,
unsigned char option_code, const char *option_value);
int g_dhcp_server_set_ip_range(GDHCPServer *server,
const char *start_ip, const char *end_ip);
void g_dhcp_server_set_debug(GDHCPServer *server,
GDHCPDebugFunc func, gpointer user_data);
void g_dhcp_server_set_lease_time(GDHCPServer *dhcp_server,
unsigned int lease_time);
void g_dhcp_server_set_save_lease(GDHCPServer *dhcp_server,
GDHCPSaveLeaseFunc func, gpointer user_data);
#ifdef __cplusplus
}
#endif
#endif /* __G_DHCP_H */

139
src/gdhcp/ipv4ll.c Normal file
View file

@ -0,0 +1,139 @@
/*
*
* IPV4 Local Link library with GLib integration
*
* Copyright (C) 2009-2010 Aldebaran Robotics. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netpacket/packet.h>
#include <net/ethernet.h>
#include <netinet/if_ether.h>
#include <arpa/inet.h>
#include <glib.h>
#include "ipv4ll.h"
/**
* Return a random link local IP (in host byte order)
*/
uint32_t ipv4ll_random_ip(int seed)
{
unsigned tmp;
if (seed)
srand(seed);
else {
struct timeval tv;
gettimeofday(&tv, NULL);
srand(tv.tv_usec);
}
do {
tmp = rand();
tmp = tmp & IN_CLASSB_HOST;
} while (tmp > (IN_CLASSB_HOST - 0x0200));
return ((LINKLOCAL_ADDR + 0x0100) + tmp);
}
/**
* Return a random delay in range of zero to secs*1000
*/
guint ipv4ll_random_delay_ms(guint secs)
{
struct timeval tv;
guint tmp;
gettimeofday(&tv, NULL);
srand(tv.tv_usec);
tmp = rand();
return tmp % (secs * 1000);
}
int ipv4ll_send_arp_packet(uint8_t* source_eth, uint32_t source_ip,
uint32_t target_ip, int ifindex)
{
struct sockaddr_ll dest;
struct ether_arp p;
uint32_t ip_source;
uint32_t ip_target;
int fd, n;
fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_ARP));
if (fd < 0)
return -errno;
memset(&dest, 0, sizeof(dest));
memset(&p, 0, sizeof(p));
dest.sll_family = AF_PACKET;
dest.sll_protocol = htons(ETH_P_ARP);
dest.sll_ifindex = ifindex;
dest.sll_halen = ETH_ALEN;
memset(dest.sll_addr, 0xFF, ETH_ALEN);
if (bind(fd, (struct sockaddr *)&dest, sizeof(dest)) < 0) {
close(fd);
return -errno;
}
ip_source = htonl(source_ip);
ip_target = htonl(target_ip);
p.arp_hrd = htons(ARPHRD_ETHER);
p.arp_pro = htons(ETHERTYPE_IP);
p.arp_hln = ETH_ALEN;
p.arp_pln = 4;
p.arp_op = htons(ARPOP_REQUEST);
memcpy(&p.arp_sha, source_eth, ETH_ALEN);
memcpy(&p.arp_spa, &ip_source, sizeof(p.arp_spa));
memcpy(&p.arp_tpa, &ip_target, sizeof(p.arp_tpa));
n = sendto(fd, &p, sizeof(p), 0,
(struct sockaddr*) &dest, sizeof(dest));
if (n < 0)
n = -errno;
close(fd);
return n;
}
int ipv4ll_arp_socket(int ifindex)
{
int fd;
struct sockaddr_ll sock;
fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_ARP));
if (fd < 0)
return fd;
sock.sll_family = AF_PACKET;
sock.sll_protocol = htons(ETH_P_ARP);
sock.sll_ifindex = ifindex;
if (bind(fd, (struct sockaddr *) &sock, sizeof(sock)) != 0) {
close(fd);
return -errno;
}
return fd;
}

55
src/gdhcp/ipv4ll.h Normal file
View file

@ -0,0 +1,55 @@
/*
*
* IPV4 Local Link library with GLib integration
*
* Copyright (C) 2009-2010 Aldebaran Robotics. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifndef __G_IPV4LL_H
#define __G_IPV4LL_H
#include <glib.h>
#ifdef __cplusplus
extern "C" {
#endif
/* 169.254.0.0 */
#define LINKLOCAL_ADDR 0xa9fe0000
/* See RFC 3927 */
#define PROBE_WAIT 1
#define PROBE_NUM 3
#define PROBE_MIN 1
#define PROBE_MAX 2
#define ANNOUNCE_WAIT 2
#define ANNOUNCE_NUM 2
#define ANNOUNCE_INTERVAL 2
#define MAX_CONFLICTS 10
#define RATE_LIMIT_INTERVAL 60
#define DEFEND_INTERVAL 10
uint32_t ipv4ll_random_ip(int seed);
guint ipv4ll_random_delay_ms(guint secs);
int ipv4ll_send_arp_packet(uint8_t* source_eth, uint32_t source_ip,
uint32_t target_ip, int ifindex);
int ipv4ll_arp_socket(int ifindex);
#ifdef __cplusplus
}
#endif
#endif /* !IPV4LL_H_ */

896
src/gdhcp/server.c Normal file
View file

@ -0,0 +1,896 @@
/*
*
* DHCP Server library with GLib integration
*
* Copyright (C) 2009-2012 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <netpacket/packet.h>
#include <net/ethernet.h>
#include <net/if_arp.h>
#include <linux/if.h>
#include <linux/filter.h>
#include <glib.h>
#include "common.h"
/* 8 hours */
#define DEFAULT_DHCP_LEASE_SEC (8*60*60)
/* 5 minutes */
#define OFFER_TIME (5*60)
struct _GDHCPServer {
int ref_count;
GDHCPType type;
bool started;
int ifindex;
char *interface;
uint32_t start_ip;
uint32_t end_ip;
uint32_t server_nip;
uint32_t lease_seconds;
int listener_sockfd;
guint listener_watch;
GIOChannel *listener_channel;
GList *lease_list;
GHashTable *nip_lease_hash;
GHashTable *option_hash; /* Options send to client */
GDHCPSaveLeaseFunc save_lease_func;
GDHCPDebugFunc debug_func;
gpointer debug_data;
};
struct dhcp_lease {
time_t expire;
uint32_t lease_nip;
uint8_t lease_mac[ETH_ALEN];
};
static inline void debug(GDHCPServer *server, const char *format, ...)
{
char str[256];
va_list ap;
if (!server->debug_func)
return;
va_start(ap, format);
if (vsnprintf(str, sizeof(str), format, ap) > 0)
server->debug_func(str, server->debug_data);
va_end(ap);
}
static struct dhcp_lease *find_lease_by_mac(GDHCPServer *dhcp_server,
const uint8_t *mac)
{
GList *list;
for (list = dhcp_server->lease_list; list; list = list->next) {
struct dhcp_lease *lease = list->data;
if (memcmp(lease->lease_mac, mac, ETH_ALEN) == 0)
return lease;
}
return NULL;
}
static void remove_lease(GDHCPServer *dhcp_server, struct dhcp_lease *lease)
{
dhcp_server->lease_list =
g_list_remove(dhcp_server->lease_list, lease);
g_hash_table_remove(dhcp_server->nip_lease_hash,
GINT_TO_POINTER((int) lease->lease_nip));
g_free(lease);
}
/* Clear the old lease and create the new one */
static int get_lease(GDHCPServer *dhcp_server, uint32_t yiaddr,
const uint8_t *mac, struct dhcp_lease **lease)
{
struct dhcp_lease *lease_nip, *lease_mac;
if (yiaddr == 0)
return -ENXIO;
if (ntohl(yiaddr) < dhcp_server->start_ip)
return -ENXIO;
if (ntohl(yiaddr) > dhcp_server->end_ip)
return -ENXIO;
if (memcmp(mac, MAC_BCAST_ADDR, ETH_ALEN) == 0)
return -ENXIO;
if (memcmp(mac, MAC_ANY_ADDR, ETH_ALEN) == 0)
return -ENXIO;
lease_mac = find_lease_by_mac(dhcp_server, mac);
lease_nip = g_hash_table_lookup(dhcp_server->nip_lease_hash,
GINT_TO_POINTER((int) ntohl(yiaddr)));
debug(dhcp_server, "lease_mac %p lease_nip %p", lease_mac, lease_nip);
if (lease_nip) {
dhcp_server->lease_list =
g_list_remove(dhcp_server->lease_list,
lease_nip);
g_hash_table_remove(dhcp_server->nip_lease_hash,
GINT_TO_POINTER((int) ntohl(yiaddr)));
if (!lease_mac)
*lease = lease_nip;
else if (lease_nip != lease_mac) {
remove_lease(dhcp_server, lease_mac);
*lease = lease_nip;
} else
*lease = lease_nip;
return 0;
}
if (lease_mac) {
dhcp_server->lease_list =
g_list_remove(dhcp_server->lease_list,
lease_mac);
g_hash_table_remove(dhcp_server->nip_lease_hash,
GINT_TO_POINTER((int) lease_mac->lease_nip));
*lease = lease_mac;
return 0;
}
*lease = g_try_new0(struct dhcp_lease, 1);
if (!*lease)
return -ENOMEM;
return 0;
}
static gint compare_expire(gconstpointer a, gconstpointer b)
{
const struct dhcp_lease *lease1 = a;
const struct dhcp_lease *lease2 = b;
return lease2->expire - lease1->expire;
}
static struct dhcp_lease *add_lease(GDHCPServer *dhcp_server, uint32_t expire,
const uint8_t *chaddr, uint32_t yiaddr)
{
struct dhcp_lease *lease = NULL;
int ret;
ret = get_lease(dhcp_server, yiaddr, chaddr, &lease);
if (ret != 0)
return NULL;
memset(lease, 0, sizeof(*lease));
memcpy(lease->lease_mac, chaddr, ETH_ALEN);
lease->lease_nip = ntohl(yiaddr);
if (expire == 0)
lease->expire = time(NULL) + dhcp_server->lease_seconds;
else
lease->expire = expire;
dhcp_server->lease_list = g_list_insert_sorted(dhcp_server->lease_list,
lease, compare_expire);
g_hash_table_insert(dhcp_server->nip_lease_hash,
GINT_TO_POINTER((int) lease->lease_nip), lease);
return lease;
}
static struct dhcp_lease *find_lease_by_nip(GDHCPServer *dhcp_server,
uint32_t nip)
{
return g_hash_table_lookup(dhcp_server->nip_lease_hash,
GINT_TO_POINTER((int) nip));
}
/* Check if the IP is taken; if it is, add it to the lease table */
static bool arp_check(uint32_t nip, const uint8_t *safe_mac)
{
/* TODO: Add ARP checking */
return true;
}
static bool is_expired_lease(struct dhcp_lease *lease)
{
if (lease->expire < time(NULL))
return true;
return false;
}
static uint32_t find_free_or_expired_nip(GDHCPServer *dhcp_server,
const uint8_t *safe_mac)
{
uint32_t ip_addr;
struct dhcp_lease *lease;
GList *list;
ip_addr = dhcp_server->start_ip;
for (; ip_addr <= dhcp_server->end_ip; ip_addr++) {
/* e.g. 192.168.55.0 */
if ((ip_addr & 0xff) == 0)
continue;
/* e.g. 192.168.55.255 */
if ((ip_addr & 0xff) == 0xff)
continue;
lease = find_lease_by_nip(dhcp_server, ip_addr);
if (lease)
continue;
if (arp_check(htonl(ip_addr), safe_mac))
return ip_addr;
}
/* The last lease is the oldest one */
list = g_list_last(dhcp_server->lease_list);
if (!list)
return 0;
lease = list->data;
if (!lease)
return 0;
if (!is_expired_lease(lease))
return 0;
if (!arp_check(lease->lease_nip, safe_mac))
return 0;
return lease->lease_nip;
}
static void lease_set_expire(GDHCPServer *dhcp_server,
struct dhcp_lease *lease, uint32_t expire)
{
dhcp_server->lease_list = g_list_remove(dhcp_server->lease_list, lease);
lease->expire = expire;
dhcp_server->lease_list = g_list_insert_sorted(dhcp_server->lease_list,
lease, compare_expire);
}
static void destroy_lease_table(GDHCPServer *dhcp_server)
{
GList *list;
g_hash_table_destroy(dhcp_server->nip_lease_hash);
dhcp_server->nip_lease_hash = NULL;
for (list = dhcp_server->lease_list; list; list = list->next) {
struct dhcp_lease *lease = list->data;
g_free(lease);
}
g_list_free(dhcp_server->lease_list);
dhcp_server->lease_list = NULL;
}
static uint32_t get_interface_address(int index)
{
struct ifreq ifr;
int sk, err;
struct sockaddr_in *server_ip;
uint32_t ret = 0;
sk = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
if (sk < 0) {
perror("Open socket error");
return 0;
}
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_ifindex = index;
err = ioctl(sk, SIOCGIFNAME, &ifr);
if (err < 0) {
perror("Get interface name error");
goto done;
}
err = ioctl(sk, SIOCGIFADDR, &ifr);
if (err < 0) {
perror("Get ip address error");
goto done;
}
server_ip = (struct sockaddr_in *) &ifr.ifr_addr;
ret = server_ip->sin_addr.s_addr;
done:
close(sk);
return ret;
}
GDHCPServer *g_dhcp_server_new(GDHCPType type,
int ifindex, GDHCPServerError *error)
{
GDHCPServer *dhcp_server = NULL;
if (ifindex < 0) {
*error = G_DHCP_SERVER_ERROR_INVALID_INDEX;
return NULL;
}
dhcp_server = g_try_new0(GDHCPServer, 1);
if (!dhcp_server) {
*error = G_DHCP_SERVER_ERROR_NOMEM;
return NULL;
}
dhcp_server->interface = get_interface_name(ifindex);
if (!dhcp_server->interface) {
*error = G_DHCP_SERVER_ERROR_INTERFACE_UNAVAILABLE;
goto error;
}
if (!interface_is_up(ifindex)) {
*error = G_DHCP_SERVER_ERROR_INTERFACE_DOWN;
goto error;
}
dhcp_server->server_nip = get_interface_address(ifindex);
if (dhcp_server->server_nip == 0) {
*error = G_DHCP_SERVER_ERROR_IP_ADDRESS_INVALID;
goto error;
}
dhcp_server->nip_lease_hash = g_hash_table_new_full(g_direct_hash,
g_direct_equal, NULL, NULL);
dhcp_server->option_hash = g_hash_table_new_full(g_direct_hash,
g_direct_equal, NULL, NULL);
dhcp_server->started = FALSE;
/* All the leases have the same fixed lease time,
* do not support DHCP_LEASE_TIME option from client.
*/
dhcp_server->lease_seconds = DEFAULT_DHCP_LEASE_SEC;
dhcp_server->type = type;
dhcp_server->ref_count = 1;
dhcp_server->ifindex = ifindex;
dhcp_server->listener_sockfd = -1;
dhcp_server->listener_watch = -1;
dhcp_server->listener_channel = NULL;
dhcp_server->save_lease_func = NULL;
dhcp_server->debug_func = NULL;
dhcp_server->debug_data = NULL;
*error = G_DHCP_SERVER_ERROR_NONE;
return dhcp_server;
error:
g_free(dhcp_server->interface);
g_free(dhcp_server);
return NULL;
}
static uint8_t check_packet_type(struct dhcp_packet *packet)
{
uint8_t *type;
if (packet->hlen != ETH_ALEN)
return 0;
if (packet->op != BOOTREQUEST)
return 0;
type = dhcp_get_option(packet, DHCP_MESSAGE_TYPE);
if (!type)
return 0;
if (*type < DHCP_MINTYPE)
return 0;
if (*type > DHCP_MAXTYPE)
return 0;
return *type;
}
static void init_packet(GDHCPServer *dhcp_server, struct dhcp_packet *packet,
struct dhcp_packet *client_packet, char type)
{
/* Sets op, htype, hlen, cookie fields
* and adds DHCP_MESSAGE_TYPE option */
dhcp_init_header(packet, type);
packet->xid = client_packet->xid;
memcpy(packet->chaddr, client_packet->chaddr,
sizeof(client_packet->chaddr));
packet->flags = client_packet->flags;
packet->gateway_nip = client_packet->gateway_nip;
packet->ciaddr = client_packet->ciaddr;
dhcp_add_option_uint32(packet, DHCP_SERVER_ID,
dhcp_server->server_nip);
}
static void add_option(gpointer key, gpointer value, gpointer user_data)
{
const char *option_value = value;
uint8_t option_code = GPOINTER_TO_INT(key);
struct in_addr nip;
struct dhcp_packet *packet = user_data;
if (!option_value)
return;
switch (option_code) {
case G_DHCP_SUBNET:
case G_DHCP_ROUTER:
case G_DHCP_DNS_SERVER:
if (inet_aton(option_value, &nip) == 0)
return;
dhcp_add_option_uint32(packet, (uint8_t) option_code,
ntohl(nip.s_addr));
break;
default:
return;
}
}
static void add_server_options(GDHCPServer *dhcp_server,
struct dhcp_packet *packet)
{
g_hash_table_foreach(dhcp_server->option_hash,
add_option, packet);
}
static bool check_requested_nip(GDHCPServer *dhcp_server,
uint32_t requested_nip)
{
struct dhcp_lease *lease;
if (requested_nip == 0)
return false;
if (requested_nip < dhcp_server->start_ip)
return false;
if (requested_nip > dhcp_server->end_ip)
return false;
lease = find_lease_by_nip(dhcp_server, requested_nip);
if (!lease)
return true;
if (!is_expired_lease(lease))
return false;
return true;
}
static void send_packet_to_client(GDHCPServer *dhcp_server,
struct dhcp_packet *dhcp_pkt)
{
const uint8_t *chaddr;
uint32_t ciaddr;
if ((dhcp_pkt->flags & htons(BROADCAST_FLAG))
|| dhcp_pkt->ciaddr == 0) {
debug(dhcp_server, "Broadcasting packet to client");
ciaddr = INADDR_BROADCAST;
chaddr = MAC_BCAST_ADDR;
} else {
debug(dhcp_server, "Unicasting packet to client ciaddr");
ciaddr = dhcp_pkt->ciaddr;
chaddr = dhcp_pkt->chaddr;
}
dhcp_send_raw_packet(dhcp_pkt,
dhcp_server->server_nip, SERVER_PORT,
ciaddr, CLIENT_PORT, chaddr,
dhcp_server->ifindex);
}
static void send_offer(GDHCPServer *dhcp_server,
struct dhcp_packet *client_packet,
struct dhcp_lease *lease,
uint32_t requested_nip)
{
struct dhcp_packet packet;
struct in_addr addr;
init_packet(dhcp_server, &packet, client_packet, DHCPOFFER);
if (lease)
packet.yiaddr = htonl(lease->lease_nip);
else if (check_requested_nip(dhcp_server, requested_nip))
packet.yiaddr = htonl(requested_nip);
else
packet.yiaddr = htonl(find_free_or_expired_nip(
dhcp_server, client_packet->chaddr));
debug(dhcp_server, "find yiaddr %u", packet.yiaddr);
if (!packet.yiaddr) {
debug(dhcp_server, "Err: Can not found lease and send offer");
return;
}
lease = add_lease(dhcp_server, OFFER_TIME,
packet.chaddr, packet.yiaddr);
if (!lease) {
debug(dhcp_server,
"Err: No free IP addresses. OFFER abandoned");
return;
}
dhcp_add_option_uint32(&packet, DHCP_LEASE_TIME,
dhcp_server->lease_seconds);
add_server_options(dhcp_server, &packet);
addr.s_addr = packet.yiaddr;
debug(dhcp_server, "Sending OFFER of %s", inet_ntoa(addr));
send_packet_to_client(dhcp_server, &packet);
}
static void save_lease(GDHCPServer *dhcp_server)
{
GList *list;
if (!dhcp_server->save_lease_func)
return;
for (list = dhcp_server->lease_list; list; list = list->next) {
struct dhcp_lease *lease = list->data;
dhcp_server->save_lease_func(lease->lease_mac,
lease->lease_nip, lease->expire);
}
}
static void send_ACK(GDHCPServer *dhcp_server,
struct dhcp_packet *client_packet, uint32_t dest)
{
struct dhcp_packet packet;
uint32_t lease_time_sec;
struct in_addr addr;
init_packet(dhcp_server, &packet, client_packet, DHCPACK);
packet.yiaddr = htonl(dest);
lease_time_sec = dhcp_server->lease_seconds;
dhcp_add_option_uint32(&packet, DHCP_LEASE_TIME, lease_time_sec);
add_server_options(dhcp_server, &packet);
addr.s_addr = htonl(dest);
debug(dhcp_server, "Sending ACK to %s", inet_ntoa(addr));
send_packet_to_client(dhcp_server, &packet);
add_lease(dhcp_server, 0, packet.chaddr, packet.yiaddr);
}
static void send_NAK(GDHCPServer *dhcp_server,
struct dhcp_packet *client_packet)
{
struct dhcp_packet packet;
init_packet(dhcp_server, &packet, client_packet, DHCPNAK);
debug(dhcp_server, "Sending NAK");
dhcp_send_raw_packet(&packet,
dhcp_server->server_nip, SERVER_PORT,
INADDR_BROADCAST, CLIENT_PORT, MAC_BCAST_ADDR,
dhcp_server->ifindex);
}
static void send_inform(GDHCPServer *dhcp_server,
struct dhcp_packet *client_packet)
{
struct dhcp_packet packet;
init_packet(dhcp_server, &packet, client_packet, DHCPACK);
add_server_options(dhcp_server, &packet);
send_packet_to_client(dhcp_server, &packet);
}
static gboolean listener_event(GIOChannel *channel, GIOCondition condition,
gpointer user_data)
{
GDHCPServer *dhcp_server = user_data;
struct dhcp_packet packet;
struct dhcp_lease *lease;
uint32_t requested_nip = 0;
uint8_t type, *server_id_option, *request_ip_option;
int re;
if (condition & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) {
dhcp_server->listener_watch = 0;
return FALSE;
}
re = dhcp_recv_l3_packet(&packet, dhcp_server->listener_sockfd);
if (re < 0)
return TRUE;
type = check_packet_type(&packet);
if (type == 0)
return TRUE;
server_id_option = dhcp_get_option(&packet, DHCP_SERVER_ID);
if (server_id_option) {
uint32_t server_nid = get_be32(server_id_option);
if (server_nid != dhcp_server->server_nip)
return TRUE;
}
request_ip_option = dhcp_get_option(&packet, DHCP_REQUESTED_IP);
if (request_ip_option)
requested_nip = get_be32(request_ip_option);
lease = find_lease_by_mac(dhcp_server, packet.chaddr);
switch (type) {
case DHCPDISCOVER:
debug(dhcp_server, "Received DISCOVER");
send_offer(dhcp_server, &packet, lease, requested_nip);
break;
case DHCPREQUEST:
debug(dhcp_server, "Received REQUEST NIP %d",
requested_nip);
if (requested_nip == 0) {
requested_nip = packet.ciaddr;
if (requested_nip == 0)
break;
}
if (lease && requested_nip == lease->lease_nip) {
debug(dhcp_server, "Sending ACK");
send_ACK(dhcp_server, &packet,
lease->lease_nip);
break;
}
if (server_id_option || !lease) {
debug(dhcp_server, "Sending NAK");
send_NAK(dhcp_server, &packet);
}
break;
case DHCPDECLINE:
debug(dhcp_server, "Received DECLINE");
if (!server_id_option)
break;
if (!request_ip_option)
break;
if (!lease)
break;
if (requested_nip == lease->lease_nip)
remove_lease(dhcp_server, lease);
break;
case DHCPRELEASE:
debug(dhcp_server, "Received RELEASE");
if (!server_id_option)
break;
if (!lease)
break;
if (packet.ciaddr == lease->lease_nip)
lease_set_expire(dhcp_server, lease,
time(NULL));
break;
case DHCPINFORM:
debug(dhcp_server, "Received INFORM");
send_inform(dhcp_server, &packet);
break;
}
return TRUE;
}
/* Caller need to load leases before call it */
int g_dhcp_server_start(GDHCPServer *dhcp_server)
{
GIOChannel *listener_channel;
int listener_sockfd;
if (dhcp_server->started)
return 0;
listener_sockfd = dhcp_l3_socket(SERVER_PORT,
dhcp_server->interface, AF_INET);
if (listener_sockfd < 0)
return -EIO;
listener_channel = g_io_channel_unix_new(listener_sockfd);
if (!listener_channel) {
close(listener_sockfd);
return -EIO;
}
dhcp_server->listener_sockfd = listener_sockfd;
dhcp_server->listener_channel = listener_channel;
g_io_channel_set_close_on_unref(listener_channel, TRUE);
dhcp_server->listener_watch =
g_io_add_watch_full(listener_channel, G_PRIORITY_HIGH,
G_IO_IN | G_IO_NVAL | G_IO_ERR | G_IO_HUP,
listener_event, dhcp_server,
NULL);
g_io_channel_unref(dhcp_server->listener_channel);
dhcp_server->started = TRUE;
return 0;
}
int g_dhcp_server_set_option(GDHCPServer *dhcp_server,
unsigned char option_code, const char *option_value)
{
struct in_addr nip;
if (!option_value)
return -EINVAL;
debug(dhcp_server, "option_code %d option_value %s",
option_code, option_value);
switch (option_code) {
case G_DHCP_SUBNET:
case G_DHCP_ROUTER:
case G_DHCP_DNS_SERVER:
if (inet_aton(option_value, &nip) == 0)
return -ENXIO;
break;
default:
return -EINVAL;
}
g_hash_table_replace(dhcp_server->option_hash,
GINT_TO_POINTER((int) option_code),
(gpointer) option_value);
return 0;
}
void g_dhcp_server_set_save_lease(GDHCPServer *dhcp_server,
GDHCPSaveLeaseFunc func, gpointer user_data)
{
if (!dhcp_server)
return;
dhcp_server->save_lease_func = func;
}
GDHCPServer *g_dhcp_server_ref(GDHCPServer *dhcp_server)
{
if (!dhcp_server)
return NULL;
__sync_fetch_and_add(&dhcp_server->ref_count, 1);
return dhcp_server;
}
void g_dhcp_server_stop(GDHCPServer *dhcp_server)
{
/* Save leases, before stop; load them before start */
save_lease(dhcp_server);
if (dhcp_server->listener_watch > 0) {
g_source_remove(dhcp_server->listener_watch);
dhcp_server->listener_watch = 0;
}
dhcp_server->listener_channel = NULL;
dhcp_server->started = FALSE;
}
void g_dhcp_server_unref(GDHCPServer *dhcp_server)
{
if (!dhcp_server)
return;
if (__sync_fetch_and_sub(&dhcp_server->ref_count, 1) != 1)
return;
g_dhcp_server_stop(dhcp_server);
g_hash_table_destroy(dhcp_server->option_hash);
destroy_lease_table(dhcp_server);
g_free(dhcp_server->interface);
g_free(dhcp_server);
}
int g_dhcp_server_set_ip_range(GDHCPServer *dhcp_server,
const char *start_ip, const char *end_ip)
{
struct in_addr _host_addr;
if (inet_aton(start_ip, &_host_addr) == 0)
return -ENXIO;
dhcp_server->start_ip = ntohl(_host_addr.s_addr);
if (inet_aton(end_ip, &_host_addr) == 0)
return -ENXIO;
dhcp_server->end_ip = ntohl(_host_addr.s_addr);
return 0;
}
void g_dhcp_server_set_lease_time(GDHCPServer *dhcp_server,
unsigned int lease_time)
{
if (!dhcp_server)
return;
dhcp_server->lease_seconds = lease_time;
}
void g_dhcp_server_set_debug(GDHCPServer *dhcp_server,
GDHCPDebugFunc func, gpointer user_data)
{
if (!dhcp_server)
return;
dhcp_server->debug_func = func;
dhcp_server->debug_data = user_data;
}

163
src/gdhcp/unaligned.h Normal file
View file

@ -0,0 +1,163 @@
/*
*
* Connection Manager
*
* Copyright (C) 2012 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <endian.h>
#include <byteswap.h>
#define get_unaligned(ptr) \
({ \
struct __attribute__((packed)) { \
typeof(*(ptr)) __v; \
} *__p = (typeof(__p)) (ptr); \
__p->__v; \
})
#define put_unaligned(val, ptr) \
do { \
struct __attribute__((packed)) { \
typeof(*(ptr)) __v; \
} *__p = (typeof(__p)) (ptr); \
__p->__v = (val); \
} while(0)
#if __BYTE_ORDER == __LITTLE_ENDIAN
static inline uint64_t get_le64(const void *ptr)
{
return get_unaligned((const uint64_t *) ptr);
}
static inline uint64_t get_be64(const void *ptr)
{
return bswap_64(get_unaligned((const uint64_t *) ptr));
}
static inline uint32_t get_le32(const void *ptr)
{
return get_unaligned((const uint32_t *) ptr);
}
static inline uint32_t get_be32(const void *ptr)
{
return bswap_32(get_unaligned((const uint32_t *) ptr));
}
static inline uint16_t get_le16(const void *ptr)
{
return get_unaligned((const uint16_t *) ptr);
}
static inline uint16_t get_be16(const void *ptr)
{
return bswap_16(get_unaligned((const uint16_t *) ptr));
}
static inline void put_be16(uint16_t val, void *ptr)
{
put_unaligned(bswap_16(val), (uint16_t *) ptr);
}
static inline void put_be32(uint32_t val, void *ptr)
{
put_unaligned(bswap_32(val), (uint32_t *) ptr);
}
static inline void put_le16(uint16_t val, void *ptr)
{
put_unaligned(val, (uint16_t *) ptr);
}
static inline void put_le32(uint32_t val, void *ptr)
{
put_unaligned(val, (uint32_t *) ptr);
}
static inline void put_be64(uint64_t val, void *ptr)
{
put_unaligned(bswap_64(val), (uint64_t *) ptr);
}
static inline void put_le64(uint64_t val, void *ptr)
{
put_unaligned(val, (uint64_t *) ptr);
}
#elif __BYTE_ORDER == __BIG_ENDIAN
static inline uint64_t get_le64(const void *ptr)
{
return bswap_64(get_unaligned((const uint64_t *) ptr));
}
static inline uint64_t get_be64(const void *ptr)
{
return get_unaligned((const uint64_t *) ptr);
}
static inline uint32_t get_le32(const void *ptr)
{
return bswap_32(get_unaligned((const uint32_t *) ptr));
}
static inline uint32_t get_be32(const void *ptr)
{
return get_unaligned((const uint32_t *) ptr);
}
static inline uint16_t get_le16(const void *ptr)
{
return bswap_16(get_unaligned((const uint16_t *) ptr));
}
static inline uint16_t get_be16(const void *ptr)
{
return get_unaligned((const uint16_t *) ptr);
}
static inline void put_be16(uint16_t val, void *ptr)
{
put_unaligned(val, (uint16_t *) ptr);
}
static inline void put_be32(uint32_t val, void *ptr)
{
put_unaligned(val, (uint32_t *) ptr);
}
static inline void put_le16(uint16_t val, void *ptr)
{
put_unaligned(bswap_16(val), (uint16_t *) ptr);
}
static inline void put_le32(uint32_t val, void *ptr)
{
put_unaligned(bswap_32(val), (uint32_t *) ptr);
}
static inline void put_be64(uint64_t val, void *ptr)
{
put_unaligned(val, (uint64_t *) ptr);
}
static inline void put_le64(uint64_t val, void *ptr)
{
put_unaligned(bswap_64(val), (uint64_t *) ptr);
}
#else
#error "Unknown byte order"
#endif

936
src/miracle-dhcp.c Normal file
View file

@ -0,0 +1,936 @@
/*
* MiracleCast - Wifi-Display/Miracast Implementation
*
* Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Small DHCP Client/Server
* Wifi-P2P requires us to use DHCP to set up a private P2P network. As all
* DHCP daemons available have horrible interfaces for ad-hoc setups, we have
* this small replacement for all DHCP operations. Once sd-dhcp is public, we
* will switch to it instead of this helper binary. However, that also requires
* DHCP-server support in sd-dhcp.
*
* This program implements a DHCP server and daemon. See --help for usage
* information. We build on gdhcp from connman as the underlying DHCP protocol
* implementation. To configure network devices, we actually invoke the "ip"
* binary.
*
* Note that this is a gross hack! We don't intend to provide a fully functional
* DHCP server or client here. This is only a replacement for the current lack
* of Wifi-P2P support in common network managers. Once they gain proper
* support, we will drop this helper!
*
* The "ip" invokation is quite fragile and ugly. However, performing these
* steps directly involves netlink operations and more. As no-one came up with
* patches, yet, we keep the hack. To anyone trying to fix it: Please, spend
* this time hacking on NetworkManager, connman and friends instead! If they
* gain Wifi-P2P support, this whole thing will get trashed.
*/
#define LOG_SUBSYSTEM "dhcp"
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <glib.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <sys/signalfd.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include "gdhcp/gdhcp.h"
#include "shl_log.h"
static const char *arg_netdev;
static const char *arg_ip_binary = "/bin/ip";
static bool arg_server;
static char arg_local[INET_ADDRSTRLEN];
static char arg_gateway[INET_ADDRSTRLEN];
static char arg_dns[INET_ADDRSTRLEN];
static char arg_subnet[INET_ADDRSTRLEN];
static char arg_from[INET_ADDRSTRLEN];
static char arg_to[INET_ADDRSTRLEN];
static int arg_comm = -1;
struct manager {
int ifindex;
GMainLoop *loop;
int sfd;
GIOChannel *sfd_chan;
guint sfd_id;
GDHCPClient *client;
char *client_addr;
GDHCPServer *server;
char *server_addr;
};
/*
* We send prefixed messages via @comm. You should use a packet-based
* socket-type so boundaries are preserved. Following packets are sent:
* sent on local lease:
* L:<addr> # local iface addr
* S:<addr> # subnet mask
* D:<addr> # primary DNS server
* G:<addr> # primary gateway
* sent on remote lease:
* R:<addr> # addr given to remote device
*/
static void write_comm(const void *msg, size_t size)
{
static bool warned;
int r;
if (arg_comm < 0)
return;
r = send(arg_comm, msg, size, MSG_NOSIGNAL);
if (r < 0 && !warned) {
warned = true;
arg_comm = -1;
log_error("cannot write to comm-socket, disabling it: %m");
}
}
static void writef_comm(const void *format, ...)
{
va_list args;
char *msg;
int r;
va_start(args, format);
r = vasprintf(&msg, format, args);
va_end(args);
if (r < 0)
return log_vENOMEM();
write_comm(msg, r);
free(msg);
}
static int flush_if_addr(void)
{
char *argv[64];
int i, r;
pid_t pid, rp;
sigset_t mask;
pid = fork();
if (pid < 0) {
return log_ERRNO();
} else if (!pid) {
/* child */
sigemptyset(&mask);
sigprocmask(SIG_SETMASK, &mask, NULL);
/* redirect stdout to stderr */
dup2(2, 1);
i = 0;
argv[i++] = (char*)arg_ip_binary;
argv[i++] = "addr";
argv[i++] = "flush";
argv[i++] = "dev";
argv[i++] = (char*)arg_netdev;
argv[i] = NULL;
execve(argv[0], argv, environ);
_exit(1);
}
log_info("flushing local if-addr");
rp = waitpid(pid, &r, 0);
if (rp != pid) {
log_error("cannot flush local if-addr via '%s'",
arg_ip_binary);
return -EFAULT;
} else if (!WIFEXITED(r)) {
log_error("flushing local if-addr via '%s' failed",
arg_ip_binary);
return -EFAULT;
} else if (WEXITSTATUS(r)) {
log_error("flushing local if-addr via '%s' failed with: %d",
arg_ip_binary, WEXITSTATUS(r));
return -EFAULT;
}
log_debug("successfully flushed local if-addr via %s",
arg_ip_binary);
return 0;
}
static int add_if_addr(const char *addr)
{
char *argv[64];
int i, r;
pid_t pid, rp;
sigset_t mask;
pid = fork();
if (pid < 0) {
return log_ERRNO();
} else if (!pid) {
/* child */
sigemptyset(&mask);
sigprocmask(SIG_SETMASK, &mask, NULL);
/* redirect stdout to stderr */
dup2(2, 1);
i = 0;
argv[i++] = (char*)arg_ip_binary;
argv[i++] = "addr";
argv[i++] = "add";
argv[i++] = (char*)addr;
argv[i++] = "dev";
argv[i++] = (char*)arg_netdev;
argv[i] = NULL;
execve(argv[0], argv, environ);
_exit(1);
}
log_info("adding local if-addr %s", addr);
rp = waitpid(pid, &r, 0);
if (rp != pid) {
log_error("cannot set local if-addr %s via '%s'",
addr, arg_ip_binary);
return -EFAULT;
} else if (!WIFEXITED(r)) {
log_error("setting local if-addr %s via '%s' failed",
addr, arg_ip_binary);
return -EFAULT;
} else if (WEXITSTATUS(r)) {
log_error("setting local if-addr %s via '%s' failed with: %d",
addr, arg_ip_binary, WEXITSTATUS(r));
return -EFAULT;
}
log_debug("successfully set local if-addr %s via %s",
addr, arg_ip_binary);
return 0;
}
int if_name_to_index(const char *name)
{
struct ifreq ifr;
int fd, r;
if (strlen(name) > sizeof(ifr.ifr_name))
return -EINVAL;
fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
if (fd < 0)
return -errno;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
r = ioctl(fd, SIOCGIFINDEX, &ifr);
if (r < 0)
r = -errno;
else
r = ifr.ifr_ifindex;
close(fd);
return r;
}
static void sig_dummy(int sig)
{
}
static void client_lease_fn(GDHCPClient *client, gpointer data)
{
struct manager *m = data;
char *addr = NULL, *a, *subnet = NULL, *gateway = NULL, *dns = NULL;
GList *l;
int r;
log_info("lease available");
addr = g_dhcp_client_get_address(client);
log_info("lease: address: %s", addr);
l = g_dhcp_client_get_option(client, G_DHCP_SUBNET);
for ( ; l; l = l->next) {
subnet = subnet ? : (char*)l->data;
log_info("lease: subnet: %s", (char*)l->data);
}
l = g_dhcp_client_get_option(client, G_DHCP_DNS_SERVER);
for ( ; l; l = l->next) {
dns = dns ? : (char*)l->data;
log_info("lease: dns-server: %s", (char*)l->data);
}
l = g_dhcp_client_get_option(client, G_DHCP_ROUTER);
for ( ; l; l = l->next) {
gateway = gateway ? : (char*)l->data;
log_info("lease: router: %s", (char*)l->data);
}
if (!addr) {
log_error("lease without IP address");
goto error;
}
if (!subnet) {
log_warning("lease without subnet mask, using 24");
subnet = "24";
}
r = asprintf(&a, "%s/%s", addr, subnet);
if (r < 0) {
log_vENOMEM();
goto error;
}
if (m->client_addr && !strcmp(m->client_addr, a)) {
log_info("given address already set");
free(a);
} else {
free(m->client_addr);
m->client_addr = a;
r = flush_if_addr();
if (r < 0) {
log_error("cannot flush addr on local interface %s",
arg_netdev);
goto error;
}
r = add_if_addr(m->client_addr);
if (r < 0) {
log_error("cannot set parameters on local interface %s",
arg_netdev);
goto error;
}
writef_comm("L:%s", addr);
writef_comm("S:%s", subnet);
if (dns)
writef_comm("D:%s", dns);
if (gateway)
writef_comm("G:%s", gateway);
}
g_free(addr);
return;
error:
g_free(addr);
g_main_loop_quit(m->loop);
}
static void client_no_lease_fn(GDHCPClient *client, gpointer data)
{
struct manager *m = data;
log_error("no lease available");
g_main_loop_quit(m->loop);
}
static void server_log_fn(const char *str, void *data)
{
log_format(NULL, 0, NULL, "gdhcp", LOG_DEBUG, "%s", str);
}
static gboolean manager_signal_fn(GIOChannel *chan, GIOCondition mask,
gpointer data)
{
struct manager *m = data;
ssize_t l;
struct signalfd_siginfo info;
if (mask & (G_IO_HUP | G_IO_ERR)) {
log_vEPIPE();
g_main_loop_quit(m->loop);
return FALSE;
}
l = read(m->sfd, &info, sizeof(info));
if (l < 0) {
log_vERRNO();
g_main_loop_quit(m->loop);
return FALSE;
} else if (l != sizeof(info)) {
log_vEFAULT();
return TRUE;
}
log_notice("received signal %d: %s",
info.ssi_signo, strsignal(info.ssi_signo));
g_main_loop_quit(m->loop);
return FALSE;
}
static void manager_free(struct manager *m)
{
if (!m)
return;
if (!arg_server) {
if (m->client) {
g_dhcp_client_stop(m->client);
if (m->client_addr) {
flush_if_addr();
free(m->client_addr);
}
g_dhcp_client_unref(m->client);
}
} else {
if (m->server) {
g_dhcp_server_stop(m->server);
g_dhcp_server_unref(m->server);
}
if (m->server_addr) {
flush_if_addr();
free(m->server_addr);
}
}
if (m->sfd >= 0) {
g_source_remove(m->sfd_id);
g_io_channel_unref(m->sfd_chan);
close(m->sfd);
}
if (m->loop)
g_main_loop_unref(m->loop);
free(m);
}
static int manager_new(struct manager **out)
{
static const int sigs[] = {
SIGINT,
SIGTERM,
SIGQUIT,
SIGHUP,
SIGPIPE,
0
};
int r, i;
sigset_t mask;
struct sigaction sig;
GDHCPClientError cerr;
GDHCPServerError serr;
struct manager *m;
m = calloc(1, sizeof(*m));
if (!m)
return log_ENOMEM();
m->sfd = -1;
if (geteuid())
log_warning("not running as uid=0, dhcp might not work");
m->ifindex = if_name_to_index(arg_netdev);
if (m->ifindex < 0) {
r = -EINVAL;
log_error("cannot find interface %s (%d)",
arg_netdev, m->ifindex);
goto error;
}
m->loop = g_main_loop_new(NULL, FALSE);
sigemptyset(&mask);
memset(&sig, 0, sizeof(sig));
sig.sa_handler = sig_dummy;
sig.sa_flags = SA_RESTART;
for (i = 0; sigs[i]; ++i) {
sigaddset(&mask, sigs[i]);
r = sigaction(sigs[i], &sig, NULL);
if (r < 0) {
r = log_ERRNO();
goto error;
}
}
r = sigprocmask(SIG_BLOCK, &mask, NULL);
if (r < 0) {
r = log_ERRNO();
goto error;
}
m->sfd = signalfd(-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK);
if (m->sfd < 0) {
r = log_ERRNO();
goto error;
}
m->sfd_chan = g_io_channel_unix_new(m->sfd);
m->sfd_id = g_io_add_watch(m->sfd_chan,
G_IO_HUP | G_IO_ERR | G_IO_IN,
manager_signal_fn,
m);
if (!arg_server) {
m->client = g_dhcp_client_new(G_DHCP_IPV4, m->ifindex,
&cerr);
if (!m->client) {
r = -EINVAL;
switch (cerr) {
case G_DHCP_CLIENT_ERROR_INTERFACE_UNAVAILABLE:
log_error("cannot create GDHCP client: interface %s unavailable",
arg_netdev);
break;
case G_DHCP_CLIENT_ERROR_INTERFACE_IN_USE:
log_error("cannot create GDHCP client: interface %s in use",
arg_netdev);
break;
case G_DHCP_CLIENT_ERROR_INTERFACE_DOWN:
log_error("cannot create GDHCP client: interface %s down",
arg_netdev);
break;
case G_DHCP_CLIENT_ERROR_NOMEM:
r = log_ENOMEM();
break;
case G_DHCP_CLIENT_ERROR_INVALID_INDEX:
log_error("cannot create GDHCP client: invalid interface %s",
arg_netdev);
break;
case G_DHCP_CLIENT_ERROR_INVALID_OPTION:
log_error("cannot create GDHCP client: invalid options");
break;
default:
log_error("cannot create GDHCP client (%d)",
cerr);
break;
}
goto error;
}
g_dhcp_client_set_send(m->client, G_DHCP_HOST_NAME,
"<hostname>");
g_dhcp_client_set_request(m->client, G_DHCP_SUBNET);
g_dhcp_client_set_request(m->client, G_DHCP_DNS_SERVER);
g_dhcp_client_set_request(m->client, G_DHCP_ROUTER);
g_dhcp_client_register_event(m->client,
G_DHCP_CLIENT_EVENT_LEASE_AVAILABLE,
client_lease_fn, m);
g_dhcp_client_register_event(m->client,
G_DHCP_CLIENT_EVENT_NO_LEASE,
client_no_lease_fn, m);
} else {
r = asprintf(&m->server_addr, "%s/%s", arg_local, arg_subnet);
if (r < 0) {
r = log_ENOMEM();
goto error;
}
r = flush_if_addr();
if (r < 0) {
log_error("cannot flush addr on local interface %s",
arg_netdev);
goto error;
}
r = add_if_addr(m->server_addr);
if (r < 0) {
log_error("cannot set parameters on local interface %s",
arg_netdev);
goto error;
}
m->server = g_dhcp_server_new(G_DHCP_IPV4, m->ifindex,
&serr);
if (!m->server) {
r = -EINVAL;
switch(serr) {
case G_DHCP_SERVER_ERROR_INTERFACE_UNAVAILABLE:
log_error("cannot create GDHCP server: interface %s unavailable",
arg_netdev);
break;
case G_DHCP_SERVER_ERROR_INTERFACE_IN_USE:
log_error("cannot create GDHCP server: interface %s in use",
arg_netdev);
break;
case G_DHCP_SERVER_ERROR_INTERFACE_DOWN:
log_error("cannot create GDHCP server: interface %s down",
arg_netdev);
break;
case G_DHCP_SERVER_ERROR_NOMEM:
r = log_ENOMEM();
break;
case G_DHCP_SERVER_ERROR_INVALID_INDEX:
log_error("cannot create GDHCP server: invalid interface %s",
arg_netdev);
break;
case G_DHCP_SERVER_ERROR_INVALID_OPTION:
log_error("cannot create GDHCP server: invalid options");
break;
case G_DHCP_SERVER_ERROR_IP_ADDRESS_INVALID:
log_error("cannot create GDHCP server: invalid ip address");
break;
default:
log_error("cannot create GDHCP server (%d)",
serr);
break;
}
goto error;
}
g_dhcp_server_set_debug(m->server, server_log_fn, NULL);
g_dhcp_server_set_lease_time(m->server, 60 * 60);
r = g_dhcp_server_set_option(m->server, G_DHCP_SUBNET,
arg_subnet);
if (r != 0) {
log_vERR(r);
goto error;
}
r = g_dhcp_server_set_option(m->server, G_DHCP_ROUTER,
arg_gateway);
if (r != 0) {
log_vERR(r);
goto error;
}
r = g_dhcp_server_set_option(m->server, G_DHCP_DNS_SERVER,
arg_dns);
if (r != 0) {
log_vERR(r);
goto error;
}
r = g_dhcp_server_set_ip_range(m->server, arg_from, arg_to);
if (r != 0) {
log_vERR(r);
goto error;
}
}
*out = m;
return 0;
error:
manager_free(m);
return r;
}
static int manager_run(struct manager *m)
{
int r;
if (!arg_server) {
log_info("running dhcp client on %s via '%s'",
arg_netdev, arg_ip_binary);
r = g_dhcp_client_start(m->client, NULL);
if (r != 0) {
log_error("cannot start DHCP client: %d", r);
return -EFAULT;
}
} else {
log_info("running dhcp server on %s via '%s'",
arg_netdev, arg_ip_binary);
r = g_dhcp_server_start(m->server);
if (r != 0) {
log_error("cannot start DHCP server: %d", r);
return -EFAULT;
}
}
g_main_loop_run(m->loop);
return 0;
}
static int make_address(char *buf, const char *prefix, const char *suffix,
const char *name)
{
int r;
struct in_addr addr;
if (!prefix)
prefix = "192.168.77";
r = snprintf(buf, INET_ADDRSTRLEN, "%s.%s", prefix, suffix);
if (r >= INET_ADDRSTRLEN)
goto error;
r = inet_pton(AF_INET, buf, &addr);
if (r != 1)
goto error;
inet_ntop(AF_INET, &addr, buf, INET_ADDRSTRLEN);
buf[INET_ADDRSTRLEN] = 0;
return 0;
error:
log_error("Invalid address --%s=%s.%s (prefix: %s suffix: %s)",
name, prefix, suffix, prefix, suffix);
return -EINVAL;
}
static int make_subnet(char *buf, const char *subnet)
{
int r;
struct in_addr addr;
r = inet_pton(AF_INET, subnet, &addr);
if (r != 1)
goto error;
inet_ntop(AF_INET, &addr, buf, INET_ADDRSTRLEN);
buf[INET_ADDRSTRLEN] = 0;
return 0;
error:
log_error("Invalid address --subnet=%s", subnet);
return -EINVAL;
}
static int help(void)
{
printf("%s [OPTIONS...] ...\n\n"
"Ad-hoc IPv4 DHCP Server/Client.\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --log-level <lvl> Maximum level for log messages\n"
" --log-time Prefix log-messages with timestamp\n"
"\n"
" --netdev <dev> Network device to run on\n"
" --ip-binary <path> Path to 'ip' binary [default: /bin/ip]\n"
" --comm-fd <int> Comm-socket FD passed through execve()\n"
"\n"
"Server Options:\n"
" --server Run as DHCP server instead of client\n"
" --prefix <net-prefix> Network prefix [default: 192.168.77]\n"
" --local <suffix> Local address suffix [default: 1]\n"
" --gateway <suffix> Gateway suffix [default: 1]\n"
" --dns <suffix> DNS suffix [default: 1]\n"
" --subnet <mask> Subnet mask [default: 255.255.255.0]\n"
" --from <suffix> Start address [default: 100]\n"
" --to <suffix> End address [default: 199]\n"
, program_invocation_short_name);
return 0;
}
static int parse_argv(int argc, char *argv[])
{
enum {
ARG_VERSION = 0x100,
ARG_LOG_LEVEL,
ARG_LOG_TIME,
ARG_NETDEV,
ARG_IP_BINARY,
ARG_COMM_FD,
ARG_SERVER,
ARG_PREFIX,
ARG_LOCAL,
ARG_GATEWAY,
ARG_DNS,
ARG_SUBNET,
ARG_FROM,
ARG_TO,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "log-level", required_argument, NULL, ARG_LOG_LEVEL },
{ "log-time", no_argument, NULL, ARG_LOG_TIME },
{ "netdev", required_argument, NULL, ARG_NETDEV },
{ "ip-binary", required_argument, NULL, ARG_IP_BINARY },
{ "comm-fd", required_argument, NULL, ARG_COMM_FD },
{ "server", no_argument, NULL, ARG_SERVER },
{ "prefix", required_argument, NULL, ARG_PREFIX },
{ "local", required_argument, NULL, ARG_LOCAL },
{ "gateway", required_argument, NULL, ARG_GATEWAY },
{ "dns", required_argument, NULL, ARG_DNS },
{ "subnet", required_argument, NULL, ARG_SUBNET },
{ "from", required_argument, NULL, ARG_FROM },
{ "to", required_argument, NULL, ARG_TO },
{}
};
int c, r;
const char *prefix = NULL, *local = NULL, *gateway = NULL;
const char *dns = NULL, *subnet = NULL, *from = NULL, *to = NULL;
while ((c = getopt_long(argc, argv, "hs:", options, NULL)) >= 0) {
switch (c) {
case 'h':
return help();
case ARG_VERSION:
puts(PACKAGE_STRING);
return 0;
case ARG_LOG_LEVEL:
log_max_sev = atoi(optarg);
break;
case ARG_LOG_TIME:
log_init_time();
break;
case ARG_NETDEV:
arg_netdev = optarg;
break;
case ARG_IP_BINARY:
arg_ip_binary = optarg;
break;
case ARG_COMM_FD:
arg_comm = atoi(optarg);
break;
case ARG_SERVER:
arg_server = true;
break;
case ARG_PREFIX:
prefix = optarg;
break;
case ARG_LOCAL:
local = optarg;
break;
case ARG_GATEWAY:
gateway = optarg;
break;
case ARG_DNS:
dns = optarg;
break;
case ARG_SUBNET:
subnet = optarg;
break;
case ARG_FROM:
from = optarg;
break;
case ARG_TO:
to = optarg;
break;
case '?':
return -EINVAL;
}
}
if (optind < argc) {
log_error("unparsed remaining arguments starting with: %s",
argv[optind]);
return -EINVAL;
}
if (!arg_netdev) {
log_error("no network-device given (see --help for --netdev)");
return -EINVAL;
}
if (access(arg_ip_binary, X_OK) < 0) {
log_error("execution of ip-binary (%s) not allowed: %m",
arg_ip_binary);
return -EINVAL;
}
if (!arg_server) {
if (prefix || local || gateway ||
dns || subnet || from || to) {
log_error("server option given, but running as client");
return -EINVAL;
}
} else {
r = make_address(arg_local, prefix, local ? : "1", "local");
if (r < 0)
return -EINVAL;
r = make_address(arg_gateway, prefix, gateway ? : "1",
"gateway");
if (r < 0)
return -EINVAL;
r = make_address(arg_dns, prefix, dns ? : "1", "dns");
if (r < 0)
return -EINVAL;
r = make_subnet(arg_subnet, subnet ? : "255.255.255.0");
if (r < 0)
return -EINVAL;
r = make_address(arg_from, prefix, from ? : "100", "from");
if (r < 0)
return -EINVAL;
r = make_address(arg_to, prefix, to ? : "199", "to");
if (r < 0)
return -EINVAL;
}
log_format(LOG_DEFAULT_BASE, NULL, LOG_INFO,
"miracle-dhcp - revision %s %s %s",
"some-rev-TODO-xyz", __DATE__, __TIME__);
return 1;
}
int main(int argc, char **argv)
{
struct manager *m = NULL;
int r;
r = parse_argv(argc, argv);
if (r < 0)
return EXIT_FAILURE;
if (!r)
return EXIT_SUCCESS;
r = manager_new(&m);
if (r < 0)
goto finish;
r = manager_run(m);
finish:
manager_free(m);
log_debug("exiting..");
return abs(r);
}

68
src/miracle.h Normal file
View file

@ -0,0 +1,68 @@
/*
* MiracleCast - Wifi-Display/Miracast Implementation
*
* Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
#include <systemd/sd-bus.h>
#include "shl_log.h"
#include "shl_macro.h"
#ifndef MIRACLE_H
#define MIRACLE_H
static inline void cleanup_free(void *p)
{
free(*(void**)p);
}
static inline void cleanup_sd_bus_message(sd_bus_message **ptr)
{
sd_bus_message_unref(*ptr);
}
#define _cleanup_free_ _shl_cleanup_(cleanup_free)
#define _cleanup_sd_bus_error_ _shl_cleanup_(sd_bus_error_free)
#define _cleanup_sd_bus_message_ _shl_cleanup_(cleanup_sd_bus_message)
static inline const char *bus_error_message(const sd_bus_error *e, int error)
{
if (e) {
if (sd_bus_error_has_name(e, SD_BUS_ERROR_ACCESS_DENIED))
return "Access denied";
if (e->message)
return e->message;
}
return strerror(error < 0 ? -error : error);
}
static inline int log_bus_parser(int r)
{
log_error("cannot parse dbus message: %s", strerror(r < 0 ? -r : r));
return r;
}
#endif /* MIRACLE_H */

484
src/miraclectl.c Normal file
View file

@ -0,0 +1,484 @@
/*
* MiracleCast - Wifi-Display/Miracast Implementation
*
* Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <errno.h>
#include <getopt.h>
#include <locale.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <systemd/sd-bus.h>
#include "miracle.h"
#include "shl_log.h"
#include "shl_macro.h"
#include "shl_util.h"
static int verb_list_links(sd_bus *bus, sd_bus_message *m)
{
char *link;
unsigned int link_cnt = 0;
const char *obj;
int r;
r = sd_bus_message_enter_container(m, 'a', "{oa{sa{sv}}}");
if (r < 0)
return log_bus_parser(r);
while ((r = sd_bus_message_enter_container(m,
'e',
"oa{sa{sv}}")) > 0) {
r = sd_bus_message_read(m, "o", &obj);
if (r < 0)
return log_bus_parser(r);
obj = shl_startswith(obj, "/org/freedesktop/miracle/link/");
if (obj) {
link = sd_bus_label_unescape(obj);
if (!link)
return log_ENOMEM();
printf("%24s %-9s\n", link, "");
free(link);
++link_cnt;
}
r = sd_bus_message_skip(m, "a{sa{sv}}");
if (r < 0)
return log_bus_parser(r);
r = sd_bus_message_exit_container(m);
if (r < 0)
return log_bus_parser(r);
}
if (r < 0)
return log_bus_parser(r);
r = sd_bus_message_exit_container(m);
if (r < 0)
return log_bus_parser(r);
return link_cnt;
}
static int verb_list_peer(sd_bus *bus, sd_bus_message *m, const char *peer)
{
_cleanup_free_ char *link = NULL;
const char *obj;
int r;
r = sd_bus_message_enter_container(m, 'a', "{sa{sv}}");
if (r < 0)
return log_bus_parser(r);
while ((r = sd_bus_message_enter_container(m,
'e',
"sa{sv}")) > 0) {
r = sd_bus_message_read(m, "s", &obj);
if (r < 0)
return log_bus_parser(r);
if (strcmp(obj, "org.freedesktop.miracle.Peer")) {
r = sd_bus_message_skip(m, "a{sv}");
if (r < 0)
return log_bus_parser(r);
r = sd_bus_message_exit_container(m);
if (r < 0)
return log_bus_parser(r);
continue;
}
r = sd_bus_message_enter_container(m, 'a', "{sv}");
if (r < 0)
return log_bus_parser(r);
while ((r = sd_bus_message_enter_container(m,
'e',
"sv")) > 0) {
r = sd_bus_message_read(m, "s", &obj);
if (r < 0)
return log_bus_parser(r);
if (!strcmp(obj, "Link")) {
r = sd_bus_message_enter_container(m,
'v',
"o");
if (r < 0)
return log_bus_parser(r);
r = sd_bus_message_read(m, "o", &obj);
if (r < 0)
return log_bus_parser(r);
obj = shl_startswith(obj,
"/org/freedesktop/miracle/link/");
if (obj) {
free(link);
link = sd_bus_label_unescape(obj);
if (!link)
return log_ENOMEM();
}
r = sd_bus_message_exit_container(m);
if (r < 0)
return log_bus_parser(r);
} else {
sd_bus_message_skip(m, "v");
}
r = sd_bus_message_exit_container(m);
if (r < 0)
return log_bus_parser(r);
}
if (r < 0)
return log_bus_parser(r);
r = sd_bus_message_exit_container(m);
if (r < 0)
return log_bus_parser(r);
r = sd_bus_message_exit_container(m);
if (r < 0)
return log_bus_parser(r);
}
if (r < 0)
return log_bus_parser(r);
r = sd_bus_message_exit_container(m);
if (r < 0)
return log_bus_parser(r);
printf("%24s %-9s\n", link ? : "<none>", peer);
return 0;
}
static int verb_list_peers(sd_bus *bus, sd_bus_message *m)
{
_cleanup_free_ char *peer = NULL;
unsigned int peer_cnt = 0;
const char *obj;
int r;
r = sd_bus_message_enter_container(m, 'a', "{oa{sa{sv}}}");
if (r < 0)
return log_bus_parser(r);
while ((r = sd_bus_message_enter_container(m,
'e',
"oa{sa{sv}}")) > 0) {
r = sd_bus_message_read(m, "o", &obj);
if (r < 0)
return log_bus_parser(r);
obj = shl_startswith(obj, "/org/freedesktop/miracle/peer/");
if (!obj) {
r = sd_bus_message_skip(m, "a{sa{sv}}");
if (r < 0)
return log_bus_parser(r);
r = sd_bus_message_exit_container(m);
if (r < 0)
return log_bus_parser(r);
continue;
}
free(peer);
peer = sd_bus_label_unescape(obj);
if (!peer)
return log_ENOMEM();
++peer_cnt;
r = verb_list_peer(bus, m, peer);
if (r < 0)
return r;
r = sd_bus_message_exit_container(m);
if (r < 0)
return log_bus_parser(r);
}
if (r < 0)
return log_bus_parser(r);
r = sd_bus_message_exit_container(m);
if (r < 0)
return log_bus_parser(r);
return peer_cnt;
}
static int verb_list(sd_bus *bus, char **args, unsigned int n)
{
_cleanup_sd_bus_message_ sd_bus_message *m = NULL;
_cleanup_sd_bus_error_ sd_bus_error err = SD_BUS_ERROR_NULL;
unsigned int link_cnt, peer_cnt;
int r;
r = sd_bus_call_method(bus,
"org.freedesktop.miracle",
"/org/freedesktop/miracle",
"org.freedesktop.DBus.ObjectManager",
"GetManagedObjects",
&err,
&m,
"");
if (r < 0) {
log_error("cannot retrieve objects: %s",
bus_error_message(&err, r));
return r;
}
printf("%24s %-9s\n", "LINK", "PEER");
/* print links */
r = verb_list_links(bus, m);
if (r < 0)
return r;
link_cnt = r;
sd_bus_message_rewind(m, true);
if (link_cnt > 0)
printf("\n");
/* print peers */
r = verb_list_peers(bus, m);
if (r < 0)
return r;
peer_cnt = r;
if (peer_cnt > 0 || !link_cnt)
printf("\n");
/* print stats */
printf(" %u peers and %u links listed.\n", peer_cnt, link_cnt);
return 0;
}
static int verb_add_link(sd_bus *bus, char **args, unsigned int n)
{
_cleanup_sd_bus_error_ sd_bus_error err = SD_BUS_ERROR_NULL;
_cleanup_sd_bus_message_ sd_bus_message *m = NULL;
_cleanup_free_ char *link = NULL;
const char *name;
int r;
r = sd_bus_call_method(bus,
"org.freedesktop.miracle",
"/org/freedesktop/miracle",
"org.freedesktop.miracle.Manager",
"AddLink",
&err,
&m,
"ss", args[1], args[2]);
if (r < 0) {
log_error("cannot add link %s:%s: %s",
args[1], args[2], bus_error_message(&err, r));
return r;
}
r = sd_bus_message_read(m, "s", &name);
if (r < 0)
return log_bus_parser(r);
link = sd_bus_label_unescape(name);
if (!link)
return log_ENOMEM();
printf("Link added as %s\n", link);
return 0;
}
static int verb_remove_link(sd_bus *bus, char **args, unsigned int n)
{
_cleanup_sd_bus_error_ sd_bus_error err = SD_BUS_ERROR_NULL;
int r;
r = sd_bus_call_method(bus,
"org.freedesktop.miracle",
"/org/freedesktop/miracle",
"org.freedesktop.miracle.Manager",
"RemoveLink",
&err,
NULL,
"s", args[1]);
if (r < 0) {
log_error("cannot remove link %s: %s",
args[1], bus_error_message(&err, r));
return r;
}
printf("Link %s removed\n", args[1]);
return 0;
}
static int help(void)
{
printf("%s [OPTIONS...] {COMMAND} ...\n\n"
"Send control command to or query the MiracleCast manager.\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --log-level <lvl> Maximum level for log messages\n"
" --log-time Prefix log-messages with timestamp\n"
"\n"
"Commands:\n"
" list List managed links and their peers\n"
" add-link LINK... Start managing the given link\n"
" remove-link LINK... Stop managing the given link\n"
, program_invocation_short_name);
return 0;
}
static int parse_argv(int argc, char *argv[])
{
enum {
ARG_VERSION = 0x100,
ARG_LOG_LEVEL,
ARG_LOG_TIME,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "log-level", required_argument, NULL, ARG_LOG_LEVEL },
{ "log-time", no_argument, NULL, ARG_LOG_TIME },
{}
};
int c;
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
switch (c) {
case 'h':
return help();
case ARG_VERSION:
puts(PACKAGE_STRING);
return 0;
case ARG_LOG_LEVEL:
log_max_sev = atoi(optarg);
break;
case ARG_LOG_TIME:
log_init_time();
break;
case '?':
return -EINVAL;
}
}
return 1;
}
static int miraclectl_main(sd_bus *bus, int argc, char *argv[])
{
static const struct {
const char *verb;
const enum {
MORE,
LESS,
EQUAL
} argc_cmp;
const int argc;
int (*dispatch) (sd_bus *bus, char **args, unsigned int n);
} verbs[] = {
{ "list", LESS, 1, verb_list },
{ "add-link", EQUAL, 3, verb_add_link },
{ "remove-link", EQUAL, 2, verb_remove_link },
};
int left;
unsigned int i;
left = argc - optind;
if (left <= 0) {
/* no argument means "list" */
i = 0;
} else {
if (!strcmp(argv[optind], "help")) {
help();
return 0;
}
for (i = 0; i < SHL_ARRAY_LENGTH(verbs); i++)
if (!strcmp(argv[optind], verbs[i].verb))
break;
if (i >= SHL_ARRAY_LENGTH(verbs)) {
log_error("unknown operation %s", argv[optind]);
return -EINVAL;
}
}
switch (verbs[i].argc_cmp) {
case EQUAL:
if (left != verbs[i].argc) {
log_error("invalid number of arguments");
return -EINVAL;
}
break;
case MORE:
if (left < verbs[i].argc) {
log_error("too few arguments");
return -EINVAL;
}
break;
case LESS:
if (left > verbs[i].argc) {
log_error("too many arguments");
return -EINVAL;
}
break;
}
return verbs[i].dispatch(bus, argv + optind, left);
}
int main(int argc, char **argv)
{
sd_bus *bus;
int r;
setlocale(LC_ALL, "");
r = parse_argv(argc, argv);
if (r < 0)
return EXIT_FAILURE;
if (!r)
return EXIT_SUCCESS;
r = sd_bus_default_system(&bus);
if (r < 0) {
log_error("cannot connect to system bus: %s", strerror(-r));
return EXIT_FAILURE;
}
r = miraclectl_main(bus, argc, argv);
sd_bus_unref(bus);
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

595
src/miracled-dbus.c Normal file
View file

@ -0,0 +1,595 @@
/*
* MiracleCast - Wifi-Display/Miracast Implementation
*
* Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#define LOG_SUBSYSTEM "dbus"
#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <systemd/sd-bus.h>
#include <systemd/sd-event.h>
#include "miracle.h"
#include "miracled.h"
#include "shl_log.h"
#include "shl_util.h"
/*
* Peer DBus
*/
static int peer_dbus_allow(sd_bus *bus, sd_bus_message *msg,
void *data, sd_bus_error *err)
{
return -EINVAL;
}
static int peer_dbus_reject(sd_bus *bus, sd_bus_message *msg,
void *data, sd_bus_error *err)
{
return -EINVAL;
}
static int peer_dbus_connect(sd_bus *bus, sd_bus_message *msg,
void *data, sd_bus_error *err)
{
return -EINVAL;
}
static int peer_dbus_disconnect(sd_bus *bus, sd_bus_message *msg,
void *data, sd_bus_error *err)
{
return -EINVAL;
}
static int peer_dbus_get_link(sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *data,
sd_bus_error *err)
{
_cleanup_free_ char *link = NULL;
struct peer *p = data;
int r;
link = shl_strcat("/org/freedesktop/miracle/link/", p->l->name);
if (!link)
return log_ENOMEM();
r = sd_bus_message_append_basic(reply, 'o', link);
if (r < 0)
return r;
return 1;
}
/*
static int peer_dbus_get_name(sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *data,
sd_bus_error *err)
{
return -EINVAL;
}
static int peer_dbus_get_connected(sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *data,
sd_bus_error *err)
{
return -EINVAL;
}
static int peer_dbus_get_interface(sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *data,
sd_bus_error *err)
{
return -EINVAL;
}
static int peer_dbus_get_local_address(sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *data,
sd_bus_error *err)
{
return -EINVAL;
}
static int peer_dbus_get_remote_address(sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *data,
sd_bus_error *err)
{
return -EINVAL;
}
*/
static const sd_bus_vtable peer_dbus_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("Allow",
"s",
NULL,
peer_dbus_allow,
0),
SD_BUS_METHOD("Reject",
NULL,
NULL,
peer_dbus_reject,
0),
SD_BUS_METHOD("Connect",
NULL,
NULL,
peer_dbus_connect,
0),
SD_BUS_METHOD("Disconnect",
NULL,
NULL,
peer_dbus_disconnect,
0),
SD_BUS_PROPERTY("Link",
"o",
peer_dbus_get_link,
0,
SD_BUS_VTABLE_PROPERTY_CONST),
/*
SD_BUS_PROPERTY("Name",
"s",
peer_dbus_get_name,
0,
SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("Connected",
"b",
peer_dbus_get_connected,
0,
SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("Interface",
"s",
peer_dbus_get_interface,
0,
0),
SD_BUS_PROPERTY("LocalAddress",
"s",
peer_dbus_get_local_address,
0,
0),
SD_BUS_PROPERTY("RemoteAddress",
"s",
peer_dbus_get_remote_address,
0,
0),
*/
SD_BUS_SIGNAL("ProvisionRequest", "ss", 0),
SD_BUS_VTABLE_END
};
static int peer_dbus_find(sd_bus *bus,
const char *path,
const char *interface,
void *data,
void **found,
sd_bus_error *err)
{
struct manager *m = data;
struct peer *p;
const char *name;
if (!(name = shl_startswith(path, "/org/freedesktop/miracle/peer/")))
return 0;
p = manager_find_peer(m, name);
if (!p)
return 0;
*found = p;
return 1;
}
/*
* Link DBus
*/
static int link_dbus_start_scan(sd_bus *bus, sd_bus_message *msg,
void *data, sd_bus_error *err)
{
return -EINVAL;
}
static int link_dbus_stop_scan(sd_bus *bus, sd_bus_message *msg,
void *data, sd_bus_error *err)
{
return -EINVAL;
}
static int link_dbus_get_type(sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *data,
sd_bus_error *err)
{
struct link *l = data;
int r;
r = sd_bus_message_append_basic(reply, 's',
link_type_to_str(l->type));
if (r < 0)
return r;
return 1;
}
static int link_dbus_get_interface(sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *data,
sd_bus_error *err)
{
struct link *l = data;
int r;
r = sd_bus_message_append_basic(reply, 's', l->interface);
if (r < 0)
return r;
return 1;
}
static int link_dbus_get_name(sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *data,
sd_bus_error *err)
{
struct link *l = data;
int r;
r = sd_bus_message_append_basic(reply, 's', l->friendly_name);
if (r < 0)
return r;
return 1;
}
static int link_dbus_set_name(sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *value,
void *data,
sd_bus_error *err)
{
return -EACCES;
}
static const sd_bus_vtable link_dbus_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("StartScan",
NULL,
NULL,
link_dbus_start_scan,
0),
SD_BUS_METHOD("StopScan",
NULL,
NULL,
link_dbus_stop_scan,
0),
SD_BUS_PROPERTY("Type",
"s",
link_dbus_get_type,
0,
SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Interface",
"s",
link_dbus_get_interface,
0,
SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_WRITABLE_PROPERTY("Name",
"s",
link_dbus_get_name,
link_dbus_set_name,
0,
SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_VTABLE_END
};
static int link_dbus_find(sd_bus *bus,
const char *path,
const char *interface,
void *data,
void **found,
sd_bus_error *err)
{
struct manager *m = data;
struct link *l;
const char *name;
if (!(name = shl_startswith(path, "/org/freedesktop/miracle/link/")))
return 0;
l = manager_find_link(m, name);
if (!l)
return 0;
*found = l;
return 1;
}
/*
* Manager DBus
*/
static int manager_dbus_add_link(sd_bus *bus, sd_bus_message *msg,
void *data, sd_bus_error *err)
{
struct manager *m = data;
const char *stype, *interface;
unsigned int type;
struct link *l;
int r;
r = sd_bus_message_read(msg, "ss", &stype, &interface);
if (r < 0)
return r;
type = link_type_from_str(stype);
if (type >= LINK_CNT)
return sd_bus_error_setf(err,
SD_BUS_ERROR_INVALID_ARGS,
"invalid type");
r = link_new(m, type, interface, &l);
if (r == -EALREADY)
return sd_bus_error_setf(err,
SD_BUS_ERROR_INVALID_ARGS,
"link already available");
else if (r < 0)
return r;
return sd_bus_reply_method_return(msg, "s", l->name);
}
static int manager_dbus_remove_link(sd_bus *bus, sd_bus_message *msg,
void *data, sd_bus_error *err)
{
_cleanup_free_ char *link = NULL;
struct manager *m = data;
struct link *l;
const char *name;
int r;
r = sd_bus_message_read(msg, "s", &name);
if (r < 0)
return r;
link = sd_bus_label_escape(name);
if (!link)
return log_ENOMEM();
l = manager_find_link(m, link);
if (!l)
return sd_bus_error_setf(err,
SD_BUS_ERROR_INVALID_ARGS,
"link not available");
link_free(l);
return sd_bus_reply_method_return(msg, NULL);
}
static const sd_bus_vtable manager_dbus_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("AddLink",
"ss",
"s",
manager_dbus_add_link,
0),
SD_BUS_METHOD("RemoveLink",
"s",
NULL,
manager_dbus_remove_link,
0),
SD_BUS_VTABLE_END
};
static int manager_dbus_enumerate(sd_bus *bus,
const char *path,
void *data,
char ***out,
sd_bus_error *err)
{
struct manager *m = data;
struct link *l;
struct peer *p;
size_t i;
char **nodes, *node;
int r;
nodes = malloc(sizeof(*nodes) * (m->link_cnt + m->peer_cnt + 2));
if (!nodes)
return log_ENOMEM();
i = 0;
MANAGER_FOREACH_LINK(l, m) {
if (i >= m->link_cnt + m->peer_cnt) {
log_warning("overflow: skipping link %s",
l->name);
continue;
}
node = shl_strcat("/org/freedesktop/miracle/link/",
l->name);
if (!node) {
r = log_ENOMEM();
goto error;
}
nodes[i++] = node;
}
MANAGER_FOREACH_PEER(p, m) {
if (i >= m->link_cnt + m->peer_cnt) {
log_warning("overflow: skipping peer %s",
p->name);
continue;
}
node = shl_strcat("/org/freedesktop/miracle/peer/",
p->name);
if (!node) {
r = log_ENOMEM();
goto error;
}
nodes[i++] = node;
}
node = strdup("/org/freedesktop/miracle");
if (!node) {
r = log_ENOMEM();
goto error;
}
nodes[i++] = node;
nodes[i] = NULL;
*out = nodes;
return 0;
error:
while (i--)
free(nodes[i]);
free(nodes);
return r;
}
int manager_dbus_connect(struct manager *m)
{
int r;
r = sd_bus_add_object_vtable(m->bus,
"/org/freedesktop/miracle",
"org.freedesktop.miracle.Manager",
manager_dbus_vtable,
m);
if (r < 0)
goto error;
r = sd_bus_add_node_enumerator(m->bus,
"/org/freedesktop/miracle",
manager_dbus_enumerate,
m);
if (r < 0)
goto error;
r = sd_bus_add_fallback_vtable(m->bus,
"/org/freedesktop/miracle/link",
"org.freedesktop.miracle.Link",
link_dbus_vtable,
link_dbus_find,
m);
if (r < 0)
goto error;
r = sd_bus_add_fallback_vtable(m->bus,
"/org/freedesktop/miracle/peer",
"org.freedesktop.miracle.Peer",
peer_dbus_vtable,
peer_dbus_find,
m);
if (r < 0)
goto error;
r = sd_bus_add_object_manager(m->bus, "/org/freedesktop/miracle");
if (r < 0)
goto error;
r = sd_bus_request_name(m->bus, "org.freedesktop.miracle", 0);
if (r < 0) {
log_error("cannot claim org.freedesktop.miracle bus-name: %d",
r);
goto error_silent;
}
return 0;
error:
log_ERR(r);
error_silent:
manager_dbus_disconnect(m);
return r;
}
void manager_dbus_disconnect(struct manager *m)
{
if (!m || !m->bus)
return;
sd_bus_release_name(m->bus, "org.freedesktop.miracle");
sd_bus_remove_object_manager(m->bus, "/org/freedesktop/miracle");
sd_bus_remove_fallback_vtable(m->bus,
"/org/freedesktop/miracle/peer",
"org.freedesktop.miracle.Peer",
peer_dbus_vtable,
peer_dbus_find,
m);
sd_bus_remove_fallback_vtable(m->bus,
"/org/freedesktop/miracle/link",
"org.freedesktop.miracle.Link",
link_dbus_vtable,
link_dbus_find,
m);
sd_bus_remove_node_enumerator(m->bus,
"/org/freedesktop/miracle",
manager_dbus_enumerate,
m);
sd_bus_remove_object_vtable(m->bus,
"/org/freedesktop/miracle",
"org.freedesktop.miracle.Manager",
manager_dbus_vtable,
m);
}

272
src/miracled-link.c Normal file
View file

@ -0,0 +1,272 @@
/*
* MiracleCast - Wifi-Display/Miracast Implementation
*
* Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#define LOG_SUBSYSTEM "link"
#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
#include <systemd/sd-bus.h>
#include "miracled.h"
#include "miracled-wifi.h"
#include "shl_dlist.h"
#include "shl_log.h"
#include "shl_util.h"
static const char *link_type_to_str_table[LINK_CNT] = {
[LINK_VIRTUAL] = "virtual",
[LINK_WIFI] = "wifi",
};
const char *link_type_to_str(unsigned int type)
{
if (type >= LINK_CNT)
return NULL;
return link_type_to_str_table[type];
}
unsigned int link_type_from_str(const char *str)
{
unsigned int i;
if (!str)
return LINK_CNT;
for (i = 0; i < LINK_CNT; ++i)
if (link_type_to_str_table[i] &&
!strcmp(link_type_to_str_table[i], str))
return i;
return LINK_CNT;
}
int link_make_name(unsigned int type, const char *interface, char **out)
{
const char *tname;
char *name, *res;
size_t tlen, ilen;
tname = link_type_to_str(type);
if (!tname || !interface)
return -EINVAL;
/* hard-coded maximum of 255 just to be safe */
tlen = strlen(tname);
ilen = strlen(interface);
if (!tlen || tlen > 255 || !ilen || ilen > 255)
return -EINVAL;
if (!out)
return 0;
name = shl_strjoin(tname, ":", interface, NULL);
if (!name)
return log_ENOMEM();
res = sd_bus_label_escape(name);
free(name);
if (!res)
return log_ENOMEM();
*out = res;
return 0;
}
/*
* Wifi Handling
*/
static void link_wifi_event_fn(struct wifi *w, void *data,
struct wifi_event *ev)
{
struct link *l = data;
struct peer *p;
switch (ev->type) {
case WIFI_HUP:
/* destroy this link */
link_free(l);
break;
case WIFI_DEV_FOUND:
peer_new_wifi(l, ev->dev_found.dev, NULL);
break;
case WIFI_DEV_LOST:
p = wifi_dev_get_data(ev->dev_lost.dev);
if (!p)
break;
peer_free(p);
break;
case WIFI_DEV_PROVISION:
case WIFI_DEV_CONNECT:
case WIFI_DEV_DISCONNECT:
p = wifi_dev_get_data(ev->dev_lost.dev);
if (!p)
break;
peer_process_wifi(p, ev);
break;
default:
log_debug("unhandled WIFI event: %u", ev->type);
break;
}
}
static int link_wifi_init(struct link *l)
{
struct wifi_dev *d;
int r;
char *path;
r = wifi_new(l->m->event, link_wifi_event_fn, l, &l->w);
if (r < 0)
return r;
path = shl_strcat("/run/wpa_supplicant/", l->interface);
if (!path)
return log_ENOMEM();
r = wifi_open(l->w, path);
free(path);
if (r < 0)
return r;
for (d = wifi_get_devs(l->w); d; d = wifi_dev_next(d))
peer_new_wifi(l, d, NULL);
return 0;
}
static void link_wifi_destroy(struct link *l)
{
wifi_close(l->w);
wifi_free(l->w);
}
/*
* Link Handling
*/
int link_new(struct manager *m,
unsigned int type,
const char *interface,
struct link **out)
{
size_t hash = 0;
char *name;
struct link *l;
int r;
if (!m)
return log_EINVAL();
r = link_make_name(type, interface, &name);
if (r < 0)
return r;
if (shl_htable_lookup_str(&m->links, name, &hash, NULL)) {
free(name);
return -EALREADY;
}
log_debug("new link: %s", name);
l = calloc(1, sizeof(*l));
if (!l) {
free(name);
return log_ENOMEM();
}
l->m = m;
l->type = type;
l->name = name;
shl_dlist_init(&l->peers);
l->interface = strdup(interface);
if (!l->interface) {
r = log_ENOMEM();
goto error;
}
l->friendly_name = strdup("");
if (!l->friendly_name) {
r = log_ENOMEM();
goto error;
}
switch (l->type) {
case LINK_VIRTUAL:
break;
case LINK_WIFI:
r = link_wifi_init(l);
if (r < 0)
goto error;
break;
}
r = shl_htable_insert_str(&m->links, &l->name, &hash);
if (r < 0) {
log_vERR(r);
goto error;
}
++m->link_cnt;
log_info("new managed link: %s", l->name);
if (out)
*out = l;
return 0;
error:
link_free(l);
return r;
}
void link_free(struct link *l)
{
struct peer *p;
if (!l)
return;
log_debug("free link: %s", l->name);
while ((p = LINK_FIRST_PEER(l)))
peer_free(p);
if (shl_htable_remove_str(&l->m->links, l->name, NULL, NULL)) {
log_info("remove managed link: %s", l->name);
--l->m->link_cnt;
}
link_wifi_destroy(l);
free(l->friendly_name);
free(l->name);
free(l->interface);
free(l);
}

159
src/miracled-peer.c Normal file
View file

@ -0,0 +1,159 @@
/*
* MiracleCast - Wifi-Display/Miracast Implementation
*
* Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#define LOG_SUBSYSTEM "peer"
#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
#include <systemd/sd-bus.h>
#include "miracled.h"
#include "miracled-wifi.h"
#include "shl_log.h"
static int peer_new(struct link *l, struct peer **out)
{
unsigned int id;
size_t hash = 0;
char *name;
struct peer *p;
int r;
if (!l)
return log_EINVAL();
id = ++l->m->peer_ids;
r = peer_make_name(id, &name);
if (r < 0)
return r;
if (shl_htable_lookup_str(&l->m->peers, name, &hash, NULL)) {
free(name);
return -EALREADY;
}
log_debug("new peer: %s", name);
p = calloc(1, sizeof(*p));
if (!p) {
free(name);
return log_ENOMEM();
}
p->l = l;
p->id = id;
p->name = name;
r = shl_htable_insert_str(&l->m->peers, &p->name, &hash);
if (r < 0) {
log_vERR(r);
goto error;
}
++l->m->peer_cnt;
shl_dlist_link(&l->peers, &p->list);
log_info("new peer: %s@%s", p->name, l->name);
if (out)
*out = p;
return 0;
error:
peer_free(p);
return r;
}
int peer_new_wifi(struct link *l, struct wifi_dev *d, struct peer **out)
{
int r;
struct peer *p;
r = peer_new(l, &p);
if (r < 0)
return r;
p->d = d;
wifi_dev_set_data(p->d, p);
if (out)
*out = p;
return 0;
}
void peer_free(struct peer *p)
{
if (!p)
return;
log_debug("free peer: %s", p->name);
if (shl_htable_remove_str(&p->l->m->peers, p->name, NULL, NULL)) {
log_info("remove managed peer: %s@%s", p->name, p->l->name);
--p->l->m->peer_cnt;
shl_dlist_unlink(&p->list);
}
wifi_dev_set_data(p->d, NULL);
free(p->name);
free(p);
}
void peer_process_wifi(struct peer *p, struct wifi_event *ev)
{
if (!p || !p->d)
return;
switch (ev->type) {
case WIFI_DEV_PROVISION:
break;
case WIFI_DEV_CONNECT:
break;
case WIFI_DEV_DISCONNECT:
break;
default:
log_debug("unhandled WIFI event: %u", ev->type);
break;
}
}
int peer_make_name(unsigned int id, char **out)
{
char buf[64] = { };
char *name;
if (!out)
return 0;
snprintf(buf, sizeof(buf) - 1, "%u", id);
name = sd_bus_label_escape(buf);
if (!name)
return log_ENOMEM();
*out = name;
return 0;
}

1238
src/miracled-wifi.c Normal file

File diff suppressed because it is too large Load diff

125
src/miracled-wifi.h Normal file
View file

@ -0,0 +1,125 @@
/*
* MiracleCast - Wifi-Display/Miracast Implementation
*
* Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
#include <systemd/sd-event.h>
#include "miracle.h"
#ifndef MIRACLED_WIFI_H
#define MIRACLED_WIFI_H
struct wifi;
struct wifi_dev;
/* wifi */
struct wifi;
struct wifi_dev;
enum wifi_event_type {
WIFI_HUP,
WIFI_DEV_FOUND,
WIFI_DEV_LOST,
WIFI_DEV_PROVISION,
WIFI_DEV_CONNECT,
WIFI_DEV_DISCONNECT,
};
enum wifi_provision_type {
WIFI_PROVISION_PBC,
WIFI_PROVISION_DISPLAY,
WIFI_PROVISION_PIN,
WIFI_PROVISION_CNT,
};
/* WPS pins are fixed to 8 chars (+1 terminating zero) */
#define WIFI_PIN_STRLEN (8 + 1)
struct wifi_event {
unsigned int type;
union {
struct wifi_event_dev_found {
struct wifi_dev *dev;
} dev_found;
struct wifi_event_dev_lost {
struct wifi_dev *dev;
} dev_lost;
struct wifi_event_dev_provision {
struct wifi_dev *dev;
unsigned int type;
char pin[WIFI_PIN_STRLEN];
} dev_provision;
struct wifi_event_dev_connect {
struct wifi_dev *dev;
} dev_connect;
struct wifi_event_dev_disconnect {
struct wifi_dev *dev;
} dev_disconnect;
};
};
typedef void (*wifi_event_t) (struct wifi *w, void *data,
struct wifi_event *ev);
int wifi_new(sd_event *event, wifi_event_t event_fn, void *data,
struct wifi **out);
void wifi_free(struct wifi *w);
void wifi_set_data(struct wifi *w, void *data);
void *wifi_get_data(struct wifi *w);
bool wifi_is_open(struct wifi *w);
int wifi_open(struct wifi *w, const char *wpa_path);
void wifi_close(struct wifi *w);
int wifi_set_discoverable(struct wifi *w, bool on);
struct wifi_dev *wifi_get_devs(struct wifi *w);
struct wifi_dev *wifi_dev_next(struct wifi_dev *d);
/* wifi device */
void wifi_dev_ref(struct wifi_dev *d);
void wifi_dev_unref(struct wifi_dev *d);
void wifi_dev_set_data(struct wifi_dev *d, void *data);
void *wifi_dev_get_data(struct wifi_dev *d);
bool wifi_dev_is_available(struct wifi_dev *d);
bool wifi_dev_is_running(struct wifi_dev *d);
bool wifi_dev_is_ready(struct wifi_dev *d);
void wifi_dev_allow(struct wifi_dev *d, const char *pin);
void wifi_dev_reject(struct wifi_dev *d);
int wifi_dev_connect(struct wifi_dev *d, unsigned int provision,
const char *pin);
void wifi_dev_disconnect(struct wifi_dev *d);
#endif /* MIRACLED_WIFI_H */

285
src/miracled.c Normal file
View file

@ -0,0 +1,285 @@
/*
* MiracleCast - Wifi-Display/Miracast Implementation
*
* Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <errno.h>
#include <getopt.h>
#include <libwfd.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/signalfd.h>
#include <systemd/sd-bus.h>
#include <systemd/sd-daemon.h>
#include <systemd/sd-event.h>
#include <unistd.h>
#include "miracled.h"
#include "shl_htable.h"
#include "shl_log.h"
/*
* Peer Handling
*/
struct peer *manager_find_peer(struct manager *m, const char *name)
{
char **elem;
bool res;
res = shl_htable_lookup_str(&m->peers, name, NULL, &elem);
if (!res)
return NULL;
return peer_from_htable(elem);
}
/*
* Link Handling
*/
struct link *manager_find_link(struct manager *m, const char *name)
{
char **elem;
bool res;
res = shl_htable_lookup_str(&m->links, name, NULL, &elem);
if (!res)
return NULL;
return link_from_htable(elem);
}
/*
* Manager Handling
*/
static int manager_signal_fn(sd_event_source *source,
const struct signalfd_siginfo *ssi,
void *data)
{
struct manager *m = data;
if (ssi->ssi_signo == SIGCHLD) {
log_debug("caught SIGCHLD for %d", (int)ssi->ssi_pid);
return 0;
}
log_notice("caught signal %d, exiting..", (int)ssi->ssi_signo);
sd_event_exit(m->event, 0);
return 0;
}
static void manager_free(struct manager *m)
{
unsigned int i;
struct link *l;
if (!m)
return;
while ((l = MANAGER_FIRST_LINK(m)))
link_free(l);
shl_htable_clear_str(&m->links, NULL, NULL);
shl_htable_clear_str(&m->peers, NULL, NULL);
manager_dbus_disconnect(m);
for (i = 0; m->sigs[i]; ++i)
sd_event_source_unref(m->sigs[i]);
sd_bus_unref(m->bus);
sd_event_unref(m->event);
free(m);
}
static int manager_new(struct manager **out)
{
struct manager *m;
static const int sigs[] = {
SIGINT, SIGTERM, SIGQUIT, SIGHUP, SIGPIPE, SIGCHLD, 0
};
unsigned int i;
sigset_t mask;
int r;
m = calloc(1, sizeof(*m));
if (!m)
return log_ENOMEM();
shl_htable_init_str(&m->links);
shl_htable_init_str(&m->peers);
r = sd_event_default(&m->event);
if (r < 0) {
log_vERR(r);
goto error;
}
r = sd_event_set_watchdog(m->event, true);
if (r < 0) {
log_vERR(r);
goto error;
}
r = sd_bus_default_system(&m->bus);
if (r < 0) {
log_error("cannot connect to system bus: %d", r);
goto error;
}
r = sd_bus_attach_event(m->bus, m->event, 0);
if (r < 0) {
log_vERR(r);
goto error;
}
for (i = 0; sigs[i]; ++i) {
sigemptyset(&mask);
sigaddset(&mask, sigs[i]);
sigprocmask(SIG_BLOCK, &mask, NULL);
r = sd_event_add_signal(m->event,
sigs[i],
manager_signal_fn,
m,
&m->sigs[i]);
if (r < 0) {
log_vERR(r);
goto error;
}
}
r = manager_dbus_connect(m);
if (r < 0)
goto error;
*out = m;
return 0;
error:
manager_free(m);
return r;
}
static int manager_run(struct manager *m)
{
return sd_event_loop(m->event);
}
static int help(void)
{
printf("%s [OPTIONS...] ...\n\n"
"Wifi-Display Daemon.\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --log-level <lvl> Maximum level for log messages\n"
" --log-time Prefix log-messages with timestamp\n"
"\n"
" --netdev <dev> Network device to run on\n"
" --wpa-rundir <dir> wpa_supplicant runtime dir [default: /run/wpa_supplicant]\n"
, program_invocation_short_name);
return 0;
}
static int parse_argv(int argc, char *argv[])
{
enum {
ARG_VERSION = 0x100,
ARG_LOG_LEVEL,
ARG_LOG_TIME,
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "log-level", required_argument, NULL, ARG_LOG_LEVEL },
{ "log-time", no_argument, NULL, ARG_LOG_TIME },
{}
};
int c;
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
switch (c) {
case 'h':
return help();
case ARG_VERSION:
puts(PACKAGE_STRING);
return 0;
case ARG_LOG_LEVEL:
log_max_sev = atoi(optarg);
break;
case ARG_LOG_TIME:
log_init_time();
break;
case '?':
return -EINVAL;
}
}
if (optind < argc) {
log_error("unparsed remaining arguments starting with: %s",
argv[optind]);
return -EINVAL;
}
log_format(LOG_DEFAULT_BASE, NULL, LOG_INFO,
"miracled - revision %s %s %s",
"some-rev-TODO-xyz", __DATE__, __TIME__);
return 1;
}
int main(int argc, char **argv)
{
struct manager *m = NULL;
int r;
r = parse_argv(argc, argv);
if (r < 0)
return EXIT_FAILURE;
if (!r)
return EXIT_SUCCESS;
r = manager_new(&m);
if (r < 0)
goto finish;
r = sd_notify(false, "READY=1\n"
"STATUS=Running..");
if (r < 0) {
log_vERR(r);
goto finish;
}
r = manager_run(m);
finish:
sd_notify(false, "STATUS=Exiting..");
manager_free(m);
log_debug("exiting..");
return abs(r);
}

131
src/miracled.h Normal file
View file

@ -0,0 +1,131 @@
/*
* MiracleCast - Wifi-Display/Miracast Implementation
*
* Copyright (c) 2013-2014 David Herrmann <dh.herrmann@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <errno.h>
#include <stdbool.h>
#include <stdlib.h>
#include <systemd/sd-bus.h>
#include <systemd/sd-event.h>
#include "miracle.h"
#include "shl_dlist.h"
#include "shl_htable.h"
#ifndef MIRACLED_H
#define MIRACLED_H
struct manager;
struct link;
struct peer;
struct wifi;
struct wifi_dev;
struct wifi_event;
/* peer */
struct peer {
struct shl_dlist list;
struct link *l;
unsigned int id;
char *name;
struct wifi_dev *d;
};
#define peer_from_htable(_p) \
shl_htable_offsetof((_p), struct peer, name)
#define peer_from_list(_p) \
shl_dlist_entry((_p), struct peer, list)
int peer_new_wifi(struct link *l, struct wifi_dev *d, struct peer **out);
void peer_free(struct peer *p);
void peer_process_wifi(struct peer *p, struct wifi_event *ev);
int peer_make_name(unsigned int id, char **out);
/* link */
enum link_type {
LINK_VIRTUAL,
LINK_WIFI,
LINK_CNT,
};
struct link {
struct manager *m;
unsigned int type;
char *interface;
char *name;
char *friendly_name;
struct shl_dlist peers;
struct wifi *w;
};
#define link_from_htable(_l) \
shl_htable_offsetof((_l), struct link, name)
#define LINK_FIRST_PEER(_l) (shl_dlist_empty(&(_l)->peers) ? \
NULL : peer_from_list((_l)->peers.next))
const char *link_type_to_str(unsigned int type);
unsigned int link_type_from_str(const char *str);
int link_make_name(unsigned int type, const char *interface, char **out);
int link_new(struct manager *m,
unsigned int type,
const char *interface,
struct link **out);
void link_free(struct link *l);
/* manager */
struct manager {
sd_event *event;
sd_bus *bus;
sd_event_source *sigs[_NSIG];
unsigned int peer_ids;
size_t link_cnt;
size_t peer_cnt;
struct shl_htable links;
struct shl_htable peers;
};
#define MANAGER_FIRST_LINK(_m) \
SHL_HTABLE_FIRST_MACRO(&(_m)->links, link_from_htable)
#define MANAGER_FOREACH_LINK(_i, _m) \
SHL_HTABLE_FOREACH_MACRO(_i, &(_m)->links, link_from_htable)
#define MANAGER_FOREACH_PEER(_i, _m) \
SHL_HTABLE_FOREACH_MACRO(_i, &(_m)->peers, peer_from_htable)
int manager_dbus_connect(struct manager *m);
void manager_dbus_disconnect(struct manager *m);
struct link *manager_find_link(struct manager *m, const char *name);
struct peer *manager_find_peer(struct manager *m, const char *name);
#endif /* MIRACLED_H */

138
src/shl_dlist.h Normal file
View file

@ -0,0 +1,138 @@
/*
* SHL - Double Linked List
*
* Copyright (c) 2010-2013 David Herrmann <dh.herrmann@gmail.com>
* Dedicated to the Public Domain
*/
/*
* A simple double linked list implementation
* This list API does not provide type-safety! It is a simple circular
* double-linked list. Objects need to embed "struct shl_dlist". This is used to
* link and iterate a list. You can get the object back from a shl_dlist pointer
* via shl_dlist_entry(). This breaks any type-safety, though. You need to make
* sure you call this on the right objects.
*/
#ifndef SHL_DLIST_H
#define SHL_DLIST_H
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
/* miscellaneous */
#define shl_dlist_offsetof(pointer, type, member) ({ \
const typeof(((type*)0)->member) *__ptr = (pointer); \
(type*)(((char*)__ptr) - offsetof(type, member)); \
})
/* double linked list */
struct shl_dlist {
struct shl_dlist *next;
struct shl_dlist *prev;
};
#define SHL_DLIST_INIT(head) { &(head), &(head) }
static inline void shl_dlist_init(struct shl_dlist *list)
{
list->next = list;
list->prev = list;
}
static inline void shl_dlist__link(struct shl_dlist *prev,
struct shl_dlist *next,
struct shl_dlist *n)
{
next->prev = n;
n->next = next;
n->prev = prev;
prev->next = n;
}
static inline void shl_dlist_link(struct shl_dlist *head,
struct shl_dlist *n)
{
return shl_dlist__link(head, head->next, n);
}
static inline void shl_dlist_link_tail(struct shl_dlist *head,
struct shl_dlist *n)
{
return shl_dlist__link(head->prev, head, n);
}
static inline void shl_dlist__unlink(struct shl_dlist *prev,
struct shl_dlist *next)
{
next->prev = prev;
prev->next = next;
}
static inline void shl_dlist_unlink(struct shl_dlist *e)
{
shl_dlist__unlink(e->prev, e->next);
e->prev = NULL;
e->next = NULL;
}
static inline bool shl_dlist_empty(struct shl_dlist *head)
{
return head->next == head;
}
static inline struct shl_dlist *shl_dlist_first(struct shl_dlist *head)
{
return head->next;
}
static inline struct shl_dlist *shl_dlist_last(struct shl_dlist *head)
{
return head->prev;
}
#define shl_dlist_entry(ptr, type, member) \
shl_dlist_offsetof((ptr), type, member)
#define shl_dlist_first_entry(head, type, member) \
shl_dlist_entry(shl_dlist_first(head), type, member)
#define shl_dlist_last_entry(head, type, member) \
shl_dlist_entry(shl_dlist_last(head), type, member)
#define shl_dlist_for_each(iter, head) \
for (iter = (head)->next; iter != (head); iter = iter->next)
#define shl_dlist_for_each_but_one(iter, start, head) \
for (iter = ((start)->next == (head)) ? \
(start)->next->next : \
(start)->next; \
iter != (start); \
iter = (iter->next == (head) && (start) != (head)) ? \
iter->next->next : \
iter->next)
#define shl_dlist_for_each_safe(iter, tmp, head) \
for (iter = (head)->next, tmp = iter->next; iter != (head); \
iter = tmp, tmp = iter->next)
#define shl_dlist_for_each_reverse(iter, head) \
for (iter = (head)->prev; iter != (head); iter = iter->prev)
#define shl_dlist_for_each_reverse_but_one(iter, start, head) \
for (iter = ((start)->prev == (head)) ? \
(start)->prev->prev : \
(start)->prev; \
iter != (start); \
iter = (iter->prev == (head) && (start) != (head)) ? \
iter->prev->prev : \
iter->prev)
#define shl_dlist_for_each_reverse_safe(iter, tmp, head) \
for (iter = (head)->prev, tmp = iter->prev; iter != (head); \
iter = tmp, tmp = iter->prev)
#endif /* SHL_DLIST_H */

464
src/shl_htable.c Normal file
View file

@ -0,0 +1,464 @@
/*
* SHL - Dynamic hash-table
*
* Written-by: Rusty Russell <rusty@rustcorp.com.au>
* Adjusted-by: David Herrmann <dh.herrmann@gmail.com>
* Licensed under LGPLv2+ - see LICENSE_htable file for details
*/
/*
* Please see ccan/htable/_info at:
* https://github.com/rustyrussell/ccan/tree/master/ccan/htable
* for information on the hashtable algorithm. This file copies the code inline
* and is released under the same conditions.
*
* At the end of the file you can find some helpers to use this htable to store
* objects with "unsigned long" or "char*" keys.
*/
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "shl_htable.h"
#define COLD __attribute__((cold))
struct htable {
/* KEEP IN SYNC WITH "struct shl_htable_int" */
size_t (*rehash)(const void *elem, void *priv);
void *priv;
unsigned int bits;
size_t elems, deleted, max, max_with_deleted;
/* These are the bits which are the same in all pointers. */
uintptr_t common_mask, common_bits;
uintptr_t perfect_bit;
uintptr_t *table;
};
#define HTABLE_INITIALIZER(name, rehash, priv) \
{ rehash, priv, 0, 0, 0, 0, 0, -1, 0, 0, &name.perfect_bit }
struct htable_iter {
size_t off;
};
/*
* INLINE COPY OF ccan/htable.c
*/
/* We use 0x1 as deleted marker. */
#define HTABLE_DELETED (0x1)
/* We clear out the bits which are always the same, and put metadata there. */
static inline uintptr_t get_extra_ptr_bits(const struct htable *ht,
uintptr_t e)
{
return e & ht->common_mask;
}
static inline void *get_raw_ptr(const struct htable *ht, uintptr_t e)
{
return (void *)((e & ~ht->common_mask) | ht->common_bits);
}
static inline uintptr_t make_hval(const struct htable *ht,
const void *p, uintptr_t bits)
{
return ((uintptr_t)p & ~ht->common_mask) | bits;
}
static inline bool entry_is_valid(uintptr_t e)
{
return e > HTABLE_DELETED;
}
static inline uintptr_t get_hash_ptr_bits(const struct htable *ht,
size_t hash)
{
/* Shuffling the extra bits (as specified in mask) down the
* end is quite expensive. But the lower bits are redundant, so
* we fold the value first. */
return (hash ^ (hash >> ht->bits))
& ht->common_mask & ~ht->perfect_bit;
}
static void htable_init(struct htable *ht,
size_t (*rehash)(const void *elem, void *priv),
void *priv)
{
struct htable empty = HTABLE_INITIALIZER(empty, NULL, NULL);
*ht = empty;
ht->rehash = rehash;
ht->priv = priv;
ht->table = &ht->perfect_bit;
}
static void htable_clear(struct htable *ht,
void (*free_cb) (void *entry, void *ctx),
void *ctx)
{
size_t i;
if (ht->table != &ht->perfect_bit) {
if (free_cb) {
for (i = 0; i < (size_t)1 << ht->bits; ++i) {
if (entry_is_valid(ht->table[i]))
free_cb(get_raw_ptr(ht, ht->table[i]),
ctx);
}
}
free((void *)ht->table);
}
htable_init(ht, ht->rehash, ht->priv);
}
size_t shl_htable_this_or_next(struct shl_htable *htable, size_t i)
{
struct htable *ht = (void*)&htable->htable;
if (ht->table != &ht->perfect_bit)
for ( ; i < (size_t)1 << ht->bits; ++i)
if (entry_is_valid(ht->table[i]))
return i;
return SIZE_MAX;
}
void *shl_htable_get_entry(struct shl_htable *htable, size_t i)
{
struct htable *ht = (void*)&htable->htable;
if (i < (size_t)1 << ht->bits)
if (entry_is_valid(ht->table[i]))
return get_raw_ptr(ht, ht->table[i]);
return NULL;
}
static void htable_visit(struct htable *ht,
void (*visit_cb) (void *elem, void *ctx),
void *ctx)
{
size_t i;
if (visit_cb && ht->table != &ht->perfect_bit) {
for (i = 0; i < (size_t)1 << ht->bits; ++i) {
if (entry_is_valid(ht->table[i]))
visit_cb(get_raw_ptr(ht, ht->table[i]), ctx);
}
}
}
static size_t hash_bucket(const struct htable *ht, size_t h)
{
return h & ((1 << ht->bits)-1);
}
static void *htable_val(const struct htable *ht,
struct htable_iter *i, size_t hash, uintptr_t perfect)
{
uintptr_t h2 = get_hash_ptr_bits(ht, hash) | perfect;
while (ht->table[i->off]) {
if (ht->table[i->off] != HTABLE_DELETED) {
if (get_extra_ptr_bits(ht, ht->table[i->off]) == h2)
return get_raw_ptr(ht, ht->table[i->off]);
}
i->off = (i->off + 1) & ((1 << ht->bits)-1);
h2 &= ~perfect;
}
return NULL;
}
static void *htable_firstval(const struct htable *ht,
struct htable_iter *i, size_t hash)
{
i->off = hash_bucket(ht, hash);
return htable_val(ht, i, hash, ht->perfect_bit);
}
static void *htable_nextval(const struct htable *ht,
struct htable_iter *i, size_t hash)
{
i->off = (i->off + 1) & ((1 << ht->bits)-1);
return htable_val(ht, i, hash, 0);
}
/* This does not expand the hash table, that's up to caller. */
static void ht_add(struct htable *ht, const void *new, size_t h)
{
size_t i;
uintptr_t perfect = ht->perfect_bit;
i = hash_bucket(ht, h);
while (entry_is_valid(ht->table[i])) {
perfect = 0;
i = (i + 1) & ((1 << ht->bits)-1);
}
ht->table[i] = make_hval(ht, new, get_hash_ptr_bits(ht, h)|perfect);
}
static COLD bool double_table(struct htable *ht)
{
unsigned int i;
size_t oldnum = (size_t)1 << ht->bits;
uintptr_t *oldtable, e;
oldtable = ht->table;
ht->table = calloc(1 << (ht->bits+1), sizeof(size_t));
if (!ht->table) {
ht->table = oldtable;
return false;
}
ht->bits++;
ht->max = ((size_t)3 << ht->bits) / 4;
ht->max_with_deleted = ((size_t)9 << ht->bits) / 10;
/* If we lost our "perfect bit", get it back now. */
if (!ht->perfect_bit && ht->common_mask) {
for (i = 0; i < sizeof(ht->common_mask) * CHAR_BIT; i++) {
if (ht->common_mask & ((size_t)1 << i)) {
ht->perfect_bit = (size_t)1 << i;
break;
}
}
}
if (oldtable != &ht->perfect_bit) {
for (i = 0; i < oldnum; i++) {
if (entry_is_valid(e = oldtable[i])) {
void *p = get_raw_ptr(ht, e);
ht_add(ht, p, ht->rehash(p, ht->priv));
}
}
free(oldtable);
}
ht->deleted = 0;
return true;
}
static COLD void rehash_table(struct htable *ht)
{
size_t start, i;
uintptr_t e;
/* Beware wrap cases: we need to start from first empty bucket. */
for (start = 0; ht->table[start]; start++);
for (i = 0; i < (size_t)1 << ht->bits; i++) {
size_t h = (i + start) & ((1 << ht->bits)-1);
e = ht->table[h];
if (!e)
continue;
if (e == HTABLE_DELETED)
ht->table[h] = 0;
else if (!(e & ht->perfect_bit)) {
void *p = get_raw_ptr(ht, e);
ht->table[h] = 0;
ht_add(ht, p, ht->rehash(p, ht->priv));
}
}
ht->deleted = 0;
}
/* We stole some bits, now we need to put them back... */
static COLD void update_common(struct htable *ht, const void *p)
{
unsigned int i;
uintptr_t maskdiff, bitsdiff;
if (ht->elems == 0) {
/* Always reveal one bit of the pointer in the bucket,
* so it's not zero or HTABLE_DELETED (1), even if
* hash happens to be 0. Assumes (void *)1 is not a
* valid pointer. */
for (i = sizeof(uintptr_t)*CHAR_BIT - 1; i > 0; i--) {
if ((uintptr_t)p & ((uintptr_t)1 << i))
break;
}
ht->common_mask = ~((uintptr_t)1 << i);
ht->common_bits = ((uintptr_t)p & ht->common_mask);
ht->perfect_bit = 1;
return;
}
/* Find bits which are unequal to old common set. */
maskdiff = ht->common_bits ^ ((uintptr_t)p & ht->common_mask);
/* These are the bits which go there in existing entries. */
bitsdiff = ht->common_bits & maskdiff;
for (i = 0; i < (size_t)1 << ht->bits; i++) {
if (!entry_is_valid(ht->table[i]))
continue;
/* Clear the bits no longer in the mask, set them as
* expected. */
ht->table[i] &= ~maskdiff;
ht->table[i] |= bitsdiff;
}
/* Take away those bits from our mask, bits and perfect bit. */
ht->common_mask &= ~maskdiff;
ht->common_bits &= ~maskdiff;
ht->perfect_bit &= ~maskdiff;
}
static bool htable_add(struct htable *ht, size_t hash, const void *p)
{
if (ht->elems+1 > ht->max && !double_table(ht))
return false;
if (ht->elems+1 + ht->deleted > ht->max_with_deleted)
rehash_table(ht);
assert(p);
if (((uintptr_t)p & ht->common_mask) != ht->common_bits)
update_common(ht, p);
ht_add(ht, p, hash);
ht->elems++;
return true;
}
static void htable_delval(struct htable *ht, struct htable_iter *i)
{
assert(i->off < (size_t)1 << ht->bits);
assert(entry_is_valid(ht->table[i->off]));
ht->elems--;
ht->table[i->off] = HTABLE_DELETED;
ht->deleted++;
}
/*
* Wrapper code to make it easier to use this hash-table as map.
*/
void shl_htable_init(struct shl_htable *htable,
bool (*compare) (const void *a, const void *b),
size_t (*rehash)(const void *elem, void *priv),
void *priv)
{
struct htable *ht = (void*)&htable->htable;
htable->compare = compare;
htable_init(ht, rehash, priv);
}
void shl_htable_clear(struct shl_htable *htable,
void (*free_cb) (void *elem, void *ctx),
void *ctx)
{
struct htable *ht = (void*)&htable->htable;
htable_clear(ht, free_cb, ctx);
}
void shl_htable_visit(struct shl_htable *htable,
void (*visit_cb) (void *elem, void *ctx),
void *ctx)
{
struct htable *ht = (void*)&htable->htable;
htable_visit(ht, visit_cb, ctx);
}
bool shl_htable_lookup(struct shl_htable *htable, const void *obj, size_t hash,
void **out)
{
struct htable *ht = (void*)&htable->htable;
struct htable_iter i;
void *c;
for (c = htable_firstval(ht, &i, hash);
c;
c = htable_nextval(ht, &i, hash)) {
if (htable->compare(obj, c)) {
if (out)
*out = c;
return true;
}
}
return false;
}
int shl_htable_insert(struct shl_htable *htable, const void *obj, size_t hash)
{
struct htable *ht = (void*)&htable->htable;
bool b;
b = htable_add(ht, hash, (void*)obj);
return b ? 0 : -ENOMEM;
}
bool shl_htable_remove(struct shl_htable *htable, const void *obj, size_t hash,
void **out)
{
struct htable *ht = (void*)&htable->htable;
struct htable_iter i;
void *c;
for (c = htable_firstval(ht, &i, hash);
c;
c = htable_nextval(ht, &i, hash)) {
if (htable->compare(obj, c)) {
if (out)
*out = c;
htable_delval(ht, &i);
return true;
}
}
return false;
}
/*
* Helpers
*/
bool shl_htable_compare_uint(const void *a, const void *b)
{
return *(const unsigned int*)a == *(const unsigned int*)b;
}
size_t shl_htable_rehash_uint(const void *elem, void *priv)
{
return (size_t)*(const unsigned int*)elem;
}
bool shl_htable_compare_ulong(const void *a, const void *b)
{
return *(const unsigned long*)a == *(const unsigned long*)b;
}
size_t shl_htable_rehash_ulong(const void *elem, void *priv)
{
return (size_t)*(const unsigned long*)elem;
}
bool shl_htable_compare_str(const void *a, const void *b)
{
if (!*(char**)a || !*(char**)b)
return *(char**)a == *(char**)b;
else
return !strcmp(*(char**)a, *(char**)b);
}
/* DJB's hash function */
size_t shl_htable_rehash_str(const void *elem, void *priv)
{
const char *str = *(char**)elem;
size_t hash = 5381;
for ( ; str && *str; ++str)
hash = (hash << 5) + hash + (size_t)*str;
return hash;
}

304
src/shl_htable.h Normal file
View file

@ -0,0 +1,304 @@
/*
* SHL - Dynamic hash-table
*
* Copyright (c) 2010-2013 David Herrmann <dh.herrmann@gmail.com>
* Licensed under LGPLv2+ - see LICENSE_htable file for details
*/
/*
* Dynamic hash-table
* Implementation of a self-resizing hashtable to store arbitrary objects.
* Entries are not allocated by the table itself but are user-allocated. A
* single entry can be stored multiple times in the hashtable. No
* maintenance-members need to be embedded in user-allocated objects. However,
* the key (and optionally the hash) must be stored in the objects.
*
* Uses internally the htable from CCAN. See LICENSE_htable.
*/
#ifndef SHL_HTABLE_H
#define SHL_HTABLE_H
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
/* miscellaneous */
#define shl_htable_offsetof(pointer, type, member) ({ \
const typeof(((type*)0)->member) *__ptr = (pointer); \
(type*)(((char*)__ptr) - offsetof(type, member)); \
})
/* htable */
struct shl_htable_int {
size_t (*rehash)(const void *elem, void *priv);
void *priv;
unsigned int bits;
size_t elems, deleted, max, max_with_deleted;
/* These are the bits which are the same in all pointers. */
uintptr_t common_mask, common_bits;
uintptr_t perfect_bit;
uintptr_t *table;
};
struct shl_htable {
bool (*compare) (const void *a, const void *b);
struct shl_htable_int htable;
};
#define SHL_HTABLE_INIT(_obj, _compare, _rehash, _priv) \
{ \
.compare = (_compare), \
.htable = { \
.rehash = (_rehash), \
.priv = (_priv), \
.bits = 0, \
.elems = 0, \
.deleted = 0, \
.max = 0, \
.max_with_deleted = 0, \
.common_mask = -1, \
.common_bits = 0, \
.perfect_bit = 0, \
.table = &(_obj).htable.perfect_bit \
} \
}
void shl_htable_init(struct shl_htable *htable,
bool (*compare) (const void *a, const void *b),
size_t (*rehash)(const void *elem, void *priv),
void *priv);
void shl_htable_clear(struct shl_htable *htable,
void (*free_cb) (void *elem, void *ctx),
void *ctx);
void shl_htable_visit(struct shl_htable *htable,
void (*visit_cb) (void *elem, void *ctx),
void *ctx);
bool shl_htable_lookup(struct shl_htable *htable, const void *obj, size_t hash,
void **out);
int shl_htable_insert(struct shl_htable *htable, const void *obj, size_t hash);
bool shl_htable_remove(struct shl_htable *htable, const void *obj, size_t hash,
void **out);
size_t shl_htable_this_or_next(struct shl_htable *htable, size_t i);
void *shl_htable_get_entry(struct shl_htable *htable, size_t i);
#define SHL_HTABLE_FOREACH(_iter, _ht) for ( \
size_t htable__i = shl_htable_this_or_next((_ht), 0); \
(_iter = shl_htable_get_entry((_ht), htable__i)); \
htable__i = shl_htable_this_or_next((_ht), htable__i + 1) \
)
#define SHL_HTABLE_FOREACH_MACRO(_iter, _ht, _accessor) for ( \
size_t htable__i = shl_htable_this_or_next((_ht), 0); \
(_iter = shl_htable_get_entry((_ht), htable__i), \
_iter = _iter ? _accessor((void*)_iter) : NULL); \
htable__i = shl_htable_this_or_next((_ht), htable__i + 1) \
)
#define SHL_HTABLE_FIRST(_ht) \
shl_htable_get_entry((_ht), shl_htable_this_or_next((_ht), 0))
#define SHL_HTABLE_FIRST_MACRO(_ht, _accessor) ({ \
void *htable__i = shl_htable_get_entry((_ht), \
shl_htable_this_or_next((_ht), 0)); \
htable__i ? _accessor(htable__i) : NULL; })
/* uint htables */
#if SIZE_MAX < UINT_MAX
# error "'size_t' is smaller than 'unsigned int'"
#endif
bool shl_htable_compare_uint(const void *a, const void *b);
size_t shl_htable_rehash_uint(const void *elem, void *priv);
#define SHL_HTABLE_INIT_UINT(_obj) \
SHL_HTABLE_INIT((_obj), shl_htable_compare_uint, \
shl_htable_rehash_uint, \
NULL)
static inline void shl_htable_init_uint(struct shl_htable *htable)
{
shl_htable_init(htable, shl_htable_compare_uint,
shl_htable_rehash_uint, NULL);
}
static inline void shl_htable_clear_uint(struct shl_htable *htable,
void (*cb) (unsigned int *elem,
void *ctx),
void *ctx)
{
shl_htable_clear(htable, (void (*) (void*, void*))cb, ctx);
}
static inline void shl_htable_visit_uint(struct shl_htable *htable,
void (*cb) (unsigned int *elem,
void *ctx),
void *ctx)
{
shl_htable_visit(htable, (void (*) (void*, void*))cb, ctx);
}
static inline bool shl_htable_lookup_uint(struct shl_htable *htable,
unsigned int key,
unsigned int **out)
{
return shl_htable_lookup(htable, (const void*)&key, (size_t)key,
(void**)out);
}
static inline int shl_htable_insert_uint(struct shl_htable *htable,
const unsigned int *key)
{
return shl_htable_insert(htable, (const void*)key, (size_t)*key);
}
static inline bool shl_htable_remove_uint(struct shl_htable *htable,
unsigned int key,
unsigned int **out)
{
return shl_htable_remove(htable, (const void*)&key, (size_t)key,
(void**)out);
}
/* ulong htables */
#if SIZE_MAX < ULONG_MAX
# error "'size_t' is smaller than 'unsigned long'"
#endif
bool shl_htable_compare_ulong(const void *a, const void *b);
size_t shl_htable_rehash_ulong(const void *elem, void *priv);
#define SHL_HTABLE_INIT_ULONG(_obj) \
SHL_HTABLE_INIT((_obj), shl_htable_compare_ulong, \
shl_htable_rehash_ulong, \
NULL)
static inline void shl_htable_init_ulong(struct shl_htable *htable)
{
shl_htable_init(htable, shl_htable_compare_ulong,
shl_htable_rehash_ulong, NULL);
}
static inline void shl_htable_clear_ulong(struct shl_htable *htable,
void (*cb) (unsigned long *elem,
void *ctx),
void *ctx)
{
shl_htable_clear(htable, (void (*) (void*, void*))cb, ctx);
}
static inline void shl_htable_visit_ulong(struct shl_htable *htable,
void (*cb) (unsigned long *elem,
void *ctx),
void *ctx)
{
shl_htable_visit(htable, (void (*) (void*, void*))cb, ctx);
}
static inline bool shl_htable_lookup_ulong(struct shl_htable *htable,
unsigned long key,
unsigned long **out)
{
return shl_htable_lookup(htable, (const void*)&key, (size_t)key,
(void**)out);
}
static inline int shl_htable_insert_ulong(struct shl_htable *htable,
const unsigned long *key)
{
return shl_htable_insert(htable, (const void*)key, (size_t)*key);
}
static inline bool shl_htable_remove_ulong(struct shl_htable *htable,
unsigned long key,
unsigned long **out)
{
return shl_htable_remove(htable, (const void*)&key, (size_t)key,
(void**)out);
}
/* string htables */
bool shl_htable_compare_str(const void *a, const void *b);
size_t shl_htable_rehash_str(const void *elem, void *priv);
#define SHL_HTABLE_INIT_STR(_obj) \
SHL_HTABLE_INIT((_obj), shl_htable_compare_str, \
shl_htable_rehash_str, \
NULL)
static inline void shl_htable_init_str(struct shl_htable *htable)
{
shl_htable_init(htable, shl_htable_compare_str,
shl_htable_rehash_str, NULL);
}
static inline void shl_htable_clear_str(struct shl_htable *htable,
void (*cb) (char **elem,
void *ctx),
void *ctx)
{
shl_htable_clear(htable, (void (*) (void*, void*))cb, ctx);
}
static inline void shl_htable_visit_str(struct shl_htable *htable,
void (*cb) (char **elem,
void *ctx),
void *ctx)
{
shl_htable_visit(htable, (void (*) (void*, void*))cb, ctx);
}
static inline size_t shl_htable_hash_str(struct shl_htable *htable,
const char *str, size_t *hash)
{
size_t h;
if (hash && *hash) {
h = *hash;
} else {
h = htable->htable.rehash((const void*)&str, NULL);
if (hash)
*hash = h;
}
return h;
}
static inline bool shl_htable_lookup_str(struct shl_htable *htable,
const char *str, size_t *hash,
char ***out)
{
size_t h;
h = shl_htable_hash_str(htable, str, hash);
return shl_htable_lookup(htable, (const void*)&str, h, (void**)out);
}
static inline int shl_htable_insert_str(struct shl_htable *htable,
char **str, size_t *hash)
{
size_t h;
h = shl_htable_hash_str(htable, *str, hash);
return shl_htable_insert(htable, (const void*)str, h);
}
static inline bool shl_htable_remove_str(struct shl_htable *htable,
const char *str, size_t *hash,
char ***out)
{
size_t h;
h = shl_htable_hash_str(htable, str, hash);
return shl_htable_remove(htable, (const void*)&str, h, (void **)out);
}
#endif /* SHL_HTABLE_H */

239
src/shl_log.c Normal file
View file

@ -0,0 +1,239 @@
/*
* SHL - Log/Debug Interface
*
* Copyright (c) 2010-2013 David Herrmann <dh.herrmann@gmail.com>
* Dedicated to the Public Domain
*/
#include <errno.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include "shl_log.h"
/*
* Locking
* Dummies to implement locking. If we ever want lock-protected logging, these
* need to be provided by the user.
*/
static inline void log_lock()
{
}
static inline void log_unlock()
{
}
/*
* Time Management
* We print seconds and microseconds since application start for each
* log-message in case log_init_time() has been called.
*/
static struct timeval log__ftime;
static bool log__have_time(void)
{
return !(log__ftime.tv_sec == 0 && log__ftime.tv_usec == 0);
}
void log_init_time(void)
{
if (!log__have_time())
gettimeofday(&log__ftime, NULL);
}
static void log__time(long long *sec, long long *usec)
{
struct timeval t;
/* In case this is called in parallel to log_init_time(), we need to
* catch negative time-diffs. Other than that, this can be called
* unlocked. */
gettimeofday(&t, NULL);
*sec = t.tv_sec - log__ftime.tv_sec;
*usec = (long long)t.tv_usec - (long long)log__ftime.tv_usec;
if (*usec < 0) {
*sec -= 1;
if (*sec < 0)
*sec = 0;
*usec = 1000000 + *usec;
}
}
/*
* Default Values
* Several logging-parameters may be omitted by applications. To provide sane
* default values we provide constants here.
*
* LOG_SUBSYSTEM: By default no subsystem is specified
*/
const char *LOG_SUBSYSTEM = NULL;
/*
* Max Severity
* Messages with severities between log_max_sev and LOG_SEV_NUM (exclusive)
* are not logged, but discarded.
*/
unsigned int log_max_sev = LOG_NOTICE;
/*
* Forward declaration so we can use the locked-versions in other functions
* here. Be careful to avoid deadlocks, though.
* Also set default log-subsystem to "log" for all logging inside this API.
*/
static void log__submit(const char *file,
int line,
const char *func,
const char *subs,
unsigned int sev,
const char *format,
va_list args);
#define LOG_SUBSYSTEM "log"
/*
* Basic logger
* The log__submit function writes the message into the current log-target. It
* must be called with log__mutex locked.
* By default the current time elapsed since the first message was logged is
* prepended to the message. file, line and func information are appended to the
* message if sev == LOG_DEBUG.
* The subsystem, if not NULL, is prepended as "SUBS: " to the message and a
* newline is always appended by default. Multiline-messages are not allowed.
*/
static const char *log__sev2str[LOG_SEV_NUM] = {
[LOG_DEBUG] = "DEBUG",
[LOG_INFO] = "INFO",
[LOG_NOTICE] = "NOTICE",
[LOG_WARNING] = "WARNING",
[LOG_ERROR] = "ERROR",
[LOG_CRITICAL] = "CRITICAL",
[LOG_ALERT] = "ALERT",
[LOG_FATAL] = "FATAL",
};
static void log__submit(const char *file,
int line,
const char *func,
const char *subs,
unsigned int sev,
const char *format,
va_list args)
{
int saved_errno = errno;
const char *prefix = NULL;
FILE *out;
long long sec, usec;
out = stderr;
log__time(&sec, &usec);
if (sev < LOG_SEV_NUM && sev > log_max_sev)
return;
if (sev < LOG_SEV_NUM)
prefix = log__sev2str[sev];
if (prefix) {
if (subs) {
if (log__have_time())
fprintf(out, "[%.4lld.%.6lld] %s: %s: ",
sec, usec, prefix, subs);
else
fprintf(out, "%s: %s: ", prefix, subs);
} else {
if (log__have_time())
fprintf(out, "[%.4lld.%.6lld] %s: ",
sec, usec, prefix);
else
fprintf(out, "%s: ", prefix);
}
} else {
if (subs) {
if (log__have_time())
fprintf(out, "[%.4lld.%.6lld] %s: ",
sec, usec, subs);
else
fprintf(out, "%s: ", subs);
} else {
if (log__have_time())
fprintf(out, "[%.4lld.%.6lld] ", sec, usec);
}
}
errno = saved_errno;
vfprintf(out, format, args);
if (sev == LOG_DEBUG || sev <= LOG_WARNING) {
if (!func)
func = "<unknown>";
if (!file)
file = "<unknown>";
if (line < 0)
line = 0;
fprintf(out, " (%s() in %s:%d)\n", func, file, line);
} else {
fprintf(out, "\n");
}
}
void log_submit(const char *file,
int line,
const char *func,
const char *subs,
unsigned int sev,
const char *format,
va_list args)
{
int saved_errno = errno;
log_lock();
errno = saved_errno;
log__submit(file, line, func, subs, sev, format, args);
log_unlock();
errno = saved_errno;
}
void log_format(const char *file,
int line,
const char *func,
const char *subs,
unsigned int sev,
const char *format,
...)
{
int saved_errno = errno;
va_list list;
va_start(list, format);
log_lock();
errno = saved_errno;
log__submit(file, line, func, subs, sev, format, list);
log_unlock();
va_end(list);
errno = saved_errno;
}
void log_llog(void *data,
const char *file,
int line,
const char *func,
const char *subs,
unsigned int sev,
const char *format,
va_list args)
{
log_submit(file, line, func, subs, sev, format, args);
}

204
src/shl_log.h Normal file
View file

@ -0,0 +1,204 @@
/*
* SHL - Log/Debug Interface
*
* Copyright (c) 2010-2013 David Herrmann <dh.herrmann@gmail.com>
* Dedicated to the Public Domain
*/
/*
* Log/Debug Interface
* This interface provides basic logging to stderr.
*
* Define BUILD_ENABLE_DEBUG before including this header to enable
* debug-messages for this file.
*/
#ifndef SHL_LOG_H
#define SHL_LOG_H
#include <errno.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdlib.h>
enum log_severity {
LOG_FATAL = 0,
LOG_ALERT = 1,
LOG_CRITICAL = 2,
LOG_ERROR = 3,
LOG_WARNING = 4,
LOG_NOTICE = 5,
LOG_INFO = 6,
LOG_DEBUG = 7,
LOG_SEV_NUM,
};
/*
* Max Severity
* Messages with severities between log_max_sev and LOG_SEV_NUM (exclusive)
* are not logged, but discarded.
* Default: LOG_NOTICE
*/
extern unsigned int log_max_sev;
/*
* Timestamping
* Call this to initialize timestamps and cause all log-messages to be prefixed
* with a timestamp. If not called, no timestamps are added.
*/
void log_init_time(void);
/*
* Log-Functions
* These functions pass a log-message to the log-subsystem. Handy helpers are
* provided below. You almost never use these directly.
*
* log_submit:
* Submit the message to the log-subsystem. This is the backend of all other
* loggers.
*
* log_format:
* Same as log_submit but first converts the arguments into a va_list object.
*
* log_llog:
* Same as log_submit but used as connection to llog.
*
* log_dummyf:
* Dummy logger used for gcc var-arg validation.
*/
__attribute__((format(printf, 6, 0)))
void log_submit(const char *file,
int line,
const char *func,
const char *subs,
unsigned int sev,
const char *format,
va_list args);
__attribute__((format(printf, 6, 7)))
void log_format(const char *file,
int line,
const char *func,
const char *subs,
unsigned int sev,
const char *format,
...);
__attribute__((format(printf, 7, 0)))
void log_llog(void *data,
const char *file,
int line,
const char *func,
const char *subs,
unsigned int sev,
const char *format,
va_list args);
static inline __attribute__((format(printf, 2, 3)))
void log_dummyf(unsigned int sev, const char *format, ...)
{
}
/*
* Default values
* All helpers automatically pick-up the file, line, func and subsystem
* parameters for a log-message. file, line and func are generated with
* __FILE__, __LINE__ and __func__ and should almost never be replaced.
* The subsystem is by default an empty string. To overwrite this, add this
* line to the top of your source file:
* #define LOG_SUBSYSTEM "mysubsystem"
* Then all following log-messages will use this string as subsystem. You can
* define it before or after including this header.
*
* If you want to change one of these, you need to directly use log_submit and
* log_format. If you want the defaults for file, line and func you can use:
* log_format(LOG_DEFAULT_BASE, subsys, sev, format, ...);
* If you want all default values, use:
* log_format(LOG_DEFAULT, sev, format, ...);
*
* If you want to change a single value, this is the default line that is used
* internally. Adjust it to your needs:
* log_format(__FILE__, __LINE__, __func__, LOG_SUBSYSTEM, LOG_ERROR,
* "your format string: %s %d", "some args", 5, ...);
*
* log_printf is the same as log_format(LOG_DEFAULT, sev, format, ...) and is
* the most basic wrapper that you can use.
*/
#ifndef LOG_SUBSYSTEM
extern const char *LOG_SUBSYSTEM;
#endif
#define LOG_DEFAULT_BASE __FILE__, __LINE__, __func__
#define LOG_DEFAULT LOG_DEFAULT_BASE, LOG_SUBSYSTEM
#define log_printf(sev, format, ...) \
log_format(LOG_DEFAULT, (sev), (format), ##__VA_ARGS__)
/*
* Helpers
* These pick up all the default values and submit the message to the
* log-subsystem. The log_debug() function produces zero-code if
* BUILD_ENABLE_DEBUG is not defined. Therefore, it can be heavily used for
* debugging and will not have any side-effects.
* Even if disabled, parameters are evaluated! So it only produces zero code
* if there are no side-effects and the compiler can optimized it away.
*/
#ifdef BUILD_ENABLE_DEBUG
#define log_debug(format, ...) \
log_printf(LOG_DEBUG, (format), ##__VA_ARGS__)
#else
#define log_debug(format, ...) \
log_dummyf(LOG_DEBUG, (format), ##__VA_ARGS__)
#endif
#define log_info(format, ...) \
log_printf(LOG_INFO, (format), ##__VA_ARGS__)
#define log_notice(format, ...) \
log_printf(LOG_NOTICE, (format), ##__VA_ARGS__)
#define log_warning(format, ...) \
log_printf(LOG_WARNING, (format), ##__VA_ARGS__)
#define log_error(format, ...) \
log_printf(LOG_ERROR, (format), ##__VA_ARGS__)
#define log_critical(format, ...) \
log_printf(LOG_CRITICAL, (format), ##__VA_ARGS__)
#define log_alert(format, ...) \
log_printf(LOG_ALERT, (format), ##__VA_ARGS__)
#define log_fatal(format, ...) \
log_printf(LOG_FATAL, (format), ##__VA_ARGS__)
#define log_EINVAL() \
(log_error("invalid arguments"), -EINVAL)
#define log_vEINVAL() \
((void)log_EINVAL())
#define log_EFAULT() \
(log_error("internal operation failed"), -EFAULT)
#define log_vEFAULT() \
((void)log_EFAULT())
#define log_ENOMEM() \
(log_error("out of memory"), -ENOMEM)
#define log_vENOMEM() \
((void)log_ENOMEM())
#define log_EPIPE() \
(log_error("fd closed unexpectedly"), -EPIPE)
#define log_vEPIPE() \
((void)log_EPIPE())
#define log_ERRNO() \
(log_error("syscall failed (%d): %m", errno), -errno)
#define log_vERRNO() \
((void)log_ERRNO())
#define log_ERR(_r) \
(errno = -(_r), log_error("syscall failed (%d): %m", (_r)), (_r))
#define log_vERR(_r) \
((void)log_ERR(_r))
#endif /* SHL_LOG_H */

219
src/shl_macro.h Normal file
View file

@ -0,0 +1,219 @@
/*
* SHL - Macros
*
* Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com>
* Dedicated to the Public Domain
*/
/*
* Macros
*/
#ifndef SHL_MACRO_H
#define SHL_MACRO_H
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
/* sanity checks required for some macros */
#if __SIZEOF_POINTER__ != 4 && __SIZEOF_POINTER__ != 8
#error "Pointer size is neither 4 nor 8 bytes"
#endif
/* gcc attributes; look them up for more information */
#define _shl_printf_(_a, _b) __attribute__((__format__(printf, _a, _b)))
#define _shl_alloc_(...) __attribute__((__alloc_size__(__VA_ARGS__)))
#define _shl_sentinel_ __attribute__((__sentinel__))
#define _shl_noreturn_ __attribute__((__noreturn__))
#define _shl_unused_ __attribute__((__unused__))
#define _shl_pure_ __attribute__((__pure__))
#define _shl_const_ __attribute__((__const__))
#define _shl_deprecated_ __attribute__((__deprecated__))
#define _shl_packed_ __attribute__((__packed__))
#define _shl_malloc_ __attribute__((__malloc__))
#define _shl_weak_ __attribute__((__weak__))
#define _shl_likely_(_val) (__builtin_expect(!!(_val), 1))
#define _shl_unlikely_(_val) (__builtin_expect(!!(_val), 0))
#define _shl_public_ __attribute__((__visibility__("default")))
#define _shl_hidden_ __attribute__((__visibility__("hidden")))
#define _shl_weakref_(_val) __attribute__((__weakref__(#_val)))
#define _shl_cleanup_(_val) __attribute__((__cleanup__(_val)))
/* 2-level stringify helper */
#define SHL__STRINGIFY(_val) #_val
#define SHL_STRINGIFY(_val) SHL__STRINGIFY(_val)
/* 2-level concatenate helper */
#define SHL__CONCATENATE(_a, _b) _a ## _b
#define SHL_CONCATENATE(_a, _b) SHL__CONCATENATE(_a, _b)
/* unique identifier with prefix */
#define SHL_UNIQUE(_prefix) SHL_CONCATENATE(_prefix, __COUNTER__)
/* array element count */
#define SHL_ARRAY_LENGTH(_array) (sizeof(_array)/sizeof(*(_array)))
/* get parent pointer by container-type, member and member-pointer */
#define shl_container_of(_ptr, _type, _member) \
({ \
const typeof( ((_type *)0)->_member ) *__mptr = (_ptr); \
(_type *)( (char *)__mptr - offsetof(_type, _member) ); \
})
/* return maximum of two values and do strict type checking */
#define shl_max(_a, _b) \
({ \
typeof(_a) __a = (_a); \
typeof(_b) __b = (_b); \
(void) (&__a == &__b); \
__a > __b ? __a : __b; \
})
/* same as shl_max() but perform explicit cast beforehand */
#define shl_max_t(_type, _a, _b) \
({ \
_type __a = (_type)(_a); \
_type __b = (_type)(_b); \
__a > __b ? __a : __b; \
})
/* return minimum of two values and do strict type checking */
#define shl_min(_a, _b) \
({ \
typeof(_a) __a = (_a); \
typeof(_b) __b = (_b); \
(void) (&__a == &__b); \
__a < __b ? __a : __b; \
})
/* same as shl_min() but perform explicit cast beforehand */
#define shl_min_t(_type, _a, _b) \
({ \
_type __a = (_type)(_a); \
_type __b = (_type)(_b); \
__a < __b ? __a : __b; \
})
/* clamp value between low and high barriers */
#define shl_clamp(_val, _low, _high) \
({ \
typeof(_val) __v = (_val); \
typeof(_low) __l = (_low); \
typeof(_high) __h = (_high); \
(void) (&__v == &__l); \
(void) (&__v == &__h); \
((__v > __h) ? __h : ((__v < __l) ? __l : __v)); \
})
/* align to next higher power-of-2 (except for: 0 => 0, overflow => 0) */
static inline size_t SHL_ALIGN_POWER2(size_t u)
{
return 1ULL << ((sizeof(u) * 8ULL) - __builtin_clzll(u - 1ULL));
}
/* zero memory or type */
#define shl_memzero(_ptr, _size) (memset((_ptr), 0, (_size)))
#define shl_zero(_ptr) (shl_memzero(&(_ptr), sizeof(_ptr)))
/* ptr <=> uint casts */
#define SHL_PTR_TO_TYPE(_type, _ptr) ((_type)((uintptr_t)(_ptr)))
#define SHL_TYPE_TO_PTR(_type, _int) ((void*)((uintptr_t)(_int)))
#define SHL_PTR_TO_INT(_ptr) SHL_PTR_TO_TYPE(int, (_ptr))
#define SHL_INT_TO_PTR(_ptr) SHL_TYPE_TO_PTR(int, (_ptr))
#define SHL_PTR_TO_UINT(_ptr) SHL_PTR_TO_TYPE(unsigned int, (_ptr))
#define SHL_UINT_TO_PTR(_ptr) SHL_TYPE_TO_PTR(unsigned int, (_ptr))
#define SHL_PTR_TO_LONG(_ptr) SHL_PTR_TO_TYPE(long, (_ptr))
#define SHL_LONG_TO_PTR(_ptr) SHL_TYPE_TO_PTR(long, (_ptr))
#define SHL_PTR_TO_ULONG(_ptr) SHL_PTR_TO_TYPE(unsigned long, (_ptr))
#define SHL_ULONG_TO_PTR(_ptr) SHL_TYPE_TO_PTR(unsigned long, (_ptr))
#define SHL_PTR_TO_S32(_ptr) SHL_PTR_TO_TYPE(int32_t, (_ptr))
#define SHL_S32_TO_PTR(_ptr) SHL_TYPE_TO_PTR(int32_t, (_ptr))
#define SHL_PTR_TO_U32(_ptr) SHL_PTR_TO_TYPE(uint32_t, (_ptr))
#define SHL_U32_TO_PTR(_ptr) SHL_TYPE_TO_PTR(uint32_t, (_ptr))
#define SHL_PTR_TO_S64(_ptr) SHL_PTR_TO_TYPE(int64_t, (_ptr))
#define SHL_S64_TO_PTR(_ptr) SHL_TYPE_TO_PTR(int64_t, (_ptr))
#define SHL_PTR_TO_U64(_ptr) SHL_PTR_TO_TYPE(uint64_t, (_ptr))
#define SHL_U64_TO_PTR(_ptr) SHL_TYPE_TO_PTR(uint64_t, (_ptr))
/* compile-time assertions */
#define shl_assert_cc(_expr) static_assert(_expr, #_expr)
/*
* Safe Multiplications
* Multiplications are subject to overflows. These helpers guarantee that the
* multiplication can be done safely and return -ERANGE if not.
*
* Note: This is horribly slow for ull/uint64_t as we need a division to test
* for overflows. Take that into account when using these. For smaller integers,
* we can simply use an upcast-multiplication which gcc should be smart enough
* to optimize.
*/
#define SHL__REAL_MULT(_max, _val, _factor) \
({ \
(_factor == 0 || *(_val) <= (_max) / (_factor)) ? \
((*(_val) *= (_factor)), 0) : \
-ERANGE; \
})
#define SHL__UPCAST_MULT(_type, _max, _val, _factor) \
({ \
_type v = *(_val) * (_type)(_factor); \
(v <= (_max)) ? \
((*(_val) = v), 0) : \
-ERANGE; \
})
static inline int shl_mult_ull(unsigned long long *val,
unsigned long long factor)
{
return SHL__REAL_MULT(ULLONG_MAX, val, factor);
}
static inline int shl_mult_ul(unsigned long *val, unsigned long factor)
{
#if ULONG_MAX < ULLONG_MAX
return SHL__UPCAST_MULT(unsigned long long, ULONG_MAX, val, factor);
#else
shl_assert_cc(sizeof(unsigned long) == sizeof(unsigned long long));
return shl_mult_ull((unsigned long long*)val, factor);
#endif
}
static inline int shl_mult_u(unsigned int *val, unsigned int factor)
{
#if UINT_MAX < ULONG_MAX
return SHL__UPCAST_MULT(unsigned long, UINT_MAX, val, factor);
#elif UINT_MAX < ULLONG_MAX
return SHL__UPCAST_MULT(unsigned long long, UINT_MAX, val, factor);
#else
shl_assert_cc(sizeof(unsigned int) == sizeof(unsigned long long));
return shl_mult_ull(val, factor);
#endif
}
static inline int shl_mult_u64(uint64_t *val, uint64_t factor)
{
return SHL__REAL_MULT(UINT64_MAX, val, factor);
}
static inline int shl_mult_u32(uint32_t *val, uint32_t factor)
{
return SHL__UPCAST_MULT(uint_fast64_t, UINT32_MAX, val, factor);
}
static inline int shl_mult_u16(uint16_t *val, uint16_t factor)
{
return SHL__UPCAST_MULT(uint_fast32_t, UINT16_MAX, val, factor);
}
static inline int shl_mult_u8(uint8_t *val, uint8_t factor)
{
return SHL__UPCAST_MULT(uint_fast16_t, UINT8_MAX, val, factor);
}
#endif /* SHL_MACRO_H */

323
src/shl_util.c Normal file
View file

@ -0,0 +1,323 @@
/*
* SHL - Utility Helpers
*
* Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com>
* Dedicated to the Public Domain
*/
/*
* Utility Helpers
*/
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "shl_macro.h"
#include "shl_util.h"
/*
* Strict atoi()
* These helpers implement a strict version of atoi() (or strtol()). They only
* parse digit/alpha characters. No whitespace or other characters are parsed.
* The unsigned-variants explicitly forbid leading +/- signs. Use the signed
* variants to allow these.
* Base-prefix parsing is only done if base=0 is requested. Otherwise,
* base-prefixes are forbidden.
* The input string must be ASCII compatbile (which includes UTF8).
*
* We also always check for overflows and return errors (but continue parsing!)
* so callers can catch it correctly.
*
* Additionally, we allow "length" parameters so strings do not necessarily have
* to be zero-terminated. We have wrappers which skip this by passing strlen().
*/
int shl_ctoi(char ch, unsigned int base)
{
unsigned int v;
switch (ch) {
case '0'...'9':
v = ch - '0';
break;
case 'a'...'z':
v = ch - 'a' + 10;
break;
case 'A'...'Z':
v = ch - 'A' + 10;
break;
default:
return -EINVAL;
}
if (v >= base)
return -EINVAL;
return v;
}
/* figure out base and skip prefix */
static unsigned int shl__skip_base(const char **str, size_t *len)
{
if (*len > 1) {
if ((*str)[0] == '0') {
if (shl_ctoi((*str)[1], 8) >= 0) {
*str += 1;
*len -= 1;
return 8;
}
}
}
if (*len > 2) {
if ((*str)[0] == '0' && (*str)[1] == 'x') {
if (shl_ctoi((*str)[2], 16) >= 0) {
*str += 2;
*len -= 2;
return 16;
}
}
}
return 10;
}
int shl_atoi_ulln(const char *str,
size_t len,
unsigned int base,
const char **next,
unsigned long long *out)
{
bool huge;
uint32_t val1;
unsigned long long val2;
size_t pos;
int r, c;
/* We use u32 as storage first so we have fast mult-overflow checks. We
* cast up to "unsigned long long" once we exceed UINT32_MAX. Overflow
* checks will get pretty slow for non-power2 bases, though. */
huge = false;
val1 = 0;
val2 = 0;
r = 0;
if (base > 36) {
if (next)
*next = str;
if (out)
*out = 0;
return -EINVAL;
}
if (base == 0)
base = shl__skip_base(&str, &len);
for (pos = 0; pos < len; ++pos) {
c = shl_ctoi(str[pos], base);
if (c < 0)
break;
/* skip calculations on error */
if (r < 0)
continue;
if (!huge) {
val2 = val1;
r = shl_mult_u32(&val1, base);
if (r >= 0 && val1 + c >= val1)
val1 += c;
else
huge = true;
}
if (huge) {
r = shl_mult_ull(&val2, base);
if (r >= 0 && val2 + c >= val2)
val2 += c;
}
}
if (next)
*next = (char*)&str[pos];
if (out) {
if (r < 0)
*out = ULLONG_MAX;
else if (huge)
*out = val2;
else
*out = val1;
}
return r;
}
int shl_atoi_uln(const char *str,
size_t len,
unsigned int base,
const char **next,
unsigned long *out)
{
unsigned long long val;
int r;
r = shl_atoi_ulln(str, len, base, next, &val);
if (r >= 0 && val > ULONG_MAX)
r = -ERANGE;
if (out)
*out = shl_min(val, (unsigned long long)ULONG_MAX);
return r;
}
int shl_atoi_un(const char *str,
size_t len,
unsigned int base,
const char **next,
unsigned int *out)
{
unsigned long long val;
int r;
r = shl_atoi_ulln(str, len, base, next, &val);
if (r >= 0 && val > UINT_MAX)
r = -ERANGE;
if (out)
*out = shl_min(val, (unsigned long long)UINT_MAX);
return r;
}
int shl_atoi_zn(const char *str,
size_t len,
unsigned int base,
const char **next,
size_t *out)
{
unsigned long long val;
int r;
r = shl_atoi_ulln(str, len, base, next, &val);
if (r >= 0 && val > SIZE_MAX)
r = -ERANGE;
if (out)
*out = shl_min(val, (unsigned long long)SIZE_MAX);
return r;
}
/*
* Greedy Realloc
* The greedy-realloc helpers simplify power-of-2 buffer allocations. If you
* have a dynamic array, simply use shl_greedy_realloc() for re-allocations
* and it makes sure your buffer-size is always a multiple of 2 and is big
* enough for your new entries.
* Default size is 64, but you can initialize your buffer to a bigger default
* if you need.
*/
void *shl_greedy_realloc(void **mem, size_t *size, size_t need)
{
size_t nsize;
void *p;
if (*size >= need)
return *mem;
nsize = SHL_ALIGN_POWER2(shl_max_t(size_t, 64U, need));
if (nsize == 0)
return NULL;
p = realloc(*mem, nsize);
if (!p)
return NULL;
*mem = p;
*size = nsize;
return p;
}
void *shl_greedy_realloc0(void **mem, size_t *size, size_t need)
{
size_t prev = *size;
uint8_t *p;
p = shl_greedy_realloc(mem, size, need);
if (!p)
return NULL;
if (*size > prev)
shl_memzero(&p[prev], *size - prev);
return p;
}
/*
* String Helpers
*/
char *shl_strcat(const char *first, const char *second)
{
size_t flen, slen;
char *str;
if (!first)
first = "";
if (!second)
second = "";
flen = strlen(first);
slen = strlen(second);
if (flen + slen + 1 <= flen)
return NULL;
str = malloc(flen + slen + 1);
if (!str)
return NULL;
strcpy(str, first);
strcpy(&str[flen], second);
return str;
}
char *shl_strjoin(const char *first, ...) {
va_list args;
size_t len, l;
const char *arg;
char *str, *p;
va_start(args, first);
for (arg = first, len = 0; arg; arg = va_arg(args, const char*)) {
l = strlen(arg);
if (len + l < len)
return NULL;
len += l;
}
va_end(args);
str = malloc(len + 1);
if (!str)
return NULL;
va_start(args, first);
for (arg = first, p = str; arg; arg = va_arg(args, const char*))
p = stpcpy(p, arg);
va_end(args);
*p = 0;
return str;
}

96
src/shl_util.h Normal file
View file

@ -0,0 +1,96 @@
/*
* SHL - Utility Helpers
*
* Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com>
* Dedicated to the Public Domain
*/
/*
* Utility Helpers
*/
#ifndef SHL_UTIL_H
#define SHL_UTIL_H
#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
/* strict atoi */
int shl_ctoi(char ch, unsigned int base);
int shl_atoi_ulln(const char *str,
size_t len,
unsigned int base,
const char **next,
unsigned long long *out);
int shl_atoi_uln(const char *str,
size_t len,
unsigned int base,
const char **next,
unsigned long *out);
int shl_atoi_un(const char *str,
size_t len,
unsigned int base,
const char **next,
unsigned int *out);
int shl_atoi_zn(const char *str,
size_t len,
unsigned int base,
const char **next,
size_t *out);
static inline int shl_atoi_ull(const char *str,
unsigned int base,
const char **next,
unsigned long long *out)
{
return shl_atoi_ulln(str, strlen(str), base, next, out);
}
static inline int shl_atoi_ul(const char *str,
unsigned int base,
const char **next,
unsigned long *out)
{
return shl_atoi_uln(str, strlen(str), base, next, out);
}
static inline int shl_atoi_u(const char *str,
unsigned int base,
const char **next,
unsigned int *out)
{
return shl_atoi_un(str, strlen(str), base, next, out);
}
static inline int shl_atoi_z(const char *str,
unsigned int base,
const char **next,
size_t *out)
{
return shl_atoi_zn(str, strlen(str), base, next, out);
}
/* greedy alloc */
void *shl_greedy_realloc(void **mem, size_t *size, size_t need);
void *shl_greedy_realloc0(void **mem, size_t *size, size_t need);
/* string helpers */
char *shl_strcat(const char *first, const char *second);
char *shl_strjoin(const char *first, ...);
static inline char *shl_startswith(const char *str, const char *prefix)
{
if (!strncmp(str, prefix, strlen(prefix)))
return (char*)str + strlen(prefix);
else
return NULL;
}
#endif /* SHL_UTIL_H */