Skip to content

Instantly share code, notes, and snippets.

@apankrat
Created November 2, 2020 08:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save apankrat/9ef1c83c8c891ca6af4e6bc9303a0ba5 to your computer and use it in GitHub Desktop.
Save apankrat/9ef1c83c8c891ca6af4e6bc9303a0ba5 to your computer and use it in GitHub Desktop.
/*
* Copyright (c) 2005 Applied Networking Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms set out in the LICENSE.tuncfg file, which is
* included in Hamachi Client distribution.
*
* 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
* LICENSE.tuncfg file for more details.
*
* (BSD license)
*/
/*
* Tuncfg is a tunneling device management and configuration daemon.
*
* The idea is that tuncfg encapsulates all root-level functionality
* normally required by a private networking software. Namely -
*
* * creation of tunneling devices; this requires an access to
* /dev/net/tun file, which _usually_ has 700 access mask
*
* * configuration of the tunneling device using ifconfig, which is
* always a root-level operation
*
* Tuncfg must be run with superuser privileges. It will daemonize and
* open a listening domain socket /var/run/tuncfg.sock.
*
* Upon accepting the connection on this socket, it will issue an open()
* call for /dev/net/tun file (thus instantiating the tunneling device)
* and pass obtained FD to the peer process. It will also query and pass
* the MAC address of the device to the peer process.
*
* It will then listen for ifconfig requests from the peer. The request
* contains just an IP address and mask. Tuncfg will lookup the tun
* device the peer owns and will assign IP/mask to it using system()
* call for ipconfig command.
*
* 'tuncfg' starts the daemon (it won't start twice, there's a check)
* 'tuncfg -d' starts the console version (useful for debugging)
*
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdlib.h>
/*
*
*/
#define LOCK_PATH "/var/run/tuncfg.lock"
#define SOCK_PATH "/var/run/tuncfg.sock"
#define TUNTAP_URL "http://www.hamachi.cc/tuntap"
#define MAX_CLIENTS 64
struct context
{
int fd;
char dev[IFNAMSIZ];
};
/*
*
*/
#define errorf(args...) { printf(args); exit(1); }
int send_with_fd(int fd, const void * buf, size_t len, int fd_to_send);
int main(int argc, char ** argv)
{
struct sockaddr_un addr = { 0 };
socklen_t alen = sizeof(addr);
struct stat st;
pid_t pid;
int fd, r, i;
struct context ctx[MAX_CLIENTS];
int ctx_n = 0;
// check we are root
if (getuid() != 0)
errorf("tuncfg: must be run with superuser permissions\n");
// lcok
fd = open(LOCK_PATH, O_CREAT);
if (fd < 0)
errorf("tuncfg: cannot open lock file %s -- %s\n",
LOCK_PATH, strerror(errno));
if (flock(fd, LOCK_EX | LOCK_NB) < 0)
errorf("tuncfg: already running\n");
// check there's /dev/net/tun
if (stat("/dev/net/tun", &st) < 0)
errorf("tuncfg: cannot stat() /dev/net/tun -- %s\n"
"tuncfg: visit %s for more information\n",
strerror(errno), TUNTAP_URL);
// open request socket
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, SOCK_PATH);
fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0)
errorf("tuncfg: cannot create domain socket -- %s\n",
strerror(errno));
unlink(SOCK_PATH);
r = bind(fd, (void*)&addr, alen);
if (r < 0)
errorf("tuncfg: cannot bind domain socket to %s -- %s\n",
SOCK_PATH, strerror(errno));
r = listen(fd, 2);
if (r < 0)
errorf("tuncfg: cannot listen on domain socket %s -- %s\n",
SOCK_PATH, strerror(errno));
// make the socket world-accessible
if (stat(SOCK_PATH, &st) < 0)
errorf("tuncfg: cannot stat() %s -- %s\n",
SOCK_PATH, strerror(errno));
if (chmod(SOCK_PATH, st.st_mode | 0777) < 0)
printf("tuncfg: failed to chmod +777 %s -- %s\n",
SOCK_PATH, strerror(errno));
// daemonize
if (argc < 2 || strcmp(argv[1], "-d"))
{
chdir("/");
pid = fork();
if (pid < 0) return 0;
if (pid > 0) exit(0);
if (setsid() < 0)
return 0;/* shouldn't happen */
pid = fork();
if (pid < 0) return 0;
if (pid > 0) exit(0);
close(0);
close(1);
close(2);
open("/dev/null", O_RDWR);
dup(0);
dup(0);
}
// loop
for (;;)
{
fd_set fdr;
int fdm;
FD_ZERO(&fdr);
FD_SET(fd, &fdr);
fdm = fd;
for (i=0; i<ctx_n; i++)
{
FD_SET(ctx[i].fd, &fdr);
if (ctx[i].fd > fdm)
fdm = ctx[i].fd;
}
do
{
r = select(fdm+1, &fdr, NULL, NULL, NULL);
if (r < 0 && errno != EINTR)
exit(EXIT_FAILURE);
}
while (r < 0);
if (FD_ISSET(fd, &fdr))
{
struct context * p;
struct ifreq ifr;
char buf[4+6];
int cli, dev = -1, tmp = -1;
cli = accept(fd, (void*)&addr, &alen);
printf("tuncfg: accept() %d %d\n", cli, errno);
if (cli < 0)
goto skip;
if (ctx_n == MAX_CLIENTS)
{
printf("tuncfg: too many clients\n");
r = (0x01 << 24);
goto done;
}
// open tap device
dev = open("/dev/net/tun", O_RDWR);
printf("tuncfg: open() %d %d\n", dev, errno);
if (dev < 0)
{
r = (0x02 << 24) | errno;
goto done;
}
// bring it up
strcpy(ifr.ifr_name, "ham%d");
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
if (ioctl(dev, TUNSETIFF, (ulong)&ifr) < 0)
{
printf("tuncfg: ioctl() -1 %d\n", errno);
r = (0x03 << 24) | errno;
goto done;
}
printf("tuncfg: ioctl() 0 %s\n", ifr.ifr_name);
strcpy(ifr.ifr_name, "ham0");
// query mac
tmp = socket(AF_INET, SOCK_DGRAM, 0);
if (tmp < 0)
{
printf("tuncfg: socket(mac) %d\n", errno);
r = (0x04 << 24) | errno;
goto done;
}
if (ioctl(tmp, SIOCGIFHWADDR, (ulong)&ifr) < 0)
{
printf("tuncfg: ioctl(mac) %d\n", errno);
r = (0x05 << 24) | errno;
goto done;
}
memcpy(buf+4, &ifr.ifr_hwaddr.sa_data, 6);
// remember
p = &ctx[ctx_n++];
p->fd = cli;
strncpy(p->dev, ifr.ifr_name, sizeof(p->dev));
r = 0;
done:
if (cli != -1)
{
memcpy(buf, &r, sizeof(r));
send_with_fd(cli, buf, sizeof(buf), dev);
}
if (tmp != -1) close(tmp);
if (dev != -1) close(dev);
if (r != 0) close(cli);
}
skip:
for (i=0; i<ctx_n; i++)
{
unsigned long v[2];
char cmd[256];
if (! FD_ISSET(ctx[i].fd, &fdr))
continue;
r = recv(ctx[i].fd, v, sizeof(v), 0);
printf("tuncfg: recv() %d %d\n", r, errno);
if (r == 0)
{
/* client's gone */
printf("tuncfg: removing %s\n", ctx[i].dev);
close(ctx[i].fd);
ctx[i--] = ctx[ctx_n--];
continue;
}
if (r != sizeof(v))
{
r = (0x06 << 24);
goto ack;
}
/* v[0] = ham<n>, v[1] = ip, v[2] = mask */
if ( (v[0] & 0xff000000) != 0x05000000 ||
(v[1] & 0xff000000) != 0xff000000 )
{
printf("tuncfg: bad arg %08x %08x\n",
v[0], v[1]);
r = (0x07 << 24);
goto ack;
}
r = sprintf(cmd,
"ifconfig %s %u.%u.%u.%u ", ctx[i].dev,
(v[0] >> 24) & 0xff, (v[0] >> 16) & 0xff,
(v[0] >> 8) & 0xff, (v[0] >> 0) & 0xff);
r += sprintf(cmd + r,
"netmask %u.%u.%u.%u ",
(v[1] >> 24) & 0xff, (v[1] >> 16) & 0xff,
(v[1] >> 8) & 0xff, (v[1] >> 0) & 0xff);
v[0] |= ~v[1];
r += sprintf(cmd + r,
"mtu 1200 broadcast %u.%u.%u.%u",
(v[0] >> 24) & 0xff, (v[0] >> 16) & 0xff,
(v[0] >> 8) & 0xff, (v[0] >> 0) & 0xff);
r = system(cmd);
printf("tuncfg: system(%s) %d %d\n", cmd, r, errno);
ack:
printf("tuncfg: config() %08x", r);
send_with_fd(ctx[i].fd, &r, sizeof(r), -1);
}
}
return 0;
}
int send_with_fd(int fd, const void * ptr, size_t len, int fd_to_send)
{
struct msghdr msg = { 0 };
struct iovec iov[1];
struct cmsghdr * hdr;
char buf[CMSG_SPACE(sizeof(int))];
if (fd_to_send != -1)
{
msg.msg_control = buf;
msg.msg_controllen = sizeof(buf);
hdr = CMSG_FIRSTHDR(&msg);
hdr->cmsg_len = CMSG_LEN(sizeof(int));
hdr->cmsg_level = SOL_SOCKET;
hdr->cmsg_type = SCM_RIGHTS;
*(int*)CMSG_DATA(hdr) = fd_to_send;
}
iov[0].iov_base = (void*)ptr;
iov[0].iov_len = len;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
return sendmsg(fd, &msg, 0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment