90c0acce05
Add TUNSETCARRIER ioctl, then we may change the carrier state of TUN dynamically. Note that we don't need an ioctl for getting carrier, it can be done by SIOCGIFFLAGS already. Ref: https://github.com/torvalds/linux/blob/v6.10/drivers/net/tun.c#L3374-L3380 Signed-off-by: Zhe Weng <wengzhe@xiaomi.com>
1320 lines
34 KiB
C
1320 lines
34 KiB
C
/****************************************************************************
|
|
* drivers/net/tun.c
|
|
*
|
|
* Copyright (C) 2015-2016 Max Nekludov. All rights reserved.
|
|
* Author: Max Nekludov <macscomp@gmail.com>
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* 3. Neither the name NuttX nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <time.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <fcntl.h>
|
|
#include <debug.h>
|
|
#include <poll.h>
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <net/if.h>
|
|
|
|
#ifdef CONFIG_NET_PKT
|
|
# include <nuttx/net/pkt.h>
|
|
#endif
|
|
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/irq.h>
|
|
#include <nuttx/mutex.h>
|
|
#include <nuttx/wqueue.h>
|
|
#include <nuttx/mm/iob.h>
|
|
#include <nuttx/net/net.h>
|
|
#include <nuttx/net/ip.h>
|
|
#include <nuttx/net/netdev.h>
|
|
#include <nuttx/net/ethernet.h>
|
|
#include <nuttx/net/tun.h>
|
|
|
|
#if defined(CONFIG_NET) && defined(CONFIG_NET_TUN)
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
/* If processing is not done at the interrupt level, then high priority
|
|
* work queue support is required.
|
|
*/
|
|
|
|
#if !defined(CONFIG_SCHED_WORKQUEUE)
|
|
# error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
|
|
#endif
|
|
|
|
/* The low priority work queue is preferred. If it is not enabled, LPWORK
|
|
* will be the same as HPWORK.
|
|
*
|
|
* NOTE: However, the network should NEVER run on the high priority work
|
|
* queue! That queue is intended only to service short back end interrupt
|
|
* processing that never suspends. Suspending the high priority work queue
|
|
* may bring the system to its knees!
|
|
*/
|
|
|
|
#define TUNWORK LPWORK
|
|
|
|
/* CONFIG_TUN_NINTERFACES determines the number of physical interfaces
|
|
* that will be supported.
|
|
*/
|
|
|
|
#ifndef CONFIG_TUN_NINTERFACES
|
|
# define CONFIG_TUN_NINTERFACES 1
|
|
#endif
|
|
|
|
/* This is a helper pointer for accessing the contents of the Ethernet
|
|
* header.
|
|
*/
|
|
|
|
#ifdef CONFIG_NET_ETHERNET
|
|
# define ETHBUF ((FAR struct eth_hdr_s *)NETLLBUF)
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
/* The tun_device_s encapsulates all state information for a single hardware
|
|
* interface
|
|
*/
|
|
|
|
struct tun_device_s
|
|
{
|
|
bool bifup; /* true:ifup false:ifdown */
|
|
bool read_wait;
|
|
bool write_wait;
|
|
struct work_s work; /* For deferring poll work to the work queue */
|
|
FAR struct pollfd *poll_fds;
|
|
mutex_t lock;
|
|
sem_t read_wait_sem;
|
|
sem_t write_wait_sem;
|
|
size_t read_d_len;
|
|
size_t write_d_len;
|
|
|
|
/* These packet buffer arrays required 16-bit alignment. That alignment
|
|
* is assured only by the preceding wide data types.
|
|
*/
|
|
|
|
FAR struct iob_s *read_buf;
|
|
FAR struct iob_s *write_buf;
|
|
|
|
/* This holds the information visible to the NuttX network */
|
|
|
|
struct net_driver_s dev; /* Interface understood by the network */
|
|
};
|
|
|
|
struct tun_driver_s
|
|
{
|
|
mutex_t lock;
|
|
uint8_t free_tuns;
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* Common TX logic */
|
|
|
|
static void tun_fd_transmit(FAR struct tun_device_s *priv);
|
|
static int tun_txpoll(FAR struct net_driver_s *dev);
|
|
#ifdef CONFIG_NET_ETHERNET
|
|
static int tun_txpoll_tap(FAR struct net_driver_s *dev);
|
|
#endif
|
|
static int tun_txpoll_tun(FAR struct net_driver_s *dev);
|
|
|
|
/* Interrupt handling */
|
|
|
|
static void tun_net_receive(FAR struct tun_device_s *priv);
|
|
#ifdef CONFIG_NET_ETHERNET
|
|
static void tun_net_receive_tap(FAR struct tun_device_s *priv);
|
|
#endif
|
|
static void tun_net_receive_tun(FAR struct tun_device_s *priv);
|
|
|
|
static void tun_txdone(FAR struct tun_device_s *priv);
|
|
|
|
/* NuttX callback functions */
|
|
|
|
static int tun_ifup(FAR struct net_driver_s *dev);
|
|
static int tun_ifdown(FAR struct net_driver_s *dev);
|
|
static int tun_txavail(FAR struct net_driver_s *dev);
|
|
#ifdef CONFIG_NET_MCASTGROUP
|
|
static int tun_addmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac);
|
|
static int tun_rmmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac);
|
|
#endif
|
|
|
|
static int tun_dev_init(FAR struct tun_device_s *priv,
|
|
FAR struct file *filep,
|
|
FAR const char *devfmt, bool tun);
|
|
static void tun_dev_uninit(FAR struct tun_device_s *priv);
|
|
|
|
/* File interface */
|
|
|
|
static int tun_close(FAR struct file *filep);
|
|
static ssize_t tun_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen);
|
|
static ssize_t tun_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen);
|
|
static int tun_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
|
|
static int tun_poll(FAR struct file *filep, FAR struct pollfd *fds,
|
|
bool setup);
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static struct tun_driver_s g_tun =
|
|
{
|
|
NXMUTEX_INITIALIZER,
|
|
};
|
|
|
|
static struct tun_device_s g_tun_devices[CONFIG_TUN_NINTERFACES];
|
|
|
|
static const struct file_operations g_tun_file_ops =
|
|
{
|
|
NULL, /* open */
|
|
tun_close, /* close */
|
|
tun_read, /* read */
|
|
tun_write, /* write */
|
|
NULL, /* seek */
|
|
tun_ioctl, /* ioctl */
|
|
NULL, /* mmap */
|
|
NULL, /* truncate */
|
|
tun_poll /* poll */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: tun_pollnotify
|
|
****************************************************************************/
|
|
|
|
static void tun_pollnotify(FAR struct tun_device_s *priv,
|
|
pollevent_t eventset)
|
|
{
|
|
FAR struct pollfd *fds = priv->poll_fds;
|
|
|
|
if (priv->read_wait && (eventset & POLLIN))
|
|
{
|
|
priv->read_wait = false;
|
|
nxsem_post(&priv->read_wait_sem);
|
|
}
|
|
|
|
if (priv->write_wait && (eventset & POLLOUT))
|
|
{
|
|
priv->write_wait = false;
|
|
nxsem_post(&priv->write_wait_sem);
|
|
}
|
|
|
|
poll_notify(&fds, 1, eventset);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tun_fd_transmit
|
|
*
|
|
* Description:
|
|
* Start hardware transmission. Called either from the txdone interrupt
|
|
* handling or from watchdog based polling.
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* May or may not be called from an interrupt handler. In either case,
|
|
* global interrupts are disabled, either explicitly or indirectly through
|
|
* interrupt handling logic.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void tun_fd_transmit(FAR struct tun_device_s *priv)
|
|
{
|
|
NETDEV_TXPACKETS(&priv->dev);
|
|
tun_pollnotify(priv, POLLIN);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tun_txpoll
|
|
*
|
|
* Description:
|
|
* The transmitter is available, check if the network has any outgoing
|
|
* packets ready to send. This is a callback from devif_poll().
|
|
* devif_poll() may be called:
|
|
*
|
|
* 1. When the preceding TX packet send is complete,
|
|
* 2. When the preceding TX packet send timesout and the interface is reset
|
|
* 3. During normal TX polling
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
*
|
|
* Returned Value:
|
|
* OK on success; a negated errno on failure
|
|
*
|
|
* Assumptions:
|
|
* May or may not be called from an interrupt handler. In either case,
|
|
* global interrupts are disabled, either explicitly or indirectly through
|
|
* interrupt handling logic.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int tun_txpoll(FAR struct net_driver_s *dev)
|
|
{
|
|
FAR struct tun_device_s *priv = (FAR struct tun_device_s *)dev->d_private;
|
|
int ret;
|
|
|
|
DEBUGASSERT(priv->read_buf == NULL);
|
|
|
|
#ifdef CONFIG_NET_PKT
|
|
/* When packet sockets are enabled, feed the frame into the tap */
|
|
|
|
pkt_input(dev);
|
|
#endif
|
|
|
|
priv->read_d_len = dev->d_len;
|
|
priv->read_buf = dev->d_iob;
|
|
netdev_iob_clear(dev);
|
|
|
|
#ifdef CONFIG_NET_ETHERNET
|
|
if (dev->d_lltype == NET_LL_ETHERNET)
|
|
{
|
|
ret = tun_txpoll_tap(dev);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
ret = tun_txpoll_tun(dev);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tun_txpoll_tap : for tap (ethernet bridge) mode
|
|
*
|
|
* Description:
|
|
* The transmitter is available, check if the network has any outgoing
|
|
* packets ready to send. This is a callback from devif_poll().
|
|
* devif_poll() may be called:
|
|
*
|
|
* 1. When the preceding TX packet send is complete,
|
|
* 2. When the preceding TX packet send timesout and the interface is reset
|
|
* 3. During normal TX polling
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
*
|
|
* Returned Value:
|
|
* OK on success; a negated errno on failure
|
|
*
|
|
* Assumptions:
|
|
* May or may not be called from an interrupt handler. In either case,
|
|
* global interrupts are disabled, either explicitly or indirectly through
|
|
* interrupt handling logic.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_NET_ETHERNET
|
|
static int tun_txpoll_tap(FAR struct net_driver_s *dev)
|
|
{
|
|
FAR struct tun_device_s *priv = (FAR struct tun_device_s *)dev->d_private;
|
|
|
|
/* Send the packet */
|
|
|
|
tun_fd_transmit(priv);
|
|
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: tun_txpoll_tun : for tun (IP tunneling) mode
|
|
*
|
|
* Description:
|
|
* The transmitter is available, check if the network has any outgoing
|
|
* packets ready to send. This is a callback from devif_poll().
|
|
* devif_poll() may be called:
|
|
*
|
|
* 1. When the preceding TX packet send is complete,
|
|
* 2. When the preceding TX packet send timesout and the interface is reset
|
|
* 3. During normal TX polling
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
*
|
|
* Returned Value:
|
|
* OK on success; a negated errno on failure
|
|
*
|
|
* Assumptions:
|
|
* May or may not be called from an interrupt handler. In either case,
|
|
* global interrupts are disabled, either explicitly or indirectly through
|
|
* interrupt handling logic.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int tun_txpoll_tun(FAR struct net_driver_s *dev)
|
|
{
|
|
FAR struct tun_device_s *priv = (FAR struct tun_device_s *)dev->d_private;
|
|
|
|
tun_fd_transmit(priv);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tun_net_receive
|
|
*
|
|
* Description:
|
|
* An interrupt was received indicating the availability of a new RX
|
|
* packet
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* Global interrupts are disabled by interrupt handling logic.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void tun_net_receive(FAR struct tun_device_s *priv)
|
|
{
|
|
#ifdef CONFIG_NET_ETHERNET
|
|
if (priv->dev.d_lltype == NET_LL_ETHERNET)
|
|
{
|
|
tun_net_receive_tap(priv);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
tun_net_receive_tun(priv);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tun_net_receive_tap : for tap (ethernet bridge) mode
|
|
*
|
|
* Description:
|
|
* An interrupt was received indicating the availability of a new RX packet
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* Global interrupts are disabled by interrupt handling logic.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_NET_ETHERNET
|
|
static void tun_net_receive_tap(FAR struct tun_device_s *priv)
|
|
{
|
|
FAR struct net_driver_s *dev = &priv->dev;
|
|
|
|
/* Copy the data data from the hardware to priv->dev.d_buf. Set amount of
|
|
* data in priv->dev.d_len
|
|
*/
|
|
|
|
NETDEV_RXPACKETS(&priv->dev);
|
|
|
|
#ifdef CONFIG_NET_PKT
|
|
/* When packet sockets are enabled, feed the frame into the tap */
|
|
|
|
pkt_input(&priv->dev);
|
|
#endif
|
|
|
|
/* We only accept IP packets of the configured type and ARP packets */
|
|
|
|
#if defined(CONFIG_NET_IPv4)
|
|
if (ETHBUF->type == HTONS(ETHTYPE_IP))
|
|
{
|
|
ninfo("IPv4 frame\n");
|
|
NETDEV_RXIPV4(&priv->dev);
|
|
|
|
/* Receive an IPv4 packet from the network device */
|
|
|
|
ipv4_input(&priv->dev);
|
|
}
|
|
else
|
|
#endif
|
|
#ifdef CONFIG_NET_IPv6
|
|
if (ETHBUF->type == HTONS(ETHTYPE_IP6))
|
|
{
|
|
ninfo("IPv6 frame\n");
|
|
NETDEV_RXIPV6(&priv->dev);
|
|
|
|
/* Give the IPv6 packet to the network layer. */
|
|
|
|
ipv6_input(&priv->dev);
|
|
}
|
|
else
|
|
#endif
|
|
#ifdef CONFIG_NET_ARP
|
|
if (ETHBUF->type == HTONS(ETHTYPE_ARP))
|
|
{
|
|
arp_input(&priv->dev);
|
|
NETDEV_RXARP(&priv->dev);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
NETDEV_RXDROPPED(&priv->dev);
|
|
priv->dev.d_len = 0;
|
|
}
|
|
|
|
/* If the above function invocation resulted in data that should be
|
|
* sent out on the network, the field d_len will set to a value > 0.
|
|
*/
|
|
|
|
if (priv->dev.d_len > 0)
|
|
{
|
|
/* And send the packet */
|
|
|
|
DEBUGASSERT(priv->write_buf == NULL);
|
|
priv->write_d_len = priv->dev.d_len;
|
|
priv->write_buf = priv->dev.d_iob;
|
|
netdev_iob_clear(&priv->dev);
|
|
tun_fd_transmit(priv);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: tun_net_receive_tun : for tun (IP tunneling) mode
|
|
*
|
|
* Description:
|
|
* An interrupt was received indicating the availability of a new RX
|
|
* packet
|
|
*
|
|
* Input Parameters:
|
|
* priv - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* Global interrupts are disabled by interrupt handling logic.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void tun_net_receive_tun(FAR struct tun_device_s *priv)
|
|
{
|
|
FAR struct net_driver_s *dev = &priv->dev;
|
|
|
|
/* Copy the data data from the hardware to dev->d_buf. Set amount of
|
|
* data in dev->d_len
|
|
*/
|
|
|
|
NETDEV_RXPACKETS(dev);
|
|
|
|
#ifdef CONFIG_NET_PKT
|
|
/* When packet sockets are enabled, feed the frame into the tap */
|
|
|
|
pkt_input(dev);
|
|
#endif
|
|
|
|
/* We only accept IP packets of the configured type */
|
|
|
|
#if defined(CONFIG_NET_IPv4)
|
|
if ((IPv4BUF->vhl & IP_VERSION_MASK) == IPv4_VERSION)
|
|
{
|
|
ninfo("IPv4 frame\n");
|
|
NETDEV_RXIPV4(dev);
|
|
|
|
/* Give the IPv4 packet to the network layer. */
|
|
|
|
ipv4_input(dev);
|
|
}
|
|
else
|
|
#endif
|
|
#if defined(CONFIG_NET_IPv6)
|
|
if ((IPv6BUF->vtc & IP_VERSION_MASK) == IPv6_VERSION)
|
|
{
|
|
ninfo("IPv6 frame\n");
|
|
NETDEV_RXIPV6(dev);
|
|
|
|
/* Give the IPv6 packet to the network layer. */
|
|
|
|
ipv6_input(dev);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
NETDEV_RXDROPPED(dev);
|
|
dev->d_len = 0;
|
|
}
|
|
|
|
/* If the above function invocation resulted in data that should be
|
|
* sent out on the network, d_len field will set to a value > 0.
|
|
*/
|
|
|
|
if (dev->d_len > 0)
|
|
{
|
|
DEBUGASSERT(priv->write_buf == NULL);
|
|
priv->write_d_len = dev->d_len;
|
|
priv->write_buf = dev->d_iob;
|
|
netdev_iob_clear(dev);
|
|
tun_fd_transmit(priv);
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tun_txdone
|
|
*
|
|
* Description:
|
|
* An interrupt was received indicating that the last TX packet(s) is done
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* Global interrupts are disabled by the watchdog logic.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void tun_txdone(FAR struct tun_device_s *priv)
|
|
{
|
|
/* Check for errors and update statistics */
|
|
|
|
NETDEV_TXDONE(&priv->dev);
|
|
|
|
/* Then poll the network for new XMIT data */
|
|
|
|
devif_poll(&priv->dev, tun_txpoll);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tun_ifup
|
|
*
|
|
* Description:
|
|
* NuttX Callback: Bring up the Ethernet interface when an IP address is
|
|
* provided
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int tun_ifup(FAR struct net_driver_s *dev)
|
|
{
|
|
FAR struct tun_device_s *priv = (FAR struct tun_device_s *)dev->d_private;
|
|
|
|
#ifdef CONFIG_NET_IPv4
|
|
ninfo("Bringing up: %u.%u.%u.%u\n",
|
|
ip4_addr1(dev->d_ipaddr), ip4_addr2(dev->d_ipaddr),
|
|
ip4_addr3(dev->d_ipaddr), ip4_addr4(dev->d_ipaddr));
|
|
#endif
|
|
#ifdef CONFIG_NET_IPv6
|
|
ninfo("Bringing up: %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
|
|
dev->d_ipv6addr[0], dev->d_ipv6addr[1], dev->d_ipv6addr[2],
|
|
dev->d_ipv6addr[3], dev->d_ipv6addr[4], dev->d_ipv6addr[5],
|
|
dev->d_ipv6addr[6], dev->d_ipv6addr[7]);
|
|
#endif
|
|
|
|
priv->bifup = true;
|
|
netdev_carrier_on(dev);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tun_ifdown
|
|
*
|
|
* Description:
|
|
* NuttX Callback: Stop the interface.
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int tun_ifdown(FAR struct net_driver_s *dev)
|
|
{
|
|
FAR struct tun_device_s *priv = (FAR struct tun_device_s *)dev->d_private;
|
|
irqstate_t flags;
|
|
|
|
netdev_carrier_off(dev);
|
|
|
|
flags = enter_critical_section();
|
|
|
|
/* Mark the device "down" */
|
|
|
|
priv->bifup = false;
|
|
|
|
leave_critical_section(flags);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tun_txavail_work
|
|
*
|
|
* Description:
|
|
* Driver callback invoked when new TX data is available. This is a
|
|
* stimulus perform an out-of-cycle poll and, thereby, reduce the TX
|
|
* latency.
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* Called in normal user mode
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void tun_txavail_work(FAR void *arg)
|
|
{
|
|
FAR struct tun_device_s *priv = (FAR struct tun_device_s *)arg;
|
|
int ret;
|
|
|
|
ret = nxmutex_lock(&priv->lock);
|
|
if (ret < 0)
|
|
{
|
|
/* Thread has been canceled, skip poll-related work */
|
|
|
|
return;
|
|
}
|
|
|
|
/* Check if there is room to hold another network packet. */
|
|
|
|
if (priv->read_d_len != 0)
|
|
{
|
|
nxmutex_unlock(&priv->lock);
|
|
return;
|
|
}
|
|
|
|
net_lock();
|
|
if (priv->bifup)
|
|
{
|
|
/* Poll the network for new XMIT data */
|
|
|
|
devif_poll(&priv->dev, tun_txpoll);
|
|
}
|
|
|
|
net_unlock();
|
|
nxmutex_unlock(&priv->lock);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tun_txavail
|
|
*
|
|
* Description:
|
|
* Driver callback invoked when new TX data is available. This is a
|
|
* stimulus perform an out-of-cycle poll and, thereby, reduce the TX
|
|
* latency.
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
* Called from the network stack with the network locked.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int tun_txavail(FAR struct net_driver_s *dev)
|
|
{
|
|
FAR struct tun_device_s *priv = (FAR struct tun_device_s *)dev->d_private;
|
|
irqstate_t flags;
|
|
|
|
flags = enter_critical_section(); /* No interrupts */
|
|
|
|
/* Schedule to perform the TX poll on the worker thread when priv->bifup
|
|
* is true.
|
|
*/
|
|
|
|
if (priv->bifup && work_available(&priv->work))
|
|
{
|
|
work_queue(TUNWORK, &priv->work, tun_txavail_work, priv, 0);
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tun_addmac
|
|
*
|
|
* Description:
|
|
* NuttX Callback: Add the specified MAC address to the hardware multicast
|
|
* address filtering
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
* mac - The MAC address to be added
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_NET_MCASTGROUP
|
|
static int tun_addmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac)
|
|
{
|
|
/* Add the MAC address to the hardware multicast routing table */
|
|
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: tun_rmmac
|
|
*
|
|
* Description:
|
|
* NuttX Callback: Remove the specified MAC address from the hardware
|
|
* multicast address filtering
|
|
*
|
|
* Input Parameters:
|
|
* dev - Reference to the NuttX driver state structure
|
|
* mac - The MAC address to be removed
|
|
*
|
|
* Returned Value:
|
|
* None
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_NET_MCASTGROUP
|
|
static int tun_rmmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac)
|
|
{
|
|
/* Add the MAC address to the hardware multicast routing table */
|
|
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: tun_dev_init
|
|
*
|
|
* Description:
|
|
* Initialize the TUN device
|
|
*
|
|
* Input Parameters:
|
|
*
|
|
* Returned Value:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int tun_dev_init(FAR struct tun_device_s *priv,
|
|
FAR struct file *filep,
|
|
FAR const char *devfmt, bool tun)
|
|
{
|
|
int ret;
|
|
|
|
/* Initialize the driver structure */
|
|
|
|
memset(priv, 0, sizeof(struct tun_device_s));
|
|
priv->dev.d_ifup = tun_ifup; /* I/F up (new IP address) callback */
|
|
priv->dev.d_ifdown = tun_ifdown; /* I/F down callback */
|
|
priv->dev.d_txavail = tun_txavail; /* New TX data callback */
|
|
#ifdef CONFIG_NET_MCASTGROUP
|
|
priv->dev.d_addmac = tun_addmac; /* Add multicast MAC address */
|
|
priv->dev.d_rmmac = tun_rmmac; /* Remove multicast MAC address */
|
|
#endif
|
|
priv->dev.d_private = priv; /* Used to recover private state from dev */
|
|
|
|
/* Initialize the mutual exlcusion and wait semaphore */
|
|
|
|
nxmutex_init(&priv->lock);
|
|
nxsem_init(&priv->read_wait_sem, 0, 0);
|
|
nxsem_init(&priv->write_wait_sem, 0, 0);
|
|
|
|
/* Assign d_ifname if specified. */
|
|
|
|
if (devfmt)
|
|
{
|
|
strlcpy(priv->dev.d_ifname, devfmt, IFNAMSIZ);
|
|
}
|
|
|
|
/* Register the device with the OS so that socket IOCTLs can be performed */
|
|
|
|
ret = netdev_register(&priv->dev, tun ? NET_LL_TUN : NET_LL_ETHERNET);
|
|
if (ret != OK)
|
|
{
|
|
nxmutex_destroy(&priv->lock);
|
|
nxsem_destroy(&priv->read_wait_sem);
|
|
nxsem_destroy(&priv->write_wait_sem);
|
|
return ret;
|
|
}
|
|
|
|
filep->f_priv = priv; /* Set link to TUN device */
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tun_dev_uninit
|
|
****************************************************************************/
|
|
|
|
static void tun_dev_uninit(FAR struct tun_device_s *priv)
|
|
{
|
|
/* Put the interface in the down state */
|
|
|
|
tun_ifdown(&priv->dev);
|
|
|
|
work_cancel_sync(TUNWORK, &priv->work);
|
|
|
|
/* Remove the device from the OS */
|
|
|
|
netdev_unregister(&priv->dev);
|
|
|
|
nxmutex_destroy(&priv->lock);
|
|
nxsem_destroy(&priv->read_wait_sem);
|
|
nxsem_destroy(&priv->write_wait_sem);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tun_close
|
|
****************************************************************************/
|
|
|
|
static int tun_close(FAR struct file *filep)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct tun_driver_s *tun = inode->i_private;
|
|
FAR struct tun_device_s *priv = filep->f_priv;
|
|
int intf;
|
|
int ret;
|
|
|
|
if (priv == NULL)
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
intf = priv - g_tun_devices;
|
|
ret = nxmutex_lock(&tun->lock);
|
|
if (ret >= 0)
|
|
{
|
|
tun->free_tuns |= (1 << intf);
|
|
tun_dev_uninit(priv);
|
|
|
|
nxmutex_unlock(&tun->lock);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tun_write
|
|
****************************************************************************/
|
|
|
|
static ssize_t tun_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen)
|
|
{
|
|
FAR struct tun_device_s *priv = filep->f_priv;
|
|
uint8_t llhdrlen;
|
|
ssize_t ret;
|
|
|
|
if (priv == NULL || buflen > CONFIG_NET_TUN_PKTSIZE)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
llhdrlen = NET_LL_HDRLEN(&priv->dev);
|
|
|
|
for (; ; )
|
|
{
|
|
/* Write must return immediately if interrupted by a signal (or if the
|
|
* thread is canceled) and no data has yet been written.
|
|
*/
|
|
|
|
ret = nxmutex_lock(&priv->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Check if there are free space to write */
|
|
|
|
if (priv->write_d_len == 0)
|
|
{
|
|
net_lock();
|
|
netdev_iob_release(&priv->dev);
|
|
ret = netdev_iob_prepare(&priv->dev, false, 0);
|
|
priv->dev.d_buf = NULL;
|
|
if (ret < 0)
|
|
{
|
|
net_unlock();
|
|
break;
|
|
}
|
|
|
|
ret = iob_trycopyin(priv->dev.d_iob, (FAR const uint8_t *)buffer,
|
|
buflen, -llhdrlen, false);
|
|
if (ret < 0)
|
|
{
|
|
net_unlock();
|
|
break;
|
|
}
|
|
|
|
priv->dev.d_len = buflen;
|
|
|
|
tun_net_receive(priv);
|
|
net_unlock();
|
|
|
|
ret = buflen;
|
|
break;
|
|
}
|
|
|
|
/* Wait if there are no free space to write */
|
|
|
|
if ((filep->f_oflags & O_NONBLOCK) != 0)
|
|
{
|
|
ret = -EAGAIN;
|
|
break;
|
|
}
|
|
|
|
priv->write_wait = true;
|
|
nxmutex_unlock(&priv->lock);
|
|
nxsem_wait(&priv->write_wait_sem);
|
|
}
|
|
|
|
nxmutex_unlock(&priv->lock);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tun_read
|
|
****************************************************************************/
|
|
|
|
static ssize_t tun_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen)
|
|
{
|
|
FAR struct tun_device_s *priv = filep->f_priv;
|
|
uint8_t llhdrlen;
|
|
ssize_t ret;
|
|
|
|
if (priv == NULL)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
llhdrlen = NET_LL_HDRLEN(&priv->dev);
|
|
|
|
for (; ; )
|
|
{
|
|
/* Read must return immediately if interrupted by a signal (or if the
|
|
* thread is canceled) and no data has yet been read.
|
|
*/
|
|
|
|
ret = nxmutex_lock(&priv->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
/* Check if there are data to read in write buffer */
|
|
|
|
if (priv->write_d_len > 0)
|
|
{
|
|
if (buflen < priv->write_d_len)
|
|
{
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
iob_copyout((FAR uint8_t *)buffer, priv->write_buf,
|
|
priv->write_d_len, -llhdrlen);
|
|
ret = priv->write_d_len;
|
|
|
|
iob_free_chain(priv->write_buf);
|
|
priv->write_buf = NULL;
|
|
priv->write_d_len = 0;
|
|
|
|
NETDEV_TXDONE(&priv->dev);
|
|
tun_pollnotify(priv, POLLOUT);
|
|
break;
|
|
}
|
|
|
|
/* Check if there are data to read in read buffer */
|
|
|
|
if (priv->read_d_len > 0)
|
|
{
|
|
if (buflen < priv->read_d_len)
|
|
{
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
iob_copyout((FAR uint8_t *)buffer, priv->read_buf,
|
|
priv->read_d_len, -llhdrlen);
|
|
ret = priv->read_d_len;
|
|
|
|
iob_free_chain(priv->read_buf);
|
|
priv->read_buf = NULL;
|
|
priv->read_d_len = 0;
|
|
|
|
net_lock();
|
|
tun_txdone(priv);
|
|
net_unlock();
|
|
break;
|
|
}
|
|
|
|
/* Wait if there are no data to read */
|
|
|
|
if ((filep->f_oflags & O_NONBLOCK) != 0)
|
|
{
|
|
ret = -EAGAIN;
|
|
break;
|
|
}
|
|
|
|
priv->read_wait = true;
|
|
nxmutex_unlock(&priv->lock);
|
|
nxsem_wait(&priv->read_wait_sem);
|
|
}
|
|
|
|
nxmutex_unlock(&priv->lock);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tun_poll
|
|
****************************************************************************/
|
|
|
|
static int tun_poll(FAR struct file *filep,
|
|
FAR struct pollfd *fds, bool setup)
|
|
{
|
|
FAR struct tun_device_s *priv = filep->f_priv;
|
|
pollevent_t eventset;
|
|
int ret;
|
|
|
|
/* Some sanity checking */
|
|
|
|
if (priv == NULL || fds == NULL)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = nxmutex_lock(&priv->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
if (setup)
|
|
{
|
|
if (priv->poll_fds)
|
|
{
|
|
ret = -EBUSY;
|
|
goto errout;
|
|
}
|
|
|
|
priv->poll_fds = fds;
|
|
|
|
eventset = 0;
|
|
|
|
/* If write buffer is empty notify App. */
|
|
|
|
if (priv->write_d_len == 0)
|
|
{
|
|
eventset |= POLLOUT;
|
|
}
|
|
|
|
/* The write buffer sometimes could be used for TX.
|
|
* So check it too.
|
|
*/
|
|
|
|
if (priv->read_d_len != 0 || priv->write_d_len != 0)
|
|
{
|
|
eventset |= POLLIN;
|
|
}
|
|
|
|
poll_notify(&fds, 1, eventset);
|
|
}
|
|
else
|
|
{
|
|
priv->poll_fds = NULL;
|
|
}
|
|
|
|
errout:
|
|
nxmutex_unlock(&priv->lock);
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: tun_ioctl
|
|
****************************************************************************/
|
|
|
|
static int tun_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
|
|
{
|
|
FAR struct inode *inode = filep->f_inode;
|
|
FAR struct tun_driver_s *tun = inode->i_private;
|
|
FAR struct tun_device_s *priv = filep->f_priv;
|
|
int ret = OK;
|
|
|
|
if (cmd == TUNSETIFF)
|
|
{
|
|
uint8_t free_tuns;
|
|
int intf;
|
|
FAR struct ifreq *ifr = (FAR struct ifreq *)arg;
|
|
|
|
if (priv != NULL || ifr == NULL ||
|
|
((ifr->ifr_flags & IFF_MASK) != IFF_TUN &&
|
|
(ifr->ifr_flags & IFF_MASK) != IFF_TAP))
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = nxmutex_lock(&tun->lock);
|
|
if (ret < 0)
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
free_tuns = tun->free_tuns;
|
|
|
|
if (free_tuns == 0)
|
|
{
|
|
nxmutex_unlock(&tun->lock);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for (intf = 0;
|
|
intf < CONFIG_TUN_NINTERFACES && !(free_tuns & 1);
|
|
intf++, free_tuns >>= 1);
|
|
|
|
ret = tun_dev_init(&g_tun_devices[intf], filep,
|
|
*ifr->ifr_name ? ifr->ifr_name : NULL,
|
|
(ifr->ifr_flags & IFF_MASK) == IFF_TUN);
|
|
if (ret != OK)
|
|
{
|
|
nxmutex_unlock(&tun->lock);
|
|
return ret;
|
|
}
|
|
|
|
tun->free_tuns &= ~(1 << intf);
|
|
|
|
priv = filep->f_priv;
|
|
strlcpy(ifr->ifr_name, priv->dev.d_ifname, IFNAMSIZ);
|
|
nxmutex_unlock(&tun->lock);
|
|
|
|
return OK;
|
|
}
|
|
else if (cmd == TUNGETIFF)
|
|
{
|
|
FAR struct ifreq *ifr = (FAR struct ifreq *)arg;
|
|
if (priv == NULL || ifr == NULL)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
strlcpy(ifr->ifr_name, priv->dev.d_ifname, IFNAMSIZ);
|
|
|
|
return OK;
|
|
}
|
|
else if (cmd == TUNSETCARRIER)
|
|
{
|
|
if (priv == NULL || arg == 0)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (*(FAR int *)((uintptr_t)arg))
|
|
{
|
|
netdev_carrier_on(&priv->dev);
|
|
}
|
|
else
|
|
{
|
|
netdev_carrier_off(&priv->dev);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
return -ENOTTY;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: tun_initialize
|
|
*
|
|
* Description:
|
|
* Instantiate a TUN network interface.
|
|
*
|
|
* Input Parameters:
|
|
*
|
|
* Returned Value:
|
|
* OK on success; Negated errno on failure.
|
|
*
|
|
* Assumptions:
|
|
*
|
|
****************************************************************************/
|
|
|
|
int tun_initialize(void)
|
|
{
|
|
g_tun.free_tuns = (1 << CONFIG_TUN_NINTERFACES) - 1;
|
|
register_driver("/dev/tun", &g_tun_file_ops, 0644, &g_tun);
|
|
return OK;
|
|
}
|
|
|
|
#endif /* CONFIG_NET && CONFIG_NET_TUN */
|