mirror of
https://github.com/clearlinux/kvmtool.git
synced 2026-06-16 02:15:47 +00:00
241 lines
4.7 KiB
C
241 lines
4.7 KiB
C
#include "kvm/uip.h"
|
|
|
|
#include <kvm/kvm.h>
|
|
#include <linux/virtio_net.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/list.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/epoll.h>
|
|
#include <fcntl.h>
|
|
|
|
#define UIP_UDP_MAX_EVENTS 1000
|
|
|
|
static struct uip_udp_socket *uip_udp_socket_find(struct uip_tx_arg *arg, u32 sip, u32 dip, u16 sport, u16 dport)
|
|
{
|
|
struct list_head *sk_head;
|
|
struct uip_udp_socket *sk;
|
|
struct mutex *sk_lock;
|
|
struct epoll_event ev;
|
|
int flags;
|
|
int ret;
|
|
|
|
sk_head = &arg->info->udp_socket_head;
|
|
sk_lock = &arg->info->udp_socket_lock;
|
|
|
|
/*
|
|
* Find existing sk
|
|
*/
|
|
mutex_lock(sk_lock);
|
|
list_for_each_entry(sk, sk_head, list) {
|
|
if (sk->sip == sip && sk->dip == dip && sk->sport == sport && sk->dport == dport) {
|
|
mutex_unlock(sk_lock);
|
|
return sk;
|
|
}
|
|
}
|
|
mutex_unlock(sk_lock);
|
|
|
|
/*
|
|
* Allocate new one
|
|
*/
|
|
sk = malloc(sizeof(*sk));
|
|
memset(sk, 0, sizeof(*sk));
|
|
|
|
sk->lock = sk_lock;
|
|
|
|
sk->fd = socket(AF_INET, SOCK_DGRAM, 0);
|
|
if (sk->fd < 0)
|
|
goto out;
|
|
|
|
/*
|
|
* Set non-blocking
|
|
*/
|
|
flags = fcntl(sk->fd, F_GETFL, 0);
|
|
flags |= O_NONBLOCK;
|
|
fcntl(sk->fd, F_SETFL, flags);
|
|
|
|
/*
|
|
* Add sk->fd to epoll_wait
|
|
*/
|
|
ev.events = EPOLLIN;
|
|
ev.data.fd = sk->fd;
|
|
ev.data.ptr = sk;
|
|
if (arg->info->udp_epollfd <= 0)
|
|
arg->info->udp_epollfd = epoll_create(UIP_UDP_MAX_EVENTS);
|
|
ret = epoll_ctl(arg->info->udp_epollfd, EPOLL_CTL_ADD, sk->fd, &ev);
|
|
if (ret == -1)
|
|
pr_warning("epoll_ctl error");
|
|
|
|
sk->addr.sin_family = AF_INET;
|
|
sk->addr.sin_addr.s_addr = dip;
|
|
sk->addr.sin_port = dport;
|
|
|
|
sk->sip = sip;
|
|
sk->dip = dip;
|
|
sk->sport = sport;
|
|
sk->dport = dport;
|
|
|
|
mutex_lock(sk_lock);
|
|
list_add_tail(&sk->list, sk_head);
|
|
mutex_unlock(sk_lock);
|
|
|
|
return sk;
|
|
|
|
out:
|
|
free(sk);
|
|
return NULL;
|
|
}
|
|
|
|
static int uip_udp_socket_send(struct uip_udp_socket *sk, struct uip_udp *udp)
|
|
{
|
|
int len;
|
|
int ret;
|
|
|
|
len = ntohs(udp->len) - uip_udp_hdrlen(udp);
|
|
|
|
ret = sendto(sk->fd, udp->payload, len, 0, (struct sockaddr *)&sk->addr, sizeof(sk->addr));
|
|
if (ret != len)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int uip_udp_make_pkg(struct uip_info *info, struct uip_udp_socket *sk, struct uip_buf *buf, u8* payload, int payload_len)
|
|
{
|
|
struct uip_eth *eth2;
|
|
struct uip_udp *udp2;
|
|
struct uip_ip *ip2;
|
|
|
|
/*
|
|
* Cook a ethernet frame
|
|
*/
|
|
eth2 = (struct uip_eth *)buf->eth;
|
|
ip2 = (struct uip_ip *)(buf->eth);
|
|
udp2 = (struct uip_udp *)(ip2 + 1);
|
|
|
|
eth2->src = info->host_mac;
|
|
eth2->dst = info->guest_mac;
|
|
eth2->type = htons(UIP_ETH_P_IP);
|
|
|
|
ip2->vhl = UIP_IP_VER_4 | UIP_IP_HDR_LEN;
|
|
ip2->tos = 0;
|
|
ip2->id = 0;
|
|
ip2->flgfrag = 0;
|
|
ip2->ttl = UIP_IP_TTL;
|
|
ip2->proto = UIP_IP_P_UDP;
|
|
ip2->csum = 0;
|
|
|
|
ip2->sip = sk->dip;
|
|
ip2->dip = sk->sip;
|
|
udp2->sport = sk->dport;
|
|
udp2->dport = sk->sport;
|
|
|
|
udp2->len = htons(payload_len + uip_udp_hdrlen(udp2));
|
|
udp2->csum = 0;
|
|
|
|
if (payload)
|
|
memcpy(udp2->payload, payload, payload_len);
|
|
|
|
ip2->len = udp2->len + htons(uip_ip_hdrlen(ip2));
|
|
ip2->csum = uip_csum_ip(ip2);
|
|
udp2->csum = uip_csum_udp(ip2, udp2);
|
|
|
|
/*
|
|
* virtio_net_hdr
|
|
*/
|
|
buf->vnet_len = info->vnet_hdr_len;
|
|
memset(buf->vnet, 0, buf->vnet_len);
|
|
|
|
buf->eth_len = ntohs(ip2->len) + uip_eth_hdrlen(&ip2->eth);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void *uip_udp_socket_thread(void *p)
|
|
{
|
|
struct epoll_event events[UIP_UDP_MAX_EVENTS];
|
|
struct uip_udp_socket *sk;
|
|
struct uip_info *info;
|
|
struct uip_buf *buf;
|
|
int payload_len;
|
|
u8 *payload;
|
|
int nfds;
|
|
int i;
|
|
|
|
kvm__set_thread_name("uip-udp");
|
|
|
|
info = p;
|
|
|
|
do {
|
|
payload = malloc(UIP_MAX_UDP_PAYLOAD);
|
|
} while (!payload);
|
|
|
|
while (1) {
|
|
nfds = epoll_wait(info->udp_epollfd, events, UIP_UDP_MAX_EVENTS, -1);
|
|
|
|
if (nfds == -1)
|
|
continue;
|
|
|
|
for (i = 0; i < nfds; i++) {
|
|
|
|
sk = events[i].data.ptr;
|
|
payload_len = recvfrom(sk->fd, payload, UIP_MAX_UDP_PAYLOAD, 0, NULL, NULL);
|
|
if (payload_len < 0)
|
|
continue;
|
|
|
|
/*
|
|
* Get free buffer to send data to guest
|
|
*/
|
|
buf = uip_buf_get_free(info);
|
|
|
|
uip_udp_make_pkg(info, sk, buf, payload, payload_len);
|
|
|
|
/*
|
|
* Send data received from socket to guest
|
|
*/
|
|
uip_buf_set_used(info, buf);
|
|
}
|
|
}
|
|
|
|
free(payload);
|
|
pthread_exit(NULL);
|
|
return NULL;
|
|
}
|
|
|
|
int uip_tx_do_ipv4_udp(struct uip_tx_arg *arg)
|
|
{
|
|
struct uip_udp_socket *sk;
|
|
struct uip_info *info;
|
|
struct uip_udp *udp;
|
|
struct uip_ip *ip;
|
|
int ret;
|
|
|
|
ip = (struct uip_ip *)(arg->eth);
|
|
udp = uip_ip_proto(ip);
|
|
info = arg->info;
|
|
|
|
if (uip_udp_is_dhcp(udp)) {
|
|
uip_tx_do_ipv4_udp_dhcp(arg);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Find socket we have allocated before, otherwise allocate one
|
|
*/
|
|
sk = uip_udp_socket_find(arg, ip->sip, ip->dip, udp->sport, udp->dport);
|
|
if (!sk)
|
|
return -1;
|
|
|
|
/*
|
|
* Send out UDP data to remote host
|
|
*/
|
|
ret = uip_udp_socket_send(sk, udp);
|
|
if (ret)
|
|
/* FIXME: icmp port unreach */
|
|
return -1;
|
|
|
|
if (!info->udp_thread)
|
|
pthread_create(&info->udp_thread, NULL, uip_udp_socket_thread, (void *)info);
|
|
|
|
return 0;
|
|
}
|