1
0
mirror of https://github.com/SoftEtherVPN/SoftEtherVPN.git synced 2025-07-13 11:14:59 +03:00
Files
SoftEtherVPN/src/Cedar/VLanUnix.c
Koichiro IWAO 49f8112d83 Cedar/VLanUnix: assign virtual interface to softether group
Interface grouping is available on FreeBSD and OpenBSD. This will allow
you to enumerate only SoftEther virtual interfaces or exclude SoftEther
virtual interfaces, and be helpful when making custom scripts to start
DHCP client when virtual interface become up (=VPN connection
established) for example.

Usage examples as follows.

List all interfaces' names available on the system:
```
$ ifconfig -l
vtnet0 lo0 vpn_client0 vpn_client1 vpn_client2
```

Display a list of SoftEther virtual interfaces:
```
$ ifconfig -g softether
vpn_client0
vpn_client1
vpn_client2
```

Display details about SoftEther virtual interfaces that are up:
```
$ ifconfig -a -u -g softether
vpn_client0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
        description: SoftEther Virtual Network Adapter
        options=80000<LINKSTATE>
        ether 5e:71:fa:f8:91:4a
        hwaddr 58:9c:fc:10:34:2a
        groups: tap softether
        media: Ethernet autoselect
        status: active
        nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
        Opened by PID 1445
```

Display details about interfaces except for SoftEther virtual interfaces:
```
$ ifconfig -a -G softether
vtnet0: flags=8863<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
        options=80028<VLAN_MTU,JUMBO_MTU,LINKSTATE>
        ether 58:9c:fc:00:f0:23
        inet6 fe80::5a9c:fcff:fe00:f023%vtnet0 prefixlen 64 scopeid 0x1
        inet 192.168.96.7 netmask 0xffffff00 broadcast 192.168.96.255
        media: Ethernet autoselect (10Gbase-T <full-duplex>)
        status: active
        nd6 options=23<PERFORMNUD,ACCEPT_RTADV,AUTO_LINKLOCAL>
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
        options=680003<RXCSUM,TXCSUM,LINKSTATE,RXCSUM_IPV6,TXCSUM_IPV6>
        inet6 ::1 prefixlen 128
        inet6 fe80::1%lo0 prefixlen 64 scopeid 0x2
        inet 127.0.0.1 netmask 0xff000000
        groups: lo
        nd6 options=23<PERFORMNUD,ACCEPT_RTADV,AUTO_LINKLOCAL>
```
2023-07-04 14:38:36 +09:00

918 lines
16 KiB
C

// SoftEther VPN Source Code - Developer Edition Master Branch
// Cedar Communication Module
// VLanUnix.c
// Virtual device driver library for UNIX
#ifdef UNIX
#include "VLanUnix.h"
#include "Connection.h"
#include "Session.h"
#include "Mayaqua/FileIO.h"
#include "Mayaqua/Mayaqua.h"
#include "Mayaqua/Memory.h"
#include "Mayaqua/Str.h"
#include "Mayaqua/TunTap.h"
#ifdef UNIX_BSD
// For "sockaddr" in <net/if_arp.h>
#include <sys/socket.h>
#endif
#include <errno.h>
#include <fcntl.h>
#include <net/if_arp.h>
#include <net/if.h>
#include <sys/ioctl.h>
#if defined(UNIX_OPENBSD) || defined(UNIX_SOLARIS)
#include <netinet/if_ether.h>
#else
#include <net/ethernet.h>
#endif
static LIST *unix_vlan = NULL;
#ifndef NO_VLAN
// Get the PACKET_ADAPTER
PACKET_ADAPTER *VLanGetPacketAdapter()
{
PACKET_ADAPTER *pa;
pa = NewPacketAdapter(VLanPaInit, VLanPaGetCancel,
VLanPaGetNextPacket, VLanPaPutPacket, VLanPaFree);
if (pa == NULL)
{
return NULL;
}
return pa;
}
// PA initialization
bool VLanPaInit(SESSION *s)
{
VLAN *v;
// Validate arguments
if (s == NULL)
{
return false;
}
// Connect to the driver
v = NewVLan(s->ClientOption->DeviceName, NULL);
if (v == NULL)
{
// Failure
return false;
}
s->PacketAdapter->Param = v;
return true;
}
// Get the cancel object
CANCEL *VLanPaGetCancel(SESSION *s)
{
VLAN *v;
// Validate arguments
if ((s == NULL) || ((v = s->PacketAdapter->Param) == NULL))
{
return NULL;
}
return VLanGetCancel(v);
}
// Release the packet adapter
void VLanPaFree(SESSION *s)
{
VLAN *v;
// Validate arguments
if ((s == NULL) || ((v = s->PacketAdapter->Param) == NULL))
{
return;
}
// End the virtual LAN card
FreeVLan(v);
s->PacketAdapter->Param = NULL;
}
// Write a packet
bool VLanPaPutPacket(SESSION *s, void *data, UINT size)
{
VLAN *v;
// Validate arguments
if ((s == NULL) || ((v = s->PacketAdapter->Param) == NULL))
{
return false;
}
return VLanPutPacket(v, data, size);
}
// Get the next packet
UINT VLanPaGetNextPacket(SESSION *s, void **data)
{
VLAN *v;
UINT size;
// Validate arguments
if (data == NULL || (s == NULL) || ((v = s->PacketAdapter->Param) == NULL))
{
return INFINITE;
}
if (VLanGetNextPacket(v, data, &size) == false)
{
return INFINITE;
}
return size;
}
// Write a packet to the virtual LAN card
bool VLanPutPacket(VLAN *v, void *buf, UINT size)
{
UINT ret;
// Validate arguments
if (v == NULL)
{
return false;
}
if (v->Halt)
{
return false;
}
if (size > MAX_PACKET_SIZE)
{
return false;
}
if (buf == NULL || size == 0)
{
if (buf != NULL)
{
Free(buf);
}
return true;
}
ret = write(v->fd, buf, size);
if (ret >= 1)
{
Free(buf);
return true;
}
if (errno == EAGAIN || ret == 0)
{
Free(buf);
return true;
}
return false;
}
// Get the next packet from the virtual LAN card
bool VLanGetNextPacket(VLAN *v, void **buf, UINT *size)
{
UCHAR tmp[TAP_READ_BUF_SIZE];
int ret;
// Validate arguments
if (v == NULL || buf == NULL || size == 0)
{
return false;
}
if (v->Halt)
{
return false;
}
// Read
ret = read(v->fd, tmp, sizeof(tmp));
if (ret == 0 ||
(ret == -1 && errno == EAGAIN))
{
// No packet
*buf = NULL;
*size = 0;
return true;
}
else if (ret == -1 || ret > TAP_READ_BUF_SIZE)
{
// Error
return false;
}
else
{
// Reading packet success
*buf = Malloc(ret);
Copy(*buf, tmp, ret);
*size = ret;
return true;
}
}
// Get the cancel object
CANCEL *VLanGetCancel(VLAN *v)
{
CANCEL *c;
int fd;
int yes = 0;
// Validate arguments
if (v == NULL)
{
return NULL;
}
c = NewCancel();
UnixDeletePipe(c->pipe_read, c->pipe_write);
c->pipe_read = c->pipe_write = -1;
fd = v->fd;
UnixSetSocketNonBlockingMode(fd, true);
c->SpecialFlag = true;
c->pipe_read = fd;
return c;
}
// Close the Virtual LAN card
void FreeVLan(VLAN *v)
{
// Validate arguments
if (v == NULL)
{
return;
}
Free(v->InstanceName);
Free(v);
}
// Create a tap
VLAN *NewBridgeTap(char *name, char *mac_address, bool create_up)
{
int fd;
VLAN *v;
// Validate arguments
if (name == NULL || mac_address == NULL)
{
return NULL;
}
fd = UnixCreateTapDeviceEx(name, UNIX_VLAN_BRIDGE_IFACE_PREFIX, mac_address, create_up);
if (fd == -1)
{
return NULL;
}
v = ZeroMalloc(sizeof(VLAN));
v->Halt = false;
v->InstanceName = CopyStr(name);
v->fd = fd;
return v;
}
// Close the tap
void FreeBridgeTap(VLAN *v)
{
// Validate arguments
if (v == NULL)
{
return;
}
UnixCloseTapDevice(v->fd);
#ifdef UNIX_BSD
UnixDestroyBridgeTapDevice(v->InstanceName);
#endif
FreeVLan(v);
}
// Get the Virtual LAN card list
VLAN *NewVLan(char *instance_name, VLAN_PARAM *param)
{
int fd;
VLAN *v;
// Validate arguments
if (instance_name == NULL)
{
return NULL;
}
// Open the tap
fd = UnixVLanGet(instance_name);
if (fd == -1)
{
return NULL;
}
v = ZeroMalloc(sizeof(VLAN));
v->Halt = false;
v->InstanceName = CopyStr(instance_name);
v->fd = fd;
return v;
}
// Generate TUN interface name
void GenerateTunName(char *name, char *prefix, char *tun_name, size_t tun_name_len)
{
char instance_name_lower[MAX_SIZE];
// Generate the device name
StrCpy(instance_name_lower, sizeof(instance_name_lower), name);
Trim(instance_name_lower);
StrLower(instance_name_lower);
Format(tun_name, tun_name_len, "%s_%s", prefix, instance_name_lower);
tun_name[15] = 0;
}
// Create a tap device
int UnixCreateTapDeviceEx(char *name, char *prefix, UCHAR *mac_address, bool create_up)
{
int fd = -1, s = -1;
char tap_name[MAX_SIZE], tap_path[MAX_SIZE];
struct ifreq ifr;
// Validate arguments
if (name == NULL)
{
return -1;
}
GenerateTunName(name, prefix, tap_name, sizeof(tap_name));
// Open the tun / tap
#ifndef UNIX_BSD
if (GetOsInfo()->OsType == OSTYPE_LINUX)
{
// Linux
if (IsFile(TAP_FILENAME_1) == false)
{
char tmp[MAX_SIZE];
Format(tmp, sizeof(tmp), "%s c 10 200", TAP_FILENAME_1);
Run("mknod", tmp, true, true);
Format(tmp, sizeof(tmp), "600 %s", TAP_FILENAME_1);
Run("chmod", tmp, true, true);
}
}
fd = open(TAP_FILENAME_1, O_RDWR);
if (fd == -1)
{
// Failure
fd = open(TAP_FILENAME_2, O_RDWR);
if (fd == -1)
{
return -1;
}
}
#else // UNIX_BSD
{
sprintf(tap_path, "%s", TAP_DIR TAP_NAME);
for (int i = 0; i < TAP_MAX; i++) {
sprintf(tap_path + StrLen(TAP_DIR TAP_NAME), "%d", i);
fd = open(tap_path, O_RDWR);
if (fd != -1)
{
break;
}
}
if (fd == -1)
{
return -1;
}
}
#endif // UNIX_BSD
#ifdef UNIX_LINUX
// Create a TAP device for Linux
// Set the name and the flags
Zero(&ifr, sizeof(ifr));
StrCpy(ifr.ifr_name, sizeof(ifr.ifr_name), tap_name);
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
if (ioctl(fd, TUNSETIFF, &ifr) == -1)
{
// Failure
close(fd);
return -1;
}
s = socket(AF_INET, SOCK_DGRAM, 0);
if (s != -1)
{
// Set the MAC address
if (mac_address != NULL)
{
Zero(&ifr, sizeof(ifr));
StrCpy(ifr.ifr_name, sizeof(ifr.ifr_name), tap_name);
ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;
Copy(&ifr.ifr_hwaddr.sa_data, mac_address, ETHER_ADDR_LEN);
ioctl(s, SIOCSIFHWADDR, &ifr);
}
if (create_up)
{
Zero(&ifr, sizeof(ifr));
StrCpy(ifr.ifr_name, sizeof(ifr.ifr_name), tap_name);
ioctl(s, SIOCGIFFLAGS, &ifr);
ifr.ifr_flags |= IFF_UP;
ioctl(s, SIOCSIFFLAGS, &ifr);
}
close(s);
}
#endif // UNIX_LINUX
#ifdef UNIX_BSD
// Create a TAP device for BSD
Zero(&ifr, sizeof(ifr));
// Get the current name
StrCpy(ifr.ifr_name, sizeof(ifr.ifr_name), tap_path + StrLen(TAP_DIR));
s = socket(AF_INET, SOCK_DGRAM, 0);
if (s != -1)
{
// Set the name, if possible
#ifdef SIOCSIFNAME
ifr.ifr_data = tap_name;
ioctl(s, SIOCSIFNAME, &ifr);
#else // SIOCSIFNAME
StrCpy(tap_name, sizeof(tap_name), ifr.ifr_name);
#endif // SIOCSIFNAME
// Set the MAC address
if (mac_address != NULL)
{
Zero(&ifr, sizeof(ifr));
StrCpy(ifr.ifr_name, sizeof(ifr.ifr_name), tap_name);
ifr.ifr_addr.sa_len = ETHER_ADDR_LEN;
ifr.ifr_addr.sa_family = AF_LINK;
Copy(&ifr.ifr_addr.sa_data, mac_address, ETHER_ADDR_LEN);
ioctl(s, SIOCSIFLLADDR, &ifr);
}
// Set interface description
#ifdef SIOCSIFDESCR
{
char desc[] = CEDAR_PRODUCT_STR " Virtual Network Adapter";
ifr.ifr_buffer.buffer = desc;
ifr.ifr_buffer.length = StrLen(desc) + 1;
ioctl(s, SIOCSIFDESCR, &ifr);
}
#endif
// Set interface group
UnixSetIfGroup(s, tap_name, CEDAR_PRODUCT_STR);
if (create_up)
{
Zero(&ifr, sizeof(ifr));
StrCpy(ifr.ifr_name, sizeof(ifr.ifr_name), tap_name);
ioctl(s, SIOCGIFFLAGS, &ifr);
ifr.ifr_flags |= IFF_UP;
ioctl(s, SIOCSIFFLAGS, &ifr);
}
close(s);
}
#endif // UNIX_BSD
#ifdef UNIX_SOLARIS
// Create a TAP device for Solaris
{
int ip_fd;
int tun_fd;
int ppa;
tun_fd = open(tap_name, O_RDWR);
if (tun_fd == -1)
{
// Failure
close(fd);
return -1;
}
ip_fd = open("/dev/ip", O_RDWR);
if (ip_fd == -1)
{
// Failure
close(tun_fd);
close(fd);
return -1;
}
ppa = -1;
ppa = ioctl(tun_fd, TUNNEWPPA, ppa);
if (ppa == -1)
{
// Failure
close(tun_fd);
close(fd);
close(ip_fd);
return -1;
}
if (ioctl(fd, I_PUSH, "ip") < 0)
{
// Failure
close(tun_fd);
close(fd);
close(ip_fd);
return -1;
}
if (ioctl(fd, IF_UNITSEL, (char *)&ppa) < 0)
{
// Failure
close(tun_fd);
close(fd);
close(ip_fd);
return -1;
}
if (ioctl(ip_fd, I_LINK, fd) < 0)
{
// Failure
close(tun_fd);
close(fd);
close(ip_fd);
return -1;
}
close(tun_fd);
close(ip_fd);
}
#endif // UNIX_SOLARIS
return fd;
}
int UnixCreateTapDevice(char *name, UCHAR *mac_address, bool create_up)
{
return UnixCreateTapDeviceEx(name, UNIX_VLAN_CLIENT_IFACE_PREFIX, mac_address, create_up);
}
// Close the tap device
void UnixCloseTapDevice(int fd)
{
// Validate arguments
if (fd == -1)
{
return;
}
close(fd);
}
// Destroy the tap device (for FreeBSD)
// FreeBSD tap device is still plumbed after closing fd so need to destroy after close
void UnixDestroyTapDeviceEx(char *name, char *prefix)
{
#ifdef UNIX_BSD
struct ifreq ifr;
char eth_name[MAX_SIZE];
int s;
Zero(&ifr, sizeof(ifr));
GenerateTunName(name, prefix, eth_name, sizeof(eth_name));
StrCpy(ifr.ifr_name, sizeof(ifr.ifr_name), eth_name);
s = socket(AF_INET, SOCK_DGRAM, 0);
if (s == -1)
{
return;
}
ioctl(s, SIOCIFDESTROY, &ifr);
close(s);
#endif // UNIX_BSD
}
void UnixDestroyBridgeTapDevice(char *name)
{
#ifdef UNIX_BSD
UnixDestroyTapDeviceEx(name, UNIX_VLAN_BRIDGE_IFACE_PREFIX);
#endif // UNIX_BSD
}
void UnixDestroyClientTapDevice(char *name)
{
#ifdef UNIX_BSD
UnixDestroyTapDeviceEx(name, UNIX_VLAN_CLIENT_IFACE_PREFIX);
#endif // UNIX_BSD
}
void UnixSetIfGroup(int fd, const char *name, const char *group_name)
{
#ifdef SIOCAIFGROUP
struct ifgroupreq ifgr;
char *tmp;
tmp = CopyStr((char *)group_name);
StrLower(tmp);
Zero(&ifgr, sizeof(ifgr));
StrCpy(ifgr.ifgr_name, sizeof(ifgr.ifgr_name), (char *) name);
StrCpy(ifgr.ifgr_group, sizeof(ifgr.ifgr_group), tmp);
ioctl(fd, SIOCAIFGROUP, &ifgr);
Free(tmp);
#endif
}
#else // NO_VLAN
void UnixCloseDevice(int fd)
{
}
void UnixDestroyTapDevice(char *name)
{
}
void UnixDestroyTapDeviceEx(char *name, char *prefix)
{
}
void UnixSetIfGroup()
{
}
int UnixCreateTapDeviceEx(char *name, char *prefix, UCHAR *mac_address, bool create_up)
{
return -1;
}
int UnixCreateTapDevice(char *name, UCHAR *mac_address, bool create_up)
{
return -1;
}
#endif // NO_VLAN
// Comparison of the VLAN list entries
int UnixCompareVLan(void *p1, void *p2)
{
UNIX_VLAN_LIST *v1, *v2;
if (p1 == NULL || p2 == NULL)
{
return 0;
}
v1 = *(UNIX_VLAN_LIST **)p1;
v2 = *(UNIX_VLAN_LIST **)p2;
if (v1 == NULL || v2 == NULL)
{
return 0;
}
return StrCmpi(v1->Name, v2->Name);
}
// Initialize the VLAN list
void UnixVLanInit()
{
unix_vlan = NewList(UnixCompareVLan);
}
// Create a VLAN
bool UnixVLanCreateEx(char *name, char *prefix, UCHAR *mac_address, bool create_up)
{
// Validate arguments
char tmp[MAX_SIZE];
if (name == NULL)
{
return false;
}
StrCpy(tmp, sizeof(tmp), name);
Trim(tmp);
name = tmp;
LockList(unix_vlan);
{
UNIX_VLAN_LIST *t, tt;
int fd;
// Check whether a device with the same name exists
Zero(&tt, sizeof(tt));
StrCpy(tt.Name, sizeof(tt.Name), name);
t = Search(unix_vlan, &tt);
if (t != NULL)
{
// Already exist
UnlockList(unix_vlan);
return false;
}
// Create a tap device
fd = UnixCreateTapDeviceEx(name, prefix, mac_address, create_up);
if (fd == -1)
{
// Failure to create
UnlockList(unix_vlan);
return false;
}
t = ZeroMalloc(sizeof(UNIX_VLAN_LIST));
t->fd = fd;
StrCpy(t->Name, sizeof(t->Name), name);
Insert(unix_vlan, t);
}
UnlockList(unix_vlan);
return true;
}
bool UnixVLanCreate(char *name, UCHAR *mac_address, bool create_up)
{
return UnixVLanCreateEx(name, UNIX_VLAN_CLIENT_IFACE_PREFIX, mac_address, create_up);
}
// Set a VLAN up/down
bool UnixVLanSetState(char* name, bool state_up)
{
#if defined(UNIX_LINUX) || defined(UNIX_BSD)
UNIX_VLAN_LIST *t, tt;
struct ifreq ifr;
int s;
char eth_name[MAX_SIZE];
LockList(unix_vlan);
{
int result;
// Find a device with the same name
Zero(&tt, sizeof(tt));
StrCpy(tt.Name, sizeof(tt.Name), name);
t = Search(unix_vlan, &tt);
if (t == NULL)
{
// No such device
UnlockList(unix_vlan);
return false;
}
GenerateTunName(name, UNIX_VLAN_CLIENT_IFACE_PREFIX, eth_name, sizeof(eth_name));
Zero(&ifr, sizeof(ifr));
StrCpy(ifr.ifr_name, sizeof(ifr.ifr_name), eth_name);
s = socket(AF_INET, SOCK_DGRAM, 0);
if (s == -1)
{
// Failed to create socket
UnlockList(unix_vlan);
return false;
}
ioctl(s, SIOCGIFFLAGS, &ifr);
if (state_up)
{
ifr.ifr_flags |= IFF_UP;
}
else
{
ifr.ifr_flags &= ~IFF_UP;
}
result = ioctl(s, SIOCSIFFLAGS, &ifr);
close(s);
}
UnlockList(unix_vlan);
#endif // UNIX_LINUX || UNIX_BSD
return true;
}
// Enumerate VLANs
TOKEN_LIST *UnixVLanEnum()
{
TOKEN_LIST *ret;
UINT i;
if (unix_vlan == NULL)
{
return NullToken();
}
ret = ZeroMalloc(sizeof(TOKEN_LIST));
LockList(unix_vlan);
{
ret->NumTokens = LIST_NUM(unix_vlan);
ret->Token = ZeroMalloc(sizeof(char *) * ret->NumTokens);
for (i = 0;i < ret->NumTokens;i++)
{
UNIX_VLAN_LIST *t = LIST_DATA(unix_vlan, i);
ret->Token[i] = CopyStr(t->Name);
}
}
UnlockList(unix_vlan);
return ret;
}
// Delete the VLAN
void UnixVLanDelete(char *name)
{
// Validate arguments
if (name == NULL || unix_vlan == NULL)
{
return;
}
LockList(unix_vlan);
{
UINT i;
UNIX_VLAN_LIST *t, tt;
Zero(&tt, sizeof(tt));
StrCpy(tt.Name, sizeof(tt.Name), name);
t = Search(unix_vlan, &tt);
if (t != NULL)
{
UnixCloseTapDevice(t->fd);
#ifdef UNIX_BSD
UnixDestroyClientTapDevice(t->Name);
#endif
Delete(unix_vlan, t);
Free(t);
}
}
UnlockList(unix_vlan);
}
// Get the VLAN
int UnixVLanGet(char *name)
{
int fd = -1;
// Validate arguments
if (name == NULL || unix_vlan == NULL)
{
return -1;
}
LockList(unix_vlan);
{
UINT i;
UNIX_VLAN_LIST *t, tt;
Zero(&tt, sizeof(tt));
StrCpy(tt.Name, sizeof(tt.Name), name);
t = Search(unix_vlan, &tt);
if (t != NULL)
{
fd = t->fd;
}
}
UnlockList(unix_vlan);
return fd;
}
// Release the VLAN list
void UnixVLanFree()
{
UINT i;
for (i = 0;i < LIST_NUM(unix_vlan);i++)
{
UNIX_VLAN_LIST *t = LIST_DATA(unix_vlan, i);
UnixCloseTapDevice(t->fd);
#ifdef UNIX_BSD
UnixDestroyClientTapDevice(t->Name);
#endif
Free(t);
}
ReleaseList(unix_vlan);
unix_vlan = NULL;
}
#endif