Merge pull request #25 from ArroyoNetworks/reattach

linux: Support for Persistent Devices and Setting Owner/Group
This commit is contained in:
Song Gao 2017-05-24 09:52:02 -07:00 committed by GitHub
commit 99d07fc117
14 changed files with 164 additions and 92 deletions

View file

@ -4,3 +4,4 @@ KOJIMA Takanori <tkojima@accense.com>
Sean Purser-Haskell <sean.purserhaskell@gmail.com> Sean Purser-Haskell <sean.purserhaskell@gmail.com>
daregod <daregod@yandex.ru> daregod <daregod@yandex.ru>
Lucus Lee <lixin9311@gmail.com> Lucus Lee <lixin9311@gmail.com>
Arroyo Networks, LLC <open.source@arroyonetworks.com>

View file

@ -151,6 +151,11 @@ You'd see the ICMP packets printed out:
2017/03/20 21:17:40 Packet Received: 45 00 00 54 e9 1d 00 00 40 01 7d 6c 0a 01 00 0a 0a 01 00 14 08 00 ee 04 21 15 00 00 58 d0 a9 64 00 08 fb a5 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 2017/03/20 21:17:40 Packet Received: 45 00 00 54 e9 1d 00 00 40 01 7d 6c 0a 01 00 0a 0a 01 00 14 08 00 ee 04 21 15 00 00 58 d0 a9 64 00 08 fb a5 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37
``` ```
#### Caveats
1. Only Point-to-Point user TUN devices are supported. TAP devices are *not* supported natively by macOS.
2. Custom interface names are not supported by macOS. Interface names are automatically generated serially, using the `utun<#>` naming convention.
### TAP on Windows: ### TAP on Windows:
To use it with windows, you will need to install a [tap driver](https://github.com/OpenVPN/tap-windows6), or [OpenVPN client](https://github.com/OpenVPN/openvpn) for windows. To use it with windows, you will need to install a [tap driver](https://github.com/OpenVPN/tap-windows6), or [OpenVPN client](https://github.com/OpenVPN/openvpn) for windows.

34
if.go
View file

@ -1,6 +1,9 @@
package water package water
import "io" import (
"errors"
"io"
)
// Interface is a TUN/TAP interface. // Interface is a TUN/TAP interface.
type Interface struct { type Interface struct {
@ -46,29 +49,14 @@ func New(config Config) (ifce *Interface, err error) {
if zeroConfig == config { if zeroConfig == config {
config = defaultConfig() config = defaultConfig()
} }
return newDev(config) switch config.DeviceType {
case TUN:
return newTUN(config)
case TAP:
return newTAP(config)
default:
return nil, errors.New("unknown device type")
} }
// NewTAP creates a new TAP interface whose name is ifName. If ifName is empty, a
// default name (tap0, tap1, ... ) will be assigned. ifName should not exceed
// 16 bytes. TAP interfaces are not supported on darwin.
// ifName cannot be specified on windows, you will need ifce.Name() to use some cmds.
//
// Note: this function is deprecated and will be removed from the library.
// Please use New() instead.
func NewTAP(ifName string) (ifce *Interface, err error) {
return newTAP(ifName)
}
// NewTUN creates a new TUN interface whose name is ifName. If ifName is empty, a
// default name (tap0, tap1, ... ) will be assigned. ifName should not exceed
// ifName cannot be specified on windows, you will need ifce.Name() to use some cmds.
//
// Note: this function is deprecated and will be removed from the library.
// Please use New() instead.
// 16 bytes. Setting interface name is NOT supported on darwin.
func NewTUN(ifName string) (ifce *Interface, err error) {
return newTUN(ifName)
} }
// IsTUN returns true if ifce is a TUN interface. // IsTUN returns true if ifce is a TUN interface.

32
if_linux.go Normal file
View file

@ -0,0 +1,32 @@
// +build linux
package water
import (
"fmt"
)
// NewTAP creates a new TAP interface whose name is ifName. If ifName is empty, a
// default name (tap0, tap1, ... ) will be assigned. ifName should not exceed
// 16 bytes. TAP interfaces are not supported on darwin.
// ifName cannot be specified on windows, you will need ifce.Name() to use some cmds.
//
// Deprecated: This function may be removed in the future. Please use New() instead.
func NewTAP(ifName string) (ifce *Interface, err error) {
fmt.Println("Deprecated: NewTAP(..) may be removed in the future. Please use New() instead.")
config := Config{DeviceType: TAP}
config.Name = ifName
return newTAP(config)
}
// NewTUN creates a new TUN interface whose name is ifName. If ifName is empty, a
// default name (tap0, tap1, ... ) will be assigned. ifName should not exceed
// ifName cannot be specified on windows, you will need ifce.Name() to use some cmds.
//
// Deprecated: This function will be removed in the future. Please use New() instead.
func NewTUN(ifName string) (ifce *Interface, err error) {
fmt.Println("Deprecated: NewTUN(..) may be removed in the future. Please use New() instead.")
config := Config{DeviceType: TUN}
config.Name = ifName
return newTUN(config)
}

View file

@ -1,16 +0,0 @@
// +build linux darwin
package water
import "errors"
func newDev(config Config) (ifce *Interface, err error) {
switch config.DeviceType {
case TUN:
return newTUN(config.Name)
case TAP:
return newTAP(config.Name)
default:
return nil, errors.New("unknown device type")
}
}

View file

@ -1,12 +0,0 @@
// +build windows
package water
import "errors"
func newDev(config Config) (ifce *Interface, err error) {
if config.DeviceType != TAP && config.DeviceType != TUN {
return nil, errors.New("unknown device type")
}
return openDev(config)
}

View file

@ -30,7 +30,7 @@ func TestBroadcast(t *testing.T) {
brd = net.IPv4(10, 0, 42, 255) brd = net.IPv4(10, 0, 42, 255)
) )
ifce, err := NewTAP("test") ifce, err := New(Config{DeviceType: TAP})
if err != nil { if err != nil {
t.Fatalf("creating TAP error: %v\n", err) t.Fatalf("creating TAP error: %v\n", err)
} }

14
params_darwin.go Normal file
View file

@ -0,0 +1,14 @@
// +build darwin
package water
// PlatformSpecificParams defines parameters in Config that are specific to
// macOS. A zero-value of such type is valid, yielding an interface
// with OS defined name.
// Currently it is not possible to set the interface name in macOS.
type PlatformSpecificParams struct {
}
func defaultPlatformSpecificParams() PlatformSpecificParams {
return PlatformSpecificParams{}
}

38
params_linux.go Normal file
View file

@ -0,0 +1,38 @@
// +build linux
package water
type DevicePermissions struct {
// ID of the user which will be granted ownership of the device.
// If set to a negative value, the owner value will not be changed.
// By default, Linux sets the owner to -1, which allows any user.
Owner uint
// ID of the group which will be granted access to the device.
// If set to a negative value, the group value will not be changed.
// By default, Linux sets the group to -1, which allows any group.
Group uint
}
// PlatformSpecificParams defines parameters in Config that are specific to
// Linux. A zero-value of such type is valid, yielding an interface
// with OS defined name.
type PlatformSpecificParams struct {
// Name is the name to be set for the interface to be created. This overrides
// the default name assigned by OS such as tap0 or tun0. A zero-value of this
// field, i.e. an empty string, indicates that the default name should be
// used.
Name string
// Enable or disable persistence mode for the interface device.
Persist bool
// Owner and Group permissions for the device.
// A zero-value of this field, i.e. nil, indicates that no changes to owner
// or group will be made.
Permissions *DevicePermissions
}
func defaultPlatformSpecificParams() PlatformSpecificParams {
return PlatformSpecificParams{}
}

View file

@ -1,18 +0,0 @@
// +build linux darwin
package water
// PlatformSpecificParams defines parameters in Config that are specific to
// Linux and macOS. A zero-value of such type is valid, yielding an interface
// with OS defined name.
type PlatformSpecificParams struct {
// Name is the name to be set for the interface to be created. This overrides
// the default name assigned by OS such as tap0 or tun0. A zero-value of this
// field, i.e. an emapty string, indicates that the default name should be
// used.
Name string
}
func defaultPlatformSpecificParams() PlatformSpecificParams {
return PlatformSpecificParams{}
}

View file

@ -61,7 +61,7 @@ type sockaddrCtl struct {
var sockaddrCtlSize uintptr = 32 var sockaddrCtlSize uintptr = 32
func newTUN(string) (ifce *Interface, err error) { func newTUN(config Config) (ifce *Interface, err error) {
var fd int var fd int
// Supposed to be socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL), but ... // Supposed to be socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL), but ...
// //
@ -122,6 +122,10 @@ func newTUN(string) (ifce *Interface, err error) {
}, nil }, nil
} }
func newTAP(config Config) (ifce *Interface, err error) {
return nil, errors.New("tap interface not implemented on this platform")
}
// tunReadCloser is a hack to work around the first 4 bytes "packet // tunReadCloser is a hack to work around the first 4 bytes "packet
// information" because there doesn't seem to be an IFF_NO_PI for darwin. // information" because there doesn't seem to be an IFF_NO_PI for darwin.
type tunReadCloser struct { type tunReadCloser struct {
@ -189,7 +193,3 @@ func (t *tunReadCloser) Close() error {
return t.f.Close() return t.f.Close()
} }
func newTAP(ifName string) (ifce *Interface, err error) {
return nil, errors.New("tap interface not implemented on this platform")
}

View file

@ -21,28 +21,46 @@ type ifReq struct {
pad [0x28 - 0x10 - 2]byte pad [0x28 - 0x10 - 2]byte
} }
func newTAP(ifName string) (ifce *Interface, err error) { func ioctl(fd uintptr, request int, argp uintptr) error {
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(request), argp)
if errno != 0 {
return os.NewSyscallError("ioctl", errno)
}
return nil
}
func newTAP(config Config) (ifce *Interface, err error) {
file, err := os.OpenFile("/dev/net/tun", os.O_RDWR, 0) file, err := os.OpenFile("/dev/net/tun", os.O_RDWR, 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
name, err := createInterface(file.Fd(), ifName, cIFF_TAP|cIFF_NO_PI) name, err := createInterface(file.Fd(), config.Name, cIFF_TAP|cIFF_NO_PI)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err = setDeviceOptions(file.Fd(), config); err != nil {
return nil, err
}
ifce = &Interface{isTAP: true, ReadWriteCloser: file, name: name} ifce = &Interface{isTAP: true, ReadWriteCloser: file, name: name}
return return
} }
func newTUN(ifName string) (ifce *Interface, err error) { func newTUN(config Config) (ifce *Interface, err error) {
file, err := os.OpenFile("/dev/net/tun", os.O_RDWR, 0) file, err := os.OpenFile("/dev/net/tun", os.O_RDWR, 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
name, err := createInterface(file.Fd(), ifName, cIFF_TUN|cIFF_NO_PI) name, err := createInterface(file.Fd(), config.Name, cIFF_TUN|cIFF_NO_PI)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err = setDeviceOptions(file.Fd(), config); err != nil {
return nil, err
}
ifce = &Interface{isTAP: false, ReadWriteCloser: file, name: name} ifce = &Interface{isTAP: false, ReadWriteCloser: file, name: name}
return return
} }
@ -51,11 +69,37 @@ func createInterface(fd uintptr, ifName string, flags uint16) (createdIFName str
var req ifReq var req ifReq
req.Flags = flags req.Flags = flags
copy(req.Name[:], ifName) copy(req.Name[:], ifName)
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TUNSETIFF), uintptr(unsafe.Pointer(&req)))
if errno != 0 { err = ioctl(fd, syscall.TUNSETIFF, uintptr(unsafe.Pointer(&req)))
err = errno if err != nil {
return return
} }
createdIFName = strings.Trim(string(req.Name[:]), "\x00") createdIFName = strings.Trim(string(req.Name[:]), "\x00")
return return
} }
func setDeviceOptions(fd uintptr, config Config) (err error) {
// Device Permissions
if config.Permissions != nil {
// Set Owner
if err = ioctl(fd, syscall.TUNSETOWNER, uintptr(config.Permissions.Owner)); err != nil {
return
}
// Set Group
if err = ioctl(fd, syscall.TUNSETGROUP, uintptr(config.Permissions.Group)); err != nil {
return
}
}
// Set/Clear Persist Device Flag
value := 0
if config.Persist {
value = 1
}
return ioctl(fd, syscall.TUNSETPERSIST, uintptr(value))
}

View file

@ -4,10 +4,10 @@ package water
import "errors" import "errors"
func newTAP(ifName string) (ifce *Interface, err error) { func newTAP(config Config) (ifce *Interface, err error) {
return nil, errors.New("tap interface not implemented on this platform") return nil, errors.New("tap interface not implemented on this platform")
} }
func newTUN(ifName string) (ifce *Interface, err error) { func newTUN(config Config) (ifce *Interface, err error) {
return nil, errors.New("tap interface not implemented on this platform") return nil, errors.New("tap interface not implemented on this platform")
} }

View file

@ -289,14 +289,10 @@ func openDev(config Config) (ifce *Interface, err error) {
return nil, errIfceNameNotFound return nil, errIfceNameNotFound
} }
func newTAP(ifName string) (ifce *Interface, err error) { func newTAP(config Config) (ifce *Interface, err error) {
config := defaultConfig()
config.DeviceType = TAP
return openDev(config) return openDev(config)
} }
func newTUN(ifName string) (ifce *Interface, err error) { func newTUN(config Config) (ifce *Interface, err error) {
config := defaultConfig()
config.DeviceType = TUN
return openDev(config) return openDev(config)
} }