├── device_unsupported.go ├── LICENSE ├── interface.go ├── device_linux.go ├── examples └── dump.go ├── README.md └── device_darwin.go /device_unsupported.go: -------------------------------------------------------------------------------- 1 | // +build !linux,!darwin 2 | 3 | package tuntap 4 | 5 | func newTUN(name string) (Interface, error) { 6 | return nil, ErrUnsupported 7 | } 8 | 9 | func newTAP(name string) (Interface, error) { 10 | return nil, ErrUnsupported 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, James Cunningham 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | The views and conclusions contained in the software and documentation are those 25 | of the authors and should not be interpreted as representing official policies, 26 | either expressed or implied, of the project. 27 | -------------------------------------------------------------------------------- /interface.go: -------------------------------------------------------------------------------- 1 | package tuntap 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | ErrBusy = errors.New("device is already in use") 9 | ErrNotReady = errors.New("device is not ready") 10 | ErrExhausted = errors.New("no devices are available") 11 | ErrUnsupported = errors.New("device is unsupported on this platform") 12 | ) 13 | 14 | // Interface represents a TUN/TAP network interface 15 | type Interface interface { 16 | // return name of TUN/TAP interface 17 | Name() string 18 | 19 | // implement io.Reader interface, read bytes into p from TUN/TAP interface 20 | Read(p []byte) (n int, err error) 21 | 22 | // implement io.Writer interface, write bytes from p to TUN/TAP interface 23 | Write(p []byte) (n int, err error) 24 | 25 | // implement io.Closer interface, must be called when done with TUN/TAP interface 26 | Close() error 27 | 28 | // return string representation of TUN/TAP interface 29 | String() string 30 | } 31 | 32 | // return a TUN interface. depending on platform, the device may not be ready 33 | // for use yet; a caller must poll the Ready() method before use. additionally 34 | // the caller is responsible for calling Close() to terminate the device. 35 | func Tun(name string) (Interface, error) { 36 | // call platform specific device creation 37 | return newTUN(name) 38 | } 39 | 40 | // return a TAP interface. depending on platform, the device may not be ready 41 | // for use yet; a caller must poll the Ready() method before use. additionally 42 | // the caller is responsible for calling Close() to terminate the device. 43 | func Tap(name string) (Interface, error) { 44 | // call platform specific device creation 45 | return newTAP(name) 46 | } 47 | -------------------------------------------------------------------------------- /device_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package tuntap 4 | 5 | import ( 6 | "os" 7 | "strings" 8 | "syscall" 9 | "unsafe" 10 | ) 11 | 12 | const ( 13 | cIFF_TUN = 0x0001 14 | cIFF_TAP = 0x0002 15 | cIFF_NO_PI = 0x1000 16 | ) 17 | 18 | type device struct { 19 | n string 20 | f *os.File 21 | } 22 | 23 | func (d *device) Name() string { return d.n } 24 | func (d *device) String() string { return d.n } 25 | func (d *device) Close() error { return d.f.Close() } 26 | func (d *device) Read(p []byte) (int, error) { return d.f.Read(p) } 27 | func (d *device) Write(p []byte) (int, error) { return d.f.Write(p) } 28 | 29 | func newTUN(name string) (Interface, error) { 30 | file, err := os.OpenFile("/dev/net/tun", os.O_RDWR, 0) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | iface, err := createTuntapInterface(file.Fd(), name, cIFF_TUN|cIFF_NO_PI) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | return &device{n: iface, f: file}, nil 41 | } 42 | 43 | func newTAP(name string) (Interface, error) { 44 | file, err := os.OpenFile("/dev/net/tun", os.O_RDWR, 0) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | iface, err := createTuntapInterface(file.Fd(), name, cIFF_TAP|cIFF_NO_PI) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | return &device{n: iface, f: file}, nil 55 | } 56 | 57 | type tuntapInterface struct { 58 | Name [0x10]byte 59 | Flags uint16 60 | pad [0x28 - 0x10 - 2]byte 61 | } 62 | 63 | func createTuntapInterface(fd uintptr, name string, flags uint16) (string, error) { 64 | var req tuntapInterface 65 | req.Flags = flags 66 | copy(req.Name[:], name) 67 | 68 | _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TUNSETIFF), uintptr(unsafe.Pointer(&req))) 69 | if errno != 0 { 70 | return "", errno 71 | } 72 | 73 | return strings.Trim(string(req.Name[:]), "\x00"), nil 74 | } 75 | -------------------------------------------------------------------------------- /examples/dump.go: -------------------------------------------------------------------------------- 1 | /* 2 | dump packets sent to TUN device to stdout 3 | 4 | example: 5 | -- Packet Received -- 6 | 7 | IP Version: 4 8 | Length: 84 9 | Protocol: 1 (1=ICMP, 6=TCP, 17=UDP) 10 | Source IP: 10.12.0.2 11 | Destination IP: 10.12.0.1 12 | Payload: [08001467cc79000156bcc24d0004130d08090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637] 13 | */ 14 | package main 15 | 16 | import ( 17 | "encoding/binary" 18 | "fmt" 19 | "net" 20 | "os" 21 | "time" 22 | 23 | "github.com/jamescun/tuntap" 24 | ) 25 | 26 | func main() { 27 | if len(os.Args) < 2 { 28 | fmt.Println("help: icmp.go ") 29 | return 30 | } 31 | 32 | tun, err := tuntap.Tun(os.Args[1]) 33 | if err != nil { 34 | fmt.Println("error: tun:", err) 35 | return 36 | } 37 | defer tun.Close() 38 | 39 | buf := make([]byte, 1500) 40 | for { 41 | n, err := tun.Read(buf) 42 | if err == tuntap.ErrNotReady { 43 | fmt.Println("warning: tun: interface not ready, waiting 1s...") 44 | time.Sleep(1 * time.Second) 45 | continue 46 | } else if err != nil { 47 | fmt.Println("error: read:", err) 48 | break 49 | } 50 | 51 | fmt.Print("\n-- Packet Received --\n\n") 52 | 53 | switch buf[0] & 0xF0 { 54 | case 0x40: 55 | fmt.Println("IP Version: 4") 56 | 57 | fmt.Printf("Length: %d\n", binary.BigEndian.Uint16(buf[2:4])) 58 | fmt.Printf("Protocol: %d (1=ICMP, 6=TCP, 17=UDP)\n", buf[9]) 59 | fmt.Printf("Source IP: %s\n", net.IP(buf[12:16])) 60 | fmt.Printf("Destination IP: %s\n", net.IP(buf[16:20])) 61 | 62 | ihl := (buf[0] & 0x0F) * 4 63 | fmt.Printf("Payload: [%x]\n", buf[ihl:n]) 64 | 65 | case 0x60: 66 | fmt.Println("IP Version: 6") 67 | 68 | fmt.Printf("Length: %d\n", binary.BigEndian.Uint16(buf[4:6])) 69 | fmt.Printf("Protocol: %d (1=ICMP, 6=TCP, 17=UDP)\n", buf[7]) 70 | fmt.Printf("Source IP: %s\n", net.IP(buf[8:24])) 71 | fmt.Printf("Destination IP: %s\n", net.IP(buf[24:40])) 72 | 73 | fmt.Printf("Payload: [%x]\n", buf[40:n]) 74 | } 75 | 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **This repository is unmaintained:** You should use the more comprehensive and well maintained [water library](https://github.com/songgao/water). 2 | 3 | TUN/TAP 4 | ======= 5 | 6 | [![GoDoc](https://godoc.org/github.com/jamescun/tuntap?status.svg)](https://godoc.org/github.com/jamescun/tuntap) [![License](https://img.shields.io/badge/license-BSD-blue.svg)](LICENSE) 7 | 8 | **NOTE:** This package is new and should be considered unstable, in terms of both API and function. 9 | 10 | tuntap is a native wrapper for interfacing with TUN/TAP network devices in an idiomatic fashion. 11 | 12 | Currently supported are Linux and Mac OS X. 13 | 14 | go get github.com/jamescun/tuntap 15 | 16 | 17 | Configuration 18 | ------------- 19 | 20 | The configuration required to open a TUN/TAP device varies by platform. The differences are noted below. 21 | 22 | ### Linux 23 | 24 | When creating a TUN/TAP device, Linux expects to be given a name for the new interface, and a new interface will be allocated for it by the kernel module. If left blank, one will be generated `(tun|tap)([0-9]+)`. 25 | 26 | tap, err := tuntap.Tap("tap1") // created tap1 device if available 27 | tun, err := tuntap.Tun("") // created tun device at first available id (tun0) 28 | 29 | 30 | ### Mac OS X 31 | 32 | On startup, the Mac OS X TUN/TAP kernel extension will allocate multiple TUN/TAP devices, up to the maximum number of each. When creating a TUN/TAP device, Mac OS X expects to be given a path to an unused device. If left blank, this package will attempt to find the first unused one. 33 | 34 | tap, err := tuntap.Tap("/dev/tap1") // open tap1 device if unused 35 | tun, err := tuntap.Tun("") // open first available tun device (tun0) 36 | 37 | Additionally, unlike Linux, a TUN/TAP device is not "ready" on OS X until it has an address assigned to it. Any attempt to read from/write to the interface will fail with ErrBusy. It is safe to backoff and try again until a successful operation. 38 | 39 | 40 | Examples 41 | -------- 42 | 43 | See the [examples](examples) directory. 44 | -------------------------------------------------------------------------------- /device_darwin.go: -------------------------------------------------------------------------------- 1 | // +build darwin 2 | 3 | package tuntap 4 | 5 | import ( 6 | "os" 7 | "strconv" 8 | "syscall" 9 | ) 10 | 11 | // maximum devices supported by driver 12 | const maxDevices = 16 13 | 14 | type device struct { 15 | n string 16 | f *os.File 17 | } 18 | 19 | func (d *device) Name() string { return d.n } 20 | func (d *device) String() string { return d.n } 21 | func (d *device) Close() error { return d.f.Close() } 22 | 23 | func (d *device) Read(p []byte) (n int, err error) { 24 | n, err = d.f.Read(p) 25 | if isNotReady(err) { 26 | err = ErrNotReady 27 | } 28 | 29 | return 30 | } 31 | 32 | func (d *device) Write(p []byte) (n int, err error) { 33 | n, err = d.f.Write(p) 34 | if isNotReady(err) { 35 | err = ErrNotReady 36 | } 37 | 38 | return 39 | } 40 | 41 | // return true if read error is result of device not being ready 42 | func isNotReady(err error) bool { 43 | if perr, ok := err.(*os.PathError); ok { 44 | if code, ok := perr.Err.(syscall.Errno); ok { 45 | if code == 0x05 { 46 | return true 47 | } 48 | } 49 | } 50 | 51 | return false 52 | } 53 | 54 | // return true if file error is result of device already being used 55 | func isBusy(err error) bool { 56 | if perr, ok := err.(*os.PathError); ok { 57 | if code, ok := perr.Err.(syscall.Errno); ok { 58 | if code == 0x10 || code == 0x11 { // device busy || exclusive lock 59 | return true 60 | } 61 | } 62 | } 63 | 64 | return false 65 | } 66 | 67 | func newTUN(name string) (Interface, error) { 68 | if len(name) == 0 { 69 | // dynamic device 70 | for i := 0; i < maxDevices; i++ { 71 | iface, err := newDevice("/dev/tun" + strconv.Itoa(i)) 72 | if err == ErrBusy { 73 | // device already used 74 | continue 75 | } else if err != nil { 76 | // other error 77 | return nil, err 78 | } 79 | 80 | return iface, nil 81 | } 82 | 83 | return nil, ErrExhausted 84 | } 85 | 86 | // static device 87 | return newDevice(name) 88 | } 89 | 90 | func newTAP(name string) (Interface, error) { 91 | if len(name) == 0 { 92 | // dynamic device 93 | for i := 0; i < maxDevices; i++ { 94 | iface, err := newDevice("/dev/tap" + strconv.Itoa(i)) 95 | if err == ErrBusy { 96 | // device already used 97 | continue 98 | } else if err != nil { 99 | // other error 100 | return nil, err 101 | } 102 | 103 | return iface, nil 104 | } 105 | 106 | return nil, ErrExhausted 107 | } 108 | 109 | // static device 110 | return newDevice(name) 111 | } 112 | 113 | func newDevice(name string) (Interface, error) { 114 | file, err := os.OpenFile(name, os.O_EXCL|os.O_RDWR, 0) 115 | if isBusy(err) { 116 | return nil, ErrBusy 117 | } else if err != nil { 118 | return nil, err 119 | } 120 | 121 | return &device{n: name, f: file}, nil 122 | } 123 | --------------------------------------------------------------------------------