blob: 4429e8e4aaf8e0bbd07ac4ec0ca1501c402c43a7 [file] [log] [blame]
/*
*
* Embedded Linux library
*
* Copyright (C) 2018 Intel Corporation. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; 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
#define _GNU_SOURCE
#include <linux/types.h>
#include <sys/uio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <stddef.h>
#include <linux/if_packet.h>
#include <netinet/udp.h>
#include <netinet/ip.h>
#include <linux/if_ether.h>
#include "io.h"
#include "util.h"
#include "private.h"
#include "dhcp-private.h"
struct dhcp_default_transport {
struct dhcp_transport super;
struct l_io *io;
};
/*
* For efficiency and simplicity of implementation, this function assumes that
* only the last buffer can have an odd number of bytes
*/
uint16_t _dhcp_checksumv(const struct iovec *iov, size_t iov_cnt)
{
uint32_t sum = 0;
size_t i, j;
size_t len = 0;
for (j = 0; j < iov_cnt; j++) {
const uint16_t *check = iov[j].iov_base;
len += iov[j].iov_len;
for (i = 0; i < iov[j].iov_len / 2; i++)
sum += check[i];
}
if (len & 0x01) {
const uint8_t *odd = iov[j].iov_base;
sum += odd[len - 1];
}
while (sum >> 16)
sum = (sum & 0xffff) + (sum >> 16);
return ~sum;
}
uint16_t _dhcp_checksum(const void *buf, size_t len)
{
struct iovec iov[1];
iov[0].iov_base = (void *) buf;
iov[0].iov_len = len;
return _dhcp_checksumv(iov, 1);
}
static bool _dhcp_default_transport_read_handler(struct l_io *io,
void *userdata)
{
struct dhcp_default_transport *transport = userdata;
int fd = l_io_get_fd(io);
char buf[1024];
ssize_t len;
len = read(fd, buf, sizeof(buf));
if (len < 0)
return false;
if (transport->super.rx_cb)
transport->super.rx_cb(buf, len, transport->super.rx_data);
return true;
}
static int kernel_socket_open(uint32_t ifindex,
const char *ifname, uint32_t port)
{
int s;
int err;
int one = 1;
struct sockaddr_in saddr;
s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (s < 0)
return -errno;
if (setsockopt(s, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one)) < 0)
goto error;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0)
goto error;
if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE,
ifname, strlen(ifname) + 1) < 0)
goto error;
memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(port);
saddr.sin_addr.s_addr = INADDR_ANY;
err = bind(s, (struct sockaddr *) &saddr, sizeof(struct sockaddr_in));
if (err < 0)
goto error;
return s;
error:
TEMP_FAILURE_RETRY(close(s));
return -errno;
}
static int _dhcp_default_transport_open(struct dhcp_transport *s,
uint32_t ifindex, const char *ifname,
uint32_t port)
{
struct dhcp_default_transport *transport =
l_container_of(s, struct dhcp_default_transport, super);
int fd;
if (transport->io)
return -EALREADY;
fd = kernel_socket_open(ifindex, ifname, port);
if (fd < 0)
return fd;
transport->io = l_io_new(fd);
l_io_set_close_on_destroy(transport->io, true);
l_io_set_read_handler(transport->io,
_dhcp_default_transport_read_handler,
transport, NULL);
transport->super.ifindex = ifindex;
return 0;
}
static void dhcp_set_ip_udp_headers(struct iphdr *ip, struct udphdr *udp,
uint32_t saddr, uint16_t sport,
uint32_t daddr, uint16_t dport,
const void *data, size_t len)
{
struct iovec iov[3];
ip->version = IPVERSION;
ip->ihl = sizeof(struct iphdr) / 4;
ip->tot_len = L_CPU_TO_BE16(len + sizeof(*ip) + sizeof(*udp));
ip->protocol = IPPROTO_UDP;
ip->saddr = L_CPU_TO_BE32(saddr);
ip->daddr = L_CPU_TO_BE32(daddr);
udp->source = L_CPU_TO_BE16(sport);
udp->dest = L_CPU_TO_BE16(dport);
udp->len = L_CPU_TO_BE16(len + sizeof(*udp));
ip->check = udp->len;
iov[0].iov_base = &ip->ttl;
iov[0].iov_len = sizeof(*ip) - 8;
iov[1].iov_base = udp;
iov[1].iov_len = sizeof(*udp);
iov[2].iov_base = (void *) data;
iov[2].iov_len = len;
udp->check = _dhcp_checksumv(iov, 3);
ip->ttl = IPDEFTTL;
ip->check = 0;
iov[0].iov_base = ip;
iov[0].iov_len = sizeof(*ip);
ip->check = _dhcp_checksumv(iov, 1);
}
static int _dhcp_default_transport_broadcast(struct dhcp_transport *s,
uint32_t saddr, uint16_t sport,
uint32_t daddr, uint16_t dport,
const void *data, size_t len)
{
int sk;
struct sockaddr_ll addr;
struct iovec iov[3];
struct iphdr ip;
struct udphdr udp;
struct msghdr msg;
sk = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IP));
if (sk < 0)
return -errno;
memset(&addr, 0, sizeof(addr));
addr.sll_family = AF_PACKET;
addr.sll_protocol = htons(ETH_P_IP);
addr.sll_ifindex = s->ifindex;
addr.sll_halen = ETH_ALEN;
memset(addr.sll_addr, 0xff, ETH_ALEN);
if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0)
goto error;
dhcp_set_ip_udp_headers(&ip, &udp,
saddr, sport, daddr, dport, data, len);
iov[0].iov_base = &ip;
iov[0].iov_len = sizeof(ip);
iov[1].iov_base = &udp;
iov[1].iov_len = sizeof(udp);
iov[2].iov_base = (void *) data;
iov[2].iov_len = len;
memset(&msg, 0, sizeof(msg));
msg.msg_name = &addr;
msg.msg_namelen = sizeof(addr);
msg.msg_iov = iov;
msg.msg_iovlen = 3;
if (sendmsg(sk, &msg, 0) < 0)
goto error;
errno = 0;
error:
close(sk);
return -errno;
}
static int _dhcp_default_transport_send(struct dhcp_transport *s,
const struct sockaddr_in *dest,
const void *data, size_t len)
{
struct dhcp_default_transport *transport =
l_container_of(s, struct dhcp_default_transport, super);
int fd = l_io_get_fd(transport->io);
int err;
err = sendto(fd, data, len, 0,
(const struct sockaddr *) dest, sizeof(*dest));
if (err < 0)
return -errno;
return 0;
}
static void _dhcp_default_transport_close(struct dhcp_transport *s)
{
struct dhcp_default_transport *transport =
l_container_of(s, struct dhcp_default_transport, super);
l_io_destroy(transport->io);
transport->io = NULL;
}
struct dhcp_transport *_dhcp_default_transport_new(void)
{
struct dhcp_default_transport *transport;
transport = l_new(struct dhcp_default_transport, 1);
transport->super.open = _dhcp_default_transport_open;
transport->super.close = _dhcp_default_transport_close;
transport->super.send = _dhcp_default_transport_send;
transport->super.broadcast = _dhcp_default_transport_broadcast;
return &transport->super;
}
void _dhcp_transport_free(struct dhcp_transport *transport)
{
if (!transport)
return;
if (transport->close)
transport->close(transport);
l_free(transport);
}
void _dhcp_transport_set_rx_callback(struct dhcp_transport *transport,
dhcp_transport_rx_cb_t rx_cb,
void *userdata)
{
if (!transport)
return;
transport->rx_cb = rx_cb;
transport->rx_data = userdata;
}