1
0
mirror of https://git.zx2c4.com/wireguard-go synced 2024-11-15 09:15:14 +01:00
wireguard-go/src/tun_linux.go
Mathias Hall-Andersen 996c7c4d8a Removed IFF_NO_PI from TUN linux
This change was needed for the Linux TUN status hack
to work properly (not increment the error counter).

This commit also updates the TUN interface to allow for
the construction / removal of the TUN info headers in-place.
2017-12-04 21:39:06 +01:00

378 lines
6.9 KiB
Go

package main
/* Implementation of the TUN device interface for linux
*/
import (
"encoding/binary"
"errors"
"fmt"
"golang.org/x/net/ipv6"
"golang.org/x/sys/unix"
"net"
"os"
"strings"
"time"
"unsafe"
)
// #include <string.h>
// #include <unistd.h>
// #include <net/if.h>
// #include <netinet/in.h>
// #include <linux/netlink.h>
// #include <linux/rtnetlink.h>
//
// /* Creates a netlink socket
// * listening to the RTMGRP_LINK multicast group
// */
//
// int bind_rtmgrp() {
// int nl_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
// if (nl_sock < 0)
// return -1;
//
// struct sockaddr_nl addr;
// memset ((void *) &addr, 0, sizeof (addr));
// addr.nl_family = AF_NETLINK;
// addr.nl_pid = getpid ();
// addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;
//
// if (bind(nl_sock, (struct sockaddr *) &addr, sizeof (addr)) < 0)
// return -1;
//
// return nl_sock;
// }
import "C"
const (
CloneDevicePath = "/dev/net/tun"
IFReqSize = unix.IFNAMSIZ + 64
)
type NativeTun struct {
fd *os.File
index int32 // if index
name string // name of interface
errors chan error // async error handling
events chan TUNEvent // device related events
}
func (tun *NativeTun) File() *os.File {
return tun.fd
}
func (tun *NativeTun) RoutineHackListener() {
/* This is needed for the detection to work across network namespaces
* If you are reading this and know a better method, please get in touch.
*/
fd := int(tun.fd.Fd())
for {
_, err := unix.Write(fd, nil)
switch err {
case unix.EINVAL:
tun.events <- TUNEventUp
case unix.EIO:
tun.events <- TUNEventDown
default:
}
time.Sleep(time.Second / 10)
}
}
func (tun *NativeTun) RoutineNetlinkListener() {
sock := int(C.bind_rtmgrp())
if sock < 0 {
tun.errors <- errors.New("Failed to create netlink event listener")
return
}
for msg := make([]byte, 1<<16); ; {
msgn, _, _, _, err := unix.Recvmsg(sock, msg[:], nil, 0)
if err != nil {
tun.errors <- fmt.Errorf("Failed to receive netlink message: %s", err.Error())
return
}
for remain := msg[:msgn]; len(remain) >= unix.SizeofNlMsghdr; {
hdr := *(*unix.NlMsghdr)(unsafe.Pointer(&remain[0]))
if int(hdr.Len) > len(remain) {
break
}
switch hdr.Type {
case unix.NLMSG_DONE:
remain = []byte{}
case unix.RTM_NEWLINK:
info := *(*unix.IfInfomsg)(unsafe.Pointer(&remain[unix.SizeofNlMsghdr]))
remain = remain[hdr.Len:]
if info.Index != tun.index {
// not our interface
continue
}
if info.Flags&unix.IFF_RUNNING != 0 {
tun.events <- TUNEventUp
}
if info.Flags&unix.IFF_RUNNING == 0 {
tun.events <- TUNEventDown
}
tun.events <- TUNEventMTUUpdate
default:
remain = remain[hdr.Len:]
}
}
}
}
func (tun *NativeTun) isUp() (bool, error) {
inter, err := net.InterfaceByName(tun.name)
return inter.Flags&net.FlagUp != 0, err
}
func (tun *NativeTun) Name() string {
return tun.name
}
func getDummySock() (int, error) {
return unix.Socket(
unix.AF_INET,
unix.SOCK_DGRAM,
0,
)
}
func getIFIndex(name string) (int32, error) {
fd, err := getDummySock()
if err != nil {
return 0, err
}
defer unix.Close(fd)
var ifr [IFReqSize]byte
copy(ifr[:], name)
_, _, errno := unix.Syscall(
unix.SYS_IOCTL,
uintptr(fd),
uintptr(unix.SIOCGIFINDEX),
uintptr(unsafe.Pointer(&ifr[0])),
)
if errno != 0 {
return 0, errno
}
index := binary.LittleEndian.Uint32(ifr[unix.IFNAMSIZ:])
return toInt32(index), nil
}
func (tun *NativeTun) setMTU(n int) error {
// open datagram socket
fd, err := unix.Socket(
unix.AF_INET,
unix.SOCK_DGRAM,
0,
)
if err != nil {
return err
}
defer unix.Close(fd)
// do ioctl call
var ifr [IFReqSize]byte
copy(ifr[:], tun.name)
binary.LittleEndian.PutUint32(ifr[16:20], uint32(n))
_, _, errno := unix.Syscall(
unix.SYS_IOCTL,
uintptr(fd),
uintptr(unix.SIOCSIFMTU),
uintptr(unsafe.Pointer(&ifr[0])),
)
if errno != 0 {
return errors.New("Failed to set MTU of TUN device")
}
return nil
}
func (tun *NativeTun) MTU() (int, error) {
// open datagram socket
fd, err := unix.Socket(
unix.AF_INET,
unix.SOCK_DGRAM,
0,
)
if err != nil {
return 0, err
}
defer unix.Close(fd)
// do ioctl call
var ifr [IFReqSize]byte
copy(ifr[:], tun.name)
_, _, errno := unix.Syscall(
unix.SYS_IOCTL,
uintptr(fd),
uintptr(unix.SIOCGIFMTU),
uintptr(unsafe.Pointer(&ifr[0])),
)
if errno != 0 {
return 0, errors.New("Failed to get MTU of TUN device")
}
// convert result to signed 32-bit int
val := binary.LittleEndian.Uint32(ifr[16:20])
if val >= (1 << 31) {
return int(toInt32(val)), nil
}
return int(val), nil
}
func (tun *NativeTun) Write(buff []byte, offset int) (int, error) {
// reserve space for header
buff = buff[offset-4:]
// add packet information header
buff[0] = 0x00
buff[1] = 0x00
if buff[4] == ipv6.Version<<4 {
buff[2] = 0x86
buff[3] = 0xdd
} else {
buff[2] = 0x08
buff[3] = 0x00
}
// write
return tun.fd.Write(buff)
}
func (tun *NativeTun) Read(buff []byte, offset int) (int, error) {
select {
case err := <-tun.errors:
return 0, err
default:
buff := buff[offset-4:]
n, err := tun.fd.Read(buff[:])
if n < 4 {
return 0, err
}
return n - 4, err
}
}
func (tun *NativeTun) Events() chan TUNEvent {
return tun.events
}
func (tun *NativeTun) Close() error {
return nil
}
func CreateTUNFromFile(name string, fd *os.File) (TUNDevice, error) {
device := &NativeTun{
fd: fd,
name: name,
events: make(chan TUNEvent, 5),
errors: make(chan error, 5),
}
// start event listener
var err error
device.index, err = getIFIndex(device.name)
if err != nil {
return nil, err
}
go device.RoutineNetlinkListener()
go device.RoutineHackListener() // cross namespace
// set default MTU
return device, device.setMTU(DefaultMTU)
}
func CreateTUN(name string) (TUNDevice, error) {
// open clone device
fd, err := os.OpenFile(CloneDevicePath, os.O_RDWR, 0)
if err != nil {
return nil, err
}
// create new device
var ifr [IFReqSize]byte
var flags uint16 = unix.IFF_TUN // | unix.IFF_NO_PI
nameBytes := []byte(name)
if len(nameBytes) >= unix.IFNAMSIZ {
return nil, errors.New("Interface name too long")
}
copy(ifr[:], nameBytes)
binary.LittleEndian.PutUint16(ifr[16:], flags)
_, _, errno := unix.Syscall(
unix.SYS_IOCTL,
uintptr(fd.Fd()),
uintptr(unix.TUNSETIFF),
uintptr(unsafe.Pointer(&ifr[0])),
)
if errno != 0 {
return nil, errno
}
// read (new) name of interface
newName := string(ifr[:])
newName = newName[:strings.Index(newName, "\000")]
device := &NativeTun{
fd: fd,
name: newName,
events: make(chan TUNEvent, 5),
errors: make(chan error, 5),
}
// start event listener
device.index, err = getIFIndex(device.name)
if err != nil {
return nil, err
}
go device.RoutineNetlinkListener()
go device.RoutineHackListener() // cross namespace
// set default MTU
return device, device.setMTU(DefaultMTU)
}