Test commit
commit
0136f16e1e
|
@ -0,0 +1,26 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
|
||||||
|
fmt.Println("Garbage Collector Thread Starting")
|
||||||
|
|
||||||
|
go memoryCleanerThread()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func memoryCleanerThread() {
|
||||||
|
|
||||||
|
for {
|
||||||
|
time.Sleep(10 * time.Minute)
|
||||||
|
fmt.Println("Time to clean memory...")
|
||||||
|
runtime.GC()
|
||||||
|
fmt.Println("Garbage Collection done.")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
# ZOREIDE
|
||||||
|
|
||||||
|
High Availability daemon to share an IP around N servers.
|
||||||
|
|
||||||
|
Zoreide is a daemon service you can use to share an IP among N servers. If the server owning the IP is down,
|
||||||
|
another one will replace it. Differently than Keepalived, it allows N servers instead of just 2. Perfect for docker-swarm
|
||||||
|
arrays.
|
||||||
|
|
||||||
|
The daemon uses Multicast in order to elect the node which will have the IP configured.
|
||||||
|
|
||||||
|
To use it, you need:
|
||||||
|
|
||||||
|
- root access to the servers
|
||||||
|
- a valid multicast IP address in your network. (otherwise you can use the ones in zoreide.json)
|
||||||
|
- linux servers under the same router.
|
||||||
|
|
||||||
|
To compile it , you need:
|
||||||
|
|
||||||
|
- golang compiler 1.4 or superior.
|
||||||
|
- git , to download this repo.
|
||||||
|
- write "go build"
|
||||||
|
- ./zoreide &
|
||||||
|
|
||||||
|
Configuration contains:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"MulticastConfig": {
|
||||||
|
"MIpAddr": "239.0.0.19",
|
||||||
|
"MPort": "9898",
|
||||||
|
"MaxDatagramSize": 18
|
||||||
|
},
|
||||||
|
"InterfaceConfig": {
|
||||||
|
"ExistingInterface": "eth0",
|
||||||
|
"BridgeIpCIDR": "10.0.1.1/24"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Where:
|
||||||
|
|
||||||
|
- MIpAddr is the multicast address all the nodes will use to align.Must be the same on all nodes.
|
||||||
|
- Mport: Multicast port to subscribe. Must be the same on all nodes.
|
||||||
|
- MaxDataGramSize : obsolete, will be removed soon.
|
||||||
|
|
||||||
|
- ExistingInterface: the name of your ingress interface (eth0, eno0 , enps18, whatever your system is using. This may be different node by node)
|
||||||
|
- BridgeIpCidr: the IP address and mask you want to share among servers.
|
||||||
|
|
||||||
|
|
||||||
|
# FAQ
|
||||||
|
|
||||||
|
- why? Keepalived wasn't enough?
|
||||||
|
- VRRP is ok, but it has just two states, active and passive. I want to have as much as nodes as I want.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- why you need more than one server to share the IP?
|
||||||
|
- because 6 is better than 2 for HA.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- MetalLb under Kubernetes can achieve the same.
|
||||||
|
- Nobody really needs kubernetes. I want a floating , HA IP without this.
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AbstractConfig struct {
|
||||||
|
MulticastConfig struct {
|
||||||
|
MIPAddr string `json:"MIpAddr"`
|
||||||
|
MPort string `json:"MPort"`
|
||||||
|
} `json:"MulticastConfig"`
|
||||||
|
InterfaceConfig struct {
|
||||||
|
ExistingInterface string `json:"ExistingInterface"`
|
||||||
|
BridgeIPCIDR string `json:"BridgeIpCIDR"`
|
||||||
|
} `json:"InterfaceConfig"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var a AbstractConfig
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
|
||||||
|
//reading json file
|
||||||
|
file, err := os.ReadFile("zoreide.json")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Cannot open config file", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal([]byte(file), &a)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Cannot marshal json: ", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
MulticastEntity.MIpAddr = a.MulticastConfig.MIPAddr
|
||||||
|
MulticastEntity.MPort = a.MulticastConfig.MPort
|
||||||
|
MulticastEntity.MaxDatagramSize = numberlenght
|
||||||
|
|
||||||
|
ZoreideBridge.BridgeIpCIDR = a.InterfaceConfig.BridgeIPCIDR
|
||||||
|
ZoreideBridge.ExistingInterface = a.InterfaceConfig.ExistingInterface
|
||||||
|
ZoreideBridge.IsActive = false
|
||||||
|
|
||||||
|
log.Println("Inizialized Generic Config: ", a)
|
||||||
|
log.Println("Inizialized Interface Config: ", ZoreideBridge)
|
||||||
|
log.Println("Inizialized Multicast Config: ", MulticastEntity)
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
module zoreide
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534
|
||||||
|
github.com/milosgajdos/tenus v0.0.3
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/docker/libcontainer v2.2.1+incompatible // indirect
|
||||||
|
github.com/google/uuid v1.2.0 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 // indirect
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,18 @@
|
||||||
|
github.com/docker/libcontainer v2.2.1+incompatible h1:++SbbkCw+X8vAd4j2gOCzZ2Nn7s2xFALTf7LZKmM1/0=
|
||||||
|
github.com/docker/libcontainer v2.2.1+incompatible/go.mod h1:osvj61pYsqhNCMLGX31xr7klUBhHb/ZBuXS0o1Fvwbw=
|
||||||
|
github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534 h1:dhy9OQKGBh4zVXbjwbxxHjRxMJtLXj3zfgpBYQaR4Q4=
|
||||||
|
github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
|
||||||
|
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||||
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/milosgajdos/tenus v0.0.3 h1:jmaJzwaY1DUyYVD0lM4U+uvP2kkEg1VahDqRFxIkVBE=
|
||||||
|
github.com/milosgajdos/tenus v0.0.3/go.mod h1:eIjx29vNeDOYWJuCnaHY2r4fq5egetV26ry3on7p8qY=
|
||||||
|
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
|
||||||
|
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005 h1:pDMpM2zh2MT0kHy037cKlSby2nEhD50SYqwQk76Nm40=
|
||||||
|
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
@ -0,0 +1,124 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-ping/ping"
|
||||||
|
"github.com/milosgajdos/tenus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AbstractBridge struct {
|
||||||
|
ExistingInterface string
|
||||||
|
BridgeIpCIDR string
|
||||||
|
IsActive bool
|
||||||
|
hIerarchyNumber int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
|
||||||
|
ZoreideBridge.initializeHierarchy()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *AbstractBridge) initializeHierarchy() {
|
||||||
|
|
||||||
|
var letterRunes = []rune("123456789")
|
||||||
|
|
||||||
|
num := make([]rune, numberlenght)
|
||||||
|
for i := range num {
|
||||||
|
num[i] = letterRunes[rand.Intn(len(letterRunes))]
|
||||||
|
}
|
||||||
|
|
||||||
|
zz, err := strconv.ParseInt(string(num), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Error generating number: ", err.Error())
|
||||||
|
b.hIerarchyNumber = 0
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
b.hIerarchyNumber = zz
|
||||||
|
log.Println("Success generating number: ", b.hIerarchyNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *AbstractBridge) configureIpAndBridgeUp() {
|
||||||
|
// we want the program to recover in case of issues
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
|
||||||
|
fmt.Println("An error happened in <configureIpAndBridgeUp()>, but Zoreide recovered. ")
|
||||||
|
fmt.Println("Error was: ", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// first we check the IP is free. Something weird could have happened in some
|
||||||
|
// other server
|
||||||
|
brIp, brIpNet, err := net.ParseCIDR(b.BridgeIpCIDR)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if doesIpExists(brIp.String()) {
|
||||||
|
log.Println("Cannot take this IP, it exists already: " + brIp.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
br, err := tenus.NewLinkFrom(b.ExistingInterface)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := br.SetLinkIp(brIp, brIpNet); err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *AbstractBridge) removeIPandBridgeInt() {
|
||||||
|
|
||||||
|
// we want the program to recover in case of issues
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
|
||||||
|
fmt.Println("An error happened in <removeIPandBridgeInt()>, but Zoreide recovered. ")
|
||||||
|
fmt.Println("Error was: ", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
br, err := tenus.NewLinkFrom(b.ExistingInterface)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
brIp, brIpNet, err := net.ParseCIDR(b.BridgeIpCIDR)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
}
|
||||||
|
if err := br.UnsetLinkIp(brIp, brIpNet); err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func doesIpExists(bridgeip string) bool {
|
||||||
|
|
||||||
|
pinger, err := ping.NewPinger(bridgeip)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Ping error: " + err.Error())
|
||||||
|
}
|
||||||
|
// just in case it doesn't stops alone
|
||||||
|
defer pinger.Stop()
|
||||||
|
pinger.Count = 5
|
||||||
|
pinger.Interval = time.Duration(10 * time.Millisecond)
|
||||||
|
pinger.Timeout = time.Duration(1 * time.Second)
|
||||||
|
pinger.Run() // blocks until finished
|
||||||
|
stats := pinger.Statistics()
|
||||||
|
|
||||||
|
return stats.PacketsRecv == 5
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var MulticastEntity AbstractMulticast
|
||||||
|
var BstChannel = make(chan string, 1)
|
||||||
|
var ZoreideBridge AbstractBridge
|
||||||
|
|
||||||
|
const numberlenght = 18
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
MulticastEntity.GetMulticastReady()
|
||||||
|
go MulticastEntity.WriteNumberToMulticast(ZoreideBridge)
|
||||||
|
go MulticastEntity.ReadNumberFromMulticast()
|
||||||
|
go ZoreideBridge.WaitAndClean(MulticastEntity)
|
||||||
|
go ZoreideBridge.HierarchyReLocator(MulticastEntity)
|
||||||
|
|
||||||
|
// Just a nice way to wait until the Operating system sends a kill signal.
|
||||||
|
// select{} was just horrible.
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||||
|
<-c
|
||||||
|
ZoreideBridge.removeIPandBridgeInt()
|
||||||
|
os.Exit(0)
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AbstractMulticast struct {
|
||||||
|
MIpAddr string
|
||||||
|
MPort string
|
||||||
|
MaxDatagramSize int
|
||||||
|
MWaddr *net.UDPAddr
|
||||||
|
MRaddr *net.UDPAddr
|
||||||
|
Wconn *net.UDPConn
|
||||||
|
Rconn *net.UDPConn
|
||||||
|
HierarchyArray []int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mip *AbstractMulticast) CreateUdpAddr() {
|
||||||
|
|
||||||
|
// This Will create the local UDP Address
|
||||||
|
addr, err := net.ResolveUDPAddr("udp4", mip.MIpAddr+":"+mip.MPort)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
mip.MRaddr = addr
|
||||||
|
log.Println("Multicast Listen addr created: ", mip.MRaddr.String())
|
||||||
|
|
||||||
|
// This will create the Writing UDP Addr
|
||||||
|
|
||||||
|
mip.MWaddr = addr
|
||||||
|
log.Println("Multicast Writing addr created: ", mip.MWaddr.String())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mip *AbstractMulticast) CreateWritingSocket() {
|
||||||
|
|
||||||
|
conn, err := net.DialUDP("udp4", nil, mip.MWaddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
mip.Wconn = conn
|
||||||
|
log.Println("Multicast Writing Socket Created: ", mip.Wconn.LocalAddr().String(), "->", mip.Wconn.RemoteAddr().String())
|
||||||
|
|
||||||
|
}
|
||||||
|
func (mip *AbstractMulticast) CreateReadingSocket() {
|
||||||
|
|
||||||
|
// Open up a connection
|
||||||
|
conn, err := net.ListenMulticastUDP("udp", nil, mip.MRaddr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("UDP " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.SetReadBuffer(mip.MaxDatagramSize)
|
||||||
|
|
||||||
|
mip.Rconn = conn
|
||||||
|
log.Println("Multicast Reading Socket Created: ", mip.Rconn.LocalAddr().String())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mip *AbstractMulticast) GetMulticastReady() {
|
||||||
|
|
||||||
|
mip.CreateUdpAddr()
|
||||||
|
mip.CreateWritingSocket()
|
||||||
|
mip.CreateReadingSocket()
|
||||||
|
}
|
|
@ -0,0 +1,121 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"slices"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (mip *AbstractMulticast) AddUniqueAndSort(hier int64) {
|
||||||
|
|
||||||
|
if slices.Contains(mip.HierarchyArray, hier) {
|
||||||
|
log.Println("Element already in the array:", hier)
|
||||||
|
} else {
|
||||||
|
mip.HierarchyArray = append(mip.HierarchyArray, hier)
|
||||||
|
sort.Slice(mip.HierarchyArray, func(i, j int) bool { return mip.HierarchyArray[i] < mip.HierarchyArray[j] })
|
||||||
|
log.Println(mip.HierarchyArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mip *AbstractMulticast) IsAlpha(hier int64) bool {
|
||||||
|
|
||||||
|
return mip.HierarchyArray[0] == hier
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mip *AbstractMulticast) WriteNumberToMulticast(br AbstractBridge) {
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
fmt.Println("An error happened in <WriteNumberToMulticast()>, but Zoreide recovered. ")
|
||||||
|
fmt.Println("Error was: ", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Println("Initiating ticker")
|
||||||
|
|
||||||
|
bstNumber := fmt.Sprintf("%d", br.hIerarchyNumber)
|
||||||
|
|
||||||
|
for range time.Tick(1 * time.Second) {
|
||||||
|
|
||||||
|
_, err := mip.Wconn.Write([]byte(bstNumber))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Cannot write to multicast:" + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mip *AbstractMulticast) ReadNumberFromMulticast() {
|
||||||
|
|
||||||
|
log.Println("Initiating reader")
|
||||||
|
buffer := make([]byte, mip.MaxDatagramSize)
|
||||||
|
|
||||||
|
// Loop forever reading from the socket
|
||||||
|
for {
|
||||||
|
|
||||||
|
_, _, err := mip.Rconn.ReadFromUDP(buffer)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("ReadFromUDP failed:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
BstChannel <- string(buffer)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *AbstractBridge) HierarchyReLocator(entity AbstractMulticast) {
|
||||||
|
|
||||||
|
log.Println("Inizializing Descalator")
|
||||||
|
|
||||||
|
for bstNumber := range BstChannel {
|
||||||
|
brdNumber, err := strconv.ParseInt(bstNumber, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Garbage received on multicast: ", bstNumber)
|
||||||
|
log.Println("Cannot convert to int64:", err.Error())
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
log.Println("Adding received:", brdNumber)
|
||||||
|
entity.AddUniqueAndSort(brdNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
// finished feeding the new number
|
||||||
|
// if Alpha:
|
||||||
|
if entity.IsAlpha(b.hIerarchyNumber) {
|
||||||
|
if b.IsActive {
|
||||||
|
log.Println("Still ALPHA. This is ok.")
|
||||||
|
b.IsActive = true // you never know. Better to reiterate.
|
||||||
|
} else {
|
||||||
|
log.Println("I'm the new ALPHA! Get out my path, losers!")
|
||||||
|
b.configureIpAndBridgeUp()
|
||||||
|
b.IsActive = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Println("GULP! There is a bigger one, better descalating")
|
||||||
|
if b.IsActive {
|
||||||
|
b.removeIPandBridgeInt()
|
||||||
|
b.IsActive = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *AbstractBridge) WaitAndClean(entity AbstractMulticast) {
|
||||||
|
|
||||||
|
log.Println("Inizializing Escalator")
|
||||||
|
|
||||||
|
for {
|
||||||
|
entity.AddUniqueAndSort(b.hIerarchyNumber)
|
||||||
|
pollTime := len(entity.HierarchyArray) + 1
|
||||||
|
time.Sleep(time.Duration(pollTime) * time.Second)
|
||||||
|
// svuotare l'array
|
||||||
|
entity.HierarchyArray = entity.HierarchyArray[:0]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,191 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
Copyright 2014 Docker, Inc.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -0,0 +1,16 @@
|
||||||
|
libcontainer
|
||||||
|
Copyright 2012-2015 Docker, Inc.
|
||||||
|
|
||||||
|
This product includes software developed at Docker, Inc. (http://www.docker.com).
|
||||||
|
|
||||||
|
The following is courtesy of our legal counsel:
|
||||||
|
|
||||||
|
|
||||||
|
Use and transfer of Docker may be subject to certain restrictions by the
|
||||||
|
United States and other governments.
|
||||||
|
It is your responsibility to ensure that your use and/or transfer does not
|
||||||
|
violate applicable laws.
|
||||||
|
|
||||||
|
For more information, please see http://www.bis.doc.gov
|
||||||
|
|
||||||
|
See also http://www.apache.org/dev/crypto.html and/or seek legal counsel.
|
|
@ -0,0 +1,2 @@
|
||||||
|
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
|
||||||
|
Guillaume J. Charmes <guillaume@docker.com> (@creack)
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Packet netlink provide access to low level Netlink sockets and messages.
|
||||||
|
//
|
||||||
|
// Actual implementations are in:
|
||||||
|
// netlink_linux.go
|
||||||
|
// netlink_darwin.go
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrWrongSockType = errors.New("Wrong socket type")
|
||||||
|
ErrShortResponse = errors.New("Got short response from netlink")
|
||||||
|
ErrInterfaceExists = errors.New("Network interface already exists")
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Route is a subnet associated with the interface to reach it.
|
||||||
|
type Route struct {
|
||||||
|
*net.IPNet
|
||||||
|
Iface *net.Interface
|
||||||
|
Default bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// An IfAddr defines IP network settings for a given network interface
|
||||||
|
type IfAddr struct {
|
||||||
|
Iface *net.Interface
|
||||||
|
IP net.IP
|
||||||
|
IPNet *net.IPNet
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
7
vendor/github.com/docker/libcontainer/netlink/netlink_linux_armppc64.go
generated
vendored
Normal file
7
vendor/github.com/docker/libcontainer/netlink/netlink_linux_armppc64.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
// +build arm ppc64 ppc64le
|
||||||
|
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
func ifrDataByte(b byte) uint8 {
|
||||||
|
return uint8(b)
|
||||||
|
}
|
7
vendor/github.com/docker/libcontainer/netlink/netlink_linux_notarm.go
generated
vendored
Normal file
7
vendor/github.com/docker/libcontainer/netlink/netlink_linux_notarm.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
// +build !arm,!ppc64,!ppc64le
|
||||||
|
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
func ifrDataByte(b byte) int8 {
|
||||||
|
return int8(b)
|
||||||
|
}
|
88
vendor/github.com/docker/libcontainer/netlink/netlink_unsupported.go
generated
vendored
Normal file
88
vendor/github.com/docker/libcontainer/netlink/netlink_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package netlink
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotImplemented = errors.New("not implemented")
|
||||||
|
)
|
||||||
|
|
||||||
|
func NetworkGetRoutes() ([]Route, error) {
|
||||||
|
return nil, ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkLinkAdd(name string, linkType string) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkLinkDel(name string) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkLinkUp(iface *net.Interface) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkLinkAddIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkLinkDelIp(iface *net.Interface, ip net.IP, ipNet *net.IPNet) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddRoute(destination, source, gateway, device string) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddDefaultGw(ip, device string) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkSetMTU(iface *net.Interface, mtu int) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkSetTxQueueLen(iface *net.Interface, txQueueLen int) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkCreateVethPair(name1, name2 string, txQueueLen int) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkChangeName(iface *net.Interface, newName string) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkSetNsFd(iface *net.Interface, fd int) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkSetNsPid(iface *net.Interface, nspid int) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkSetMaster(iface, master *net.Interface) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func NetworkLinkDown(iface *net.Interface) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateBridge(name string, setMacAddr bool) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteBridge(name string) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddToBridge(iface, master *net.Interface) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ParentDeathSignal int
|
||||||
|
|
||||||
|
func (p ParentDeathSignal) Restore() error {
|
||||||
|
if p == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
current, err := GetParentDeathSignal()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if p == current {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return p.Set()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ParentDeathSignal) Set() error {
|
||||||
|
return SetParentDeathSignal(uintptr(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execv(cmd string, args []string, env []string) error {
|
||||||
|
name, err := exec.LookPath(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return syscall.Exec(name, args, env)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetParentDeathSignal(sig uintptr) error {
|
||||||
|
if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, sig, 0); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetParentDeathSignal() (ParentDeathSignal, error) {
|
||||||
|
var sig int
|
||||||
|
_, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_GET_PDEATHSIG, uintptr(unsafe.Pointer(&sig)), 0)
|
||||||
|
if err != 0 {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
return ParentDeathSignal(sig), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetKeepCaps() error {
|
||||||
|
if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_KEEPCAPS, 1, 0); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClearKeepCaps() error {
|
||||||
|
if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_KEEPCAPS, 0, 0); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Setctty() error {
|
||||||
|
if _, _, err := syscall.RawSyscall(syscall.SYS_IOCTL, 0, uintptr(syscall.TIOCSCTTY), 0); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// look in /proc to find the process start time so that we can verify
|
||||||
|
// that this pid has started after ourself
|
||||||
|
func GetProcessStartTime(pid int) (string, error) {
|
||||||
|
data, err := ioutil.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "stat"))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(string(data), " ")
|
||||||
|
// the starttime is located at pos 22
|
||||||
|
// from the man page
|
||||||
|
//
|
||||||
|
// starttime %llu (was %lu before Linux 2.6)
|
||||||
|
// (22) The time the process started after system boot. In kernels before Linux 2.6, this
|
||||||
|
// value was expressed in jiffies. Since Linux 2.6, the value is expressed in clock ticks
|
||||||
|
// (divide by sysconf(_SC_CLK_TCK)).
|
||||||
|
return parts[22-1], nil // starts at 1
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Via http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=7b21fddd087678a70ad64afc0f632e0f1071b092
|
||||||
|
//
|
||||||
|
// We need different setns values for the different platforms and arch
|
||||||
|
// We are declaring the macro here because the SETNS syscall does not exist in th stdlib
|
||||||
|
var setNsMap = map[string]uintptr{
|
||||||
|
"linux/386": 346,
|
||||||
|
"linux/arm64": 268,
|
||||||
|
"linux/amd64": 308,
|
||||||
|
"linux/arm": 375,
|
||||||
|
"linux/ppc": 350,
|
||||||
|
"linux/ppc64": 350,
|
||||||
|
"linux/ppc64le": 350,
|
||||||
|
"linux/s390x": 339,
|
||||||
|
}
|
||||||
|
|
||||||
|
var sysSetns = setNsMap[fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)]
|
||||||
|
|
||||||
|
func SysSetns() uint32 {
|
||||||
|
return uint32(sysSetns)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Setns(fd uintptr, flags uintptr) error {
|
||||||
|
ns, exists := setNsMap[fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)]
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("unsupported platform %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
||||||
|
_, _, err := syscall.RawSyscall(ns, fd, flags, 0)
|
||||||
|
if err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// +build linux,386
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Setuid sets the uid of the calling thread to the specified uid.
|
||||||
|
func Setuid(uid int) (err error) {
|
||||||
|
_, _, e1 := syscall.RawSyscall(syscall.SYS_SETUID, uintptr(uid), 0, 0)
|
||||||
|
if e1 != 0 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setgid sets the gid of the calling thread to the specified gid.
|
||||||
|
func Setgid(gid int) (err error) {
|
||||||
|
_, _, e1 := syscall.RawSyscall(syscall.SYS_SETGID32, uintptr(gid), 0, 0)
|
||||||
|
if e1 != 0 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
// +build linux,arm64 linux,amd64 linux,ppc linux,ppc64 linux,ppc64le linux,s390x
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Setuid sets the uid of the calling thread to the specified uid.
|
||||||
|
func Setuid(uid int) (err error) {
|
||||||
|
_, _, e1 := syscall.RawSyscall(syscall.SYS_SETUID, uintptr(uid), 0, 0)
|
||||||
|
if e1 != 0 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setgid sets the gid of the calling thread to the specified gid.
|
||||||
|
func Setgid(gid int) (err error) {
|
||||||
|
_, _, e1 := syscall.RawSyscall(syscall.SYS_SETGID, uintptr(gid), 0, 0)
|
||||||
|
if e1 != 0 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// +build linux,arm
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Setuid sets the uid of the calling thread to the specified uid.
|
||||||
|
func Setuid(uid int) (err error) {
|
||||||
|
_, _, e1 := syscall.RawSyscall(syscall.SYS_SETUID32, uintptr(uid), 0, 0)
|
||||||
|
if e1 != 0 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setgid sets the gid of the calling thread to the specified gid.
|
||||||
|
func Setgid(gid int) (err error) {
|
||||||
|
_, _, e1 := syscall.RawSyscall(syscall.SYS_SETGID32, uintptr(gid), 0, 0)
|
||||||
|
if e1 != 0 {
|
||||||
|
err = e1
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
// +build cgo,linux cgo,freebsd
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <unistd.h>
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
func GetClockTicks() int {
|
||||||
|
return int(C.sysconf(C._SC_CLK_TCK))
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
// +build !cgo windows
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
func GetClockTicks() int {
|
||||||
|
// TODO figure out a better alternative for platforms where we're missing cgo
|
||||||
|
//
|
||||||
|
// TODO Windows. This could be implemented using Win32 QueryPerformanceFrequency().
|
||||||
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms644905(v=vs.85).aspx
|
||||||
|
//
|
||||||
|
// An example of its usage can be found here.
|
||||||
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx
|
||||||
|
|
||||||
|
return 100
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _zero uintptr
|
||||||
|
|
||||||
|
// Returns the size of xattrs and nil error
|
||||||
|
// Requires path, takes allocated []byte or nil as last argument
|
||||||
|
func Llistxattr(path string, dest []byte) (size int, err error) {
|
||||||
|
pathBytes, err := syscall.BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
var newpathBytes unsafe.Pointer
|
||||||
|
if len(dest) > 0 {
|
||||||
|
newpathBytes = unsafe.Pointer(&dest[0])
|
||||||
|
} else {
|
||||||
|
newpathBytes = unsafe.Pointer(&_zero)
|
||||||
|
}
|
||||||
|
|
||||||
|
_size, _, errno := syscall.Syscall6(syscall.SYS_LLISTXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(newpathBytes), uintptr(len(dest)), 0, 0, 0)
|
||||||
|
size = int(_size)
|
||||||
|
if errno != 0 {
|
||||||
|
return -1, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
return size, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a []byte slice if the xattr is set and nil otherwise
|
||||||
|
// Requires path and its attribute as arguments
|
||||||
|
func Lgetxattr(path string, attr string) ([]byte, error) {
|
||||||
|
var sz int
|
||||||
|
pathBytes, err := syscall.BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
attrBytes, err := syscall.BytePtrFromString(attr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start with a 128 length byte array
|
||||||
|
sz = 128
|
||||||
|
dest := make([]byte, sz)
|
||||||
|
destBytes := unsafe.Pointer(&dest[0])
|
||||||
|
_sz, _, errno := syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(destBytes), uintptr(len(dest)), 0, 0)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case errno == syscall.ENODATA:
|
||||||
|
return nil, errno
|
||||||
|
case errno == syscall.ENOTSUP:
|
||||||
|
return nil, errno
|
||||||
|
case errno == syscall.ERANGE:
|
||||||
|
// 128 byte array might just not be good enough,
|
||||||
|
// A dummy buffer is used ``uintptr(0)`` to get real size
|
||||||
|
// of the xattrs on disk
|
||||||
|
_sz, _, errno = syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(unsafe.Pointer(nil)), uintptr(0), 0, 0)
|
||||||
|
sz = int(_sz)
|
||||||
|
if sz < 0 {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
dest = make([]byte, sz)
|
||||||
|
destBytes := unsafe.Pointer(&dest[0])
|
||||||
|
_sz, _, errno = syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(destBytes), uintptr(len(dest)), 0, 0)
|
||||||
|
if errno != 0 {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
case errno != 0:
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
sz = int(_sz)
|
||||||
|
return dest[:sz], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lsetxattr(path string, attr string, data []byte, flags int) error {
|
||||||
|
pathBytes, err := syscall.BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
attrBytes, err := syscall.BytePtrFromString(attr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var dataBytes unsafe.Pointer
|
||||||
|
if len(data) > 0 {
|
||||||
|
dataBytes = unsafe.Pointer(&data[0])
|
||||||
|
} else {
|
||||||
|
dataBytes = unsafe.Pointer(&_zero)
|
||||||
|
}
|
||||||
|
_, _, errno := syscall.Syscall6(syscall.SYS_LSETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(dataBytes), uintptr(len(data)), uintptr(flags), 0)
|
||||||
|
if errno != 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
# https://editorconfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
[*.go]
|
||||||
|
indent_style = tab
|
|
@ -0,0 +1,2 @@
|
||||||
|
/ping
|
||||||
|
/dist
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
issues:
|
||||||
|
exclude-rules:
|
||||||
|
- path: _test.go
|
||||||
|
linters:
|
||||||
|
- errcheck
|
|
@ -0,0 +1,46 @@
|
||||||
|
project_name: ping
|
||||||
|
before:
|
||||||
|
hooks:
|
||||||
|
- go mod download
|
||||||
|
builds:
|
||||||
|
- binary: ping
|
||||||
|
dir: cmd/ping
|
||||||
|
goarch:
|
||||||
|
- amd64
|
||||||
|
- arm
|
||||||
|
- arm64
|
||||||
|
goarm:
|
||||||
|
- 6
|
||||||
|
- 7
|
||||||
|
goos:
|
||||||
|
- darwin
|
||||||
|
- freebsd
|
||||||
|
- linux
|
||||||
|
- windows
|
||||||
|
archives:
|
||||||
|
- files:
|
||||||
|
- LICENSE
|
||||||
|
- README.md
|
||||||
|
format_overrides:
|
||||||
|
- goos: windows
|
||||||
|
format: zip
|
||||||
|
wrap_in_directory: true
|
||||||
|
# TODO: Decide if we want packages (name conflcits with /bin/ping?)
|
||||||
|
# nfpms:
|
||||||
|
# homepage: https://github.com/go-ping/ping
|
||||||
|
# maintainer: 'Go Ping Maintainers <go-ping@example.com>'
|
||||||
|
# description: Ping written in Go.
|
||||||
|
# license: MIT
|
||||||
|
# formats:
|
||||||
|
# - deb
|
||||||
|
# - rpm
|
||||||
|
checksum:
|
||||||
|
name_template: 'checksums.txt'
|
||||||
|
snapshot:
|
||||||
|
name_template: "{{ .Tag }}-{{ .ShortCommit }}"
|
||||||
|
changelog:
|
||||||
|
sort: asc
|
||||||
|
filters:
|
||||||
|
exclude:
|
||||||
|
- '^docs:'
|
||||||
|
- '^test:'
|
|
@ -0,0 +1,44 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
First off, thanks for taking the time to contribute!
|
||||||
|
|
||||||
|
Remember that this is open source software so please consider the other people who will read your code.
|
||||||
|
Make it look nice for them, document your logic in comments and add or update the unit test cases.
|
||||||
|
|
||||||
|
This library is used by various other projects, companies and individuals in live production environments so please discuss any breaking changes with us before making them.
|
||||||
|
Feel free to join us in the #go-ping channel of the [Gophers Slack](https://invite.slack.golangbridge.org/).
|
||||||
|
|
||||||
|
## Pull Requests
|
||||||
|
|
||||||
|
[Fork the repo on GitHub](https://github.com/go-ping/ping/fork) and clone it to your local machine.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/YOUR_USERNAME/ping.git && cd ping
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is a guide on [how to configure a remote repository](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/configuring-a-remote-for-a-fork).
|
||||||
|
|
||||||
|
Check out a new branch, make changes, run tests, commit & sign-off, then push branch to your fork.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git checkout -b <BRANCH_NAME>
|
||||||
|
# edit files
|
||||||
|
$ make style vet test
|
||||||
|
$ git add <CHANGED_FILES>
|
||||||
|
$ git commit -s
|
||||||
|
$ git push <FORK> <BRANCH_NAME>
|
||||||
|
```
|
||||||
|
|
||||||
|
Open a [new pull request](https://github.com/go-ping/ping/compare) in the main `go-ping/ping` repository.
|
||||||
|
Please describe the purpose of your PR and remember link it to any related issues.
|
||||||
|
|
||||||
|
*We may ask you to rebase your feature branch or squash the commits in order to keep the history clean.*
|
||||||
|
|
||||||
|
## Development Guides
|
||||||
|
|
||||||
|
- Run `make style vet test` before committing your changes.
|
||||||
|
- Document your logic in code comments.
|
||||||
|
- Add tests for bug fixes and new features.
|
||||||
|
- Use UNIX-style (LF) line endings.
|
||||||
|
- End every file with a single blank line.
|
||||||
|
- Use the UTF-8 character set.
|
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Cameron Sparr and contributors.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,32 @@
|
||||||
|
GO ?= go
|
||||||
|
GOFMT ?= $(GO)fmt
|
||||||
|
GOOPTS ?=
|
||||||
|
GO111MODULE :=
|
||||||
|
pkgs = ./...
|
||||||
|
|
||||||
|
all: style vet build test
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build:
|
||||||
|
@echo ">> building ping"
|
||||||
|
GO111MODULE=$(GO111MODULE) $(GO) build $(GOOPTS) ./cmd/ping
|
||||||
|
|
||||||
|
.PHONY: style
|
||||||
|
style:
|
||||||
|
@echo ">> checking code style"
|
||||||
|
@fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \
|
||||||
|
if [ -n "$${fmtRes}" ]; then \
|
||||||
|
echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \
|
||||||
|
echo "Please ensure you are using $$($(GO) version) for formatting code."; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
@echo ">> running all tests"
|
||||||
|
GO111MODULE=$(GO111MODULE) $(GO) test -race -cover $(GOOPTS) $(pkgs)
|
||||||
|
|
||||||
|
.PHONY: vet
|
||||||
|
vet:
|
||||||
|
@echo ">> vetting code"
|
||||||
|
GO111MODULE=$(GO111MODULE) $(GO) vet $(GOOPTS) $(pkgs)
|
|
@ -0,0 +1,141 @@
|
||||||
|
# go-ping
|
||||||
|
[![PkgGoDev](https://pkg.go.dev/badge/github.com/go-ping/ping)](https://pkg.go.dev/github.com/go-ping/ping)
|
||||||
|
[![Circle CI](https://circleci.com/gh/go-ping/ping.svg?style=svg)](https://circleci.com/gh/go-ping/ping)
|
||||||
|
|
||||||
|
A simple but powerful ICMP echo (ping) library for Go, inspired by
|
||||||
|
[go-fastping](https://github.com/tatsushid/go-fastping).
|
||||||
|
|
||||||
|
Here is a very simple example that sends and receives three packets:
|
||||||
|
|
||||||
|
```go
|
||||||
|
pinger, err := ping.NewPinger("www.google.com")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
pinger.Count = 3
|
||||||
|
err = pinger.Run() // Blocks until finished.
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
stats := pinger.Statistics() // get send/receive/duplicate/rtt stats
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is an example that emulates the traditional UNIX ping command:
|
||||||
|
|
||||||
|
```go
|
||||||
|
pinger, err := ping.NewPinger("www.google.com")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for Ctrl-C.
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c, os.Interrupt)
|
||||||
|
go func() {
|
||||||
|
for _ = range c {
|
||||||
|
pinger.Stop()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
pinger.OnRecv = func(pkt *ping.Packet) {
|
||||||
|
fmt.Printf("%d bytes from %s: icmp_seq=%d time=%v\n",
|
||||||
|
pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt)
|
||||||
|
}
|
||||||
|
|
||||||
|
pinger.OnDuplicateRecv = func(pkt *ping.Packet) {
|
||||||
|
fmt.Printf("%d bytes from %s: icmp_seq=%d time=%v ttl=%v (DUP!)\n",
|
||||||
|
pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt, pkt.Ttl)
|
||||||
|
}
|
||||||
|
|
||||||
|
pinger.OnFinish = func(stats *ping.Statistics) {
|
||||||
|
fmt.Printf("\n--- %s ping statistics ---\n", stats.Addr)
|
||||||
|
fmt.Printf("%d packets transmitted, %d packets received, %v%% packet loss\n",
|
||||||
|
stats.PacketsSent, stats.PacketsRecv, stats.PacketLoss)
|
||||||
|
fmt.Printf("round-trip min/avg/max/stddev = %v/%v/%v/%v\n",
|
||||||
|
stats.MinRtt, stats.AvgRtt, stats.MaxRtt, stats.StdDevRtt)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("PING %s (%s):\n", pinger.Addr(), pinger.IPAddr())
|
||||||
|
err = pinger.Run()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It sends ICMP Echo Request packet(s) and waits for an Echo Reply in
|
||||||
|
response. If it receives a response, it calls the `OnRecv` callback
|
||||||
|
unless a packet with that sequence number has already been received,
|
||||||
|
in which case it calls the `OnDuplicateRecv` callback. When it's
|
||||||
|
finished, it calls the `OnFinish` callback.
|
||||||
|
|
||||||
|
For a full ping example, see
|
||||||
|
[cmd/ping/ping.go](https://github.com/go-ping/ping/blob/master/cmd/ping/ping.go).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```
|
||||||
|
go get -u github.com/go-ping/ping
|
||||||
|
```
|
||||||
|
|
||||||
|
To install the native Go ping executable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get -u github.com/go-ping/ping/...
|
||||||
|
$GOPATH/bin/ping
|
||||||
|
```
|
||||||
|
|
||||||
|
## Supported Operating Systems
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
This library attempts to send an "unprivileged" ping via UDP. On Linux,
|
||||||
|
this must be enabled with the following sysctl command:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo sysctl -w net.ipv4.ping_group_range="0 2147483647"
|
||||||
|
```
|
||||||
|
|
||||||
|
If you do not wish to do this, you can call `pinger.SetPrivileged(true)`
|
||||||
|
in your code and then use setcap on your binary to allow it to bind to
|
||||||
|
raw sockets (or just run it as root):
|
||||||
|
|
||||||
|
```
|
||||||
|
setcap cap_net_raw=+ep /path/to/your/compiled/binary
|
||||||
|
```
|
||||||
|
|
||||||
|
See [this blog](https://sturmflut.github.io/linux/ubuntu/2015/01/17/unprivileged-icmp-sockets-on-linux/)
|
||||||
|
and the Go [x/net/icmp](https://godoc.org/golang.org/x/net/icmp) package
|
||||||
|
for more details.
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
You must use `pinger.SetPrivileged(true)`, otherwise you will receive
|
||||||
|
the following error:
|
||||||
|
|
||||||
|
```
|
||||||
|
socket: The requested protocol has not been configured into the system, or no implementation for it exists.
|
||||||
|
```
|
||||||
|
|
||||||
|
Despite the method name, this should work without the need to elevate
|
||||||
|
privileges and has been tested on Windows 10. Please note that accessing
|
||||||
|
packet TTL values is not supported due to limitations in the Go
|
||||||
|
x/net/ipv4 and x/net/ipv6 packages.
|
||||||
|
|
||||||
|
### Plan 9 from Bell Labs
|
||||||
|
|
||||||
|
There is no support for Plan 9. This is because the entire `x/net/ipv4`
|
||||||
|
and `x/net/ipv6` packages are not implemented by the Go programming
|
||||||
|
language.
|
||||||
|
|
||||||
|
## Maintainers and Getting Help:
|
||||||
|
|
||||||
|
This repo was originally in the personal account of
|
||||||
|
[sparrc](https://github.com/sparrc), but is now maintained by the
|
||||||
|
[go-ping organization](https://github.com/go-ping).
|
||||||
|
|
||||||
|
For support and help, you usually find us in the #go-ping channel of
|
||||||
|
Gophers Slack. See https://invite.slack.golangbridge.org/ for an invite
|
||||||
|
to the Gophers Slack org.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Refer to [CONTRIBUTING.md](https://github.com/go-ping/ping/blob/master/CONTRIBUTING.md)
|
|
@ -0,0 +1,53 @@
|
||||||
|
package ping
|
||||||
|
|
||||||
|
import "log"
|
||||||
|
|
||||||
|
type Logger interface {
|
||||||
|
Fatalf(format string, v ...interface{})
|
||||||
|
Errorf(format string, v ...interface{})
|
||||||
|
Warnf(format string, v ...interface{})
|
||||||
|
Infof(format string, v ...interface{})
|
||||||
|
Debugf(format string, v ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type StdLogger struct {
|
||||||
|
Logger *log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l StdLogger) Fatalf(format string, v ...interface{}) {
|
||||||
|
l.Logger.Printf("FATAL: "+format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l StdLogger) Errorf(format string, v ...interface{}) {
|
||||||
|
l.Logger.Printf("ERROR: "+format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l StdLogger) Warnf(format string, v ...interface{}) {
|
||||||
|
l.Logger.Printf("WARN: "+format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l StdLogger) Infof(format string, v ...interface{}) {
|
||||||
|
l.Logger.Printf("INFO: "+format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l StdLogger) Debugf(format string, v ...interface{}) {
|
||||||
|
l.Logger.Printf("DEBUG: "+format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type NoopLogger struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l NoopLogger) Fatalf(format string, v ...interface{}) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l NoopLogger) Errorf(format string, v ...interface{}) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l NoopLogger) Warnf(format string, v ...interface{}) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l NoopLogger) Infof(format string, v ...interface{}) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l NoopLogger) Debugf(format string, v ...interface{}) {
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
package ping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/icmp"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
)
|
||||||
|
|
||||||
|
type packetConn interface {
|
||||||
|
Close() error
|
||||||
|
ICMPRequestType() icmp.Type
|
||||||
|
ReadFrom(b []byte) (n int, ttl int, src net.Addr, err error)
|
||||||
|
SetFlagTTL() error
|
||||||
|
SetReadDeadline(t time.Time) error
|
||||||
|
WriteTo(b []byte, dst net.Addr) (int, error)
|
||||||
|
SetTTL(ttl int)
|
||||||
|
}
|
||||||
|
|
||||||
|
type icmpConn struct {
|
||||||
|
c *icmp.PacketConn
|
||||||
|
ttl int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *icmpConn) Close() error {
|
||||||
|
return c.c.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *icmpConn) SetTTL(ttl int) {
|
||||||
|
c.ttl = ttl
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *icmpConn) SetReadDeadline(t time.Time) error {
|
||||||
|
return c.c.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *icmpConn) WriteTo(b []byte, dst net.Addr) (int, error) {
|
||||||
|
if c.c.IPv6PacketConn() != nil {
|
||||||
|
if err := c.c.IPv6PacketConn().SetHopLimit(c.ttl); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.c.IPv4PacketConn() != nil {
|
||||||
|
if err := c.c.IPv4PacketConn().SetTTL(c.ttl); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.c.WriteTo(b, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
type icmpv4Conn struct {
|
||||||
|
icmpConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *icmpv4Conn) SetFlagTTL() error {
|
||||||
|
err := c.c.IPv4PacketConn().SetControlMessage(ipv4.FlagTTL, true)
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *icmpv4Conn) ReadFrom(b []byte) (int, int, net.Addr, error) {
|
||||||
|
var ttl int
|
||||||
|
n, cm, src, err := c.c.IPv4PacketConn().ReadFrom(b)
|
||||||
|
if cm != nil {
|
||||||
|
ttl = cm.TTL
|
||||||
|
}
|
||||||
|
return n, ttl, src, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c icmpv4Conn) ICMPRequestType() icmp.Type {
|
||||||
|
return ipv4.ICMPTypeEcho
|
||||||
|
}
|
||||||
|
|
||||||
|
type icmpV6Conn struct {
|
||||||
|
icmpConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *icmpV6Conn) SetFlagTTL() error {
|
||||||
|
err := c.c.IPv6PacketConn().SetControlMessage(ipv6.FlagHopLimit, true)
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *icmpV6Conn) ReadFrom(b []byte) (int, int, net.Addr, error) {
|
||||||
|
var ttl int
|
||||||
|
n, cm, src, err := c.c.IPv6PacketConn().ReadFrom(b)
|
||||||
|
if cm != nil {
|
||||||
|
ttl = cm.HopLimit
|
||||||
|
}
|
||||||
|
return n, ttl, src, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c icmpV6Conn) ICMPRequestType() icmp.Type {
|
||||||
|
return ipv6.ICMPTypeEchoRequest
|
||||||
|
}
|
|
@ -0,0 +1,821 @@
|
||||||
|
// Package ping is a simple but powerful ICMP echo (ping) library.
|
||||||
|
//
|
||||||
|
// Here is a very simple example that sends and receives three packets:
|
||||||
|
//
|
||||||
|
// pinger, err := ping.NewPinger("www.google.com")
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
// pinger.Count = 3
|
||||||
|
// err = pinger.Run() // blocks until finished
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
// stats := pinger.Statistics() // get send/receive/rtt stats
|
||||||
|
//
|
||||||
|
// Here is an example that emulates the traditional UNIX ping command:
|
||||||
|
//
|
||||||
|
// pinger, err := ping.NewPinger("www.google.com")
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
// // Listen for Ctrl-C.
|
||||||
|
// c := make(chan os.Signal, 1)
|
||||||
|
// signal.Notify(c, os.Interrupt)
|
||||||
|
// go func() {
|
||||||
|
// for _ = range c {
|
||||||
|
// pinger.Stop()
|
||||||
|
// }
|
||||||
|
// }()
|
||||||
|
// pinger.OnRecv = func(pkt *ping.Packet) {
|
||||||
|
// fmt.Printf("%d bytes from %s: icmp_seq=%d time=%v\n",
|
||||||
|
// pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt)
|
||||||
|
// }
|
||||||
|
// pinger.OnFinish = func(stats *ping.Statistics) {
|
||||||
|
// fmt.Printf("\n--- %s ping statistics ---\n", stats.Addr)
|
||||||
|
// fmt.Printf("%d packets transmitted, %d packets received, %v%% packet loss\n",
|
||||||
|
// stats.PacketsSent, stats.PacketsRecv, stats.PacketLoss)
|
||||||
|
// fmt.Printf("round-trip min/avg/max/stddev = %v/%v/%v/%v\n",
|
||||||
|
// stats.MinRtt, stats.AvgRtt, stats.MaxRtt, stats.StdDevRtt)
|
||||||
|
// }
|
||||||
|
// fmt.Printf("PING %s (%s):\n", pinger.Addr(), pinger.IPAddr())
|
||||||
|
// err = pinger.Run()
|
||||||
|
// if err != nil {
|
||||||
|
// panic(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// It sends ICMP Echo Request packet(s) and waits for an Echo Reply in response.
|
||||||
|
// If it receives a response, it calls the OnRecv callback. When it's finished,
|
||||||
|
// it calls the OnFinish callback.
|
||||||
|
//
|
||||||
|
// For a full ping example, see "cmd/ping/ping.go".
|
||||||
|
//
|
||||||
|
package ping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"golang.org/x/net/icmp"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
timeSliceLength = 8
|
||||||
|
trackerLength = len(uuid.UUID{})
|
||||||
|
protocolICMP = 1
|
||||||
|
protocolIPv6ICMP = 58
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ipv4Proto = map[string]string{"icmp": "ip4:icmp", "udp": "udp4"}
|
||||||
|
ipv6Proto = map[string]string{"icmp": "ip6:ipv6-icmp", "udp": "udp6"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// New returns a new Pinger struct pointer.
|
||||||
|
func New(addr string) *Pinger {
|
||||||
|
r := rand.New(rand.NewSource(getSeed()))
|
||||||
|
firstUUID := uuid.New()
|
||||||
|
var firstSequence = map[uuid.UUID]map[int]struct{}{}
|
||||||
|
firstSequence[firstUUID] = make(map[int]struct{})
|
||||||
|
return &Pinger{
|
||||||
|
Count: -1,
|
||||||
|
Interval: time.Second,
|
||||||
|
RecordRtts: true,
|
||||||
|
Size: timeSliceLength + trackerLength,
|
||||||
|
Timeout: time.Duration(math.MaxInt64),
|
||||||
|
|
||||||
|
addr: addr,
|
||||||
|
done: make(chan interface{}),
|
||||||
|
id: r.Intn(math.MaxUint16),
|
||||||
|
trackerUUIDs: []uuid.UUID{firstUUID},
|
||||||
|
ipaddr: nil,
|
||||||
|
ipv4: false,
|
||||||
|
network: "ip",
|
||||||
|
protocol: "udp",
|
||||||
|
awaitingSequences: firstSequence,
|
||||||
|
TTL: 64,
|
||||||
|
logger: StdLogger{Logger: log.New(log.Writer(), log.Prefix(), log.Flags())},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPinger returns a new Pinger and resolves the address.
|
||||||
|
func NewPinger(addr string) (*Pinger, error) {
|
||||||
|
p := New(addr)
|
||||||
|
return p, p.Resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pinger represents a packet sender/receiver.
|
||||||
|
type Pinger struct {
|
||||||
|
// Interval is the wait time between each packet send. Default is 1s.
|
||||||
|
Interval time.Duration
|
||||||
|
|
||||||
|
// Timeout specifies a timeout before ping exits, regardless of how many
|
||||||
|
// packets have been received.
|
||||||
|
Timeout time.Duration
|
||||||
|
|
||||||
|
// Count tells pinger to stop after sending (and receiving) Count echo
|
||||||
|
// packets. If this option is not specified, pinger will operate until
|
||||||
|
// interrupted.
|
||||||
|
Count int
|
||||||
|
|
||||||
|
// Debug runs in debug mode
|
||||||
|
Debug bool
|
||||||
|
|
||||||
|
// Number of packets sent
|
||||||
|
PacketsSent int
|
||||||
|
|
||||||
|
// Number of packets received
|
||||||
|
PacketsRecv int
|
||||||
|
|
||||||
|
// Number of duplicate packets received
|
||||||
|
PacketsRecvDuplicates int
|
||||||
|
|
||||||
|
// Round trip time statistics
|
||||||
|
minRtt time.Duration
|
||||||
|
maxRtt time.Duration
|
||||||
|
avgRtt time.Duration
|
||||||
|
stdDevRtt time.Duration
|
||||||
|
stddevm2 time.Duration
|
||||||
|
statsMu sync.RWMutex
|
||||||
|
|
||||||
|
// If true, keep a record of rtts of all received packets.
|
||||||
|
// Set to false to avoid memory bloat for long running pings.
|
||||||
|
RecordRtts bool
|
||||||
|
|
||||||
|
// rtts is all of the Rtts
|
||||||
|
rtts []time.Duration
|
||||||
|
|
||||||
|
// OnSetup is called when Pinger has finished setting up the listening socket
|
||||||
|
OnSetup func()
|
||||||
|
|
||||||
|
// OnSend is called when Pinger sends a packet
|
||||||
|
OnSend func(*Packet)
|
||||||
|
|
||||||
|
// OnRecv is called when Pinger receives and processes a packet
|
||||||
|
OnRecv func(*Packet)
|
||||||
|
|
||||||
|
// OnFinish is called when Pinger exits
|
||||||
|
OnFinish func(*Statistics)
|
||||||
|
|
||||||
|
// OnDuplicateRecv is called when a packet is received that has already been received.
|
||||||
|
OnDuplicateRecv func(*Packet)
|
||||||
|
|
||||||
|
// Size of packet being sent
|
||||||
|
Size int
|
||||||
|
|
||||||
|
// Tracker: Used to uniquely identify packets - Deprecated
|
||||||
|
Tracker uint64
|
||||||
|
|
||||||
|
// Source is the source IP address
|
||||||
|
Source string
|
||||||
|
|
||||||
|
// Channel and mutex used to communicate when the Pinger should stop between goroutines.
|
||||||
|
done chan interface{}
|
||||||
|
lock sync.Mutex
|
||||||
|
|
||||||
|
ipaddr *net.IPAddr
|
||||||
|
addr string
|
||||||
|
|
||||||
|
// trackerUUIDs is the list of UUIDs being used for sending packets.
|
||||||
|
trackerUUIDs []uuid.UUID
|
||||||
|
|
||||||
|
ipv4 bool
|
||||||
|
id int
|
||||||
|
sequence int
|
||||||
|
// awaitingSequences are in-flight sequence numbers we keep track of to help remove duplicate receipts
|
||||||
|
awaitingSequences map[uuid.UUID]map[int]struct{}
|
||||||
|
// network is one of "ip", "ip4", or "ip6".
|
||||||
|
network string
|
||||||
|
// protocol is "icmp" or "udp".
|
||||||
|
protocol string
|
||||||
|
|
||||||
|
logger Logger
|
||||||
|
|
||||||
|
TTL int
|
||||||
|
}
|
||||||
|
|
||||||
|
type packet struct {
|
||||||
|
bytes []byte
|
||||||
|
nbytes int
|
||||||
|
ttl int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Packet represents a received and processed ICMP echo packet.
|
||||||
|
type Packet struct {
|
||||||
|
// Rtt is the round-trip time it took to ping.
|
||||||
|
Rtt time.Duration
|
||||||
|
|
||||||
|
// IPAddr is the address of the host being pinged.
|
||||||
|
IPAddr *net.IPAddr
|
||||||
|
|
||||||
|
// Addr is the string address of the host being pinged.
|
||||||
|
Addr string
|
||||||
|
|
||||||
|
// NBytes is the number of bytes in the message.
|
||||||
|
Nbytes int
|
||||||
|
|
||||||
|
// Seq is the ICMP sequence number.
|
||||||
|
Seq int
|
||||||
|
|
||||||
|
// TTL is the Time To Live on the packet.
|
||||||
|
Ttl int
|
||||||
|
|
||||||
|
// ID is the ICMP identifier.
|
||||||
|
ID int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Statistics represent the stats of a currently running or finished
|
||||||
|
// pinger operation.
|
||||||
|
type Statistics struct {
|
||||||
|
// PacketsRecv is the number of packets received.
|
||||||
|
PacketsRecv int
|
||||||
|
|
||||||
|
// PacketsSent is the number of packets sent.
|
||||||
|
PacketsSent int
|
||||||
|
|
||||||
|
// PacketsRecvDuplicates is the number of duplicate responses there were to a sent packet.
|
||||||
|
PacketsRecvDuplicates int
|
||||||
|
|
||||||
|
// PacketLoss is the percentage of packets lost.
|
||||||
|
PacketLoss float64
|
||||||
|
|
||||||
|
// IPAddr is the address of the host being pinged.
|
||||||
|
IPAddr *net.IPAddr
|
||||||
|
|
||||||
|
// Addr is the string address of the host being pinged.
|
||||||
|
Addr string
|
||||||
|
|
||||||
|
// Rtts is all of the round-trip times sent via this pinger.
|
||||||
|
Rtts []time.Duration
|
||||||
|
|
||||||
|
// MinRtt is the minimum round-trip time sent via this pinger.
|
||||||
|
MinRtt time.Duration
|
||||||
|
|
||||||
|
// MaxRtt is the maximum round-trip time sent via this pinger.
|
||||||
|
MaxRtt time.Duration
|
||||||
|
|
||||||
|
// AvgRtt is the average round-trip time sent via this pinger.
|
||||||
|
AvgRtt time.Duration
|
||||||
|
|
||||||
|
// StdDevRtt is the standard deviation of the round-trip times sent via
|
||||||
|
// this pinger.
|
||||||
|
StdDevRtt time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pinger) updateStatistics(pkt *Packet) {
|
||||||
|
p.statsMu.Lock()
|
||||||
|
defer p.statsMu.Unlock()
|
||||||
|
|
||||||
|
p.PacketsRecv++
|
||||||
|
if p.RecordRtts {
|
||||||
|
p.rtts = append(p.rtts, pkt.Rtt)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.PacketsRecv == 1 || pkt.Rtt < p.minRtt {
|
||||||
|
p.minRtt = pkt.Rtt
|
||||||
|
}
|
||||||
|
|
||||||
|
if pkt.Rtt > p.maxRtt {
|
||||||
|
p.maxRtt = pkt.Rtt
|
||||||
|
}
|
||||||
|
|
||||||
|
pktCount := time.Duration(p.PacketsRecv)
|
||||||
|
// welford's online method for stddev
|
||||||
|
// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm
|
||||||
|
delta := pkt.Rtt - p.avgRtt
|
||||||
|
p.avgRtt += delta / pktCount
|
||||||
|
delta2 := pkt.Rtt - p.avgRtt
|
||||||
|
p.stddevm2 += delta * delta2
|
||||||
|
|
||||||
|
p.stdDevRtt = time.Duration(math.Sqrt(float64(p.stddevm2 / pktCount)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetIPAddr sets the ip address of the target host.
|
||||||
|
func (p *Pinger) SetIPAddr(ipaddr *net.IPAddr) {
|
||||||
|
p.ipv4 = isIPv4(ipaddr.IP)
|
||||||
|
|
||||||
|
p.ipaddr = ipaddr
|
||||||
|
p.addr = ipaddr.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPAddr returns the ip address of the target host.
|
||||||
|
func (p *Pinger) IPAddr() *net.IPAddr {
|
||||||
|
return p.ipaddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve does the DNS lookup for the Pinger address and sets IP protocol.
|
||||||
|
func (p *Pinger) Resolve() error {
|
||||||
|
if len(p.addr) == 0 {
|
||||||
|
return errors.New("addr cannot be empty")
|
||||||
|
}
|
||||||
|
ipaddr, err := net.ResolveIPAddr(p.network, p.addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.ipv4 = isIPv4(ipaddr.IP)
|
||||||
|
|
||||||
|
p.ipaddr = ipaddr
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAddr resolves and sets the ip address of the target host, addr can be a
|
||||||
|
// DNS name like "www.google.com" or IP like "127.0.0.1".
|
||||||
|
func (p *Pinger) SetAddr(addr string) error {
|
||||||
|
oldAddr := p.addr
|
||||||
|
p.addr = addr
|
||||||
|
err := p.Resolve()
|
||||||
|
if err != nil {
|
||||||
|
p.addr = oldAddr
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr returns the string ip address of the target host.
|
||||||
|
func (p *Pinger) Addr() string {
|
||||||
|
return p.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNetwork allows configuration of DNS resolution.
|
||||||
|
// * "ip" will automatically select IPv4 or IPv6.
|
||||||
|
// * "ip4" will select IPv4.
|
||||||
|
// * "ip6" will select IPv6.
|
||||||
|
func (p *Pinger) SetNetwork(n string) {
|
||||||
|
switch n {
|
||||||
|
case "ip4":
|
||||||
|
p.network = "ip4"
|
||||||
|
case "ip6":
|
||||||
|
p.network = "ip6"
|
||||||
|
default:
|
||||||
|
p.network = "ip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPrivileged sets the type of ping pinger will send.
|
||||||
|
// false means pinger will send an "unprivileged" UDP ping.
|
||||||
|
// true means pinger will send a "privileged" raw ICMP ping.
|
||||||
|
// NOTE: setting to true requires that it be run with super-user privileges.
|
||||||
|
func (p *Pinger) SetPrivileged(privileged bool) {
|
||||||
|
if privileged {
|
||||||
|
p.protocol = "icmp"
|
||||||
|
} else {
|
||||||
|
p.protocol = "udp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Privileged returns whether pinger is running in privileged mode.
|
||||||
|
func (p *Pinger) Privileged() bool {
|
||||||
|
return p.protocol == "icmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogger sets the logger to be used to log events from the pinger.
|
||||||
|
func (p *Pinger) SetLogger(logger Logger) {
|
||||||
|
p.logger = logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetID sets the ICMP identifier.
|
||||||
|
func (p *Pinger) SetID(id int) {
|
||||||
|
p.id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the ICMP identifier.
|
||||||
|
func (p *Pinger) ID() int {
|
||||||
|
return p.id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run runs the pinger. This is a blocking function that will exit when it's
|
||||||
|
// done. If Count or Interval are not specified, it will run continuously until
|
||||||
|
// it is interrupted.
|
||||||
|
func (p *Pinger) Run() error {
|
||||||
|
var conn packetConn
|
||||||
|
var err error
|
||||||
|
if p.Size < timeSliceLength+trackerLength {
|
||||||
|
return fmt.Errorf("size %d is less than minimum required size %d", p.Size, timeSliceLength+trackerLength)
|
||||||
|
}
|
||||||
|
if p.ipaddr == nil {
|
||||||
|
err = p.Resolve()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if conn, err = p.listen(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
conn.SetTTL(p.TTL)
|
||||||
|
return p.run(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pinger) run(conn packetConn) error {
|
||||||
|
if err := conn.SetFlagTTL(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer p.finish()
|
||||||
|
|
||||||
|
recv := make(chan *packet, 5)
|
||||||
|
defer close(recv)
|
||||||
|
|
||||||
|
if handler := p.OnSetup; handler != nil {
|
||||||
|
handler()
|
||||||
|
}
|
||||||
|
|
||||||
|
var g errgroup.Group
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
defer p.Stop()
|
||||||
|
return p.recvICMP(conn, recv)
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
defer p.Stop()
|
||||||
|
return p.runLoop(conn, recv)
|
||||||
|
})
|
||||||
|
|
||||||
|
return g.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pinger) runLoop(
|
||||||
|
conn packetConn,
|
||||||
|
recvCh <-chan *packet,
|
||||||
|
) error {
|
||||||
|
logger := p.logger
|
||||||
|
if logger == nil {
|
||||||
|
logger = NoopLogger{}
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := time.NewTicker(p.Timeout)
|
||||||
|
interval := time.NewTicker(p.Interval)
|
||||||
|
defer func() {
|
||||||
|
p.Stop()
|
||||||
|
interval.Stop()
|
||||||
|
timeout.Stop()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := p.sendICMP(conn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-p.done:
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case <-timeout.C:
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case r := <-recvCh:
|
||||||
|
err := p.processPacket(r)
|
||||||
|
if err != nil {
|
||||||
|
// FIXME: this logs as FATAL but continues
|
||||||
|
logger.Fatalf("processing received packet: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
case <-interval.C:
|
||||||
|
if p.Count > 0 && p.PacketsSent >= p.Count {
|
||||||
|
interval.Stop()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := p.sendICMP(conn)
|
||||||
|
if err != nil {
|
||||||
|
// FIXME: this logs as FATAL but continues
|
||||||
|
logger.Fatalf("sending packet: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.Count > 0 && p.PacketsRecv >= p.Count {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pinger) Stop() {
|
||||||
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
|
|
||||||
|
open := true
|
||||||
|
select {
|
||||||
|
case _, open = <-p.done:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
if open {
|
||||||
|
close(p.done)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pinger) finish() {
|
||||||
|
handler := p.OnFinish
|
||||||
|
if handler != nil {
|
||||||
|
s := p.Statistics()
|
||||||
|
handler(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Statistics returns the statistics of the pinger. This can be run while the
|
||||||
|
// pinger is running or after it is finished. OnFinish calls this function to
|
||||||
|
// get it's finished statistics.
|
||||||
|
func (p *Pinger) Statistics() *Statistics {
|
||||||
|
p.statsMu.RLock()
|
||||||
|
defer p.statsMu.RUnlock()
|
||||||
|
sent := p.PacketsSent
|
||||||
|
loss := float64(sent-p.PacketsRecv) / float64(sent) * 100
|
||||||
|
s := Statistics{
|
||||||
|
PacketsSent: sent,
|
||||||
|
PacketsRecv: p.PacketsRecv,
|
||||||
|
PacketsRecvDuplicates: p.PacketsRecvDuplicates,
|
||||||
|
PacketLoss: loss,
|
||||||
|
Rtts: p.rtts,
|
||||||
|
Addr: p.addr,
|
||||||
|
IPAddr: p.ipaddr,
|
||||||
|
MaxRtt: p.maxRtt,
|
||||||
|
MinRtt: p.minRtt,
|
||||||
|
AvgRtt: p.avgRtt,
|
||||||
|
StdDevRtt: p.stdDevRtt,
|
||||||
|
}
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
type expBackoff struct {
|
||||||
|
baseDelay time.Duration
|
||||||
|
maxExp int64
|
||||||
|
c int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *expBackoff) Get() time.Duration {
|
||||||
|
if b.c < b.maxExp {
|
||||||
|
b.c++
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.baseDelay * time.Duration(rand.Int63n(1<<b.c))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newExpBackoff(baseDelay time.Duration, maxExp int64) expBackoff {
|
||||||
|
return expBackoff{baseDelay: baseDelay, maxExp: maxExp}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pinger) recvICMP(
|
||||||
|
conn packetConn,
|
||||||
|
recv chan<- *packet,
|
||||||
|
) error {
|
||||||
|
// Start by waiting for 50 µs and increase to a possible maximum of ~ 100 ms.
|
||||||
|
expBackoff := newExpBackoff(50*time.Microsecond, 11)
|
||||||
|
delay := expBackoff.Get()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-p.done:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
bytes := make([]byte, p.getMessageLength())
|
||||||
|
if err := conn.SetReadDeadline(time.Now().Add(delay)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var n, ttl int
|
||||||
|
var err error
|
||||||
|
n, ttl, _, err = conn.ReadFrom(bytes)
|
||||||
|
if err != nil {
|
||||||
|
if neterr, ok := err.(*net.OpError); ok {
|
||||||
|
if neterr.Timeout() {
|
||||||
|
// Read timeout
|
||||||
|
delay = expBackoff.Get()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-p.done:
|
||||||
|
return nil
|
||||||
|
case recv <- &packet{bytes: bytes, nbytes: n, ttl: ttl}:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPacketUUID scans the tracking slice for matches.
|
||||||
|
func (p *Pinger) getPacketUUID(pkt []byte) (*uuid.UUID, error) {
|
||||||
|
var packetUUID uuid.UUID
|
||||||
|
err := packetUUID.UnmarshalBinary(pkt[timeSliceLength : timeSliceLength+trackerLength])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error decoding tracking UUID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range p.trackerUUIDs {
|
||||||
|
if item == packetUUID {
|
||||||
|
return &packetUUID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCurrentTrackerUUID grabs the latest tracker UUID.
|
||||||
|
func (p *Pinger) getCurrentTrackerUUID() uuid.UUID {
|
||||||
|
return p.trackerUUIDs[len(p.trackerUUIDs)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pinger) processPacket(recv *packet) error {
|
||||||
|
receivedAt := time.Now()
|
||||||
|
var proto int
|
||||||
|
if p.ipv4 {
|
||||||
|
proto = protocolICMP
|
||||||
|
} else {
|
||||||
|
proto = protocolIPv6ICMP
|
||||||
|
}
|
||||||
|
|
||||||
|
var m *icmp.Message
|
||||||
|
var err error
|
||||||
|
if m, err = icmp.ParseMessage(proto, recv.bytes); err != nil {
|
||||||
|
return fmt.Errorf("error parsing icmp message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Type != ipv4.ICMPTypeEchoReply && m.Type != ipv6.ICMPTypeEchoReply {
|
||||||
|
// Not an echo reply, ignore it
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
inPkt := &Packet{
|
||||||
|
Nbytes: recv.nbytes,
|
||||||
|
IPAddr: p.ipaddr,
|
||||||
|
Addr: p.addr,
|
||||||
|
Ttl: recv.ttl,
|
||||||
|
ID: p.id,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch pkt := m.Body.(type) {
|
||||||
|
case *icmp.Echo:
|
||||||
|
if !p.matchID(pkt.ID) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pkt.Data) < timeSliceLength+trackerLength {
|
||||||
|
return fmt.Errorf("insufficient data received; got: %d %v",
|
||||||
|
len(pkt.Data), pkt.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
pktUUID, err := p.getPacketUUID(pkt.Data)
|
||||||
|
if err != nil || pktUUID == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp := bytesToTime(pkt.Data[:timeSliceLength])
|
||||||
|
inPkt.Rtt = receivedAt.Sub(timestamp)
|
||||||
|
inPkt.Seq = pkt.Seq
|
||||||
|
// If we've already received this sequence, ignore it.
|
||||||
|
if _, inflight := p.awaitingSequences[*pktUUID][pkt.Seq]; !inflight {
|
||||||
|
p.PacketsRecvDuplicates++
|
||||||
|
if p.OnDuplicateRecv != nil {
|
||||||
|
p.OnDuplicateRecv(inPkt)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// remove it from the list of sequences we're waiting for so we don't get duplicates.
|
||||||
|
delete(p.awaitingSequences[*pktUUID], pkt.Seq)
|
||||||
|
p.updateStatistics(inPkt)
|
||||||
|
default:
|
||||||
|
// Very bad, not sure how this can happen
|
||||||
|
return fmt.Errorf("invalid ICMP echo reply; type: '%T', '%v'", pkt, pkt)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := p.OnRecv
|
||||||
|
if handler != nil {
|
||||||
|
handler(inPkt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pinger) sendICMP(conn packetConn) error {
|
||||||
|
var dst net.Addr = p.ipaddr
|
||||||
|
if p.protocol == "udp" {
|
||||||
|
dst = &net.UDPAddr{IP: p.ipaddr.IP, Zone: p.ipaddr.Zone}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentUUID := p.getCurrentTrackerUUID()
|
||||||
|
uuidEncoded, err := currentUUID.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to marshal UUID binary: %w", err)
|
||||||
|
}
|
||||||
|
t := append(timeToBytes(time.Now()), uuidEncoded...)
|
||||||
|
if remainSize := p.Size - timeSliceLength - trackerLength; remainSize > 0 {
|
||||||
|
t = append(t, bytes.Repeat([]byte{1}, remainSize)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
body := &icmp.Echo{
|
||||||
|
ID: p.id,
|
||||||
|
Seq: p.sequence,
|
||||||
|
Data: t,
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := &icmp.Message{
|
||||||
|
Type: conn.ICMPRequestType(),
|
||||||
|
Code: 0,
|
||||||
|
Body: body,
|
||||||
|
}
|
||||||
|
|
||||||
|
msgBytes, err := msg.Marshal(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
if _, err := conn.WriteTo(msgBytes, dst); err != nil {
|
||||||
|
if neterr, ok := err.(*net.OpError); ok {
|
||||||
|
if neterr.Err == syscall.ENOBUFS {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
handler := p.OnSend
|
||||||
|
if handler != nil {
|
||||||
|
outPkt := &Packet{
|
||||||
|
Nbytes: len(msgBytes),
|
||||||
|
IPAddr: p.ipaddr,
|
||||||
|
Addr: p.addr,
|
||||||
|
Seq: p.sequence,
|
||||||
|
ID: p.id,
|
||||||
|
}
|
||||||
|
handler(outPkt)
|
||||||
|
}
|
||||||
|
// mark this sequence as in-flight
|
||||||
|
p.awaitingSequences[currentUUID][p.sequence] = struct{}{}
|
||||||
|
p.PacketsSent++
|
||||||
|
p.sequence++
|
||||||
|
if p.sequence > 65535 {
|
||||||
|
newUUID := uuid.New()
|
||||||
|
p.trackerUUIDs = append(p.trackerUUIDs, newUUID)
|
||||||
|
p.awaitingSequences[newUUID] = make(map[int]struct{})
|
||||||
|
p.sequence = 0
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pinger) listen() (packetConn, error) {
|
||||||
|
var (
|
||||||
|
conn packetConn
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if p.ipv4 {
|
||||||
|
var c icmpv4Conn
|
||||||
|
c.c, err = icmp.ListenPacket(ipv4Proto[p.protocol], p.Source)
|
||||||
|
conn = &c
|
||||||
|
} else {
|
||||||
|
var c icmpV6Conn
|
||||||
|
c.c, err = icmp.ListenPacket(ipv6Proto[p.protocol], p.Source)
|
||||||
|
conn = &c
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
p.Stop()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bytesToTime(b []byte) time.Time {
|
||||||
|
var nsec int64
|
||||||
|
for i := uint8(0); i < 8; i++ {
|
||||||
|
nsec += int64(b[i]) << ((7 - i) * 8)
|
||||||
|
}
|
||||||
|
return time.Unix(nsec/1000000000, nsec%1000000000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isIPv4(ip net.IP) bool {
|
||||||
|
return len(ip.To4()) == net.IPv4len
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeToBytes(t time.Time) []byte {
|
||||||
|
nsec := t.UnixNano()
|
||||||
|
b := make([]byte, 8)
|
||||||
|
for i := uint8(0); i < 8; i++ {
|
||||||
|
b[i] = byte((nsec >> ((7 - i) * 8)) & 0xff)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
var seed int64 = time.Now().UnixNano()
|
||||||
|
|
||||||
|
// getSeed returns a goroutine-safe unique seed
|
||||||
|
func getSeed() int64 {
|
||||||
|
return atomic.AddInt64(&seed, 1)
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package ping
|
||||||
|
|
||||||
|
// Returns the length of an ICMP message.
|
||||||
|
func (p *Pinger) getMessageLength() int {
|
||||||
|
return p.Size + 8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempts to match the ID of an ICMP packet.
|
||||||
|
func (p *Pinger) matchID(ID int) bool {
|
||||||
|
// On Linux we can only match ID if we are privileged.
|
||||||
|
if p.protocol == "icmp" {
|
||||||
|
if ID != p.id {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
// +build !linux,!windows
|
||||||
|
|
||||||
|
package ping
|
||||||
|
|
||||||
|
// Returns the length of an ICMP message.
|
||||||
|
func (p *Pinger) getMessageLength() int {
|
||||||
|
return p.Size + 8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempts to match the ID of an ICMP packet.
|
||||||
|
func (p *Pinger) matchID(ID int) bool {
|
||||||
|
if ID != p.id {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package ping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Returns the length of an ICMP message, plus the IP packet header.
|
||||||
|
func (p *Pinger) getMessageLength() int {
|
||||||
|
if p.ipv4 {
|
||||||
|
return p.Size + 8 + ipv4.HeaderLen
|
||||||
|
}
|
||||||
|
return p.Size + 8 + ipv6.HeaderLen
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempts to match the ID of an ICMP packet.
|
||||||
|
func (p *Pinger) matchID(ID int) bool {
|
||||||
|
if ID != p.id {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.4.3
|
||||||
|
- 1.5.3
|
||||||
|
- tip
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -v ./...
|
|
@ -0,0 +1,10 @@
|
||||||
|
# How to contribute
|
||||||
|
|
||||||
|
We definitely welcome patches and contribution to this project!
|
||||||
|
|
||||||
|
### Legal requirements
|
||||||
|
|
||||||
|
In order to protect both you and ourselves, you will need to sign the
|
||||||
|
[Contributor License Agreement](https://cla.developers.google.com/clas).
|
||||||
|
|
||||||
|
You may have already signed it for other Google projects.
|
|
@ -0,0 +1,9 @@
|
||||||
|
Paul Borman <borman@google.com>
|
||||||
|
bmatsuo
|
||||||
|
shawnps
|
||||||
|
theory
|
||||||
|
jboverfelt
|
||||||
|
dsymonds
|
||||||
|
cd1
|
||||||
|
wallclockbuilder
|
||||||
|
dansouza
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2009,2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,19 @@
|
||||||
|
# uuid ![build status](https://travis-ci.org/google/uuid.svg?branch=master)
|
||||||
|
The uuid package generates and inspects UUIDs based on
|
||||||
|
[RFC 4122](http://tools.ietf.org/html/rfc4122)
|
||||||
|
and DCE 1.1: Authentication and Security Services.
|
||||||
|
|
||||||
|
This package is based on the github.com/pborman/uuid package (previously named
|
||||||
|
code.google.com/p/go-uuid). It differs from these earlier packages in that
|
||||||
|
a UUID is a 16 byte array rather than a byte slice. One loss due to this
|
||||||
|
change is the ability to represent an invalid UUID (vs a NIL UUID).
|
||||||
|
|
||||||
|
###### Install
|
||||||
|
`go get github.com/google/uuid`
|
||||||
|
|
||||||
|
###### Documentation
|
||||||
|
[![GoDoc](https://godoc.org/github.com/google/uuid?status.svg)](http://godoc.org/github.com/google/uuid)
|
||||||
|
|
||||||
|
Full `go doc` style documentation for the package can be viewed online without
|
||||||
|
installing this package by using the GoDoc site here:
|
||||||
|
http://pkg.go.dev/github.com/google/uuid
|
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Domain represents a Version 2 domain
|
||||||
|
type Domain byte
|
||||||
|
|
||||||
|
// Domain constants for DCE Security (Version 2) UUIDs.
|
||||||
|
const (
|
||||||
|
Person = Domain(0)
|
||||||
|
Group = Domain(1)
|
||||||
|
Org = Domain(2)
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewDCESecurity returns a DCE Security (Version 2) UUID.
|
||||||
|
//
|
||||||
|
// The domain should be one of Person, Group or Org.
|
||||||
|
// On a POSIX system the id should be the users UID for the Person
|
||||||
|
// domain and the users GID for the Group. The meaning of id for
|
||||||
|
// the domain Org or on non-POSIX systems is site defined.
|
||||||
|
//
|
||||||
|
// For a given domain/id pair the same token may be returned for up to
|
||||||
|
// 7 minutes and 10 seconds.
|
||||||
|
func NewDCESecurity(domain Domain, id uint32) (UUID, error) {
|
||||||
|
uuid, err := NewUUID()
|
||||||
|
if err == nil {
|
||||||
|
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
|
||||||
|
uuid[9] = byte(domain)
|
||||||
|
binary.BigEndian.PutUint32(uuid[0:], id)
|
||||||
|
}
|
||||||
|
return uuid, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
|
||||||
|
// domain with the id returned by os.Getuid.
|
||||||
|
//
|
||||||
|
// NewDCESecurity(Person, uint32(os.Getuid()))
|
||||||
|
func NewDCEPerson() (UUID, error) {
|
||||||
|
return NewDCESecurity(Person, uint32(os.Getuid()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
|
||||||
|
// domain with the id returned by os.Getgid.
|
||||||
|
//
|
||||||
|
// NewDCESecurity(Group, uint32(os.Getgid()))
|
||||||
|
func NewDCEGroup() (UUID, error) {
|
||||||
|
return NewDCESecurity(Group, uint32(os.Getgid()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain returns the domain for a Version 2 UUID. Domains are only defined
|
||||||
|
// for Version 2 UUIDs.
|
||||||
|
func (uuid UUID) Domain() Domain {
|
||||||
|
return Domain(uuid[9])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2
|
||||||
|
// UUIDs.
|
||||||
|
func (uuid UUID) ID() uint32 {
|
||||||
|
return binary.BigEndian.Uint32(uuid[0:4])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Domain) String() string {
|
||||||
|
switch d {
|
||||||
|
case Person:
|
||||||
|
return "Person"
|
||||||
|
case Group:
|
||||||
|
return "Group"
|
||||||
|
case Org:
|
||||||
|
return "Org"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Domain%d", int(d))
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package uuid generates and inspects UUIDs.
|
||||||
|
//
|
||||||
|
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security
|
||||||
|
// Services.
|
||||||
|
//
|
||||||
|
// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to
|
||||||
|
// maps or compared directly.
|
||||||
|
package uuid
|
|
@ -0,0 +1,53 @@
|
||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
|
"hash"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Well known namespace IDs and UUIDs
|
||||||
|
var (
|
||||||
|
NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
|
||||||
|
NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
|
||||||
|
NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
|
||||||
|
NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
|
||||||
|
Nil UUID // empty UUID, all zeros
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewHash returns a new UUID derived from the hash of space concatenated with
|
||||||
|
// data generated by h. The hash should be at least 16 byte in length. The
|
||||||
|
// first 16 bytes of the hash are used to form the UUID. The version of the
|
||||||
|
// UUID will be the lower 4 bits of version. NewHash is used to implement
|
||||||
|
// NewMD5 and NewSHA1.
|
||||||
|
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
|
||||||
|
h.Reset()
|
||||||
|
h.Write(space[:]) //nolint:errcheck
|
||||||
|
h.Write(data) //nolint:errcheck
|
||||||
|
s := h.Sum(nil)
|
||||||
|
var uuid UUID
|
||||||
|
copy(uuid[:], s)
|
||||||
|
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
|
||||||
|
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMD5 returns a new MD5 (Version 3) UUID based on the
|
||||||
|
// supplied name space and data. It is the same as calling:
|
||||||
|
//
|
||||||
|
// NewHash(md5.New(), space, data, 3)
|
||||||
|
func NewMD5(space UUID, data []byte) UUID {
|
||||||
|
return NewHash(md5.New(), space, data, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
|
||||||
|
// supplied name space and data. It is the same as calling:
|
||||||
|
//
|
||||||
|
// NewHash(sha1.New(), space, data, 5)
|
||||||
|
func NewSHA1(space UUID, data []byte) UUID {
|
||||||
|
return NewHash(sha1.New(), space, data, 5)
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// MarshalText implements encoding.TextMarshaler.
|
||||||
|
func (uuid UUID) MarshalText() ([]byte, error) {
|
||||||
|
var js [36]byte
|
||||||
|
encodeHex(js[:], uuid)
|
||||||
|
return js[:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||||
|
func (uuid *UUID) UnmarshalText(data []byte) error {
|
||||||
|
id, err := ParseBytes(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*uuid = id
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalBinary implements encoding.BinaryMarshaler.
|
||||||
|
func (uuid UUID) MarshalBinary() ([]byte, error) {
|
||||||
|
return uuid[:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
|
||||||
|
func (uuid *UUID) UnmarshalBinary(data []byte) error {
|
||||||
|
if len(data) != 16 {
|
||||||
|
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
|
||||||
|
}
|
||||||
|
copy(uuid[:], data)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
nodeMu sync.Mutex
|
||||||
|
ifname string // name of interface being used
|
||||||
|
nodeID [6]byte // hardware for version 1 UUIDs
|
||||||
|
zeroID [6]byte // nodeID with only 0's
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeInterface returns the name of the interface from which the NodeID was
|
||||||
|
// derived. The interface "user" is returned if the NodeID was set by
|
||||||
|
// SetNodeID.
|
||||||
|
func NodeInterface() string {
|
||||||
|
defer nodeMu.Unlock()
|
||||||
|
nodeMu.Lock()
|
||||||
|
return ifname
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
|
||||||
|
// If name is "" then the first usable interface found will be used or a random
|
||||||
|
// Node ID will be generated. If a named interface cannot be found then false
|
||||||
|
// is returned.
|
||||||
|
//
|
||||||
|
// SetNodeInterface never fails when name is "".
|
||||||
|
func SetNodeInterface(name string) bool {
|
||||||
|
defer nodeMu.Unlock()
|
||||||
|
nodeMu.Lock()
|
||||||
|
return setNodeInterface(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setNodeInterface(name string) bool {
|
||||||
|
iname, addr := getHardwareInterface(name) // null implementation for js
|
||||||
|
if iname != "" && addr != nil {
|
||||||
|
ifname = iname
|
||||||
|
copy(nodeID[:], addr)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// We found no interfaces with a valid hardware address. If name
|
||||||
|
// does not specify a specific interface generate a random Node ID
|
||||||
|
// (section 4.1.6)
|
||||||
|
if name == "" {
|
||||||
|
ifname = "random"
|
||||||
|
randomBits(nodeID[:])
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
|
||||||
|
// if not already set.
|
||||||
|
func NodeID() []byte {
|
||||||
|
defer nodeMu.Unlock()
|
||||||
|
nodeMu.Lock()
|
||||||
|
if nodeID == zeroID {
|
||||||
|
setNodeInterface("")
|
||||||
|
}
|
||||||
|
nid := nodeID
|
||||||
|
return nid[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
|
||||||
|
// of id are used. If id is less than 6 bytes then false is returned and the
|
||||||
|
// Node ID is not set.
|
||||||
|
func SetNodeID(id []byte) bool {
|
||||||
|
if len(id) < 6 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer nodeMu.Unlock()
|
||||||
|
nodeMu.Lock()
|
||||||
|
copy(nodeID[:], id)
|
||||||
|
ifname = "user"
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
|
||||||
|
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
|
||||||
|
func (uuid UUID) NodeID() []byte {
|
||||||
|
var node [6]byte
|
||||||
|
copy(node[:], uuid[10:])
|
||||||
|
return node[:]
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright 2017 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build js
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
// getHardwareInterface returns nil values for the JS version of the code.
|
||||||
|
// This remvoves the "net" dependency, because it is not used in the browser.
|
||||||
|
// Using the "net" library inflates the size of the transpiled JS code by 673k bytes.
|
||||||
|
func getHardwareInterface(name string) (string, []byte) { return "", nil }
|
|
@ -0,0 +1,33 @@
|
||||||
|
// Copyright 2017 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !js
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
var interfaces []net.Interface // cached list of interfaces
|
||||||
|
|
||||||
|
// getHardwareInterface returns the name and hardware address of interface name.
|
||||||
|
// If name is "" then the name and hardware address of one of the system's
|
||||||
|
// interfaces is returned. If no interfaces are found (name does not exist or
|
||||||
|
// there are no interfaces) then "", nil is returned.
|
||||||
|
//
|
||||||
|
// Only addresses of at least 6 bytes are returned.
|
||||||
|
func getHardwareInterface(name string) (string, []byte) {
|
||||||
|
if interfaces == nil {
|
||||||
|
var err error
|
||||||
|
interfaces, err = net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, ifs := range interfaces {
|
||||||
|
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
|
||||||
|
return ifs.Name, ifs.HardwareAddr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Scan implements sql.Scanner so UUIDs can be read from databases transparently.
|
||||||
|
// Currently, database types that map to string and []byte are supported. Please
|
||||||
|
// consult database-specific driver documentation for matching types.
|
||||||
|
func (uuid *UUID) Scan(src interface{}) error {
|
||||||
|
switch src := src.(type) {
|
||||||
|
case nil:
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case string:
|
||||||
|
// if an empty UUID comes from a table, we return a null UUID
|
||||||
|
if src == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// see Parse for required string format
|
||||||
|
u, err := Parse(src)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Scan: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*uuid = u
|
||||||
|
|
||||||
|
case []byte:
|
||||||
|
// if an empty UUID comes from a table, we return a null UUID
|
||||||
|
if len(src) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes a simple slice of bytes if 16 bytes
|
||||||
|
// otherwise attempts to parse
|
||||||
|
if len(src) != 16 {
|
||||||
|
return uuid.Scan(string(src))
|
||||||
|
}
|
||||||
|
copy((*uuid)[:], src)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements sql.Valuer so that UUIDs can be written to databases
|
||||||
|
// transparently. Currently, UUIDs map to strings. Please consult
|
||||||
|
// database-specific driver documentation for matching types.
|
||||||
|
func (uuid UUID) Value() (driver.Value, error) {
|
||||||
|
return uuid.String(), nil
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
|
||||||
|
// 1582.
|
||||||
|
type Time int64
|
||||||
|
|
||||||
|
const (
|
||||||
|
lillian = 2299160 // Julian day of 15 Oct 1582
|
||||||
|
unix = 2440587 // Julian day of 1 Jan 1970
|
||||||
|
epoch = unix - lillian // Days between epochs
|
||||||
|
g1582 = epoch * 86400 // seconds between epochs
|
||||||
|
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
timeMu sync.Mutex
|
||||||
|
lasttime uint64 // last time we returned
|
||||||
|
clockSeq uint16 // clock sequence for this run
|
||||||
|
|
||||||
|
timeNow = time.Now // for testing
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnixTime converts t the number of seconds and nanoseconds using the Unix
|
||||||
|
// epoch of 1 Jan 1970.
|
||||||
|
func (t Time) UnixTime() (sec, nsec int64) {
|
||||||
|
sec = int64(t - g1582ns100)
|
||||||
|
nsec = (sec % 10000000) * 100
|
||||||
|
sec /= 10000000
|
||||||
|
return sec, nsec
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
|
||||||
|
// clock sequence as well as adjusting the clock sequence as needed. An error
|
||||||
|
// is returned if the current time cannot be determined.
|
||||||
|
func GetTime() (Time, uint16, error) {
|
||||||
|
defer timeMu.Unlock()
|
||||||
|
timeMu.Lock()
|
||||||
|
return getTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTime() (Time, uint16, error) {
|
||||||
|
t := timeNow()
|
||||||
|
|
||||||
|
// If we don't have a clock sequence already, set one.
|
||||||
|
if clockSeq == 0 {
|
||||||
|
setClockSequence(-1)
|
||||||
|
}
|
||||||
|
now := uint64(t.UnixNano()/100) + g1582ns100
|
||||||
|
|
||||||
|
// If time has gone backwards with this clock sequence then we
|
||||||
|
// increment the clock sequence
|
||||||
|
if now <= lasttime {
|
||||||
|
clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000
|
||||||
|
}
|
||||||
|
lasttime = now
|
||||||
|
return Time(now), clockSeq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClockSequence returns the current clock sequence, generating one if not
|
||||||
|
// already set. The clock sequence is only used for Version 1 UUIDs.
|
||||||
|
//
|
||||||
|
// The uuid package does not use global static storage for the clock sequence or
|
||||||
|
// the last time a UUID was generated. Unless SetClockSequence is used, a new
|
||||||
|
// random clock sequence is generated the first time a clock sequence is
|
||||||
|
// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1)
|
||||||
|
func ClockSequence() int {
|
||||||
|
defer timeMu.Unlock()
|
||||||
|
timeMu.Lock()
|
||||||
|
return clockSequence()
|
||||||
|
}
|
||||||
|
|
||||||
|
func clockSequence() int {
|
||||||
|
if clockSeq == 0 {
|
||||||
|
setClockSequence(-1)
|
||||||
|
}
|
||||||
|
return int(clockSeq & 0x3fff)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to
|
||||||
|
// -1 causes a new sequence to be generated.
|
||||||
|
func SetClockSequence(seq int) {
|
||||||
|
defer timeMu.Unlock()
|
||||||
|
timeMu.Lock()
|
||||||
|
setClockSequence(seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setClockSequence(seq int) {
|
||||||
|
if seq == -1 {
|
||||||
|
var b [2]byte
|
||||||
|
randomBits(b[:]) // clock sequence
|
||||||
|
seq = int(b[0])<<8 | int(b[1])
|
||||||
|
}
|
||||||
|
oldSeq := clockSeq
|
||||||
|
clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant
|
||||||
|
if oldSeq != clockSeq {
|
||||||
|
lasttime = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
|
||||||
|
// uuid. The time is only defined for version 1 and 2 UUIDs.
|
||||||
|
func (uuid UUID) Time() Time {
|
||||||
|
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
|
||||||
|
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
|
||||||
|
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
|
||||||
|
return Time(time)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClockSequence returns the clock sequence encoded in uuid.
|
||||||
|
// The clock sequence is only well defined for version 1 and 2 UUIDs.
|
||||||
|
func (uuid UUID) ClockSequence() int {
|
||||||
|
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// randomBits completely fills slice b with random data.
|
||||||
|
func randomBits(b []byte) {
|
||||||
|
if _, err := io.ReadFull(rander, b); err != nil {
|
||||||
|
panic(err.Error()) // rand should never fail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// xvalues returns the value of a byte as a hexadecimal digit or 255.
|
||||||
|
var xvalues = [256]byte{
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
}
|
||||||
|
|
||||||
|
// xtob converts hex characters x1 and x2 into a byte.
|
||||||
|
func xtob(x1, x2 byte) (byte, bool) {
|
||||||
|
b1 := xvalues[x1]
|
||||||
|
b2 := xvalues[x2]
|
||||||
|
return (b1 << 4) | b2, b1 != 255 && b2 != 255
|
||||||
|
}
|
|
@ -0,0 +1,251 @@
|
||||||
|
// Copyright 2018 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
|
||||||
|
// 4122.
|
||||||
|
type UUID [16]byte
|
||||||
|
|
||||||
|
// A Version represents a UUID's version.
|
||||||
|
type Version byte
|
||||||
|
|
||||||
|
// A Variant represents a UUID's variant.
|
||||||
|
type Variant byte
|
||||||
|
|
||||||
|
// Constants returned by Variant.
|
||||||
|
const (
|
||||||
|
Invalid = Variant(iota) // Invalid UUID
|
||||||
|
RFC4122 // The variant specified in RFC4122
|
||||||
|
Reserved // Reserved, NCS backward compatibility.
|
||||||
|
Microsoft // Reserved, Microsoft Corporation backward compatibility.
|
||||||
|
Future // Reserved for future definition.
|
||||||
|
)
|
||||||
|
|
||||||
|
var rander = rand.Reader // random function
|
||||||
|
|
||||||
|
type invalidLengthError struct{ len int }
|
||||||
|
|
||||||
|
func (err invalidLengthError) Error() string {
|
||||||
|
return fmt.Sprintf("invalid UUID length: %d", err.len)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse decodes s into a UUID or returns an error. Both the standard UUID
|
||||||
|
// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
|
||||||
|
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the
|
||||||
|
// Microsoft encoding {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} and the raw hex
|
||||||
|
// encoding: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.
|
||||||
|
func Parse(s string) (UUID, error) {
|
||||||
|
var uuid UUID
|
||||||
|
switch len(s) {
|
||||||
|
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
case 36:
|
||||||
|
|
||||||
|
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
case 36 + 9:
|
||||||
|
if strings.ToLower(s[:9]) != "urn:uuid:" {
|
||||||
|
return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9])
|
||||||
|
}
|
||||||
|
s = s[9:]
|
||||||
|
|
||||||
|
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||||
|
case 36 + 2:
|
||||||
|
s = s[1:]
|
||||||
|
|
||||||
|
// xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
case 32:
|
||||||
|
var ok bool
|
||||||
|
for i := range uuid {
|
||||||
|
uuid[i], ok = xtob(s[i*2], s[i*2+1])
|
||||||
|
if !ok {
|
||||||
|
return uuid, errors.New("invalid UUID format")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uuid, nil
|
||||||
|
default:
|
||||||
|
return uuid, invalidLengthError{len(s)}
|
||||||
|
}
|
||||||
|
// s is now at least 36 bytes long
|
||||||
|
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||||
|
return uuid, errors.New("invalid UUID format")
|
||||||
|
}
|
||||||
|
for i, x := range [16]int{
|
||||||
|
0, 2, 4, 6,
|
||||||
|
9, 11,
|
||||||
|
14, 16,
|
||||||
|
19, 21,
|
||||||
|
24, 26, 28, 30, 32, 34} {
|
||||||
|
v, ok := xtob(s[x], s[x+1])
|
||||||
|
if !ok {
|
||||||
|
return uuid, errors.New("invalid UUID format")
|
||||||
|
}
|
||||||
|
uuid[i] = v
|
||||||
|
}
|
||||||
|
return uuid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseBytes is like Parse, except it parses a byte slice instead of a string.
|
||||||
|
func ParseBytes(b []byte) (UUID, error) {
|
||||||
|
var uuid UUID
|
||||||
|
switch len(b) {
|
||||||
|
case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
if !bytes.Equal(bytes.ToLower(b[:9]), []byte("urn:uuid:")) {
|
||||||
|
return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9])
|
||||||
|
}
|
||||||
|
b = b[9:]
|
||||||
|
case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
|
||||||
|
b = b[1:]
|
||||||
|
case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
var ok bool
|
||||||
|
for i := 0; i < 32; i += 2 {
|
||||||
|
uuid[i/2], ok = xtob(b[i], b[i+1])
|
||||||
|
if !ok {
|
||||||
|
return uuid, errors.New("invalid UUID format")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uuid, nil
|
||||||
|
default:
|
||||||
|
return uuid, invalidLengthError{len(b)}
|
||||||
|
}
|
||||||
|
// s is now at least 36 bytes long
|
||||||
|
// it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
|
||||||
|
return uuid, errors.New("invalid UUID format")
|
||||||
|
}
|
||||||
|
for i, x := range [16]int{
|
||||||
|
0, 2, 4, 6,
|
||||||
|
9, 11,
|
||||||
|
14, 16,
|
||||||
|
19, 21,
|
||||||
|
24, 26, 28, 30, 32, 34} {
|
||||||
|
v, ok := xtob(b[x], b[x+1])
|
||||||
|
if !ok {
|
||||||
|
return uuid, errors.New("invalid UUID format")
|
||||||
|
}
|
||||||
|
uuid[i] = v
|
||||||
|
}
|
||||||
|
return uuid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustParse is like Parse but panics if the string cannot be parsed.
|
||||||
|
// It simplifies safe initialization of global variables holding compiled UUIDs.
|
||||||
|
func MustParse(s string) UUID {
|
||||||
|
uuid, err := Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(`uuid: Parse(` + s + `): ` + err.Error())
|
||||||
|
}
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromBytes creates a new UUID from a byte slice. Returns an error if the slice
|
||||||
|
// does not have a length of 16. The bytes are copied from the slice.
|
||||||
|
func FromBytes(b []byte) (uuid UUID, err error) {
|
||||||
|
err = uuid.UnmarshalBinary(b)
|
||||||
|
return uuid, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must returns uuid if err is nil and panics otherwise.
|
||||||
|
func Must(uuid UUID, err error) UUID {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
// , or "" if uuid is invalid.
|
||||||
|
func (uuid UUID) String() string {
|
||||||
|
var buf [36]byte
|
||||||
|
encodeHex(buf[:], uuid)
|
||||||
|
return string(buf[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// URN returns the RFC 2141 URN form of uuid,
|
||||||
|
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
|
||||||
|
func (uuid UUID) URN() string {
|
||||||
|
var buf [36 + 9]byte
|
||||||
|
copy(buf[:], "urn:uuid:")
|
||||||
|
encodeHex(buf[9:], uuid)
|
||||||
|
return string(buf[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeHex(dst []byte, uuid UUID) {
|
||||||
|
hex.Encode(dst, uuid[:4])
|
||||||
|
dst[8] = '-'
|
||||||
|
hex.Encode(dst[9:13], uuid[4:6])
|
||||||
|
dst[13] = '-'
|
||||||
|
hex.Encode(dst[14:18], uuid[6:8])
|
||||||
|
dst[18] = '-'
|
||||||
|
hex.Encode(dst[19:23], uuid[8:10])
|
||||||
|
dst[23] = '-'
|
||||||
|
hex.Encode(dst[24:], uuid[10:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant returns the variant encoded in uuid.
|
||||||
|
func (uuid UUID) Variant() Variant {
|
||||||
|
switch {
|
||||||
|
case (uuid[8] & 0xc0) == 0x80:
|
||||||
|
return RFC4122
|
||||||
|
case (uuid[8] & 0xe0) == 0xc0:
|
||||||
|
return Microsoft
|
||||||
|
case (uuid[8] & 0xe0) == 0xe0:
|
||||||
|
return Future
|
||||||
|
default:
|
||||||
|
return Reserved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version returns the version of uuid.
|
||||||
|
func (uuid UUID) Version() Version {
|
||||||
|
return Version(uuid[6] >> 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Version) String() string {
|
||||||
|
if v > 15 {
|
||||||
|
return fmt.Sprintf("BAD_VERSION_%d", v)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("VERSION_%d", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Variant) String() string {
|
||||||
|
switch v {
|
||||||
|
case RFC4122:
|
||||||
|
return "RFC4122"
|
||||||
|
case Reserved:
|
||||||
|
return "Reserved"
|
||||||
|
case Microsoft:
|
||||||
|
return "Microsoft"
|
||||||
|
case Future:
|
||||||
|
return "Future"
|
||||||
|
case Invalid:
|
||||||
|
return "Invalid"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("BadVariant%d", int(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRand sets the random number generator to r, which implements io.Reader.
|
||||||
|
// If r.Read returns an error when the package requests random data then
|
||||||
|
// a panic will be issued.
|
||||||
|
//
|
||||||
|
// Calling SetRand with nil sets the random number generator to the default
|
||||||
|
// generator.
|
||||||
|
func SetRand(r io.Reader) {
|
||||||
|
if r == nil {
|
||||||
|
rander = rand.Reader
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rander = r
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
|
||||||
|
// sequence, and the current time. If the NodeID has not been set by SetNodeID
|
||||||
|
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
|
||||||
|
// be set NewUUID returns nil. If clock sequence has not been set by
|
||||||
|
// SetClockSequence then it will be set automatically. If GetTime fails to
|
||||||
|
// return the current NewUUID returns nil and an error.
|
||||||
|
//
|
||||||
|
// In most cases, New should be used.
|
||||||
|
func NewUUID() (UUID, error) {
|
||||||
|
var uuid UUID
|
||||||
|
now, seq, err := GetTime()
|
||||||
|
if err != nil {
|
||||||
|
return uuid, err
|
||||||
|
}
|
||||||
|
|
||||||
|
timeLow := uint32(now & 0xffffffff)
|
||||||
|
timeMid := uint16((now >> 32) & 0xffff)
|
||||||
|
timeHi := uint16((now >> 48) & 0x0fff)
|
||||||
|
timeHi |= 0x1000 // Version 1
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(uuid[0:], timeLow)
|
||||||
|
binary.BigEndian.PutUint16(uuid[4:], timeMid)
|
||||||
|
binary.BigEndian.PutUint16(uuid[6:], timeHi)
|
||||||
|
binary.BigEndian.PutUint16(uuid[8:], seq)
|
||||||
|
|
||||||
|
nodeMu.Lock()
|
||||||
|
if nodeID == zeroID {
|
||||||
|
setNodeInterface("")
|
||||||
|
}
|
||||||
|
copy(uuid[10:], nodeID[:])
|
||||||
|
nodeMu.Unlock()
|
||||||
|
|
||||||
|
return uuid, nil
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package uuid
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
// New creates a new random UUID or panics. New is equivalent to
|
||||||
|
// the expression
|
||||||
|
//
|
||||||
|
// uuid.Must(uuid.NewRandom())
|
||||||
|
func New() UUID {
|
||||||
|
return Must(NewRandom())
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewString creates a new random UUID and returns it as a string or panics.
|
||||||
|
// NewString is equivalent to the expression
|
||||||
|
//
|
||||||
|
// uuid.New().String()
|
||||||
|
func NewString() string {
|
||||||
|
return Must(NewRandom()).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRandom returns a Random (Version 4) UUID.
|
||||||
|
//
|
||||||
|
// The strength of the UUIDs is based on the strength of the crypto/rand
|
||||||
|
// package.
|
||||||
|
//
|
||||||
|
// A note about uniqueness derived from the UUID Wikipedia entry:
|
||||||
|
//
|
||||||
|
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
|
||||||
|
// hit by a meteorite is estimated to be one chance in 17 billion, that
|
||||||
|
// means the probability is about 0.00000000006 (6 × 10−11),
|
||||||
|
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
|
||||||
|
// year and having one duplicate.
|
||||||
|
func NewRandom() (UUID, error) {
|
||||||
|
return NewRandomFromReader(rander)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRandomFromReader returns a UUID based on bytes read from a given io.Reader.
|
||||||
|
func NewRandomFromReader(r io.Reader) (UUID, error) {
|
||||||
|
var uuid UUID
|
||||||
|
_, err := io.ReadFull(r, uuid[:])
|
||||||
|
if err != nil {
|
||||||
|
return Nil, err
|
||||||
|
}
|
||||||
|
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
|
||||||
|
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
|
||||||
|
return uuid, nil
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
.vagrant
|
|
@ -0,0 +1,191 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
Copyright 2014 Milos Gajdos
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -0,0 +1,6 @@
|
||||||
|
test:
|
||||||
|
docker-compose run --rm test make _test
|
||||||
|
|
||||||
|
_test:
|
||||||
|
go test
|
||||||
|
|
|
@ -0,0 +1,247 @@
|
||||||
|
# Linux networking in Golang
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/milosgajdos/tenus?status.svg)](https://godoc.org/github.com/milosgajdos/tenus)
|
||||||
|
[![License](https://img.shields.io/:license-apache-blue.svg)](https://opensource.org/licenses/Apache-2.0)
|
||||||
|
|
||||||
|
**tenus** is a [Golang](http://golang.org/) package which allows you to configure and manage Linux network devices programmatically. It communicates with Linux Kernel via [netlink](http://man7.org/linux/man-pages/man7/netlink.7.html) to facilitate creation and configuration of network devices on the Linux host. The package also allows for more advanced network setups with Linux containers including [Docker](https://github.com/dotcloud/docker/).
|
||||||
|
|
||||||
|
**tenus** uses [runc](https://github.com/opencontainers/runc)'s implementation of **netlink** protocol. The package only works with newer Linux Kernels (3.10+) which are shipping reasonably new `netlink` protocol implementation, so **if you are running older kernel this package won't be of much use to you** I'm afraid. I have developed this package on Ubuntu [Trusty Tahr](http://releases.ubuntu.com/14.04/) which ships with 3.13+ and verified its functionality on [Precise Pangolin](http://releases.ubuntu.com/12.04/) with upgraded kernel to version 3.10. I could worked around the `netlink` issues by using `ioctl` syscalls, but I decided to prefer "pure netlink" implementation, so suck it old Kernels.
|
||||||
|
|
||||||
|
At the moment only functional tests are available, but the interface design should hopefully allow for easy (ish) unit testing in the future. I do appreciate that the package's **test coverage is not great at the moment**, but the core functionality should be covered. I would massively welcome PRs.
|
||||||
|
|
||||||
|
## Get started
|
||||||
|
|
||||||
|
There is a ```Vagrantfile``` available in the repo so using [vagrant](https://github.com/mitchellh/vagrant) is the easiest way to get started:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
milosgajdos@bimbonet ~ $ git clone https://github.com/milosgajdos/tenus.git
|
||||||
|
milosgajdos@bimbonet ~ $ vagrant up
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note** using the provided ```Vagrantfile``` will take quite a long time to spin the VM as vagrant will setup Ubuntu Trusty VM with all the prerequisities:
|
||||||
|
|
||||||
|
* it will install golang and docker onto the VM
|
||||||
|
* it will export ```GOPATH``` and ```go get``` the **tenus** package onto the VM
|
||||||
|
* it will also "**pull**" Docker ubuntu image so that you can run the tests once the VM is set up
|
||||||
|
|
||||||
|
At the moment running the tests require Docker to be installed, but in the future I'd love to separate tests per interface so that you can run only chosen test sets.
|
||||||
|
|
||||||
|
Once the VM is running, ```cd``` into particular repo directory and you can run the tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
milosgajdos@bimbonet ~ $ cd $GOPATH/src/github.com/milosgajdos/tenus
|
||||||
|
milosgajdos@bimbonet ~ $ sudo go test
|
||||||
|
```
|
||||||
|
|
||||||
|
If you don't want to use the provided ```Vagrantfile```, you can simply run your own Linux VM (with 3.10+ kernel) and follow the regular golang development flow:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
milosgajdos@bimbonet ~ $ go get github.com/milosgajdos/tenus
|
||||||
|
milosgajdos@bimbonet ~ $ cd $GOPATH/src/github.com/milosgajdos/tenus
|
||||||
|
milosgajdos@bimbonet ~ $ sudo go test
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you've got the package and ran the tests (you don't need to run the tests!), you can start hacking. Below you can find simple code samples to get started with the package.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Below you can find a few code snippets which can help you get started writing your own programs.
|
||||||
|
|
||||||
|
### New network bridge, add dummy link into it
|
||||||
|
|
||||||
|
The example below shows a simple program example which creates a new network bridge, a new dummy network link and adds it into the bridge.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/milosgajdos/tenus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create a new network bridge
|
||||||
|
br, err := tenus.NewBridgeWithName("mybridge")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bring the bridge up
|
||||||
|
if err = br.SetLinkUp(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a dummy link
|
||||||
|
dl, err := tenus.NewLink("mydummylink")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the dummy link into bridge
|
||||||
|
if err = br.AddSlaveIfc(dl.NetInterface()); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bring the dummy link up
|
||||||
|
if err = dl.SetLinkUp(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### New network bridge, veth pair, one peer in Docker
|
||||||
|
|
||||||
|
The example below shows how you can create a new network bride, configure its IP address, add a new veth pair and send one of the veth peers into Docker with a given name.
|
||||||
|
|
||||||
|
**!! You must make sure that particular Docker is runnig if you want the code sample below to work properly !!** So before you compile and run the program below you should create a particular docker with the below used name:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
milosgajdos@bimbonet ~ $ docker run -i -t --rm --privileged -h vethdckr --name vethdckr ubuntu:14.04 /bin/bash
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/milosgajdos/tenus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// CREATE BRIDGE AND BRING IT UP
|
||||||
|
br, err := tenus.NewBridgeWithName("vethbridge")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
brIp, brIpNet, err := net.ParseCIDR("10.0.41.1/16")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := br.SetLinkIp(brIp, brIpNet); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = br.SetLinkUp(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CREATE VETH PAIR
|
||||||
|
veth, err := tenus.NewVethPairWithOptions("myveth01", tenus.VethOptions{PeerName: "myveth02"})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ASSIGN IP ADDRESS TO THE HOST VETH INTERFACE
|
||||||
|
vethHostIp, vethHostIpNet, err := net.ParseCIDR("10.0.41.2/16")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := veth.SetLinkIp(vethHostIp, vethHostIpNet); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ADD MYVETH01 INTERFACE TO THE MYBRIDGE BRIDGE
|
||||||
|
myveth01, err := net.InterfaceByName("myveth01")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = br.AddSlaveIfc(myveth01); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = veth.SetLinkUp(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PASS VETH PEER INTERFACE TO A RUNNING DOCKER BY PID
|
||||||
|
pid, err := tenus.DockerPidByName("vethdckr", "/var/run/docker.sock")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := veth.SetPeerLinkNsPid(pid); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ALLOCATE AND SET IP FOR THE NEW DOCKER INTERFACE
|
||||||
|
vethGuestIp, vethGuestIpNet, err := net.ParseCIDR("10.0.41.5/16")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := veth.SetPeerLinkNetInNs(pid, vethGuestIp, vethGuestIpNet, nil); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Working with existing bridges and interfaces
|
||||||
|
|
||||||
|
The following examples show how to retrieve exisiting interfaces as a tenus link and bridge
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/milosgajdos/tenus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// RETRIEVE EXISTING BRIDGE
|
||||||
|
br, err := tenus.BridgeFromName("bridge0")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// REMOVING AN IP FROM A BRIDGE INTERFACE (BEFORE RECONFIGURATION)
|
||||||
|
brIp, brIpNet, err := net.ParseCIDR("10.0.41.1/16")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := br.UnsetLinkIp(brIp, brIpNet); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RETRIEVE EXISTING INTERFACE
|
||||||
|
dl, err := tenus.NewLinkFrom("eth0")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RENAMING AN INTERFACE BY NAME
|
||||||
|
if err := tenus.RenameInterfaceByName("vethPSQSEl", "vethNEWNAME"); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### VLAN and MAC VLAN interfaces
|
||||||
|
|
||||||
|
You can check out [VLAN](https://gist.github.com/milosgajdos/9f68b1818dca886e9ae8) and [Mac VLAN](https://gist.github.com/milosgajdos/296fb90d076f259a5b0a) examples, too.
|
||||||
|
|
||||||
|
### More examples
|
||||||
|
|
||||||
|
Repo contains few more code sample in ```examples``` folder so make sure to check them out if you're interested.
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
This is just a rough beginning of the project which I put together over couple of weeks in my free time. I'd like to integrate this into my own Docker fork and test the advanced netowrking functionality with the core of Docker as oppose to configuring network interfaces from a separate golang program, because advanced networking in Docker was the main motivation for writing this package.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
More in depth package documentation is available via [godoc](http://godoc.org/github.com/milosgajdos/tenus)
|
|
@ -0,0 +1,31 @@
|
||||||
|
# -*- mode: ruby -*-
|
||||||
|
# vi: set ft=ruby :
|
||||||
|
|
||||||
|
$provision = <<SCRIPT
|
||||||
|
apt-get update -qq && apt-get install -y vim curl python-software-properties golang
|
||||||
|
add-apt-repository -y "deb https://get.docker.io/ubuntu docker main"
|
||||||
|
curl -s https://get.docker.io/gpg | sudo apt-key add -
|
||||||
|
apt-get update -qq; apt-get install -y lxc-docker
|
||||||
|
docker pull ubuntu
|
||||||
|
cat > /etc/profile.d/envvar.sh <<'EOF'
|
||||||
|
export GOPATH=/opt/golang
|
||||||
|
export PATH=$PATH:$GOPATH/bin
|
||||||
|
EOF
|
||||||
|
. /etc/profile.d/envvar.sh
|
||||||
|
go get "github.com/milosgajdos/tenus"
|
||||||
|
SCRIPT
|
||||||
|
|
||||||
|
VAGRANTFILE_API_VERSION = "2"
|
||||||
|
|
||||||
|
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||||
|
config.vm.box = "ubuntu/trusty64"
|
||||||
|
config.vm.hostname = "tenus"
|
||||||
|
config.vm.network :private_network, ip: "10.0.2.88"
|
||||||
|
config.vm.network :private_network, ip: "10.0.2.89"
|
||||||
|
|
||||||
|
config.vm.provider "virtualbox" do |v|
|
||||||
|
v.customize ['modifyvm', :id, '--nicpromisc1', 'allow-all']
|
||||||
|
end
|
||||||
|
|
||||||
|
config.vm.provision "shell", inline: $provision
|
||||||
|
end
|
|
@ -0,0 +1,153 @@
|
||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/docker/libcontainer/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bridger embeds Linker interface and adds one extra function.
|
||||||
|
type Bridger interface {
|
||||||
|
// Linker interface
|
||||||
|
Linker
|
||||||
|
// AddSlaveIfc adds network interface to the network bridge
|
||||||
|
AddSlaveIfc(*net.Interface) error
|
||||||
|
//RemoveSlaveIfc removes network interface from the network bridge
|
||||||
|
RemoveSlaveIfc(*net.Interface) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bridge is Link which has zero or more slave network interfaces.
|
||||||
|
// Bridge implements Bridger interface.
|
||||||
|
type Bridge struct {
|
||||||
|
Link
|
||||||
|
slaveIfcs []net.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBridge creates new network bridge on Linux host.
|
||||||
|
//
|
||||||
|
// It is equivalent of running: ip link add name br${RANDOM STRING} type bridge
|
||||||
|
// NewBridge returns Bridger which is initialized to a pointer of type Bridge if the
|
||||||
|
// bridge was created successfully on the Linux host. Newly created bridge is assigned
|
||||||
|
// a random name starting with "br".
|
||||||
|
// It returns error if the bridge could not be created.
|
||||||
|
func NewBridge() (Bridger, error) {
|
||||||
|
brDev := makeNetInterfaceName("br")
|
||||||
|
|
||||||
|
if ok, err := NetInterfaceNameValid(brDev); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(brDev); err == nil {
|
||||||
|
return nil, fmt.Errorf("Interface name %s already assigned on the host", brDev)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAdd(brDev, "bridge"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newIfc, err := net.InterfaceByName(brDev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Bridge{
|
||||||
|
Link: Link{
|
||||||
|
ifc: newIfc,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBridge creates new network bridge on Linux host with the name passed as a parameter.
|
||||||
|
// It is equivalent of running: ip link add name ${ifcName} type bridge
|
||||||
|
// It returns error if the bridge can not be created.
|
||||||
|
func NewBridgeWithName(ifcName string) (Bridger, error) {
|
||||||
|
if ok, err := NetInterfaceNameValid(ifcName); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(ifcName); err == nil {
|
||||||
|
return nil, fmt.Errorf("Interface name %s already assigned on the host", ifcName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAdd(ifcName, "bridge"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newIfc, err := net.InterfaceByName(ifcName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Bridge{
|
||||||
|
Link: Link{
|
||||||
|
ifc: newIfc,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BridgeFromName returns a tenus network bridge from an existing bridge of given name on the Linux host.
|
||||||
|
// It returns error if the bridge of the given name cannot be found.
|
||||||
|
func BridgeFromName(ifcName string) (Bridger, error) {
|
||||||
|
if ok, err := NetInterfaceNameValid(ifcName); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newIfc, err := net.InterfaceByName(ifcName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Bridge{
|
||||||
|
Link: Link{
|
||||||
|
ifc: newIfc,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddToBridge adds network interfaces to network bridge.
|
||||||
|
// It is equivalent of running: ip link set ${netIfc name} master ${netBridge name}
|
||||||
|
// It returns error when it fails to add the network interface to bridge.
|
||||||
|
func AddToBridge(netIfc, netBridge *net.Interface) error {
|
||||||
|
return netlink.NetworkSetMaster(netIfc, netBridge)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddToBridge adds network interfaces to network bridge.
|
||||||
|
// It is equivalent of running: ip link set dev ${netIfc name} nomaster
|
||||||
|
// It returns error when it fails to remove the network interface from the bridge.
|
||||||
|
func RemoveFromBridge(netIfc *net.Interface) error {
|
||||||
|
return netlink.NetworkSetNoMaster(netIfc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSlaveIfc adds network interface to network bridge.
|
||||||
|
// It is equivalent of running: ip link set ${ifc name} master ${bridge name}
|
||||||
|
// It returns error if the network interface could not be added to the bridge.
|
||||||
|
func (br *Bridge) AddSlaveIfc(ifc *net.Interface) error {
|
||||||
|
if err := netlink.NetworkSetMaster(ifc, br.ifc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
br.slaveIfcs = append(br.slaveIfcs, *ifc)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveSlaveIfc removes network interface from the network bridge.
|
||||||
|
// It is equivalent of running: ip link set dev ${netIfc name} nomaster
|
||||||
|
// It returns error if the network interface is not in the bridge or
|
||||||
|
// it could not be removed from the bridge.
|
||||||
|
func (br *Bridge) RemoveSlaveIfc(ifc *net.Interface) error {
|
||||||
|
if err := netlink.NetworkSetNoMaster(ifc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for index, i := range br.slaveIfcs {
|
||||||
|
// I could reflect.DeepEqual(), but there is not point to import reflect for one operation
|
||||||
|
if i.Name == ifc.Name && bytes.Equal(i.HardwareAddr, ifc.HardwareAddr) {
|
||||||
|
br.slaveIfcs = append(br.slaveIfcs[:index], br.slaveIfcs[index+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Package tenus allows to configure and manage Linux network devices programmatically.
|
||||||
|
//
|
||||||
|
// You can create, configure and manage various advanced Linux network setups directly from your Go code.
|
||||||
|
// tenus also allows you to configure advanced network setups with Linux containers including Docker.
|
||||||
|
// It leverages Linux Kernenl's netlink facility and exposes easier to work with programming API than
|
||||||
|
// the one provided by netlink.
|
||||||
|
//
|
||||||
|
// Actual implementations are in:
|
||||||
|
// link_linux.go, bridge_linux.go, veth_linux.go, vlan_linux.go and macvlan_linux.go
|
||||||
|
package tenus
|
|
@ -0,0 +1,13 @@
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
test:
|
||||||
|
image: golang
|
||||||
|
cap_add:
|
||||||
|
- CAP_NET_ADMIN
|
||||||
|
volumes:
|
||||||
|
- ${PWD}:/src
|
||||||
|
- ${HOME}/.cache/go-build:/go/pkg/mod
|
||||||
|
working_dir: /src
|
||||||
|
environment:
|
||||||
|
- GOCACHE=/go/pkg/mod
|
|
@ -0,0 +1,242 @@
|
||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/docker/libcontainer/netlink"
|
||||||
|
"github.com/docker/libcontainer/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
// generates random string for makeNetInterfaceName()
|
||||||
|
func randomString(size int) string {
|
||||||
|
alphanum := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||||
|
bytes := make([]byte, size)
|
||||||
|
rand.Read(bytes)
|
||||||
|
|
||||||
|
for i, b := range bytes {
|
||||||
|
bytes[i] = alphanum[b%byte(len(alphanum))]
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeNetInterfaceName(base string) string {
|
||||||
|
return makeNetInterfaceName(base)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generates new unused network interfaces name with given prefix
|
||||||
|
func makeNetInterfaceName(base string) string {
|
||||||
|
for {
|
||||||
|
name := base + randomString(6)
|
||||||
|
if _, err := net.InterfaceByName(name); err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates MTU LinkOption
|
||||||
|
func validMtu(mtu int) error {
|
||||||
|
if mtu < 0 {
|
||||||
|
return errors.New("MTU must be a positive integer!")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates MacAddress LinkOption
|
||||||
|
func validMacAddress(macaddr string) error {
|
||||||
|
if _, err := net.ParseMAC(macaddr); err != nil {
|
||||||
|
return fmt.Errorf("Can not parse MAC address: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := FindInterfaceByMacAddress(macaddr); err == nil {
|
||||||
|
return fmt.Errorf("MAC Address already assigned on the host: %s", macaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates MacAddress LinkOption
|
||||||
|
func validNs(ns int) error {
|
||||||
|
if ns < 0 {
|
||||||
|
return fmt.Errorf("Incorrect Network Namespace PID specified: %d", ns)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates Flags LinkOption
|
||||||
|
func validFlags(flags net.Flags) error {
|
||||||
|
if (flags & syscall.IFF_UP) != syscall.IFF_UP {
|
||||||
|
return fmt.Errorf("Unsupported network flags specified: %v", flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetInterfaceNameValid checks if the network interface name is valid.
|
||||||
|
// It accepts interface name as a string. It returns error if invalid interface name is supplied.
|
||||||
|
func NetInterfaceNameValid(name string) (bool, error) {
|
||||||
|
if name == "" {
|
||||||
|
return false, errors.New("Interface name can not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(name) == 1 {
|
||||||
|
return false, fmt.Errorf("Interface name too short: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(name) > netlink.IFNAMSIZ {
|
||||||
|
return false, fmt.Errorf("Interface name too long: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, char := range name {
|
||||||
|
if unicode.IsSpace(char) || char > 0x7F {
|
||||||
|
return false, fmt.Errorf("Invalid characters in interface name: %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindInterfaceByMacAddress returns *net.Interface which has a given MAC address assigned.
|
||||||
|
// It returns nil and error if invalid MAC address is supplied or if there is no network interface
|
||||||
|
// with the given MAC address assigned on Linux host.
|
||||||
|
func FindInterfaceByMacAddress(macaddr string) (*net.Interface, error) {
|
||||||
|
if macaddr == "" {
|
||||||
|
return nil, errors.New("Empty MAC address specified!")
|
||||||
|
}
|
||||||
|
|
||||||
|
ifcs, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hwaddr, err := net.ParseMAC(macaddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ifc := range ifcs {
|
||||||
|
if bytes.Equal(hwaddr, ifc.HardwareAddr) {
|
||||||
|
return &ifc, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("Could not find interface with MAC address on the host: %s", macaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DockerPidByName returns PID of the running docker container.
|
||||||
|
// It accepts Docker container name and Docker host as parameters and queries Docker API via HTTP.
|
||||||
|
// Docker host passed as an argument can be either full path to Docker UNIX socket or HOST:PORT address string.
|
||||||
|
// It returns error if Docker container can not be found or if an error occurs when querying Docker API.
|
||||||
|
func DockerPidByName(name string, dockerHost string) (int, error) {
|
||||||
|
var network string
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
return 0, errors.New("Docker name can not be empty!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if dockerHost == "" {
|
||||||
|
return 0, errors.New("Docker host can not be empty!")
|
||||||
|
}
|
||||||
|
|
||||||
|
if filepath.IsAbs(dockerHost) {
|
||||||
|
network = "unix"
|
||||||
|
} else {
|
||||||
|
network = "tcp"
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "http://docker.socket/containers/"+name+"/json", nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("Fail to create http request: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := time.Duration(2 * time.Second)
|
||||||
|
httpTransport := &http.Transport{
|
||||||
|
Dial: func(proto string, addr string) (net.Conn, error) {
|
||||||
|
return net.DialTimeout(network, dockerHost, timeout)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
dockerClient := http.Client{Transport: httpTransport}
|
||||||
|
|
||||||
|
resp, err := dockerClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("Failed to create http client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch resp.StatusCode {
|
||||||
|
case http.StatusNotFound:
|
||||||
|
return 0, fmt.Errorf("Docker container \"%s\" does not seem to exist!", name)
|
||||||
|
case http.StatusInternalServerError:
|
||||||
|
return 0, fmt.Errorf("Could not retrieve Docker %s pid due to Docker server error", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := struct {
|
||||||
|
State struct {
|
||||||
|
Pid float64
|
||||||
|
}
|
||||||
|
}{}
|
||||||
|
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&data)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("Unable to decode json response: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(data.State.Pid), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetNsHandle returns a file descriptor handle for network namespace specified by PID.
|
||||||
|
// It returns error if network namespace could not be found or if network namespace path could not be opened.
|
||||||
|
func NetNsHandle(nspid int) (uintptr, error) {
|
||||||
|
if nspid <= 0 || nspid == 1 {
|
||||||
|
return 0, fmt.Errorf("Incorred PID specified: %d", nspid)
|
||||||
|
}
|
||||||
|
|
||||||
|
nsPath := path.Join("/", "proc", strconv.Itoa(nspid), "ns/net")
|
||||||
|
if nsPath == "" {
|
||||||
|
return 0, fmt.Errorf("Could not find Network namespace for pid: %d", nspid)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(nsPath)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("Could not open Network Namespace: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.Fd(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNetNsToPid sets network namespace to the one specied by PID.
|
||||||
|
// It returns error if the network namespace could not be set.
|
||||||
|
func SetNetNsToPid(nspid int) error {
|
||||||
|
if nspid <= 0 || nspid == 1 {
|
||||||
|
return fmt.Errorf("Incorred PID specified: %d", nspid)
|
||||||
|
}
|
||||||
|
|
||||||
|
nsFd, err := NetNsHandle(nspid)
|
||||||
|
defer syscall.Close(int(nsFd))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Could not get network namespace handle: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := system.Setns(nsFd, syscall.CLONE_NEWNET); err != nil {
|
||||||
|
return fmt.Errorf("Unable to set the network namespace: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,333 @@
|
||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/docker/libcontainer/netlink"
|
||||||
|
"github.com/docker/libcontainer/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LinkOptions allows you to specify network link options.
|
||||||
|
type LinkOptions struct {
|
||||||
|
// MAC address
|
||||||
|
MacAddr string
|
||||||
|
// Maximum Transmission Unit
|
||||||
|
MTU int
|
||||||
|
// Link network flags i.e. FlagUp, FlagLoopback, FlagMulticast
|
||||||
|
Flags net.Flags
|
||||||
|
// Network namespace in which the network link should be created
|
||||||
|
Ns int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linker is a generic Linux network link
|
||||||
|
type Linker interface {
|
||||||
|
// NetInterface returns the link's logical network interface
|
||||||
|
NetInterface() *net.Interface
|
||||||
|
// DeleteLink deletes the link from Linux host
|
||||||
|
DeleteLink() error
|
||||||
|
// SetLinkMTU sets the link's MTU.
|
||||||
|
SetLinkMTU(int) error
|
||||||
|
// SetLinkMacAddress sets the link's MAC address.
|
||||||
|
SetLinkMacAddress(string) error
|
||||||
|
// SetLinkUp brings the link up
|
||||||
|
SetLinkUp() error
|
||||||
|
// SetLinkDown brings the link down
|
||||||
|
SetLinkDown() error
|
||||||
|
// SetLinkIp configures the link's IP address
|
||||||
|
SetLinkIp(net.IP, *net.IPNet) error
|
||||||
|
// UnsetLinkIp remove and IP address from the link
|
||||||
|
UnsetLinkIp(net.IP, *net.IPNet) error
|
||||||
|
// SetLinkDefaultGw configures the link's default gateway
|
||||||
|
SetLinkDefaultGw(*net.IP) error
|
||||||
|
// SetLinkNetNsPid moves the link to network namespace specified by PID
|
||||||
|
SetLinkNetNsPid(int) error
|
||||||
|
// SetLinkNetInNs configures network settings of the link in network namespace
|
||||||
|
SetLinkNetInNs(int, net.IP, *net.IPNet, *net.IP) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link has a logical network interface
|
||||||
|
type Link struct {
|
||||||
|
ifc *net.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLink creates new network link on Linux host.
|
||||||
|
//
|
||||||
|
// It is equivalent of running: ip link add name ${ifcName} type dummy
|
||||||
|
// NewLink returns Linker which is initialized to a pointer of type Link if the
|
||||||
|
// link was created successfully on the Linux host.
|
||||||
|
// It returns error if the network link could not be created on Linux host.
|
||||||
|
func NewLink(ifcName string) (Linker, error) {
|
||||||
|
if ok, err := NetInterfaceNameValid(ifcName); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(ifcName); err == nil {
|
||||||
|
return nil, fmt.Errorf("Interface name %s already assigned on the host", ifcName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAdd(ifcName, "dummy"); err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not create new link %s: %s", ifcName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newIfc, err := net.InterfaceByName(ifcName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Link{
|
||||||
|
ifc: newIfc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLinkFrom creates new tenus link on Linux host from an existing interface of given name
|
||||||
|
func NewLinkFrom(ifcName string) (Linker, error) {
|
||||||
|
if ok, err := NetInterfaceNameValid(ifcName); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newIfc, err := net.InterfaceByName(ifcName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Link{
|
||||||
|
ifc: newIfc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLinkWithOptions creates new network link on Linux host and sets some of its network
|
||||||
|
// parameters passed in as LinkOptions
|
||||||
|
//
|
||||||
|
// Calling NewLinkWithOptions is equivalent of running following commands one after another if
|
||||||
|
// particular option is passed in as a parameter:
|
||||||
|
// ip link add name ${ifcName} type dummy
|
||||||
|
// ip link set dev ${ifcName} address ${MAC address}
|
||||||
|
// ip link set dev ${ifcName} mtu ${MTU value}
|
||||||
|
// ip link set dev ${ifcName} up
|
||||||
|
// NewLinkWithOptions returns Linker which is initialized to a pointer of type Link if the network
|
||||||
|
// link with given LinkOptions was created successfully on the Linux host.
|
||||||
|
// It attempts to delete the link if any of the LinkOptions are incorrect or if setting the options
|
||||||
|
// failed and returns error.
|
||||||
|
func NewLinkWithOptions(ifcName string, opts LinkOptions) (Linker, error) {
|
||||||
|
if ok, err := NetInterfaceNameValid(ifcName); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(ifcName); err == nil {
|
||||||
|
return nil, fmt.Errorf("Interface name %s already assigned on the host", ifcName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAdd(ifcName, "dummy"); err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not create new link %s: %s", ifcName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newIfc, err := net.InterfaceByName(ifcName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts != LinkOptions{}) {
|
||||||
|
errOpts := setLinkOptions(newIfc, opts)
|
||||||
|
if errOpts != nil {
|
||||||
|
if errDel := DeleteLink(newIfc.Name); err != nil {
|
||||||
|
return nil, fmt.Errorf("Incorrect options specified: %s. Attempt to delete the link failed: %s",
|
||||||
|
errOpts, errDel)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("Could not set link options: %s", errOpts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Link{
|
||||||
|
ifc: newIfc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteLink deletes netowrk link from Linux Host
|
||||||
|
// It is equivalent of running: ip link delete dev ${name}
|
||||||
|
func DeleteLink(name string) error {
|
||||||
|
return netlink.NetworkLinkDel(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetInterface returns link's logical network interface.
|
||||||
|
func (l *Link) NetInterface() *net.Interface {
|
||||||
|
return l.ifc
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteLink deletes link interface on Linux host.
|
||||||
|
// It is equivalent of running: ip link delete dev ${interface name}
|
||||||
|
func (l *Link) DeleteLink() error {
|
||||||
|
return netlink.NetworkLinkDel(l.NetInterface().Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLinkMTU sets link's MTU.
|
||||||
|
// It is equivalent of running: ip link set dev ${interface name} mtu ${MTU value}
|
||||||
|
func (l *Link) SetLinkMTU(mtu int) error {
|
||||||
|
return netlink.NetworkSetMTU(l.NetInterface(), mtu)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLinkMacAddress sets link's MAC address.
|
||||||
|
// It is equivalent of running: ip link set dev ${interface name} address ${address}
|
||||||
|
func (l *Link) SetLinkMacAddress(macaddr string) error {
|
||||||
|
return netlink.NetworkSetMacAddress(l.NetInterface(), macaddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLinkUp brings the link up.
|
||||||
|
// It is equivalent of running: ip link set dev ${interface name} up
|
||||||
|
func (l *Link) SetLinkUp() error {
|
||||||
|
return netlink.NetworkLinkUp(l.NetInterface())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLinkDown brings the link down.
|
||||||
|
// It is equivalent of running: ip link set dev ${interface name} down
|
||||||
|
func (l *Link) SetLinkDown() error {
|
||||||
|
return netlink.NetworkLinkDown(l.NetInterface())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLinkIp configures the link's IP address.
|
||||||
|
// It is equivalent of running: ip address add ${address}/${mask} dev ${interface name}
|
||||||
|
func (l *Link) SetLinkIp(ip net.IP, network *net.IPNet) error {
|
||||||
|
return netlink.NetworkLinkAddIp(l.NetInterface(), ip, network)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsetLinkIp configures the link's IP address.
|
||||||
|
// It is equivalent of running: ip address del ${address}/${mask} dev ${interface name}
|
||||||
|
func (l *Link) UnsetLinkIp(ip net.IP, network *net.IPNet) error {
|
||||||
|
return netlink.NetworkLinkDelIp(l.NetInterface(), ip, network)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLinkDefaultGw configures the link's default Gateway.
|
||||||
|
// It is equivalent of running: ip route add default via ${ip address}
|
||||||
|
func (l *Link) SetLinkDefaultGw(gw *net.IP) error {
|
||||||
|
return netlink.AddDefaultGw(gw.String(), l.NetInterface().Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLinkNetNsPid moves the link to Network namespace specified by PID.
|
||||||
|
func (l *Link) SetLinkNetNsPid(nspid int) error {
|
||||||
|
return netlink.NetworkSetNsPid(l.NetInterface(), nspid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLinkNetInNs configures network settings of the link in network namespace specified by PID.
|
||||||
|
func (l *Link) SetLinkNetInNs(nspid int, ip net.IP, network *net.IPNet, gw *net.IP) error {
|
||||||
|
origNs, _ := NetNsHandle(os.Getpid())
|
||||||
|
defer syscall.Close(int(origNs))
|
||||||
|
defer system.Setns(origNs, syscall.CLONE_NEWNET)
|
||||||
|
|
||||||
|
if err := SetNetNsToPid(nspid); err != nil {
|
||||||
|
return fmt.Errorf("Setting network namespace failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAddIp(l.NetInterface(), ip, network); err != nil {
|
||||||
|
return fmt.Errorf("Unable to set IP: %s in pid: %d network namespace", ip.String(), nspid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkUp(l.ifc); err != nil {
|
||||||
|
return fmt.Errorf("Unable to bring %s interface UP: %d", l.ifc.Name, nspid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gw != nil {
|
||||||
|
if err := netlink.AddDefaultGw(gw.String(), l.NetInterface().Name); err != nil {
|
||||||
|
return fmt.Errorf("Unable to set Default gateway: %s in pid: %d network namespace", gw.String(), nspid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLinkNsFd sets the link's Linux namespace to the one specified by filesystem path.
|
||||||
|
func (l *Link) SetLinkNsFd(nspath string) error {
|
||||||
|
fd, err := syscall.Open(nspath, syscall.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Could not attach to Network namespace: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return netlink.NetworkSetNsFd(l.NetInterface(), fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLinkNsToDocker sets the link's Linux namespace to a running Docker one specified by Docker name.
|
||||||
|
func (l *Link) SetLinkNsToDocker(name string, dockerHost string) error {
|
||||||
|
pid, err := DockerPidByName(name, dockerHost)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to find docker %s : %s", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return l.SetLinkNetNsPid(pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenameInterfaceByName renames an interface of given name.
|
||||||
|
func RenameInterfaceByName(old string, newName string) error {
|
||||||
|
iface, err := net.InterfaceByName(old)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return netlink.NetworkChangeName(iface, newName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setLinkOptions validates and sets link's various options passed in as LinkOptions.
|
||||||
|
func setLinkOptions(ifc *net.Interface, opts LinkOptions) error {
|
||||||
|
macaddr, mtu, flags, ns := opts.MacAddr, opts.MTU, opts.Flags, opts.Ns
|
||||||
|
|
||||||
|
// if MTU is passed in LinkOptions
|
||||||
|
if mtu != 0 {
|
||||||
|
if err := validMtu(mtu); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkSetMTU(ifc, mtu); err != nil {
|
||||||
|
return fmt.Errorf("Unable to set MTU: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if MacAddress is passed in LinkOptions
|
||||||
|
if macaddr != "" {
|
||||||
|
if err := validMacAddress(macaddr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkSetMacAddress(ifc, macaddr); err != nil {
|
||||||
|
return fmt.Errorf("Unable to set MAC Address: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if ns is passed in LinkOptions
|
||||||
|
if ns != 0 {
|
||||||
|
if err := validNs(ns); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkSetNsPid(ifc, ns); err != nil {
|
||||||
|
return fmt.Errorf("Unable to set Network namespace: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if flags is passed in LinkOptions
|
||||||
|
if flags != 0 {
|
||||||
|
if err := validFlags(flags); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ns != 0 && (ns != 1 || ns != os.Getpid()) {
|
||||||
|
if (flags & syscall.IFF_UP) == syscall.IFF_UP {
|
||||||
|
origNs, _ := NetNsHandle(os.Getpid())
|
||||||
|
defer syscall.Close(int(origNs))
|
||||||
|
defer system.Setns(origNs, syscall.CLONE_NEWNET)
|
||||||
|
|
||||||
|
if err := SetNetNsToPid(ns); err != nil {
|
||||||
|
return fmt.Errorf("Switching to %d network namespace failed: %s", ns, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkUp(ifc); err != nil {
|
||||||
|
return fmt.Errorf("Unable to bring %s interface UP: %d", ifc.Name, ns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := netlink.NetworkLinkUp(ifc); err != nil {
|
||||||
|
return fmt.Errorf("Could not bring up network link %s: %s", ifc.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,196 @@
|
||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/docker/libcontainer/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Default MacVlan mode
|
||||||
|
const (
|
||||||
|
default_mode = "bridge"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Supported macvlan modes by tenus package
|
||||||
|
var MacVlanModes = map[string]bool{
|
||||||
|
"private": true,
|
||||||
|
"vepa": true,
|
||||||
|
"bridge": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// MacVlanOptions allows you to specify some options for macvlan link.
|
||||||
|
type MacVlanOptions struct {
|
||||||
|
// macvlan device name
|
||||||
|
Dev string
|
||||||
|
// macvlan mode
|
||||||
|
Mode string
|
||||||
|
// MAC address
|
||||||
|
MacAddr string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MacVlaner embeds Linker interface and adds few more functions.
|
||||||
|
type MacVlaner interface {
|
||||||
|
// Linker interface
|
||||||
|
Linker
|
||||||
|
// MasterNetInterface returns macvlan master network device
|
||||||
|
MasterNetInterface() *net.Interface
|
||||||
|
// Mode returns macvlan link's network mode
|
||||||
|
Mode() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MacVlanLink is Link which has a master network device and operates in
|
||||||
|
// a given network mode. It implements MacVlaner interface.
|
||||||
|
type MacVlanLink struct {
|
||||||
|
Link
|
||||||
|
// Master device logical network interface
|
||||||
|
masterIfc *net.Interface
|
||||||
|
// macvlan operatio nmode
|
||||||
|
mode string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMacVlanLink creates macvlan network link
|
||||||
|
//
|
||||||
|
// It is equivalent of running:
|
||||||
|
// ip link add name mc${RANDOM STRING} link ${master interface} type macvlan
|
||||||
|
// NewMacVlanLink returns MacVlaner which is initialized to a pointer of type MacVlanLink if the
|
||||||
|
// macvlan link was created successfully on the Linux host. Newly created link is assigned
|
||||||
|
// a random name starting with "mc". It sets the macvlan mode to "bridge" mode which is a default.
|
||||||
|
// It returns error if the link could not be created.
|
||||||
|
func NewMacVlanLink(masterDev string) (MacVlaner, error) {
|
||||||
|
macVlanDev := makeNetInterfaceName("mc")
|
||||||
|
|
||||||
|
if ok, err := NetInterfaceNameValid(masterDev); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(masterDev); err != nil {
|
||||||
|
return nil, fmt.Errorf("Master MAC VLAN device %s does not exist on the host", masterDev)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAddMacVlan(masterDev, macVlanDev, default_mode); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
macVlanIfc, err := net.InterfaceByName(macVlanDev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
masterIfc, err := net.InterfaceByName(masterDev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &MacVlanLink{
|
||||||
|
Link: Link{
|
||||||
|
ifc: macVlanIfc,
|
||||||
|
},
|
||||||
|
masterIfc: masterIfc,
|
||||||
|
mode: default_mode,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMacVlanLinkWithOptions creates macvlan network link and sets som of its network parameters
|
||||||
|
// passed in as MacVlanOptions.
|
||||||
|
//
|
||||||
|
// It is equivalent of running:
|
||||||
|
// ip link add name ${macvlan name} link ${master interface} address ${macaddress} type macvlan mode ${mode}
|
||||||
|
// NewMacVlanLinkWithOptions returns MacVlaner which is initialized to a pointer of type MacVlanLink if the
|
||||||
|
// macvlan link was created successfully on the Linux host. If particular option is empty, it sets default value if possible.
|
||||||
|
// It returns error if the macvlan link could not be created or if incorrect options have been passed.
|
||||||
|
func NewMacVlanLinkWithOptions(masterDev string, opts MacVlanOptions) (MacVlaner, error) {
|
||||||
|
if ok, err := NetInterfaceNameValid(masterDev); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(masterDev); err != nil {
|
||||||
|
return nil, fmt.Errorf("Master MAC VLAN device %s does not exist on the host", masterDev)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateMacVlanOptions(&opts); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAddMacVlan(masterDev, opts.Dev, opts.Mode); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
macVlanIfc, err := net.InterfaceByName(opts.Dev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.MacAddr != "" {
|
||||||
|
if err := netlink.NetworkSetMacAddress(macVlanIfc, opts.MacAddr); err != nil {
|
||||||
|
if errDel := DeleteLink(macVlanIfc.Name); errDel != nil {
|
||||||
|
return nil, fmt.Errorf("Incorrect options specified. Attempt to delete the link failed: %s", errDel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hwaddr, err := net.ParseMAC(opts.MacAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
macVlanIfc.HardwareAddr = hwaddr
|
||||||
|
}
|
||||||
|
|
||||||
|
masterIfc, err := net.InterfaceByName(masterDev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &MacVlanLink{
|
||||||
|
Link: Link{
|
||||||
|
ifc: macVlanIfc,
|
||||||
|
},
|
||||||
|
masterIfc: masterIfc,
|
||||||
|
mode: opts.Mode,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetInterface returns macvlan link's network interface
|
||||||
|
func (macvln *MacVlanLink) NetInterface() *net.Interface {
|
||||||
|
return macvln.ifc
|
||||||
|
}
|
||||||
|
|
||||||
|
// MasterNetInterface returns macvlan link's master network interface
|
||||||
|
func (macvln *MacVlanLink) MasterNetInterface() *net.Interface {
|
||||||
|
return macvln.masterIfc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode returns macvlan link's network operation mode
|
||||||
|
func (macvln *MacVlanLink) Mode() string {
|
||||||
|
return macvln.mode
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateMacVlanOptions(opts *MacVlanOptions) error {
|
||||||
|
if opts.Dev != "" {
|
||||||
|
if ok, err := NetInterfaceNameValid(opts.Dev); !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(opts.Dev); err == nil {
|
||||||
|
return fmt.Errorf("MAC VLAN device %s already assigned on the host", opts.Dev)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
opts.Dev = makeNetInterfaceName("mc")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Mode != "" {
|
||||||
|
if _, ok := MacVlanModes[opts.Mode]; !ok {
|
||||||
|
return fmt.Errorf("Unsupported MacVlan mode specified: %s", opts.Mode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
opts.Mode = default_mode
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.MacAddr != "" {
|
||||||
|
if _, err := net.ParseMAC(opts.MacAddr); err != nil {
|
||||||
|
return fmt.Errorf("Incorrect MAC ADDRESS specified: %s", opts.MacAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/docker/libcontainer/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MacVtaper embeds MacVlaner interface
|
||||||
|
type MacVtaper interface {
|
||||||
|
MacVlaner
|
||||||
|
}
|
||||||
|
|
||||||
|
// MacVtapLink is MacVlanLink. It implements MacVtaper interface
|
||||||
|
type MacVtapLink struct {
|
||||||
|
*MacVlanLink
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMacVtapLink creates macvtap network link
|
||||||
|
//
|
||||||
|
// It is equivalent of running:
|
||||||
|
// ip link add name mvt${RANDOM STRING} link ${master interface} type macvtap
|
||||||
|
// NewMacVtapLink returns MacVtaper which is initialized to a pointer of type MacVtapLink if the
|
||||||
|
// macvtap link was created successfully on the Linux host. Newly created link is assigned
|
||||||
|
// a random name starting with "mvt". It sets the macvlan mode to "bridge" which is a default.
|
||||||
|
// It returns error if the link could not be created.
|
||||||
|
func NewMacVtapLink(masterDev string) (MacVtaper, error) {
|
||||||
|
macVtapDev := makeNetInterfaceName("mvt")
|
||||||
|
|
||||||
|
if ok, err := NetInterfaceNameValid(masterDev); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(masterDev); err != nil {
|
||||||
|
return nil, fmt.Errorf("Master MAC VTAP device %s does not exist on the host", masterDev)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAddMacVtap(masterDev, macVtapDev, default_mode); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
macVtapIfc, err := net.InterfaceByName(macVtapDev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
masterIfc, err := net.InterfaceByName(masterDev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &MacVtapLink{
|
||||||
|
MacVlanLink: &MacVlanLink{
|
||||||
|
Link: Link{
|
||||||
|
ifc: macVtapIfc,
|
||||||
|
},
|
||||||
|
masterIfc: masterIfc,
|
||||||
|
mode: default_mode,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMacVtapLinkWithOptions creates macvtap network link and can set some of its network parameters
|
||||||
|
// passed in as MacVlanOptions.
|
||||||
|
//
|
||||||
|
// It is equivalent of running:
|
||||||
|
// ip link add name ${macvlan name} link ${master interface} address ${macaddress} type macvtap mode ${mode}
|
||||||
|
// NewMacVtapLinkWithOptions returns MacVtaper which is initialized to a pointer of type MacVtapLink if the
|
||||||
|
// macvtap link was created successfully on the Linux host. It returns error if the macvtap link could not be created.
|
||||||
|
func NewMacVtapLinkWithOptions(masterDev string, opts MacVlanOptions) (MacVtaper, error) {
|
||||||
|
if ok, err := NetInterfaceNameValid(masterDev); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(masterDev); err != nil {
|
||||||
|
return nil, fmt.Errorf("Master MAC VLAN device %s does not exist on the host", masterDev)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateMacVlanOptions(&opts); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAddMacVtap(masterDev, opts.Dev, opts.Mode); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
macVtapIfc, err := net.InterfaceByName(opts.Dev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.MacAddr != "" {
|
||||||
|
if err := netlink.NetworkSetMacAddress(macVtapIfc, opts.MacAddr); err != nil {
|
||||||
|
if errDel := DeleteLink(macVtapIfc.Name); errDel != nil {
|
||||||
|
return nil, fmt.Errorf("Incorrect options specified. Attempt to delete the link failed: %s",
|
||||||
|
errDel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hwaddr, err := net.ParseMAC(opts.MacAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
macVtapIfc.HardwareAddr = hwaddr
|
||||||
|
}
|
||||||
|
|
||||||
|
masterIfc, err := net.InterfaceByName(masterDev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &MacVtapLink{
|
||||||
|
MacVlanLink: &MacVlanLink{
|
||||||
|
Link: Link{
|
||||||
|
ifc: macVtapIfc,
|
||||||
|
},
|
||||||
|
masterIfc: masterIfc,
|
||||||
|
mode: opts.Mode,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/libcontainer/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NetworkOptions struct {
|
||||||
|
IpAddr string
|
||||||
|
Gw string
|
||||||
|
Routes []netlink.Route
|
||||||
|
}
|
|
@ -0,0 +1,216 @@
|
||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/docker/libcontainer/netlink"
|
||||||
|
"github.com/docker/libcontainer/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VethOptions allows you to specify options for veth link.
|
||||||
|
type VethOptions struct {
|
||||||
|
// Veth pair's peer interface name
|
||||||
|
PeerName string
|
||||||
|
// TX queue length
|
||||||
|
TxQueueLen int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vether embeds Linker interface and adds few more functions mostly to handle peer link interface
|
||||||
|
type Vether interface {
|
||||||
|
// Linker interface
|
||||||
|
Linker
|
||||||
|
// PeerNetInterface returns peer network interface
|
||||||
|
PeerNetInterface() *net.Interface
|
||||||
|
// SetPeerLinkUp sets peer link up - which also brings up the other peer in VethPair
|
||||||
|
SetPeerLinkUp() error
|
||||||
|
// DeletePeerLink deletes peer link - this also deletes the other peer in VethPair
|
||||||
|
DeletePeerLink() error
|
||||||
|
// SetPeerLinkIp configures peer link's IP address
|
||||||
|
SetPeerLinkIp(net.IP, *net.IPNet) error
|
||||||
|
// SetPeerLinkNsToDocker sends peer link into Docker
|
||||||
|
SetPeerLinkNsToDocker(string, string) error
|
||||||
|
// SetPeerLinkNsPid sends peer link into container specified by PID
|
||||||
|
SetPeerLinkNsPid(int) error
|
||||||
|
// SetPeerLinkNsFd sends peer link into container specified by path
|
||||||
|
SetPeerLinkNsFd(string) error
|
||||||
|
// SetPeerLinkNetInNs configures peer link's IP network in network namespace specified by PID
|
||||||
|
SetPeerLinkNetInNs(int, net.IP, *net.IPNet, *net.IP) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// VethPair is a Link. Veth links are created in pairs called peers.
|
||||||
|
type VethPair struct {
|
||||||
|
Link
|
||||||
|
// Peer network interface
|
||||||
|
peerIfc *net.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVethPair creates a pair of veth network links.
|
||||||
|
//
|
||||||
|
// It is equivalent of running:
|
||||||
|
// ip link add name veth${RANDOM STRING} type veth peer name veth${RANDOM STRING}.
|
||||||
|
// NewVethPair returns Vether which is initialized to a pointer of type VethPair if the
|
||||||
|
// veth link was successfully created on Linux host. Newly created pair of veth links
|
||||||
|
// are assigned random names starting with "veth".
|
||||||
|
// NewVethPair returns error if the veth pair could not be created.
|
||||||
|
func NewVethPair() (Vether, error) {
|
||||||
|
ifcName := makeNetInterfaceName("veth")
|
||||||
|
peerName := makeNetInterfaceName("veth")
|
||||||
|
|
||||||
|
if err := netlink.NetworkCreateVethPair(ifcName, peerName, 0); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newIfc, err := net.InterfaceByName(ifcName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
peerIfc, err := net.InterfaceByName(peerName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &VethPair{
|
||||||
|
Link: Link{
|
||||||
|
ifc: newIfc,
|
||||||
|
},
|
||||||
|
peerIfc: peerIfc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVethPairWithOptions creates a pair of veth network links.
|
||||||
|
//
|
||||||
|
// It is equivalent of running:
|
||||||
|
// ip link add name ${first device name} type veth peer name ${second device name}
|
||||||
|
// NewVethPairWithOptions returns Vether which is initialized to a pointer of type VethPair if the
|
||||||
|
// veth link was successfully created on the Linux host. It accepts VethOptions which allow you to set
|
||||||
|
// peer interface name. It returns error if the veth pair could not be created.
|
||||||
|
func NewVethPairWithOptions(ifcName string, opts VethOptions) (Vether, error) {
|
||||||
|
peerName := opts.PeerName
|
||||||
|
txQLen := opts.TxQueueLen
|
||||||
|
|
||||||
|
if ok, err := NetInterfaceNameValid(ifcName); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(ifcName); err == nil {
|
||||||
|
return nil, fmt.Errorf("Interface name %s already assigned on the host", ifcName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if peerName != "" {
|
||||||
|
if ok, err := NetInterfaceNameValid(peerName); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(peerName); err == nil {
|
||||||
|
return nil, fmt.Errorf("Interface name %s already assigned on the host", peerName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
peerName = makeNetInterfaceName("veth")
|
||||||
|
}
|
||||||
|
|
||||||
|
if txQLen < 0 {
|
||||||
|
return nil, fmt.Errorf("TX queue length must be a positive integer: %d", txQLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkCreateVethPair(ifcName, peerName, txQLen); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newIfc, err := net.InterfaceByName(ifcName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
peerIfc, err := net.InterfaceByName(peerName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &VethPair{
|
||||||
|
Link: Link{
|
||||||
|
ifc: newIfc,
|
||||||
|
},
|
||||||
|
peerIfc: peerIfc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetInterface returns veth link's primary network interface
|
||||||
|
func (veth *VethPair) NetInterface() *net.Interface {
|
||||||
|
return veth.ifc
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetInterface returns veth link's peer network interface
|
||||||
|
func (veth *VethPair) PeerNetInterface() *net.Interface {
|
||||||
|
return veth.peerIfc
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPeerLinkUp sets peer link up
|
||||||
|
func (veth *VethPair) SetPeerLinkUp() error {
|
||||||
|
return netlink.NetworkLinkUp(veth.peerIfc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePeerLink deletes peer link. It also deletes the other peer interface in VethPair
|
||||||
|
func (veth *VethPair) DeletePeerLink() error {
|
||||||
|
return netlink.NetworkLinkDel(veth.peerIfc.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPeerLinkIp configures peer link's IP address
|
||||||
|
func (veth *VethPair) SetPeerLinkIp(ip net.IP, nw *net.IPNet) error {
|
||||||
|
return netlink.NetworkLinkAddIp(veth.peerIfc, ip, nw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPeerLinkNsToDocker sends peer link into Docker
|
||||||
|
func (veth *VethPair) SetPeerLinkNsToDocker(name string, dockerHost string) error {
|
||||||
|
pid, err := DockerPidByName(name, dockerHost)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Failed to find docker %s : %s", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return netlink.NetworkSetNsPid(veth.peerIfc, pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPeerLinkNsPid sends peer link into container specified by PID
|
||||||
|
func (veth *VethPair) SetPeerLinkNsPid(nspid int) error {
|
||||||
|
return netlink.NetworkSetNsPid(veth.peerIfc, nspid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPeerLinkNsFd sends peer link into container specified by path
|
||||||
|
func (veth *VethPair) SetPeerLinkNsFd(nspath string) error {
|
||||||
|
fd, err := syscall.Open(nspath, syscall.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Could not attach to Network namespace: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return netlink.NetworkSetNsFd(veth.peerIfc, fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPeerLinkNetInNs configures peer link's IP network in network namespace specified by PID
|
||||||
|
func (veth *VethPair) SetPeerLinkNetInNs(nspid int, ip net.IP, network *net.IPNet, gw *net.IP) error {
|
||||||
|
origNs, _ := NetNsHandle(os.Getpid())
|
||||||
|
defer syscall.Close(int(origNs))
|
||||||
|
defer system.Setns(origNs, syscall.CLONE_NEWNET)
|
||||||
|
|
||||||
|
if err := SetNetNsToPid(nspid); err != nil {
|
||||||
|
return fmt.Errorf("Setting network namespace failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAddIp(veth.peerIfc, ip, network); err != nil {
|
||||||
|
return fmt.Errorf("Unable to set IP: %s in pid: %d network namespace", ip.String(), nspid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkUp(veth.peerIfc); err != nil {
|
||||||
|
return fmt.Errorf("Unable to bring %s interface UP: %d", veth.peerIfc.Name, nspid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gw != nil {
|
||||||
|
if err := netlink.AddDefaultGw(gw.String(), veth.peerIfc.Name); err != nil {
|
||||||
|
return fmt.Errorf("Unable to set Default gateway: %s in pid: %d network namespace", gw.String(), nspid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,182 @@
|
||||||
|
package tenus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/docker/libcontainer/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
// VlanOptions allows you to specify options for vlan link.
|
||||||
|
type VlanOptions struct {
|
||||||
|
// Name of the vlan device
|
||||||
|
Dev string
|
||||||
|
// VLAN tag id
|
||||||
|
Id uint16
|
||||||
|
// MAC address
|
||||||
|
MacAddr string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vlaner is interface which embeds Linker interface and adds few more functions.
|
||||||
|
type Vlaner interface {
|
||||||
|
// Linker interface
|
||||||
|
Linker
|
||||||
|
// MasterNetInterface returns vlan master network interface
|
||||||
|
MasterNetInterface() *net.Interface
|
||||||
|
// Id returns VLAN tag
|
||||||
|
Id() uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// VlanLink is a Link which has a master network device.
|
||||||
|
// Each VlanLink has a VLAN tag id
|
||||||
|
type VlanLink struct {
|
||||||
|
Link
|
||||||
|
// Master device logical network interface
|
||||||
|
masterIfc *net.Interface
|
||||||
|
// VLAN tag
|
||||||
|
id uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVlanLink creates vlan network link.
|
||||||
|
//
|
||||||
|
// It is equivalent of running:
|
||||||
|
// ip link add name vlan${RANDOM STRING} link ${master interface name} type vlan id ${tag}
|
||||||
|
// NewVlanLink returns Vlaner which is initialized to a pointer of type VlanLink if the
|
||||||
|
// vlan link was successfully created on the Linux host. Newly created link is assigned
|
||||||
|
// a random name starting with "vlan". It returns error if the link can not be created.
|
||||||
|
func NewVlanLink(masterDev string, id uint16) (Vlaner, error) {
|
||||||
|
vlanDev := makeNetInterfaceName("vlan")
|
||||||
|
|
||||||
|
if ok, err := NetInterfaceNameValid(masterDev); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(masterDev); err != nil {
|
||||||
|
return nil, fmt.Errorf("Master VLAN device %s does not exist on the host", masterDev)
|
||||||
|
}
|
||||||
|
|
||||||
|
if id <= 0 {
|
||||||
|
return nil, fmt.Errorf("VLAN id must be a postive Integer: %d", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAddVlan(masterDev, vlanDev, id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vlanIfc, err := net.InterfaceByName(vlanDev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
masterIfc, err := net.InterfaceByName(masterDev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &VlanLink{
|
||||||
|
Link: Link{
|
||||||
|
ifc: vlanIfc,
|
||||||
|
},
|
||||||
|
masterIfc: masterIfc,
|
||||||
|
id: id,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVlanLinkWithOptions creates vlan network link and sets some of its network parameters
|
||||||
|
// to values passed in as VlanOptions
|
||||||
|
//
|
||||||
|
// It is equivalent of running:
|
||||||
|
// ip link add name ${vlan name} link ${master interface} address ${macaddress} type vlan id ${tag}
|
||||||
|
// NewVlanLinkWithOptions returns Vlaner which is initialized to a pointer of type VlanLink if the
|
||||||
|
// vlan link was created successfully on the Linux host. It accepts VlanOptions which allow you to set
|
||||||
|
// link's options. It returns error if the link could not be created.
|
||||||
|
func NewVlanLinkWithOptions(masterDev string, opts VlanOptions) (Vlaner, error) {
|
||||||
|
if ok, err := NetInterfaceNameValid(masterDev); !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(masterDev); err != nil {
|
||||||
|
return nil, fmt.Errorf("Master VLAN device %s does not exist on the host", masterDev)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateVlanOptions(&opts); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.NetworkLinkAddVlan(masterDev, opts.Dev, opts.Id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vlanIfc, err := net.InterfaceByName(opts.Dev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.MacAddr != "" {
|
||||||
|
if err := netlink.NetworkSetMacAddress(vlanIfc, opts.MacAddr); err != nil {
|
||||||
|
if errDel := DeleteLink(vlanIfc.Name); errDel != nil {
|
||||||
|
return nil, fmt.Errorf("Incorrect options specified. Attempt to delete the link failed: %s",
|
||||||
|
errDel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hwaddr, err := net.ParseMAC(opts.MacAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vlanIfc.HardwareAddr = hwaddr
|
||||||
|
}
|
||||||
|
|
||||||
|
masterIfc, err := net.InterfaceByName(masterDev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Could not find the new interface: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &VlanLink{
|
||||||
|
Link: Link{
|
||||||
|
ifc: vlanIfc,
|
||||||
|
},
|
||||||
|
masterIfc: masterIfc,
|
||||||
|
id: opts.Id,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetInterface returns vlan link's network interface
|
||||||
|
func (vln *VlanLink) NetInterface() *net.Interface {
|
||||||
|
return vln.ifc
|
||||||
|
}
|
||||||
|
|
||||||
|
// MasterNetInterface returns vlan link's master network interface
|
||||||
|
func (vln *VlanLink) MasterNetInterface() *net.Interface {
|
||||||
|
return vln.masterIfc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Id returns vlan link's vlan tag id
|
||||||
|
func (vln *VlanLink) Id() uint16 {
|
||||||
|
return vln.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateVlanOptions(opts *VlanOptions) error {
|
||||||
|
if opts.Dev != "" {
|
||||||
|
if ok, err := NetInterfaceNameValid(opts.Dev); !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.InterfaceByName(opts.Dev); err == nil {
|
||||||
|
return fmt.Errorf("VLAN device %s already assigned on the host", opts.Dev)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
opts.Dev = makeNetInterfaceName("vlan")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Id <= 0 {
|
||||||
|
return fmt.Errorf("Incorrect VLAN tag specified: %d", opts.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := net.ParseMAC(opts.MacAddr); err != nil {
|
||||||
|
return fmt.Errorf("Incorrect MacAddress specified: %s", opts.MacAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
# This source code refers to The Go Authors for copyright purposes.
|
||||||
|
# The master list of authors is in the main Go distribution,
|
||||||
|
# visible at http://tip.golang.org/AUTHORS.
|
|
@ -0,0 +1,3 @@
|
||||||
|
# This source code was written by the Go contributors.
|
||||||
|
# The master list of contributors is in the main Go distribution,
|
||||||
|
# visible at http://tip.golang.org/CONTRIBUTORS.
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,22 @@
|
||||||
|
Additional IP Rights Grant (Patents)
|
||||||
|
|
||||||
|
"This implementation" means the copyrightable works distributed by
|
||||||
|
Google as part of the Go project.
|
||||||
|
|
||||||
|
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||||
|
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||||
|
patent license to make, have made, use, offer to sell, sell, import,
|
||||||
|
transfer and otherwise run, modify and propagate the contents of this
|
||||||
|
implementation of Go, where such license applies only to those patent
|
||||||
|
claims, both currently owned or controlled by Google and acquired in
|
||||||
|
the future, licensable by Google that are necessarily infringed by this
|
||||||
|
implementation of Go. This grant does not include claims that would be
|
||||||
|
infringed only as a consequence of further modification of this
|
||||||
|
implementation. If you or your agent or exclusive licensee institute or
|
||||||
|
order or agree to the institution of patent litigation against any
|
||||||
|
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||||
|
that this implementation of Go or any code incorporated within this
|
||||||
|
implementation of Go constitutes direct or contributory patent
|
||||||
|
infringement, or inducement of patent infringement, then any patent
|
||||||
|
rights granted to you under this License for this implementation of Go
|
||||||
|
shall terminate as of the date such litigation is filed.
|
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package bpf
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Assemble converts insts into raw instructions suitable for loading
|
||||||
|
// into a BPF virtual machine.
|
||||||
|
//
|
||||||
|
// Currently, no optimization is attempted, the assembled program flow
|
||||||
|
// is exactly as provided.
|
||||||
|
func Assemble(insts []Instruction) ([]RawInstruction, error) {
|
||||||
|
ret := make([]RawInstruction, len(insts))
|
||||||
|
var err error
|
||||||
|
for i, inst := range insts {
|
||||||
|
ret[i], err = inst.Assemble()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("assembling instruction %d: %s", i+1, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disassemble attempts to parse raw back into
|
||||||
|
// Instructions. Unrecognized RawInstructions are assumed to be an
|
||||||
|
// extension not implemented by this package, and are passed through
|
||||||
|
// unchanged to the output. The allDecoded value reports whether insts
|
||||||
|
// contains no RawInstructions.
|
||||||
|
func Disassemble(raw []RawInstruction) (insts []Instruction, allDecoded bool) {
|
||||||
|
insts = make([]Instruction, len(raw))
|
||||||
|
allDecoded = true
|
||||||
|
for i, r := range raw {
|
||||||
|
insts[i] = r.Disassemble()
|
||||||
|
if _, ok := insts[i].(RawInstruction); ok {
|
||||||
|
allDecoded = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return insts, allDecoded
|
||||||
|
}
|
|
@ -0,0 +1,222 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package bpf
|
||||||
|
|
||||||
|
// A Register is a register of the BPF virtual machine.
|
||||||
|
type Register uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RegA is the accumulator register. RegA is always the
|
||||||
|
// destination register of ALU operations.
|
||||||
|
RegA Register = iota
|
||||||
|
// RegX is the indirection register, used by LoadIndirect
|
||||||
|
// operations.
|
||||||
|
RegX
|
||||||
|
)
|
||||||
|
|
||||||
|
// An ALUOp is an arithmetic or logic operation.
|
||||||
|
type ALUOp uint16
|
||||||
|
|
||||||
|
// ALU binary operation types.
|
||||||
|
const (
|
||||||
|
ALUOpAdd ALUOp = iota << 4
|
||||||
|
ALUOpSub
|
||||||
|
ALUOpMul
|
||||||
|
ALUOpDiv
|
||||||
|
ALUOpOr
|
||||||
|
ALUOpAnd
|
||||||
|
ALUOpShiftLeft
|
||||||
|
ALUOpShiftRight
|
||||||
|
aluOpNeg // Not exported because it's the only unary ALU operation, and gets its own instruction type.
|
||||||
|
ALUOpMod
|
||||||
|
ALUOpXor
|
||||||
|
)
|
||||||
|
|
||||||
|
// A JumpTest is a comparison operator used in conditional jumps.
|
||||||
|
type JumpTest uint16
|
||||||
|
|
||||||
|
// Supported operators for conditional jumps.
|
||||||
|
// K can be RegX for JumpIfX
|
||||||
|
const (
|
||||||
|
// K == A
|
||||||
|
JumpEqual JumpTest = iota
|
||||||
|
// K != A
|
||||||
|
JumpNotEqual
|
||||||
|
// K > A
|
||||||
|
JumpGreaterThan
|
||||||
|
// K < A
|
||||||
|
JumpLessThan
|
||||||
|
// K >= A
|
||||||
|
JumpGreaterOrEqual
|
||||||
|
// K <= A
|
||||||
|
JumpLessOrEqual
|
||||||
|
// K & A != 0
|
||||||
|
JumpBitsSet
|
||||||
|
// K & A == 0
|
||||||
|
JumpBitsNotSet
|
||||||
|
)
|
||||||
|
|
||||||
|
// An Extension is a function call provided by the kernel that
|
||||||
|
// performs advanced operations that are expensive or impossible
|
||||||
|
// within the BPF virtual machine.
|
||||||
|
//
|
||||||
|
// Extensions are only implemented by the Linux kernel.
|
||||||
|
//
|
||||||
|
// TODO: should we prune this list? Some of these extensions seem
|
||||||
|
// either broken or near-impossible to use correctly, whereas other
|
||||||
|
// (len, random, ifindex) are quite useful.
|
||||||
|
type Extension int
|
||||||
|
|
||||||
|
// Extension functions available in the Linux kernel.
|
||||||
|
const (
|
||||||
|
// extOffset is the negative maximum number of instructions used
|
||||||
|
// to load instructions by overloading the K argument.
|
||||||
|
extOffset = -0x1000
|
||||||
|
// ExtLen returns the length of the packet.
|
||||||
|
ExtLen Extension = 1
|
||||||
|
// ExtProto returns the packet's L3 protocol type.
|
||||||
|
ExtProto Extension = 0
|
||||||
|
// ExtType returns the packet's type (skb->pkt_type in the kernel)
|
||||||
|
//
|
||||||
|
// TODO: better documentation. How nice an API do we want to
|
||||||
|
// provide for these esoteric extensions?
|
||||||
|
ExtType Extension = 4
|
||||||
|
// ExtPayloadOffset returns the offset of the packet payload, or
|
||||||
|
// the first protocol header that the kernel does not know how to
|
||||||
|
// parse.
|
||||||
|
ExtPayloadOffset Extension = 52
|
||||||
|
// ExtInterfaceIndex returns the index of the interface on which
|
||||||
|
// the packet was received.
|
||||||
|
ExtInterfaceIndex Extension = 8
|
||||||
|
// ExtNetlinkAttr returns the netlink attribute of type X at
|
||||||
|
// offset A.
|
||||||
|
ExtNetlinkAttr Extension = 12
|
||||||
|
// ExtNetlinkAttrNested returns the nested netlink attribute of
|
||||||
|
// type X at offset A.
|
||||||
|
ExtNetlinkAttrNested Extension = 16
|
||||||
|
// ExtMark returns the packet's mark value.
|
||||||
|
ExtMark Extension = 20
|
||||||
|
// ExtQueue returns the packet's assigned hardware queue.
|
||||||
|
ExtQueue Extension = 24
|
||||||
|
// ExtLinkLayerType returns the packet's hardware address type
|
||||||
|
// (e.g. Ethernet, Infiniband).
|
||||||
|
ExtLinkLayerType Extension = 28
|
||||||
|
// ExtRXHash returns the packets receive hash.
|
||||||
|
//
|
||||||
|
// TODO: figure out what this rxhash actually is.
|
||||||
|
ExtRXHash Extension = 32
|
||||||
|
// ExtCPUID returns the ID of the CPU processing the current
|
||||||
|
// packet.
|
||||||
|
ExtCPUID Extension = 36
|
||||||
|
// ExtVLANTag returns the packet's VLAN tag.
|
||||||
|
ExtVLANTag Extension = 44
|
||||||
|
// ExtVLANTagPresent returns non-zero if the packet has a VLAN
|
||||||
|
// tag.
|
||||||
|
//
|
||||||
|
// TODO: I think this might be a lie: it reads bit 0x1000 of the
|
||||||
|
// VLAN header, which changed meaning in recent revisions of the
|
||||||
|
// spec - this extension may now return meaningless information.
|
||||||
|
ExtVLANTagPresent Extension = 48
|
||||||
|
// ExtVLANProto returns 0x8100 if the frame has a VLAN header,
|
||||||
|
// 0x88a8 if the frame has a "Q-in-Q" double VLAN header, or some
|
||||||
|
// other value if no VLAN information is present.
|
||||||
|
ExtVLANProto Extension = 60
|
||||||
|
// ExtRand returns a uniformly random uint32.
|
||||||
|
ExtRand Extension = 56
|
||||||
|
)
|
||||||
|
|
||||||
|
// The following gives names to various bit patterns used in opcode construction.
|
||||||
|
|
||||||
|
const (
|
||||||
|
opMaskCls uint16 = 0x7
|
||||||
|
// opClsLoad masks
|
||||||
|
opMaskLoadDest = 0x01
|
||||||
|
opMaskLoadWidth = 0x18
|
||||||
|
opMaskLoadMode = 0xe0
|
||||||
|
// opClsALU & opClsJump
|
||||||
|
opMaskOperand = 0x08
|
||||||
|
opMaskOperator = 0xf0
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// +---------------+-----------------+---+---+---+
|
||||||
|
// | AddrMode (3b) | LoadWidth (2b) | 0 | 0 | 0 |
|
||||||
|
// +---------------+-----------------+---+---+---+
|
||||||
|
opClsLoadA uint16 = iota
|
||||||
|
// +---------------+-----------------+---+---+---+
|
||||||
|
// | AddrMode (3b) | LoadWidth (2b) | 0 | 0 | 1 |
|
||||||
|
// +---------------+-----------------+---+---+---+
|
||||||
|
opClsLoadX
|
||||||
|
// +---+---+---+---+---+---+---+---+
|
||||||
|
// | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
|
||||||
|
// +---+---+---+---+---+---+---+---+
|
||||||
|
opClsStoreA
|
||||||
|
// +---+---+---+---+---+---+---+---+
|
||||||
|
// | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
|
||||||
|
// +---+---+---+---+---+---+---+---+
|
||||||
|
opClsStoreX
|
||||||
|
// +---------------+-----------------+---+---+---+
|
||||||
|
// | Operator (4b) | OperandSrc (1b) | 1 | 0 | 0 |
|
||||||
|
// +---------------+-----------------+---+---+---+
|
||||||
|
opClsALU
|
||||||
|
// +-----------------------------+---+---+---+---+
|
||||||
|
// | TestOperator (4b) | 0 | 1 | 0 | 1 |
|
||||||
|
// +-----------------------------+---+---+---+---+
|
||||||
|
opClsJump
|
||||||
|
// +---+-------------------------+---+---+---+---+
|
||||||
|
// | 0 | 0 | 0 | RetSrc (1b) | 0 | 1 | 1 | 0 |
|
||||||
|
// +---+-------------------------+---+---+---+---+
|
||||||
|
opClsReturn
|
||||||
|
// +---+-------------------------+---+---+---+---+
|
||||||
|
// | 0 | 0 | 0 | TXAorTAX (1b) | 0 | 1 | 1 | 1 |
|
||||||
|
// +---+-------------------------+---+---+---+---+
|
||||||
|
opClsMisc
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
opAddrModeImmediate uint16 = iota << 5
|
||||||
|
opAddrModeAbsolute
|
||||||
|
opAddrModeIndirect
|
||||||
|
opAddrModeScratch
|
||||||
|
opAddrModePacketLen // actually an extension, not an addressing mode.
|
||||||
|
opAddrModeMemShift
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
opLoadWidth4 uint16 = iota << 3
|
||||||
|
opLoadWidth2
|
||||||
|
opLoadWidth1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Operand for ALU and Jump instructions
|
||||||
|
type opOperand uint16
|
||||||
|
|
||||||
|
// Supported operand sources.
|
||||||
|
const (
|
||||||
|
opOperandConstant opOperand = iota << 3
|
||||||
|
opOperandX
|
||||||
|
)
|
||||||
|
|
||||||
|
// An jumpOp is a conditional jump condition.
|
||||||
|
type jumpOp uint16
|
||||||
|
|
||||||
|
// Supported jump conditions.
|
||||||
|
const (
|
||||||
|
opJumpAlways jumpOp = iota << 4
|
||||||
|
opJumpEqual
|
||||||
|
opJumpGT
|
||||||
|
opJumpGE
|
||||||
|
opJumpSet
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
opRetSrcConstant uint16 = iota << 4
|
||||||
|
opRetSrcA
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
opMiscTAX = 0x00
|
||||||
|
opMiscTXA = 0x80
|
||||||
|
)
|
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Package bpf implements marshaling and unmarshaling of programs for the
|
||||||
|
Berkeley Packet Filter virtual machine, and provides a Go implementation
|
||||||
|
of the virtual machine.
|
||||||
|
|
||||||
|
BPF's main use is to specify a packet filter for network taps, so that
|
||||||
|
the kernel doesn't have to expensively copy every packet it sees to
|
||||||
|
userspace. However, it's been repurposed to other areas where running
|
||||||
|
user code in-kernel is needed. For example, Linux's seccomp uses BPF
|
||||||
|
to apply security policies to system calls. For simplicity, this
|
||||||
|
documentation refers only to packets, but other uses of BPF have their
|
||||||
|
own data payloads.
|
||||||
|
|
||||||
|
BPF programs run in a restricted virtual machine. It has almost no
|
||||||
|
access to kernel functions, and while conditional branches are
|
||||||
|
allowed, they can only jump forwards, to guarantee that there are no
|
||||||
|
infinite loops.
|
||||||
|
|
||||||
|
The virtual machine
|
||||||
|
|
||||||
|
The BPF VM is an accumulator machine. Its main register, called
|
||||||
|
register A, is an implicit source and destination in all arithmetic
|
||||||
|
and logic operations. The machine also has 16 scratch registers for
|
||||||
|
temporary storage, and an indirection register (register X) for
|
||||||
|
indirect memory access. All registers are 32 bits wide.
|
||||||
|
|
||||||
|
Each run of a BPF program is given one packet, which is placed in the
|
||||||
|
VM's read-only "main memory". LoadAbsolute and LoadIndirect
|
||||||
|
instructions can fetch up to 32 bits at a time into register A for
|
||||||
|
examination.
|
||||||
|
|
||||||
|
The goal of a BPF program is to produce and return a verdict (uint32),
|
||||||
|
which tells the kernel what to do with the packet. In the context of
|
||||||
|
packet filtering, the returned value is the number of bytes of the
|
||||||
|
packet to forward to userspace, or 0 to ignore the packet. Other
|
||||||
|
contexts like seccomp define their own return values.
|
||||||
|
|
||||||
|
In order to simplify programs, attempts to read past the end of the
|
||||||
|
packet terminate the program execution with a verdict of 0 (ignore
|
||||||
|
packet). This means that the vast majority of BPF programs don't need
|
||||||
|
to do any explicit bounds checking.
|
||||||
|
|
||||||
|
In addition to the bytes of the packet, some BPF programs have access
|
||||||
|
to extensions, which are essentially calls to kernel utility
|
||||||
|
functions. Currently, the only extensions supported by this package
|
||||||
|
are the Linux packet filter extensions.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
|
||||||
|
This packet filter selects all ARP packets.
|
||||||
|
|
||||||
|
bpf.Assemble([]bpf.Instruction{
|
||||||
|
// Load "EtherType" field from the ethernet header.
|
||||||
|
bpf.LoadAbsolute{Off: 12, Size: 2},
|
||||||
|
// Skip over the next instruction if EtherType is not ARP.
|
||||||
|
bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 0x0806, SkipTrue: 1},
|
||||||
|
// Verdict is "send up to 4k of the packet to userspace."
|
||||||
|
bpf.RetConstant{Val: 4096},
|
||||||
|
// Verdict is "ignore packet."
|
||||||
|
bpf.RetConstant{Val: 0},
|
||||||
|
})
|
||||||
|
|
||||||
|
This packet filter captures a random 1% sample of traffic.
|
||||||
|
|
||||||
|
bpf.Assemble([]bpf.Instruction{
|
||||||
|
// Get a 32-bit random number from the Linux kernel.
|
||||||
|
bpf.LoadExtension{Num: bpf.ExtRand},
|
||||||
|
// 1% dice roll?
|
||||||
|
bpf.JumpIf{Cond: bpf.JumpLessThan, Val: 2^32/100, SkipFalse: 1},
|
||||||
|
// Capture.
|
||||||
|
bpf.RetConstant{Val: 4096},
|
||||||
|
// Ignore.
|
||||||
|
bpf.RetConstant{Val: 0},
|
||||||
|
})
|
||||||
|
|
||||||
|
*/
|
||||||
|
package bpf // import "golang.org/x/net/bpf"
|
|
@ -0,0 +1,726 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package bpf
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// An Instruction is one instruction executed by the BPF virtual
|
||||||
|
// machine.
|
||||||
|
type Instruction interface {
|
||||||
|
// Assemble assembles the Instruction into a RawInstruction.
|
||||||
|
Assemble() (RawInstruction, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A RawInstruction is a raw BPF virtual machine instruction.
|
||||||
|
type RawInstruction struct {
|
||||||
|
// Operation to execute.
|
||||||
|
Op uint16
|
||||||
|
// For conditional jump instructions, the number of instructions
|
||||||
|
// to skip if the condition is true/false.
|
||||||
|
Jt uint8
|
||||||
|
Jf uint8
|
||||||
|
// Constant parameter. The meaning depends on the Op.
|
||||||
|
K uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (ri RawInstruction) Assemble() (RawInstruction, error) { return ri, nil }
|
||||||
|
|
||||||
|
// Disassemble parses ri into an Instruction and returns it. If ri is
|
||||||
|
// not recognized by this package, ri itself is returned.
|
||||||
|
func (ri RawInstruction) Disassemble() Instruction {
|
||||||
|
switch ri.Op & opMaskCls {
|
||||||
|
case opClsLoadA, opClsLoadX:
|
||||||
|
reg := Register(ri.Op & opMaskLoadDest)
|
||||||
|
sz := 0
|
||||||
|
switch ri.Op & opMaskLoadWidth {
|
||||||
|
case opLoadWidth4:
|
||||||
|
sz = 4
|
||||||
|
case opLoadWidth2:
|
||||||
|
sz = 2
|
||||||
|
case opLoadWidth1:
|
||||||
|
sz = 1
|
||||||
|
default:
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
switch ri.Op & opMaskLoadMode {
|
||||||
|
case opAddrModeImmediate:
|
||||||
|
if sz != 4 {
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
return LoadConstant{Dst: reg, Val: ri.K}
|
||||||
|
case opAddrModeScratch:
|
||||||
|
if sz != 4 || ri.K > 15 {
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
return LoadScratch{Dst: reg, N: int(ri.K)}
|
||||||
|
case opAddrModeAbsolute:
|
||||||
|
if ri.K > extOffset+0xffffffff {
|
||||||
|
return LoadExtension{Num: Extension(-extOffset + ri.K)}
|
||||||
|
}
|
||||||
|
return LoadAbsolute{Size: sz, Off: ri.K}
|
||||||
|
case opAddrModeIndirect:
|
||||||
|
return LoadIndirect{Size: sz, Off: ri.K}
|
||||||
|
case opAddrModePacketLen:
|
||||||
|
if sz != 4 {
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
return LoadExtension{Num: ExtLen}
|
||||||
|
case opAddrModeMemShift:
|
||||||
|
return LoadMemShift{Off: ri.K}
|
||||||
|
default:
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
|
||||||
|
case opClsStoreA:
|
||||||
|
if ri.Op != opClsStoreA || ri.K > 15 {
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
return StoreScratch{Src: RegA, N: int(ri.K)}
|
||||||
|
|
||||||
|
case opClsStoreX:
|
||||||
|
if ri.Op != opClsStoreX || ri.K > 15 {
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
return StoreScratch{Src: RegX, N: int(ri.K)}
|
||||||
|
|
||||||
|
case opClsALU:
|
||||||
|
switch op := ALUOp(ri.Op & opMaskOperator); op {
|
||||||
|
case ALUOpAdd, ALUOpSub, ALUOpMul, ALUOpDiv, ALUOpOr, ALUOpAnd, ALUOpShiftLeft, ALUOpShiftRight, ALUOpMod, ALUOpXor:
|
||||||
|
switch operand := opOperand(ri.Op & opMaskOperand); operand {
|
||||||
|
case opOperandX:
|
||||||
|
return ALUOpX{Op: op}
|
||||||
|
case opOperandConstant:
|
||||||
|
return ALUOpConstant{Op: op, Val: ri.K}
|
||||||
|
default:
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
case aluOpNeg:
|
||||||
|
return NegateA{}
|
||||||
|
default:
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
|
||||||
|
case opClsJump:
|
||||||
|
switch op := jumpOp(ri.Op & opMaskOperator); op {
|
||||||
|
case opJumpAlways:
|
||||||
|
return Jump{Skip: ri.K}
|
||||||
|
case opJumpEqual, opJumpGT, opJumpGE, opJumpSet:
|
||||||
|
cond, skipTrue, skipFalse := jumpOpToTest(op, ri.Jt, ri.Jf)
|
||||||
|
switch operand := opOperand(ri.Op & opMaskOperand); operand {
|
||||||
|
case opOperandX:
|
||||||
|
return JumpIfX{Cond: cond, SkipTrue: skipTrue, SkipFalse: skipFalse}
|
||||||
|
case opOperandConstant:
|
||||||
|
return JumpIf{Cond: cond, Val: ri.K, SkipTrue: skipTrue, SkipFalse: skipFalse}
|
||||||
|
default:
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
|
||||||
|
case opClsReturn:
|
||||||
|
switch ri.Op {
|
||||||
|
case opClsReturn | opRetSrcA:
|
||||||
|
return RetA{}
|
||||||
|
case opClsReturn | opRetSrcConstant:
|
||||||
|
return RetConstant{Val: ri.K}
|
||||||
|
default:
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
|
||||||
|
case opClsMisc:
|
||||||
|
switch ri.Op {
|
||||||
|
case opClsMisc | opMiscTAX:
|
||||||
|
return TAX{}
|
||||||
|
case opClsMisc | opMiscTXA:
|
||||||
|
return TXA{}
|
||||||
|
default:
|
||||||
|
return ri
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("unreachable") // switch is exhaustive on the bit pattern
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func jumpOpToTest(op jumpOp, skipTrue uint8, skipFalse uint8) (JumpTest, uint8, uint8) {
|
||||||
|
var test JumpTest
|
||||||
|
|
||||||
|
// Decode "fake" jump conditions that don't appear in machine code
|
||||||
|
// Ensures the Assemble -> Disassemble stage recreates the same instructions
|
||||||
|
// See https://github.com/golang/go/issues/18470
|
||||||
|
if skipTrue == 0 {
|
||||||
|
switch op {
|
||||||
|
case opJumpEqual:
|
||||||
|
test = JumpNotEqual
|
||||||
|
case opJumpGT:
|
||||||
|
test = JumpLessOrEqual
|
||||||
|
case opJumpGE:
|
||||||
|
test = JumpLessThan
|
||||||
|
case opJumpSet:
|
||||||
|
test = JumpBitsNotSet
|
||||||
|
}
|
||||||
|
|
||||||
|
return test, skipFalse, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
switch op {
|
||||||
|
case opJumpEqual:
|
||||||
|
test = JumpEqual
|
||||||
|
case opJumpGT:
|
||||||
|
test = JumpGreaterThan
|
||||||
|
case opJumpGE:
|
||||||
|
test = JumpGreaterOrEqual
|
||||||
|
case opJumpSet:
|
||||||
|
test = JumpBitsSet
|
||||||
|
}
|
||||||
|
|
||||||
|
return test, skipTrue, skipFalse
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadConstant loads Val into register Dst.
|
||||||
|
type LoadConstant struct {
|
||||||
|
Dst Register
|
||||||
|
Val uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a LoadConstant) Assemble() (RawInstruction, error) {
|
||||||
|
return assembleLoad(a.Dst, 4, opAddrModeImmediate, a.Val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the instruction in assembler notation.
|
||||||
|
func (a LoadConstant) String() string {
|
||||||
|
switch a.Dst {
|
||||||
|
case RegA:
|
||||||
|
return fmt.Sprintf("ld #%d", a.Val)
|
||||||
|
case RegX:
|
||||||
|
return fmt.Sprintf("ldx #%d", a.Val)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadScratch loads scratch[N] into register Dst.
|
||||||
|
type LoadScratch struct {
|
||||||
|
Dst Register
|
||||||
|
N int // 0-15
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a LoadScratch) Assemble() (RawInstruction, error) {
|
||||||
|
if a.N < 0 || a.N > 15 {
|
||||||
|
return RawInstruction{}, fmt.Errorf("invalid scratch slot %d", a.N)
|
||||||
|
}
|
||||||
|
return assembleLoad(a.Dst, 4, opAddrModeScratch, uint32(a.N))
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the instruction in assembler notation.
|
||||||
|
func (a LoadScratch) String() string {
|
||||||
|
switch a.Dst {
|
||||||
|
case RegA:
|
||||||
|
return fmt.Sprintf("ld M[%d]", a.N)
|
||||||
|
case RegX:
|
||||||
|
return fmt.Sprintf("ldx M[%d]", a.N)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAbsolute loads packet[Off:Off+Size] as an integer value into
|
||||||
|
// register A.
|
||||||
|
type LoadAbsolute struct {
|
||||||
|
Off uint32
|
||||||
|
Size int // 1, 2 or 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a LoadAbsolute) Assemble() (RawInstruction, error) {
|
||||||
|
return assembleLoad(RegA, a.Size, opAddrModeAbsolute, a.Off)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the instruction in assembler notation.
|
||||||
|
func (a LoadAbsolute) String() string {
|
||||||
|
switch a.Size {
|
||||||
|
case 1: // byte
|
||||||
|
return fmt.Sprintf("ldb [%d]", a.Off)
|
||||||
|
case 2: // half word
|
||||||
|
return fmt.Sprintf("ldh [%d]", a.Off)
|
||||||
|
case 4: // word
|
||||||
|
if a.Off > extOffset+0xffffffff {
|
||||||
|
return LoadExtension{Num: Extension(a.Off + 0x1000)}.String()
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("ld [%d]", a.Off)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadIndirect loads packet[X+Off:X+Off+Size] as an integer value
|
||||||
|
// into register A.
|
||||||
|
type LoadIndirect struct {
|
||||||
|
Off uint32
|
||||||
|
Size int // 1, 2 or 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a LoadIndirect) Assemble() (RawInstruction, error) {
|
||||||
|
return assembleLoad(RegA, a.Size, opAddrModeIndirect, a.Off)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the instruction in assembler notation.
|
||||||
|
func (a LoadIndirect) String() string {
|
||||||
|
switch a.Size {
|
||||||
|
case 1: // byte
|
||||||
|
return fmt.Sprintf("ldb [x + %d]", a.Off)
|
||||||
|
case 2: // half word
|
||||||
|
return fmt.Sprintf("ldh [x + %d]", a.Off)
|
||||||
|
case 4: // word
|
||||||
|
return fmt.Sprintf("ld [x + %d]", a.Off)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadMemShift multiplies the first 4 bits of the byte at packet[Off]
|
||||||
|
// by 4 and stores the result in register X.
|
||||||
|
//
|
||||||
|
// This instruction is mainly useful to load into X the length of an
|
||||||
|
// IPv4 packet header in a single instruction, rather than have to do
|
||||||
|
// the arithmetic on the header's first byte by hand.
|
||||||
|
type LoadMemShift struct {
|
||||||
|
Off uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a LoadMemShift) Assemble() (RawInstruction, error) {
|
||||||
|
return assembleLoad(RegX, 1, opAddrModeMemShift, a.Off)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the instruction in assembler notation.
|
||||||
|
func (a LoadMemShift) String() string {
|
||||||
|
return fmt.Sprintf("ldx 4*([%d]&0xf)", a.Off)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadExtension invokes a linux-specific extension and stores the
|
||||||
|
// result in register A.
|
||||||
|
type LoadExtension struct {
|
||||||
|
Num Extension
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a LoadExtension) Assemble() (RawInstruction, error) {
|
||||||
|
if a.Num == ExtLen {
|
||||||
|
return assembleLoad(RegA, 4, opAddrModePacketLen, 0)
|
||||||
|
}
|
||||||
|
return assembleLoad(RegA, 4, opAddrModeAbsolute, uint32(extOffset+a.Num))
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the instruction in assembler notation.
|
||||||
|
func (a LoadExtension) String() string {
|
||||||
|
switch a.Num {
|
||||||
|
case ExtLen:
|
||||||
|
return "ld #len"
|
||||||
|
case ExtProto:
|
||||||
|
return "ld #proto"
|
||||||
|
case ExtType:
|
||||||
|
return "ld #type"
|
||||||
|
case ExtPayloadOffset:
|
||||||
|
return "ld #poff"
|
||||||
|
case ExtInterfaceIndex:
|
||||||
|
return "ld #ifidx"
|
||||||
|
case ExtNetlinkAttr:
|
||||||
|
return "ld #nla"
|
||||||
|
case ExtNetlinkAttrNested:
|
||||||
|
return "ld #nlan"
|
||||||
|
case ExtMark:
|
||||||
|
return "ld #mark"
|
||||||
|
case ExtQueue:
|
||||||
|
return "ld #queue"
|
||||||
|
case ExtLinkLayerType:
|
||||||
|
return "ld #hatype"
|
||||||
|
case ExtRXHash:
|
||||||
|
return "ld #rxhash"
|
||||||
|
case ExtCPUID:
|
||||||
|
return "ld #cpu"
|
||||||
|
case ExtVLANTag:
|
||||||
|
return "ld #vlan_tci"
|
||||||
|
case ExtVLANTagPresent:
|
||||||
|
return "ld #vlan_avail"
|
||||||
|
case ExtVLANProto:
|
||||||
|
return "ld #vlan_tpid"
|
||||||
|
case ExtRand:
|
||||||
|
return "ld #rand"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreScratch stores register Src into scratch[N].
|
||||||
|
type StoreScratch struct {
|
||||||
|
Src Register
|
||||||
|
N int // 0-15
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a StoreScratch) Assemble() (RawInstruction, error) {
|
||||||
|
if a.N < 0 || a.N > 15 {
|
||||||
|
return RawInstruction{}, fmt.Errorf("invalid scratch slot %d", a.N)
|
||||||
|
}
|
||||||
|
var op uint16
|
||||||
|
switch a.Src {
|
||||||
|
case RegA:
|
||||||
|
op = opClsStoreA
|
||||||
|
case RegX:
|
||||||
|
op = opClsStoreX
|
||||||
|
default:
|
||||||
|
return RawInstruction{}, fmt.Errorf("invalid source register %v", a.Src)
|
||||||
|
}
|
||||||
|
|
||||||
|
return RawInstruction{
|
||||||
|
Op: op,
|
||||||
|
K: uint32(a.N),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the instruction in assembler notation.
|
||||||
|
func (a StoreScratch) String() string {
|
||||||
|
switch a.Src {
|
||||||
|
case RegA:
|
||||||
|
return fmt.Sprintf("st M[%d]", a.N)
|
||||||
|
case RegX:
|
||||||
|
return fmt.Sprintf("stx M[%d]", a.N)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ALUOpConstant executes A = A <Op> Val.
|
||||||
|
type ALUOpConstant struct {
|
||||||
|
Op ALUOp
|
||||||
|
Val uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a ALUOpConstant) Assemble() (RawInstruction, error) {
|
||||||
|
return RawInstruction{
|
||||||
|
Op: opClsALU | uint16(opOperandConstant) | uint16(a.Op),
|
||||||
|
K: a.Val,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the instruction in assembler notation.
|
||||||
|
func (a ALUOpConstant) String() string {
|
||||||
|
switch a.Op {
|
||||||
|
case ALUOpAdd:
|
||||||
|
return fmt.Sprintf("add #%d", a.Val)
|
||||||
|
case ALUOpSub:
|
||||||
|
return fmt.Sprintf("sub #%d", a.Val)
|
||||||
|
case ALUOpMul:
|
||||||
|
return fmt.Sprintf("mul #%d", a.Val)
|
||||||
|
case ALUOpDiv:
|
||||||
|
return fmt.Sprintf("div #%d", a.Val)
|
||||||
|
case ALUOpMod:
|
||||||
|
return fmt.Sprintf("mod #%d", a.Val)
|
||||||
|
case ALUOpAnd:
|
||||||
|
return fmt.Sprintf("and #%d", a.Val)
|
||||||
|
case ALUOpOr:
|
||||||
|
return fmt.Sprintf("or #%d", a.Val)
|
||||||
|
case ALUOpXor:
|
||||||
|
return fmt.Sprintf("xor #%d", a.Val)
|
||||||
|
case ALUOpShiftLeft:
|
||||||
|
return fmt.Sprintf("lsh #%d", a.Val)
|
||||||
|
case ALUOpShiftRight:
|
||||||
|
return fmt.Sprintf("rsh #%d", a.Val)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ALUOpX executes A = A <Op> X
|
||||||
|
type ALUOpX struct {
|
||||||
|
Op ALUOp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a ALUOpX) Assemble() (RawInstruction, error) {
|
||||||
|
return RawInstruction{
|
||||||
|
Op: opClsALU | uint16(opOperandX) | uint16(a.Op),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the instruction in assembler notation.
|
||||||
|
func (a ALUOpX) String() string {
|
||||||
|
switch a.Op {
|
||||||
|
case ALUOpAdd:
|
||||||
|
return "add x"
|
||||||
|
case ALUOpSub:
|
||||||
|
return "sub x"
|
||||||
|
case ALUOpMul:
|
||||||
|
return "mul x"
|
||||||
|
case ALUOpDiv:
|
||||||
|
return "div x"
|
||||||
|
case ALUOpMod:
|
||||||
|
return "mod x"
|
||||||
|
case ALUOpAnd:
|
||||||
|
return "and x"
|
||||||
|
case ALUOpOr:
|
||||||
|
return "or x"
|
||||||
|
case ALUOpXor:
|
||||||
|
return "xor x"
|
||||||
|
case ALUOpShiftLeft:
|
||||||
|
return "lsh x"
|
||||||
|
case ALUOpShiftRight:
|
||||||
|
return "rsh x"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NegateA executes A = -A.
|
||||||
|
type NegateA struct{}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a NegateA) Assemble() (RawInstruction, error) {
|
||||||
|
return RawInstruction{
|
||||||
|
Op: opClsALU | uint16(aluOpNeg),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the instruction in assembler notation.
|
||||||
|
func (a NegateA) String() string {
|
||||||
|
return fmt.Sprintf("neg")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jump skips the following Skip instructions in the program.
|
||||||
|
type Jump struct {
|
||||||
|
Skip uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a Jump) Assemble() (RawInstruction, error) {
|
||||||
|
return RawInstruction{
|
||||||
|
Op: opClsJump | uint16(opJumpAlways),
|
||||||
|
K: a.Skip,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the instruction in assembler notation.
|
||||||
|
func (a Jump) String() string {
|
||||||
|
return fmt.Sprintf("ja %d", a.Skip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JumpIf skips the following Skip instructions in the program if A
|
||||||
|
// <Cond> Val is true.
|
||||||
|
type JumpIf struct {
|
||||||
|
Cond JumpTest
|
||||||
|
Val uint32
|
||||||
|
SkipTrue uint8
|
||||||
|
SkipFalse uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a JumpIf) Assemble() (RawInstruction, error) {
|
||||||
|
return jumpToRaw(a.Cond, opOperandConstant, a.Val, a.SkipTrue, a.SkipFalse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the instruction in assembler notation.
|
||||||
|
func (a JumpIf) String() string {
|
||||||
|
return jumpToString(a.Cond, fmt.Sprintf("#%d", a.Val), a.SkipTrue, a.SkipFalse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JumpIfX skips the following Skip instructions in the program if A
|
||||||
|
// <Cond> X is true.
|
||||||
|
type JumpIfX struct {
|
||||||
|
Cond JumpTest
|
||||||
|
SkipTrue uint8
|
||||||
|
SkipFalse uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a JumpIfX) Assemble() (RawInstruction, error) {
|
||||||
|
return jumpToRaw(a.Cond, opOperandX, 0, a.SkipTrue, a.SkipFalse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the instruction in assembler notation.
|
||||||
|
func (a JumpIfX) String() string {
|
||||||
|
return jumpToString(a.Cond, "x", a.SkipTrue, a.SkipFalse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// jumpToRaw assembles a jump instruction into a RawInstruction
|
||||||
|
func jumpToRaw(test JumpTest, operand opOperand, k uint32, skipTrue, skipFalse uint8) (RawInstruction, error) {
|
||||||
|
var (
|
||||||
|
cond jumpOp
|
||||||
|
flip bool
|
||||||
|
)
|
||||||
|
switch test {
|
||||||
|
case JumpEqual:
|
||||||
|
cond = opJumpEqual
|
||||||
|
case JumpNotEqual:
|
||||||
|
cond, flip = opJumpEqual, true
|
||||||
|
case JumpGreaterThan:
|
||||||
|
cond = opJumpGT
|
||||||
|
case JumpLessThan:
|
||||||
|
cond, flip = opJumpGE, true
|
||||||
|
case JumpGreaterOrEqual:
|
||||||
|
cond = opJumpGE
|
||||||
|
case JumpLessOrEqual:
|
||||||
|
cond, flip = opJumpGT, true
|
||||||
|
case JumpBitsSet:
|
||||||
|
cond = opJumpSet
|
||||||
|
case JumpBitsNotSet:
|
||||||
|
cond, flip = opJumpSet, true
|
||||||
|
default:
|
||||||
|
return RawInstruction{}, fmt.Errorf("unknown JumpTest %v", test)
|
||||||
|
}
|
||||||
|
jt, jf := skipTrue, skipFalse
|
||||||
|
if flip {
|
||||||
|
jt, jf = jf, jt
|
||||||
|
}
|
||||||
|
return RawInstruction{
|
||||||
|
Op: opClsJump | uint16(cond) | uint16(operand),
|
||||||
|
Jt: jt,
|
||||||
|
Jf: jf,
|
||||||
|
K: k,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// jumpToString converts a jump instruction to assembler notation
|
||||||
|
func jumpToString(cond JumpTest, operand string, skipTrue, skipFalse uint8) string {
|
||||||
|
switch cond {
|
||||||
|
// K == A
|
||||||
|
case JumpEqual:
|
||||||
|
return conditionalJump(operand, skipTrue, skipFalse, "jeq", "jneq")
|
||||||
|
// K != A
|
||||||
|
case JumpNotEqual:
|
||||||
|
return fmt.Sprintf("jneq %s,%d", operand, skipTrue)
|
||||||
|
// K > A
|
||||||
|
case JumpGreaterThan:
|
||||||
|
return conditionalJump(operand, skipTrue, skipFalse, "jgt", "jle")
|
||||||
|
// K < A
|
||||||
|
case JumpLessThan:
|
||||||
|
return fmt.Sprintf("jlt %s,%d", operand, skipTrue)
|
||||||
|
// K >= A
|
||||||
|
case JumpGreaterOrEqual:
|
||||||
|
return conditionalJump(operand, skipTrue, skipFalse, "jge", "jlt")
|
||||||
|
// K <= A
|
||||||
|
case JumpLessOrEqual:
|
||||||
|
return fmt.Sprintf("jle %s,%d", operand, skipTrue)
|
||||||
|
// K & A != 0
|
||||||
|
case JumpBitsSet:
|
||||||
|
if skipFalse > 0 {
|
||||||
|
return fmt.Sprintf("jset %s,%d,%d", operand, skipTrue, skipFalse)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("jset %s,%d", operand, skipTrue)
|
||||||
|
// K & A == 0, there is no assembler instruction for JumpBitNotSet, use JumpBitSet and invert skips
|
||||||
|
case JumpBitsNotSet:
|
||||||
|
return jumpToString(JumpBitsSet, operand, skipFalse, skipTrue)
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("unknown JumpTest %#v", cond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func conditionalJump(operand string, skipTrue, skipFalse uint8, positiveJump, negativeJump string) string {
|
||||||
|
if skipTrue > 0 {
|
||||||
|
if skipFalse > 0 {
|
||||||
|
return fmt.Sprintf("%s %s,%d,%d", positiveJump, operand, skipTrue, skipFalse)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s %s,%d", positiveJump, operand, skipTrue)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s %s,%d", negativeJump, operand, skipFalse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetA exits the BPF program, returning the value of register A.
|
||||||
|
type RetA struct{}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a RetA) Assemble() (RawInstruction, error) {
|
||||||
|
return RawInstruction{
|
||||||
|
Op: opClsReturn | opRetSrcA,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the instruction in assembler notation.
|
||||||
|
func (a RetA) String() string {
|
||||||
|
return fmt.Sprintf("ret a")
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetConstant exits the BPF program, returning a constant value.
|
||||||
|
type RetConstant struct {
|
||||||
|
Val uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a RetConstant) Assemble() (RawInstruction, error) {
|
||||||
|
return RawInstruction{
|
||||||
|
Op: opClsReturn | opRetSrcConstant,
|
||||||
|
K: a.Val,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the instruction in assembler notation.
|
||||||
|
func (a RetConstant) String() string {
|
||||||
|
return fmt.Sprintf("ret #%d", a.Val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TXA copies the value of register X to register A.
|
||||||
|
type TXA struct{}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a TXA) Assemble() (RawInstruction, error) {
|
||||||
|
return RawInstruction{
|
||||||
|
Op: opClsMisc | opMiscTXA,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the instruction in assembler notation.
|
||||||
|
func (a TXA) String() string {
|
||||||
|
return fmt.Sprintf("txa")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TAX copies the value of register A to register X.
|
||||||
|
type TAX struct{}
|
||||||
|
|
||||||
|
// Assemble implements the Instruction Assemble method.
|
||||||
|
func (a TAX) Assemble() (RawInstruction, error) {
|
||||||
|
return RawInstruction{
|
||||||
|
Op: opClsMisc | opMiscTAX,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the instruction in assembler notation.
|
||||||
|
func (a TAX) String() string {
|
||||||
|
return fmt.Sprintf("tax")
|
||||||
|
}
|
||||||
|
|
||||||
|
func assembleLoad(dst Register, loadSize int, mode uint16, k uint32) (RawInstruction, error) {
|
||||||
|
var (
|
||||||
|
cls uint16
|
||||||
|
sz uint16
|
||||||
|
)
|
||||||
|
switch dst {
|
||||||
|
case RegA:
|
||||||
|
cls = opClsLoadA
|
||||||
|
case RegX:
|
||||||
|
cls = opClsLoadX
|
||||||
|
default:
|
||||||
|
return RawInstruction{}, fmt.Errorf("invalid target register %v", dst)
|
||||||
|
}
|
||||||
|
switch loadSize {
|
||||||
|
case 1:
|
||||||
|
sz = opLoadWidth1
|
||||||
|
case 2:
|
||||||
|
sz = opLoadWidth2
|
||||||
|
case 4:
|
||||||
|
sz = opLoadWidth4
|
||||||
|
default:
|
||||||
|
return RawInstruction{}, fmt.Errorf("invalid load byte length %d", sz)
|
||||||
|
}
|
||||||
|
return RawInstruction{
|
||||||
|
Op: cls | sz | mode,
|
||||||
|
K: k,
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package bpf
|
||||||
|
|
||||||
|
// A Setter is a type which can attach a compiled BPF filter to itself.
|
||||||
|
type Setter interface {
|
||||||
|
SetBPF(filter []RawInstruction) error
|
||||||
|
}
|
|
@ -0,0 +1,150 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package bpf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A VM is an emulated BPF virtual machine.
|
||||||
|
type VM struct {
|
||||||
|
filter []Instruction
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewVM returns a new VM using the input BPF program.
|
||||||
|
func NewVM(filter []Instruction) (*VM, error) {
|
||||||
|
if len(filter) == 0 {
|
||||||
|
return nil, errors.New("one or more Instructions must be specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, ins := range filter {
|
||||||
|
check := len(filter) - (i + 1)
|
||||||
|
switch ins := ins.(type) {
|
||||||
|
// Check for out-of-bounds jumps in instructions
|
||||||
|
case Jump:
|
||||||
|
if check <= int(ins.Skip) {
|
||||||
|
return nil, fmt.Errorf("cannot jump %d instructions; jumping past program bounds", ins.Skip)
|
||||||
|
}
|
||||||
|
case JumpIf:
|
||||||
|
if check <= int(ins.SkipTrue) {
|
||||||
|
return nil, fmt.Errorf("cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue)
|
||||||
|
}
|
||||||
|
if check <= int(ins.SkipFalse) {
|
||||||
|
return nil, fmt.Errorf("cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse)
|
||||||
|
}
|
||||||
|
case JumpIfX:
|
||||||
|
if check <= int(ins.SkipTrue) {
|
||||||
|
return nil, fmt.Errorf("cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue)
|
||||||
|
}
|
||||||
|
if check <= int(ins.SkipFalse) {
|
||||||
|
return nil, fmt.Errorf("cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse)
|
||||||
|
}
|
||||||
|
// Check for division or modulus by zero
|
||||||
|
case ALUOpConstant:
|
||||||
|
if ins.Val != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ins.Op {
|
||||||
|
case ALUOpDiv, ALUOpMod:
|
||||||
|
return nil, errors.New("cannot divide by zero using ALUOpConstant")
|
||||||
|
}
|
||||||
|
// Check for unknown extensions
|
||||||
|
case LoadExtension:
|
||||||
|
switch ins.Num {
|
||||||
|
case ExtLen:
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("extension %d not implemented", ins.Num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure last instruction is a return instruction
|
||||||
|
switch filter[len(filter)-1].(type) {
|
||||||
|
case RetA, RetConstant:
|
||||||
|
default:
|
||||||
|
return nil, errors.New("BPF program must end with RetA or RetConstant")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Though our VM works using disassembled instructions, we
|
||||||
|
// attempt to assemble the input filter anyway to ensure it is compatible
|
||||||
|
// with an operating system VM.
|
||||||
|
_, err := Assemble(filter)
|
||||||
|
|
||||||
|
return &VM{
|
||||||
|
filter: filter,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run runs the VM's BPF program against the input bytes.
|
||||||
|
// Run returns the number of bytes accepted by the BPF program, and any errors
|
||||||
|
// which occurred while processing the program.
|
||||||
|
func (v *VM) Run(in []byte) (int, error) {
|
||||||
|
var (
|
||||||
|
// Registers of the virtual machine
|
||||||
|
regA uint32
|
||||||
|
regX uint32
|
||||||
|
regScratch [16]uint32
|
||||||
|
|
||||||
|
// OK is true if the program should continue processing the next
|
||||||
|
// instruction, or false if not, causing the loop to break
|
||||||
|
ok = true
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(mdlayher): implement:
|
||||||
|
// - NegateA:
|
||||||
|
// - would require a change from uint32 registers to int32
|
||||||
|
// registers
|
||||||
|
|
||||||
|
// TODO(mdlayher): add interop tests that check signedness of ALU
|
||||||
|
// operations against kernel implementation, and make sure Go
|
||||||
|
// implementation matches behavior
|
||||||
|
|
||||||
|
for i := 0; i < len(v.filter) && ok; i++ {
|
||||||
|
ins := v.filter[i]
|
||||||
|
|
||||||
|
switch ins := ins.(type) {
|
||||||
|
case ALUOpConstant:
|
||||||
|
regA = aluOpConstant(ins, regA)
|
||||||
|
case ALUOpX:
|
||||||
|
regA, ok = aluOpX(ins, regA, regX)
|
||||||
|
case Jump:
|
||||||
|
i += int(ins.Skip)
|
||||||
|
case JumpIf:
|
||||||
|
jump := jumpIf(ins, regA)
|
||||||
|
i += jump
|
||||||
|
case JumpIfX:
|
||||||
|
jump := jumpIfX(ins, regA, regX)
|
||||||
|
i += jump
|
||||||
|
case LoadAbsolute:
|
||||||
|
regA, ok = loadAbsolute(ins, in)
|
||||||
|
case LoadConstant:
|
||||||
|
regA, regX = loadConstant(ins, regA, regX)
|
||||||
|
case LoadExtension:
|
||||||
|
regA = loadExtension(ins, in)
|
||||||
|
case LoadIndirect:
|
||||||
|
regA, ok = loadIndirect(ins, in, regX)
|
||||||
|
case LoadMemShift:
|
||||||
|
regX, ok = loadMemShift(ins, in)
|
||||||
|
case LoadScratch:
|
||||||
|
regA, regX = loadScratch(ins, regScratch, regA, regX)
|
||||||
|
case RetA:
|
||||||
|
return int(regA), nil
|
||||||
|
case RetConstant:
|
||||||
|
return int(ins.Val), nil
|
||||||
|
case StoreScratch:
|
||||||
|
regScratch = storeScratch(ins, regScratch, regA, regX)
|
||||||
|
case TAX:
|
||||||
|
regX = regA
|
||||||
|
case TXA:
|
||||||
|
regA = regX
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("unknown Instruction at index %d: %T", i, ins)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
|
@ -0,0 +1,182 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package bpf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func aluOpConstant(ins ALUOpConstant, regA uint32) uint32 {
|
||||||
|
return aluOpCommon(ins.Op, regA, ins.Val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func aluOpX(ins ALUOpX, regA uint32, regX uint32) (uint32, bool) {
|
||||||
|
// Guard against division or modulus by zero by terminating
|
||||||
|
// the program, as the OS BPF VM does
|
||||||
|
if regX == 0 {
|
||||||
|
switch ins.Op {
|
||||||
|
case ALUOpDiv, ALUOpMod:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return aluOpCommon(ins.Op, regA, regX), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func aluOpCommon(op ALUOp, regA uint32, value uint32) uint32 {
|
||||||
|
switch op {
|
||||||
|
case ALUOpAdd:
|
||||||
|
return regA + value
|
||||||
|
case ALUOpSub:
|
||||||
|
return regA - value
|
||||||
|
case ALUOpMul:
|
||||||
|
return regA * value
|
||||||
|
case ALUOpDiv:
|
||||||
|
// Division by zero not permitted by NewVM and aluOpX checks
|
||||||
|
return regA / value
|
||||||
|
case ALUOpOr:
|
||||||
|
return regA | value
|
||||||
|
case ALUOpAnd:
|
||||||
|
return regA & value
|
||||||
|
case ALUOpShiftLeft:
|
||||||
|
return regA << value
|
||||||
|
case ALUOpShiftRight:
|
||||||
|
return regA >> value
|
||||||
|
case ALUOpMod:
|
||||||
|
// Modulus by zero not permitted by NewVM and aluOpX checks
|
||||||
|
return regA % value
|
||||||
|
case ALUOpXor:
|
||||||
|
return regA ^ value
|
||||||
|
default:
|
||||||
|
return regA
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func jumpIf(ins JumpIf, regA uint32) int {
|
||||||
|
return jumpIfCommon(ins.Cond, ins.SkipTrue, ins.SkipFalse, regA, ins.Val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jumpIfX(ins JumpIfX, regA uint32, regX uint32) int {
|
||||||
|
return jumpIfCommon(ins.Cond, ins.SkipTrue, ins.SkipFalse, regA, regX)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jumpIfCommon(cond JumpTest, skipTrue, skipFalse uint8, regA uint32, value uint32) int {
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
switch cond {
|
||||||
|
case JumpEqual:
|
||||||
|
ok = regA == value
|
||||||
|
case JumpNotEqual:
|
||||||
|
ok = regA != value
|
||||||
|
case JumpGreaterThan:
|
||||||
|
ok = regA > value
|
||||||
|
case JumpLessThan:
|
||||||
|
ok = regA < value
|
||||||
|
case JumpGreaterOrEqual:
|
||||||
|
ok = regA >= value
|
||||||
|
case JumpLessOrEqual:
|
||||||
|
ok = regA <= value
|
||||||
|
case JumpBitsSet:
|
||||||
|
ok = (regA & value) != 0
|
||||||
|
case JumpBitsNotSet:
|
||||||
|
ok = (regA & value) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
return int(skipTrue)
|
||||||
|
}
|
||||||
|
|
||||||
|
return int(skipFalse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadAbsolute(ins LoadAbsolute, in []byte) (uint32, bool) {
|
||||||
|
offset := int(ins.Off)
|
||||||
|
size := int(ins.Size)
|
||||||
|
|
||||||
|
return loadCommon(in, offset, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConstant(ins LoadConstant, regA uint32, regX uint32) (uint32, uint32) {
|
||||||
|
switch ins.Dst {
|
||||||
|
case RegA:
|
||||||
|
regA = ins.Val
|
||||||
|
case RegX:
|
||||||
|
regX = ins.Val
|
||||||
|
}
|
||||||
|
|
||||||
|
return regA, regX
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadExtension(ins LoadExtension, in []byte) uint32 {
|
||||||
|
switch ins.Num {
|
||||||
|
case ExtLen:
|
||||||
|
return uint32(len(in))
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unimplemented extension: %d", ins.Num))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadIndirect(ins LoadIndirect, in []byte, regX uint32) (uint32, bool) {
|
||||||
|
offset := int(ins.Off) + int(regX)
|
||||||
|
size := int(ins.Size)
|
||||||
|
|
||||||
|
return loadCommon(in, offset, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMemShift(ins LoadMemShift, in []byte) (uint32, bool) {
|
||||||
|
offset := int(ins.Off)
|
||||||
|
|
||||||
|
// Size of LoadMemShift is always 1 byte
|
||||||
|
if !inBounds(len(in), offset, 1) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mask off high 4 bits and multiply low 4 bits by 4
|
||||||
|
return uint32(in[offset]&0x0f) * 4, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func inBounds(inLen int, offset int, size int) bool {
|
||||||
|
return offset+size <= inLen
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadCommon(in []byte, offset int, size int) (uint32, bool) {
|
||||||
|
if !inBounds(len(in), offset, size) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch size {
|
||||||
|
case 1:
|
||||||
|
return uint32(in[offset]), true
|
||||||
|
case 2:
|
||||||
|
return uint32(binary.BigEndian.Uint16(in[offset : offset+size])), true
|
||||||
|
case 4:
|
||||||
|
return uint32(binary.BigEndian.Uint32(in[offset : offset+size])), true
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("invalid load size: %d", size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadScratch(ins LoadScratch, regScratch [16]uint32, regA uint32, regX uint32) (uint32, uint32) {
|
||||||
|
switch ins.Dst {
|
||||||
|
case RegA:
|
||||||
|
regA = regScratch[ins.N]
|
||||||
|
case RegX:
|
||||||
|
regX = regScratch[ins.N]
|
||||||
|
}
|
||||||
|
|
||||||
|
return regA, regX
|
||||||
|
}
|
||||||
|
|
||||||
|
func storeScratch(ins StoreScratch, regScratch [16]uint32, regA uint32, regX uint32) [16]uint32 {
|
||||||
|
switch ins.Src {
|
||||||
|
case RegA:
|
||||||
|
regScratch[ins.N] = regA
|
||||||
|
case RegX:
|
||||||
|
regScratch[ins.N] = regX
|
||||||
|
}
|
||||||
|
|
||||||
|
return regScratch
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package icmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/net/internal/iana"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A DstUnreach represents an ICMP destination unreachable message
|
||||||
|
// body.
|
||||||
|
type DstUnreach struct {
|
||||||
|
Data []byte // data, known as original datagram field
|
||||||
|
Extensions []Extension // extensions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len implements the Len method of MessageBody interface.
|
||||||
|
func (p *DstUnreach) Len(proto int) int {
|
||||||
|
if p == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
l, _ := multipartMessageBodyDataLen(proto, true, p.Data, p.Extensions)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal implements the Marshal method of MessageBody interface.
|
||||||
|
func (p *DstUnreach) Marshal(proto int) ([]byte, error) {
|
||||||
|
var typ Type
|
||||||
|
switch proto {
|
||||||
|
case iana.ProtocolICMP:
|
||||||
|
typ = ipv4.ICMPTypeDestinationUnreachable
|
||||||
|
case iana.ProtocolIPv6ICMP:
|
||||||
|
typ = ipv6.ICMPTypeDestinationUnreachable
|
||||||
|
default:
|
||||||
|
return nil, errInvalidProtocol
|
||||||
|
}
|
||||||
|
if !validExtensions(typ, p.Extensions) {
|
||||||
|
return nil, errInvalidExtension
|
||||||
|
}
|
||||||
|
return marshalMultipartMessageBody(proto, true, p.Data, p.Extensions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseDstUnreach parses b as an ICMP destination unreachable message
|
||||||
|
// body.
|
||||||
|
func parseDstUnreach(proto int, typ Type, b []byte) (MessageBody, error) {
|
||||||
|
if len(b) < 4 {
|
||||||
|
return nil, errMessageTooShort
|
||||||
|
}
|
||||||
|
p := &DstUnreach{}
|
||||||
|
var err error
|
||||||
|
p.Data, p.Extensions, err = parseMultipartMessageBody(proto, typ, b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
|
@ -0,0 +1,173 @@
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package icmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"golang.org/x/net/internal/iana"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An Echo represents an ICMP echo request or reply message body.
|
||||||
|
type Echo struct {
|
||||||
|
ID int // identifier
|
||||||
|
Seq int // sequence number
|
||||||
|
Data []byte // data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len implements the Len method of MessageBody interface.
|
||||||
|
func (p *Echo) Len(proto int) int {
|
||||||
|
if p == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 4 + len(p.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal implements the Marshal method of MessageBody interface.
|
||||||
|
func (p *Echo) Marshal(proto int) ([]byte, error) {
|
||||||
|
b := make([]byte, 4+len(p.Data))
|
||||||
|
binary.BigEndian.PutUint16(b[:2], uint16(p.ID))
|
||||||
|
binary.BigEndian.PutUint16(b[2:4], uint16(p.Seq))
|
||||||
|
copy(b[4:], p.Data)
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseEcho parses b as an ICMP echo request or reply message body.
|
||||||
|
func parseEcho(proto int, _ Type, b []byte) (MessageBody, error) {
|
||||||
|
bodyLen := len(b)
|
||||||
|
if bodyLen < 4 {
|
||||||
|
return nil, errMessageTooShort
|
||||||
|
}
|
||||||
|
p := &Echo{ID: int(binary.BigEndian.Uint16(b[:2])), Seq: int(binary.BigEndian.Uint16(b[2:4]))}
|
||||||
|
if bodyLen > 4 {
|
||||||
|
p.Data = make([]byte, bodyLen-4)
|
||||||
|
copy(p.Data, b[4:])
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// An ExtendedEchoRequest represents an ICMP extended echo request
|
||||||
|
// message body.
|
||||||
|
type ExtendedEchoRequest struct {
|
||||||
|
ID int // identifier
|
||||||
|
Seq int // sequence number
|
||||||
|
Local bool // must be true when identifying by name or index
|
||||||
|
Extensions []Extension // extensions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len implements the Len method of MessageBody interface.
|
||||||
|
func (p *ExtendedEchoRequest) Len(proto int) int {
|
||||||
|
if p == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
l, _ := multipartMessageBodyDataLen(proto, false, nil, p.Extensions)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal implements the Marshal method of MessageBody interface.
|
||||||
|
func (p *ExtendedEchoRequest) Marshal(proto int) ([]byte, error) {
|
||||||
|
var typ Type
|
||||||
|
switch proto {
|
||||||
|
case iana.ProtocolICMP:
|
||||||
|
typ = ipv4.ICMPTypeExtendedEchoRequest
|
||||||
|
case iana.ProtocolIPv6ICMP:
|
||||||
|
typ = ipv6.ICMPTypeExtendedEchoRequest
|
||||||
|
default:
|
||||||
|
return nil, errInvalidProtocol
|
||||||
|
}
|
||||||
|
if !validExtensions(typ, p.Extensions) {
|
||||||
|
return nil, errInvalidExtension
|
||||||
|
}
|
||||||
|
b, err := marshalMultipartMessageBody(proto, false, nil, p.Extensions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint16(b[:2], uint16(p.ID))
|
||||||
|
b[2] = byte(p.Seq)
|
||||||
|
if p.Local {
|
||||||
|
b[3] |= 0x01
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseExtendedEchoRequest parses b as an ICMP extended echo request
|
||||||
|
// message body.
|
||||||
|
func parseExtendedEchoRequest(proto int, typ Type, b []byte) (MessageBody, error) {
|
||||||
|
if len(b) < 4 {
|
||||||
|
return nil, errMessageTooShort
|
||||||
|
}
|
||||||
|
p := &ExtendedEchoRequest{ID: int(binary.BigEndian.Uint16(b[:2])), Seq: int(b[2])}
|
||||||
|
if b[3]&0x01 != 0 {
|
||||||
|
p.Local = true
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
_, p.Extensions, err = parseMultipartMessageBody(proto, typ, b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// An ExtendedEchoReply represents an ICMP extended echo reply message
|
||||||
|
// body.
|
||||||
|
type ExtendedEchoReply struct {
|
||||||
|
ID int // identifier
|
||||||
|
Seq int // sequence number
|
||||||
|
State int // 3-bit state working together with Message.Code
|
||||||
|
Active bool // probed interface is active
|
||||||
|
IPv4 bool // probed interface runs IPv4
|
||||||
|
IPv6 bool // probed interface runs IPv6
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len implements the Len method of MessageBody interface.
|
||||||
|
func (p *ExtendedEchoReply) Len(proto int) int {
|
||||||
|
if p == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal implements the Marshal method of MessageBody interface.
|
||||||
|
func (p *ExtendedEchoReply) Marshal(proto int) ([]byte, error) {
|
||||||
|
b := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint16(b[:2], uint16(p.ID))
|
||||||
|
b[2] = byte(p.Seq)
|
||||||
|
b[3] = byte(p.State<<5) & 0xe0
|
||||||
|
if p.Active {
|
||||||
|
b[3] |= 0x04
|
||||||
|
}
|
||||||
|
if p.IPv4 {
|
||||||
|
b[3] |= 0x02
|
||||||
|
}
|
||||||
|
if p.IPv6 {
|
||||||
|
b[3] |= 0x01
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseExtendedEchoReply parses b as an ICMP extended echo reply
|
||||||
|
// message body.
|
||||||
|
func parseExtendedEchoReply(proto int, _ Type, b []byte) (MessageBody, error) {
|
||||||
|
if len(b) < 4 {
|
||||||
|
return nil, errMessageTooShort
|
||||||
|
}
|
||||||
|
p := &ExtendedEchoReply{
|
||||||
|
ID: int(binary.BigEndian.Uint16(b[:2])),
|
||||||
|
Seq: int(b[2]),
|
||||||
|
State: int(b[3]) >> 5,
|
||||||
|
}
|
||||||
|
if b[3]&0x04 != 0 {
|
||||||
|
p.Active = true
|
||||||
|
}
|
||||||
|
if b[3]&0x02 != 0 {
|
||||||
|
p.IPv4 = true
|
||||||
|
}
|
||||||
|
if b[3]&0x01 != 0 {
|
||||||
|
p.IPv6 = true
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package icmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ net.PacketConn = &PacketConn{}
|
||||||
|
|
||||||
|
// A PacketConn represents a packet network endpoint that uses either
|
||||||
|
// ICMPv4 or ICMPv6.
|
||||||
|
type PacketConn struct {
|
||||||
|
c net.PacketConn
|
||||||
|
p4 *ipv4.PacketConn
|
||||||
|
p6 *ipv6.PacketConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PacketConn) ok() bool { return c != nil && c.c != nil }
|
||||||
|
|
||||||
|
// IPv4PacketConn returns the ipv4.PacketConn of c.
|
||||||
|
// It returns nil when c is not created as the endpoint for ICMPv4.
|
||||||
|
func (c *PacketConn) IPv4PacketConn() *ipv4.PacketConn {
|
||||||
|
if !c.ok() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.p4
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPv6PacketConn returns the ipv6.PacketConn of c.
|
||||||
|
// It returns nil when c is not created as the endpoint for ICMPv6.
|
||||||
|
func (c *PacketConn) IPv6PacketConn() *ipv6.PacketConn {
|
||||||
|
if !c.ok() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.p6
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFrom reads an ICMP message from the connection.
|
||||||
|
func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||||
|
if !c.ok() {
|
||||||
|
return 0, nil, errInvalidConn
|
||||||
|
}
|
||||||
|
// Please be informed that ipv4.NewPacketConn enables
|
||||||
|
// IP_STRIPHDR option by default on Darwin.
|
||||||
|
// See golang.org/issue/9395 for further information.
|
||||||
|
if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && c.p4 != nil {
|
||||||
|
n, _, peer, err := c.p4.ReadFrom(b)
|
||||||
|
return n, peer, err
|
||||||
|
}
|
||||||
|
return c.c.ReadFrom(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo writes the ICMP message b to dst.
|
||||||
|
// The provided dst must be net.UDPAddr when c is a non-privileged
|
||||||
|
// datagram-oriented ICMP endpoint.
|
||||||
|
// Otherwise it must be net.IPAddr.
|
||||||
|
func (c *PacketConn) WriteTo(b []byte, dst net.Addr) (int, error) {
|
||||||
|
if !c.ok() {
|
||||||
|
return 0, errInvalidConn
|
||||||
|
}
|
||||||
|
return c.c.WriteTo(b, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the endpoint.
|
||||||
|
func (c *PacketConn) Close() error {
|
||||||
|
if !c.ok() {
|
||||||
|
return errInvalidConn
|
||||||
|
}
|
||||||
|
return c.c.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalAddr returns the local network address.
|
||||||
|
func (c *PacketConn) LocalAddr() net.Addr {
|
||||||
|
if !c.ok() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.c.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDeadline sets the read and write deadlines associated with the
|
||||||
|
// endpoint.
|
||||||
|
func (c *PacketConn) SetDeadline(t time.Time) error {
|
||||||
|
if !c.ok() {
|
||||||
|
return errInvalidConn
|
||||||
|
}
|
||||||
|
return c.c.SetDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadDeadline sets the read deadline associated with the
|
||||||
|
// endpoint.
|
||||||
|
func (c *PacketConn) SetReadDeadline(t time.Time) error {
|
||||||
|
if !c.ok() {
|
||||||
|
return errInvalidConn
|
||||||
|
}
|
||||||
|
return c.c.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWriteDeadline sets the write deadline associated with the
|
||||||
|
// endpoint.
|
||||||
|
func (c *PacketConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
if !c.ok() {
|
||||||
|
return errInvalidConn
|
||||||
|
}
|
||||||
|
return c.c.SetWriteDeadline(t)
|
||||||
|
}
|
|
@ -0,0 +1,170 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package icmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An Extension represents an ICMP extension.
|
||||||
|
type Extension interface {
|
||||||
|
// Len returns the length of ICMP extension.
|
||||||
|
// The provided proto must be either the ICMPv4 or ICMPv6
|
||||||
|
// protocol number.
|
||||||
|
Len(proto int) int
|
||||||
|
|
||||||
|
// Marshal returns the binary encoding of ICMP extension.
|
||||||
|
// The provided proto must be either the ICMPv4 or ICMPv6
|
||||||
|
// protocol number.
|
||||||
|
Marshal(proto int) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const extensionVersion = 2
|
||||||
|
|
||||||
|
func validExtensionHeader(b []byte) bool {
|
||||||
|
v := int(b[0]&0xf0) >> 4
|
||||||
|
s := binary.BigEndian.Uint16(b[2:4])
|
||||||
|
if s != 0 {
|
||||||
|
s = checksum(b)
|
||||||
|
}
|
||||||
|
if v != extensionVersion || s != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseExtensions parses b as a list of ICMP extensions.
|
||||||
|
// The length attribute l must be the length attribute field in
|
||||||
|
// received icmp messages.
|
||||||
|
//
|
||||||
|
// It will return a list of ICMP extensions and an adjusted length
|
||||||
|
// attribute that represents the length of the padded original
|
||||||
|
// datagram field. Otherwise, it returns an error.
|
||||||
|
func parseExtensions(typ Type, b []byte, l int) ([]Extension, int, error) {
|
||||||
|
// Still a lot of non-RFC 4884 compliant implementations are
|
||||||
|
// out there. Set the length attribute l to 128 when it looks
|
||||||
|
// inappropriate for backwards compatibility.
|
||||||
|
//
|
||||||
|
// A minimal extension at least requires 8 octets; 4 octets
|
||||||
|
// for an extension header, and 4 octets for a single object
|
||||||
|
// header.
|
||||||
|
//
|
||||||
|
// See RFC 4884 for further information.
|
||||||
|
switch typ {
|
||||||
|
case ipv4.ICMPTypeExtendedEchoRequest, ipv6.ICMPTypeExtendedEchoRequest:
|
||||||
|
if len(b) < 8 || !validExtensionHeader(b) {
|
||||||
|
return nil, -1, errNoExtension
|
||||||
|
}
|
||||||
|
l = 0
|
||||||
|
default:
|
||||||
|
if 128 > l || l+8 > len(b) {
|
||||||
|
l = 128
|
||||||
|
}
|
||||||
|
if l+8 > len(b) {
|
||||||
|
return nil, -1, errNoExtension
|
||||||
|
}
|
||||||
|
if !validExtensionHeader(b[l:]) {
|
||||||
|
if l == 128 {
|
||||||
|
return nil, -1, errNoExtension
|
||||||
|
}
|
||||||
|
l = 128
|
||||||
|
if !validExtensionHeader(b[l:]) {
|
||||||
|
return nil, -1, errNoExtension
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var exts []Extension
|
||||||
|
for b = b[l+4:]; len(b) >= 4; {
|
||||||
|
ol := int(binary.BigEndian.Uint16(b[:2]))
|
||||||
|
if 4 > ol || ol > len(b) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch b[2] {
|
||||||
|
case classMPLSLabelStack:
|
||||||
|
ext, err := parseMPLSLabelStack(b[:ol])
|
||||||
|
if err != nil {
|
||||||
|
return nil, -1, err
|
||||||
|
}
|
||||||
|
exts = append(exts, ext)
|
||||||
|
case classInterfaceInfo:
|
||||||
|
ext, err := parseInterfaceInfo(b[:ol])
|
||||||
|
if err != nil {
|
||||||
|
return nil, -1, err
|
||||||
|
}
|
||||||
|
exts = append(exts, ext)
|
||||||
|
case classInterfaceIdent:
|
||||||
|
ext, err := parseInterfaceIdent(b[:ol])
|
||||||
|
if err != nil {
|
||||||
|
return nil, -1, err
|
||||||
|
}
|
||||||
|
exts = append(exts, ext)
|
||||||
|
default:
|
||||||
|
ext := &RawExtension{Data: make([]byte, ol)}
|
||||||
|
copy(ext.Data, b[:ol])
|
||||||
|
exts = append(exts, ext)
|
||||||
|
}
|
||||||
|
b = b[ol:]
|
||||||
|
}
|
||||||
|
return exts, l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validExtensions(typ Type, exts []Extension) bool {
|
||||||
|
switch typ {
|
||||||
|
case ipv4.ICMPTypeDestinationUnreachable, ipv4.ICMPTypeTimeExceeded, ipv4.ICMPTypeParameterProblem,
|
||||||
|
ipv6.ICMPTypeDestinationUnreachable, ipv6.ICMPTypeTimeExceeded:
|
||||||
|
for i := range exts {
|
||||||
|
switch exts[i].(type) {
|
||||||
|
case *MPLSLabelStack, *InterfaceInfo, *RawExtension:
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case ipv4.ICMPTypeExtendedEchoRequest, ipv6.ICMPTypeExtendedEchoRequest:
|
||||||
|
var n int
|
||||||
|
for i := range exts {
|
||||||
|
switch exts[i].(type) {
|
||||||
|
case *InterfaceIdent:
|
||||||
|
n++
|
||||||
|
case *RawExtension:
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Not a single InterfaceIdent object or a combo of
|
||||||
|
// RawExtension and InterfaceIdent objects is not
|
||||||
|
// allowed.
|
||||||
|
if n == 1 && len(exts) > 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A RawExtension represents a raw extension.
|
||||||
|
//
|
||||||
|
// A raw extension is excluded from message processing and can be used
|
||||||
|
// to construct applications such as protocol conformance testing.
|
||||||
|
type RawExtension struct {
|
||||||
|
Data []byte // data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len implements the Len method of Extension interface.
|
||||||
|
func (p *RawExtension) Len(proto int) int {
|
||||||
|
if p == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return len(p.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal implements the Marshal method of Extension interface.
|
||||||
|
func (p *RawExtension) Marshal(proto int) ([]byte, error) {
|
||||||
|
return p.Data, nil
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows
|
||||||
|
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris windows
|
||||||
|
|
||||||
|
package icmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sockaddr(family int, address string) (syscall.Sockaddr, error) {
|
||||||
|
switch family {
|
||||||
|
case syscall.AF_INET:
|
||||||
|
a, err := net.ResolveIPAddr("ip4", address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(a.IP) == 0 {
|
||||||
|
a.IP = net.IPv4zero
|
||||||
|
}
|
||||||
|
if a.IP = a.IP.To4(); a.IP == nil {
|
||||||
|
return nil, net.InvalidAddrError("non-ipv4 address")
|
||||||
|
}
|
||||||
|
sa := &syscall.SockaddrInet4{}
|
||||||
|
copy(sa.Addr[:], a.IP)
|
||||||
|
return sa, nil
|
||||||
|
case syscall.AF_INET6:
|
||||||
|
a, err := net.ResolveIPAddr("ip6", address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(a.IP) == 0 {
|
||||||
|
a.IP = net.IPv6unspecified
|
||||||
|
}
|
||||||
|
if a.IP.Equal(net.IPv4zero) {
|
||||||
|
a.IP = net.IPv6unspecified
|
||||||
|
}
|
||||||
|
if a.IP = a.IP.To16(); a.IP == nil || a.IP.To4() != nil {
|
||||||
|
return nil, net.InvalidAddrError("non-ipv6 address")
|
||||||
|
}
|
||||||
|
sa := &syscall.SockaddrInet6{ZoneId: zoneToUint32(a.Zone)}
|
||||||
|
copy(sa.Addr[:], a.IP)
|
||||||
|
return sa, nil
|
||||||
|
default:
|
||||||
|
return nil, net.InvalidAddrError("unexpected family")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func zoneToUint32(zone string) uint32 {
|
||||||
|
if zone == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if ifi, err := net.InterfaceByName(zone); err == nil {
|
||||||
|
return uint32(ifi.Index)
|
||||||
|
}
|
||||||
|
n, err := strconv.Atoi(zone)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return uint32(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func last(s string, b byte) int {
|
||||||
|
i := len(s)
|
||||||
|
for i--; i >= 0; i-- {
|
||||||
|
if s[i] == b {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
|
@ -0,0 +1,322 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package icmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/net/internal/iana"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
classInterfaceInfo = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
attrMTU = 1 << iota
|
||||||
|
attrName
|
||||||
|
attrIPAddr
|
||||||
|
attrIfIndex
|
||||||
|
)
|
||||||
|
|
||||||
|
// An InterfaceInfo represents interface and next-hop identification.
|
||||||
|
type InterfaceInfo struct {
|
||||||
|
Class int // extension object class number
|
||||||
|
Type int // extension object sub-type
|
||||||
|
Interface *net.Interface
|
||||||
|
Addr *net.IPAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ifi *InterfaceInfo) nameLen() int {
|
||||||
|
if len(ifi.Interface.Name) > 63 {
|
||||||
|
return 64
|
||||||
|
}
|
||||||
|
l := 1 + len(ifi.Interface.Name)
|
||||||
|
return (l + 3) &^ 3
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ifi *InterfaceInfo) attrsAndLen(proto int) (attrs, l int) {
|
||||||
|
l = 4
|
||||||
|
if ifi.Interface != nil && ifi.Interface.Index > 0 {
|
||||||
|
attrs |= attrIfIndex
|
||||||
|
l += 4
|
||||||
|
if len(ifi.Interface.Name) > 0 {
|
||||||
|
attrs |= attrName
|
||||||
|
l += ifi.nameLen()
|
||||||
|
}
|
||||||
|
if ifi.Interface.MTU > 0 {
|
||||||
|
attrs |= attrMTU
|
||||||
|
l += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ifi.Addr != nil {
|
||||||
|
switch proto {
|
||||||
|
case iana.ProtocolICMP:
|
||||||
|
if ifi.Addr.IP.To4() != nil {
|
||||||
|
attrs |= attrIPAddr
|
||||||
|
l += 4 + net.IPv4len
|
||||||
|
}
|
||||||
|
case iana.ProtocolIPv6ICMP:
|
||||||
|
if ifi.Addr.IP.To16() != nil && ifi.Addr.IP.To4() == nil {
|
||||||
|
attrs |= attrIPAddr
|
||||||
|
l += 4 + net.IPv6len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len implements the Len method of Extension interface.
|
||||||
|
func (ifi *InterfaceInfo) Len(proto int) int {
|
||||||
|
_, l := ifi.attrsAndLen(proto)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal implements the Marshal method of Extension interface.
|
||||||
|
func (ifi *InterfaceInfo) Marshal(proto int) ([]byte, error) {
|
||||||
|
attrs, l := ifi.attrsAndLen(proto)
|
||||||
|
b := make([]byte, l)
|
||||||
|
if err := ifi.marshal(proto, b, attrs, l); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ifi *InterfaceInfo) marshal(proto int, b []byte, attrs, l int) error {
|
||||||
|
binary.BigEndian.PutUint16(b[:2], uint16(l))
|
||||||
|
b[2], b[3] = classInterfaceInfo, byte(ifi.Type)
|
||||||
|
for b = b[4:]; len(b) > 0 && attrs != 0; {
|
||||||
|
switch {
|
||||||
|
case attrs&attrIfIndex != 0:
|
||||||
|
b = ifi.marshalIfIndex(proto, b)
|
||||||
|
attrs &^= attrIfIndex
|
||||||
|
case attrs&attrIPAddr != 0:
|
||||||
|
b = ifi.marshalIPAddr(proto, b)
|
||||||
|
attrs &^= attrIPAddr
|
||||||
|
case attrs&attrName != 0:
|
||||||
|
b = ifi.marshalName(proto, b)
|
||||||
|
attrs &^= attrName
|
||||||
|
case attrs&attrMTU != 0:
|
||||||
|
b = ifi.marshalMTU(proto, b)
|
||||||
|
attrs &^= attrMTU
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ifi *InterfaceInfo) marshalIfIndex(proto int, b []byte) []byte {
|
||||||
|
binary.BigEndian.PutUint32(b[:4], uint32(ifi.Interface.Index))
|
||||||
|
return b[4:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ifi *InterfaceInfo) parseIfIndex(b []byte) ([]byte, error) {
|
||||||
|
if len(b) < 4 {
|
||||||
|
return nil, errMessageTooShort
|
||||||
|
}
|
||||||
|
ifi.Interface.Index = int(binary.BigEndian.Uint32(b[:4]))
|
||||||
|
return b[4:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ifi *InterfaceInfo) marshalIPAddr(proto int, b []byte) []byte {
|
||||||
|
switch proto {
|
||||||
|
case iana.ProtocolICMP:
|
||||||
|
binary.BigEndian.PutUint16(b[:2], uint16(iana.AddrFamilyIPv4))
|
||||||
|
copy(b[4:4+net.IPv4len], ifi.Addr.IP.To4())
|
||||||
|
b = b[4+net.IPv4len:]
|
||||||
|
case iana.ProtocolIPv6ICMP:
|
||||||
|
binary.BigEndian.PutUint16(b[:2], uint16(iana.AddrFamilyIPv6))
|
||||||
|
copy(b[4:4+net.IPv6len], ifi.Addr.IP.To16())
|
||||||
|
b = b[4+net.IPv6len:]
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ifi *InterfaceInfo) parseIPAddr(b []byte) ([]byte, error) {
|
||||||
|
if len(b) < 4 {
|
||||||
|
return nil, errMessageTooShort
|
||||||
|
}
|
||||||
|
afi := int(binary.BigEndian.Uint16(b[:2]))
|
||||||
|
b = b[4:]
|
||||||
|
switch afi {
|
||||||
|
case iana.AddrFamilyIPv4:
|
||||||
|
if len(b) < net.IPv4len {
|
||||||
|
return nil, errMessageTooShort
|
||||||
|
}
|
||||||
|
ifi.Addr.IP = make(net.IP, net.IPv4len)
|
||||||
|
copy(ifi.Addr.IP, b[:net.IPv4len])
|
||||||
|
b = b[net.IPv4len:]
|
||||||
|
case iana.AddrFamilyIPv6:
|
||||||
|
if len(b) < net.IPv6len {
|
||||||
|
return nil, errMessageTooShort
|
||||||
|
}
|
||||||
|
ifi.Addr.IP = make(net.IP, net.IPv6len)
|
||||||
|
copy(ifi.Addr.IP, b[:net.IPv6len])
|
||||||
|
b = b[net.IPv6len:]
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ifi *InterfaceInfo) marshalName(proto int, b []byte) []byte {
|
||||||
|
l := byte(ifi.nameLen())
|
||||||
|
b[0] = l
|
||||||
|
copy(b[1:], []byte(ifi.Interface.Name))
|
||||||
|
return b[l:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ifi *InterfaceInfo) parseName(b []byte) ([]byte, error) {
|
||||||
|
if 4 > len(b) || len(b) < int(b[0]) {
|
||||||
|
return nil, errMessageTooShort
|
||||||
|
}
|
||||||
|
l := int(b[0])
|
||||||
|
if l%4 != 0 || 4 > l || l > 64 {
|
||||||
|
return nil, errInvalidExtension
|
||||||
|
}
|
||||||
|
var name [63]byte
|
||||||
|
copy(name[:], b[1:l])
|
||||||
|
ifi.Interface.Name = strings.Trim(string(name[:]), "\000")
|
||||||
|
return b[l:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ifi *InterfaceInfo) marshalMTU(proto int, b []byte) []byte {
|
||||||
|
binary.BigEndian.PutUint32(b[:4], uint32(ifi.Interface.MTU))
|
||||||
|
return b[4:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ifi *InterfaceInfo) parseMTU(b []byte) ([]byte, error) {
|
||||||
|
if len(b) < 4 {
|
||||||
|
return nil, errMessageTooShort
|
||||||
|
}
|
||||||
|
ifi.Interface.MTU = int(binary.BigEndian.Uint32(b[:4]))
|
||||||
|
return b[4:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInterfaceInfo(b []byte) (Extension, error) {
|
||||||
|
ifi := &InterfaceInfo{
|
||||||
|
Class: int(b[2]),
|
||||||
|
Type: int(b[3]),
|
||||||
|
}
|
||||||
|
if ifi.Type&(attrIfIndex|attrName|attrMTU) != 0 {
|
||||||
|
ifi.Interface = &net.Interface{}
|
||||||
|
}
|
||||||
|
if ifi.Type&attrIPAddr != 0 {
|
||||||
|
ifi.Addr = &net.IPAddr{}
|
||||||
|
}
|
||||||
|
attrs := ifi.Type & (attrIfIndex | attrIPAddr | attrName | attrMTU)
|
||||||
|
for b = b[4:]; len(b) > 0 && attrs != 0; {
|
||||||
|
var err error
|
||||||
|
switch {
|
||||||
|
case attrs&attrIfIndex != 0:
|
||||||
|
b, err = ifi.parseIfIndex(b)
|
||||||
|
attrs &^= attrIfIndex
|
||||||
|
case attrs&attrIPAddr != 0:
|
||||||
|
b, err = ifi.parseIPAddr(b)
|
||||||
|
attrs &^= attrIPAddr
|
||||||
|
case attrs&attrName != 0:
|
||||||
|
b, err = ifi.parseName(b)
|
||||||
|
attrs &^= attrName
|
||||||
|
case attrs&attrMTU != 0:
|
||||||
|
b, err = ifi.parseMTU(b)
|
||||||
|
attrs &^= attrMTU
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ifi.Interface != nil && ifi.Interface.Name != "" && ifi.Addr != nil && ifi.Addr.IP.To16() != nil && ifi.Addr.IP.To4() == nil {
|
||||||
|
ifi.Addr.Zone = ifi.Interface.Name
|
||||||
|
}
|
||||||
|
return ifi, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
classInterfaceIdent = 3
|
||||||
|
typeInterfaceByName = 1
|
||||||
|
typeInterfaceByIndex = 2
|
||||||
|
typeInterfaceByAddress = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// An InterfaceIdent represents interface identification.
|
||||||
|
type InterfaceIdent struct {
|
||||||
|
Class int // extension object class number
|
||||||
|
Type int // extension object sub-type
|
||||||
|
Name string // interface name
|
||||||
|
Index int // interface index
|
||||||
|
AFI int // address family identifier; see address family numbers in IANA registry
|
||||||
|
Addr []byte // address
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len implements the Len method of Extension interface.
|
||||||
|
func (ifi *InterfaceIdent) Len(_ int) int {
|
||||||
|
switch ifi.Type {
|
||||||
|
case typeInterfaceByName:
|
||||||
|
l := len(ifi.Name)
|
||||||
|
if l > 255 {
|
||||||
|
l = 255
|
||||||
|
}
|
||||||
|
return 4 + (l+3)&^3
|
||||||
|
case typeInterfaceByIndex:
|
||||||
|
return 4 + 4
|
||||||
|
case typeInterfaceByAddress:
|
||||||
|
return 4 + 4 + (len(ifi.Addr)+3)&^3
|
||||||
|
default:
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal implements the Marshal method of Extension interface.
|
||||||
|
func (ifi *InterfaceIdent) Marshal(proto int) ([]byte, error) {
|
||||||
|
b := make([]byte, ifi.Len(proto))
|
||||||
|
if err := ifi.marshal(proto, b); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ifi *InterfaceIdent) marshal(proto int, b []byte) error {
|
||||||
|
l := ifi.Len(proto)
|
||||||
|
binary.BigEndian.PutUint16(b[:2], uint16(l))
|
||||||
|
b[2], b[3] = classInterfaceIdent, byte(ifi.Type)
|
||||||
|
switch ifi.Type {
|
||||||
|
case typeInterfaceByName:
|
||||||
|
copy(b[4:], ifi.Name)
|
||||||
|
case typeInterfaceByIndex:
|
||||||
|
binary.BigEndian.PutUint32(b[4:4+4], uint32(ifi.Index))
|
||||||
|
case typeInterfaceByAddress:
|
||||||
|
binary.BigEndian.PutUint16(b[4:4+2], uint16(ifi.AFI))
|
||||||
|
b[4+2] = byte(len(ifi.Addr))
|
||||||
|
copy(b[4+4:], ifi.Addr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInterfaceIdent(b []byte) (Extension, error) {
|
||||||
|
ifi := &InterfaceIdent{
|
||||||
|
Class: int(b[2]),
|
||||||
|
Type: int(b[3]),
|
||||||
|
}
|
||||||
|
switch ifi.Type {
|
||||||
|
case typeInterfaceByName:
|
||||||
|
ifi.Name = strings.Trim(string(b[4:]), "\x00")
|
||||||
|
case typeInterfaceByIndex:
|
||||||
|
if len(b[4:]) < 4 {
|
||||||
|
return nil, errInvalidExtension
|
||||||
|
}
|
||||||
|
ifi.Index = int(binary.BigEndian.Uint32(b[4 : 4+4]))
|
||||||
|
case typeInterfaceByAddress:
|
||||||
|
if len(b[4:]) < 4 {
|
||||||
|
return nil, errInvalidExtension
|
||||||
|
}
|
||||||
|
ifi.AFI = int(binary.BigEndian.Uint16(b[4 : 4+2]))
|
||||||
|
l := int(b[4+2])
|
||||||
|
if len(b[4+4:]) < l {
|
||||||
|
return nil, errInvalidExtension
|
||||||
|
}
|
||||||
|
ifi.Addr = make([]byte, l)
|
||||||
|
copy(ifi.Addr, b[4+4:])
|
||||||
|
}
|
||||||
|
return ifi, nil
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package icmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"golang.org/x/net/internal/socket"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// freebsdVersion is set in sys_freebsd.go.
|
||||||
|
// See http://www.freebsd.org/doc/en/books/porters-handbook/freebsd-versions.html.
|
||||||
|
var freebsdVersion uint32
|
||||||
|
|
||||||
|
// ParseIPv4Header returns the IPv4 header of the IPv4 packet that
|
||||||
|
// triggered an ICMP error message.
|
||||||
|
// This is found in the Data field of the ICMP error message body.
|
||||||
|
//
|
||||||
|
// The provided b must be in the format used by a raw ICMP socket on
|
||||||
|
// the local system.
|
||||||
|
// This may differ from the wire format, and the format used by a raw
|
||||||
|
// IP socket, depending on the system.
|
||||||
|
//
|
||||||
|
// To parse an IPv6 header, use ipv6.ParseHeader.
|
||||||
|
func ParseIPv4Header(b []byte) (*ipv4.Header, error) {
|
||||||
|
if len(b) < ipv4.HeaderLen {
|
||||||
|
return nil, errHeaderTooShort
|
||||||
|
}
|
||||||
|
hdrlen := int(b[0]&0x0f) << 2
|
||||||
|
if hdrlen > len(b) {
|
||||||
|
return nil, errBufferTooShort
|
||||||
|
}
|
||||||
|
h := &ipv4.Header{
|
||||||
|
Version: int(b[0] >> 4),
|
||||||
|
Len: hdrlen,
|
||||||
|
TOS: int(b[1]),
|
||||||
|
ID: int(binary.BigEndian.Uint16(b[4:6])),
|
||||||
|
FragOff: int(binary.BigEndian.Uint16(b[6:8])),
|
||||||
|
TTL: int(b[8]),
|
||||||
|
Protocol: int(b[9]),
|
||||||
|
Checksum: int(binary.BigEndian.Uint16(b[10:12])),
|
||||||
|
Src: net.IPv4(b[12], b[13], b[14], b[15]),
|
||||||
|
Dst: net.IPv4(b[16], b[17], b[18], b[19]),
|
||||||
|
}
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "darwin", "ios":
|
||||||
|
h.TotalLen = int(socket.NativeEndian.Uint16(b[2:4]))
|
||||||
|
case "freebsd":
|
||||||
|
if freebsdVersion >= 1000000 {
|
||||||
|
h.TotalLen = int(binary.BigEndian.Uint16(b[2:4]))
|
||||||
|
} else {
|
||||||
|
h.TotalLen = int(socket.NativeEndian.Uint16(b[2:4]))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
h.TotalLen = int(binary.BigEndian.Uint16(b[2:4]))
|
||||||
|
}
|
||||||
|
h.Flags = ipv4.HeaderFlags(h.FragOff&0xe000) >> 13
|
||||||
|
h.FragOff = h.FragOff & 0x1fff
|
||||||
|
if hdrlen-ipv4.HeaderLen > 0 {
|
||||||
|
h.Options = make([]byte, hdrlen-ipv4.HeaderLen)
|
||||||
|
copy(h.Options, b[ipv4.HeaderLen:])
|
||||||
|
}
|
||||||
|
return h, nil
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package icmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"golang.org/x/net/internal/iana"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ipv6PseudoHeaderLen = 2*net.IPv6len + 8
|
||||||
|
|
||||||
|
// IPv6PseudoHeader returns an IPv6 pseudo header for checksum
|
||||||
|
// calculation.
|
||||||
|
func IPv6PseudoHeader(src, dst net.IP) []byte {
|
||||||
|
b := make([]byte, ipv6PseudoHeaderLen)
|
||||||
|
copy(b, src.To16())
|
||||||
|
copy(b[net.IPv6len:], dst.To16())
|
||||||
|
b[len(b)-1] = byte(iana.ProtocolIPv6ICMP)
|
||||||
|
return b
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows
|
||||||
|
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris windows
|
||||||
|
|
||||||
|
package icmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/net/internal/iana"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
)
|
||||||
|
|
||||||
|
const sysIP_STRIPHDR = 0x17 // for now only darwin supports this option
|
||||||
|
|
||||||
|
// ListenPacket listens for incoming ICMP packets addressed to
|
||||||
|
// address. See net.Dial for the syntax of address.
|
||||||
|
//
|
||||||
|
// For non-privileged datagram-oriented ICMP endpoints, network must
|
||||||
|
// be "udp4" or "udp6". The endpoint allows to read, write a few
|
||||||
|
// limited ICMP messages such as echo request and echo reply.
|
||||||
|
// Currently only Darwin and Linux support this.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
// ListenPacket("udp4", "192.168.0.1")
|
||||||
|
// ListenPacket("udp4", "0.0.0.0")
|
||||||
|
// ListenPacket("udp6", "fe80::1%en0")
|
||||||
|
// ListenPacket("udp6", "::")
|
||||||
|
//
|
||||||
|
// For privileged raw ICMP endpoints, network must be "ip4" or "ip6"
|
||||||
|
// followed by a colon and an ICMP protocol number or name.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
// ListenPacket("ip4:icmp", "192.168.0.1")
|
||||||
|
// ListenPacket("ip4:1", "0.0.0.0")
|
||||||
|
// ListenPacket("ip6:ipv6-icmp", "fe80::1%en0")
|
||||||
|
// ListenPacket("ip6:58", "::")
|
||||||
|
func ListenPacket(network, address string) (*PacketConn, error) {
|
||||||
|
var family, proto int
|
||||||
|
switch network {
|
||||||
|
case "udp4":
|
||||||
|
family, proto = syscall.AF_INET, iana.ProtocolICMP
|
||||||
|
case "udp6":
|
||||||
|
family, proto = syscall.AF_INET6, iana.ProtocolIPv6ICMP
|
||||||
|
default:
|
||||||
|
i := last(network, ':')
|
||||||
|
if i < 0 {
|
||||||
|
i = len(network)
|
||||||
|
}
|
||||||
|
switch network[:i] {
|
||||||
|
case "ip4":
|
||||||
|
proto = iana.ProtocolICMP
|
||||||
|
case "ip6":
|
||||||
|
proto = iana.ProtocolIPv6ICMP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var cerr error
|
||||||
|
var c net.PacketConn
|
||||||
|
switch family {
|
||||||
|
case syscall.AF_INET, syscall.AF_INET6:
|
||||||
|
s, err := syscall.Socket(family, syscall.SOCK_DGRAM, proto)
|
||||||
|
if err != nil {
|
||||||
|
return nil, os.NewSyscallError("socket", err)
|
||||||
|
}
|
||||||
|
if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && family == syscall.AF_INET {
|
||||||
|
if err := syscall.SetsockoptInt(s, iana.ProtocolIP, sysIP_STRIPHDR, 1); err != nil {
|
||||||
|
syscall.Close(s)
|
||||||
|
return nil, os.NewSyscallError("setsockopt", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sa, err := sockaddr(family, address)
|
||||||
|
if err != nil {
|
||||||
|
syscall.Close(s)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := syscall.Bind(s, sa); err != nil {
|
||||||
|
syscall.Close(s)
|
||||||
|
return nil, os.NewSyscallError("bind", err)
|
||||||
|
}
|
||||||
|
f := os.NewFile(uintptr(s), "datagram-oriented icmp")
|
||||||
|
c, cerr = net.FilePacketConn(f)
|
||||||
|
f.Close()
|
||||||
|
default:
|
||||||
|
c, cerr = net.ListenPacket(network, address)
|
||||||
|
}
|
||||||
|
if cerr != nil {
|
||||||
|
return nil, cerr
|
||||||
|
}
|
||||||
|
switch proto {
|
||||||
|
case iana.ProtocolICMP:
|
||||||
|
return &PacketConn{c: c, p4: ipv4.NewPacketConn(c)}, nil
|
||||||
|
case iana.ProtocolIPv6ICMP:
|
||||||
|
return &PacketConn{c: c, p6: ipv6.NewPacketConn(c)}, nil
|
||||||
|
default:
|
||||||
|
return &PacketConn{c: c}, nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows
|
||||||
|
// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!windows
|
||||||
|
|
||||||
|
package icmp
|
||||||
|
|
||||||
|
// ListenPacket listens for incoming ICMP packets addressed to
|
||||||
|
// address. See net.Dial for the syntax of address.
|
||||||
|
//
|
||||||
|
// For non-privileged datagram-oriented ICMP endpoints, network must
|
||||||
|
// be "udp4" or "udp6". The endpoint allows to read, write a few
|
||||||
|
// limited ICMP messages such as echo request and echo reply.
|
||||||
|
// Currently only Darwin and Linux support this.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
// ListenPacket("udp4", "192.168.0.1")
|
||||||
|
// ListenPacket("udp4", "0.0.0.0")
|
||||||
|
// ListenPacket("udp6", "fe80::1%en0")
|
||||||
|
// ListenPacket("udp6", "::")
|
||||||
|
//
|
||||||
|
// For privileged raw ICMP endpoints, network must be "ip4" or "ip6"
|
||||||
|
// followed by a colon and an ICMP protocol number or name.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
// ListenPacket("ip4:icmp", "192.168.0.1")
|
||||||
|
// ListenPacket("ip4:1", "0.0.0.0")
|
||||||
|
// ListenPacket("ip6:ipv6-icmp", "fe80::1%en0")
|
||||||
|
// ListenPacket("ip6:58", "::")
|
||||||
|
func ListenPacket(network, address string) (*PacketConn, error) {
|
||||||
|
return nil, errNotImplemented
|
||||||
|
}
|
|
@ -0,0 +1,162 @@
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package icmp provides basic functions for the manipulation of
|
||||||
|
// messages used in the Internet Control Message Protocols,
|
||||||
|
// ICMPv4 and ICMPv6.
|
||||||
|
//
|
||||||
|
// ICMPv4 and ICMPv6 are defined in RFC 792 and RFC 4443.
|
||||||
|
// Multi-part message support for ICMP is defined in RFC 4884.
|
||||||
|
// ICMP extensions for MPLS are defined in RFC 4950.
|
||||||
|
// ICMP extensions for interface and next-hop identification are
|
||||||
|
// defined in RFC 5837.
|
||||||
|
// PROBE: A utility for probing interfaces is defined in RFC 8335.
|
||||||
|
package icmp // import "golang.org/x/net/icmp"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"golang.org/x/net/internal/iana"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BUG(mikio): This package is not implemented on JS, NaCl and Plan 9.
|
||||||
|
|
||||||
|
var (
|
||||||
|
errInvalidConn = errors.New("invalid connection")
|
||||||
|
errInvalidProtocol = errors.New("invalid protocol")
|
||||||
|
errMessageTooShort = errors.New("message too short")
|
||||||
|
errHeaderTooShort = errors.New("header too short")
|
||||||
|
errBufferTooShort = errors.New("buffer too short")
|
||||||
|
errInvalidBody = errors.New("invalid body")
|
||||||
|
errNoExtension = errors.New("no extension")
|
||||||
|
errInvalidExtension = errors.New("invalid extension")
|
||||||
|
errNotImplemented = errors.New("not implemented on " + runtime.GOOS + "/" + runtime.GOARCH)
|
||||||
|
)
|
||||||
|
|
||||||
|
func checksum(b []byte) uint16 {
|
||||||
|
csumcv := len(b) - 1 // checksum coverage
|
||||||
|
s := uint32(0)
|
||||||
|
for i := 0; i < csumcv; i += 2 {
|
||||||
|
s += uint32(b[i+1])<<8 | uint32(b[i])
|
||||||
|
}
|
||||||
|
if csumcv&1 == 0 {
|
||||||
|
s += uint32(b[csumcv])
|
||||||
|
}
|
||||||
|
s = s>>16 + s&0xffff
|
||||||
|
s = s + s>>16
|
||||||
|
return ^uint16(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Type represents an ICMP message type.
|
||||||
|
type Type interface {
|
||||||
|
Protocol() int
|
||||||
|
}
|
||||||
|
|
||||||
|
// A Message represents an ICMP message.
|
||||||
|
type Message struct {
|
||||||
|
Type Type // type, either ipv4.ICMPType or ipv6.ICMPType
|
||||||
|
Code int // code
|
||||||
|
Checksum int // checksum
|
||||||
|
Body MessageBody // body
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal returns the binary encoding of the ICMP message m.
|
||||||
|
//
|
||||||
|
// For an ICMPv4 message, the returned message always contains the
|
||||||
|
// calculated checksum field.
|
||||||
|
//
|
||||||
|
// For an ICMPv6 message, the returned message contains the calculated
|
||||||
|
// checksum field when psh is not nil, otherwise the kernel will
|
||||||
|
// compute the checksum field during the message transmission.
|
||||||
|
// When psh is not nil, it must be the pseudo header for IPv6.
|
||||||
|
func (m *Message) Marshal(psh []byte) ([]byte, error) {
|
||||||
|
var mtype byte
|
||||||
|
switch typ := m.Type.(type) {
|
||||||
|
case ipv4.ICMPType:
|
||||||
|
mtype = byte(typ)
|
||||||
|
case ipv6.ICMPType:
|
||||||
|
mtype = byte(typ)
|
||||||
|
default:
|
||||||
|
return nil, errInvalidProtocol
|
||||||
|
}
|
||||||
|
b := []byte{mtype, byte(m.Code), 0, 0}
|
||||||
|
proto := m.Type.Protocol()
|
||||||
|
if proto == iana.ProtocolIPv6ICMP && psh != nil {
|
||||||
|
b = append(psh, b...)
|
||||||
|
}
|
||||||
|
if m.Body != nil && m.Body.Len(proto) != 0 {
|
||||||
|
mb, err := m.Body.Marshal(proto)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b = append(b, mb...)
|
||||||
|
}
|
||||||
|
if proto == iana.ProtocolIPv6ICMP {
|
||||||
|
if psh == nil { // cannot calculate checksum here
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
off, l := 2*net.IPv6len, len(b)-len(psh)
|
||||||
|
binary.BigEndian.PutUint32(b[off:off+4], uint32(l))
|
||||||
|
}
|
||||||
|
s := checksum(b)
|
||||||
|
// Place checksum back in header; using ^= avoids the
|
||||||
|
// assumption the checksum bytes are zero.
|
||||||
|
b[len(psh)+2] ^= byte(s)
|
||||||
|
b[len(psh)+3] ^= byte(s >> 8)
|
||||||
|
return b[len(psh):], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var parseFns = map[Type]func(int, Type, []byte) (MessageBody, error){
|
||||||
|
ipv4.ICMPTypeDestinationUnreachable: parseDstUnreach,
|
||||||
|
ipv4.ICMPTypeTimeExceeded: parseTimeExceeded,
|
||||||
|
ipv4.ICMPTypeParameterProblem: parseParamProb,
|
||||||
|
|
||||||
|
ipv4.ICMPTypeEcho: parseEcho,
|
||||||
|
ipv4.ICMPTypeEchoReply: parseEcho,
|
||||||
|
ipv4.ICMPTypeExtendedEchoRequest: parseExtendedEchoRequest,
|
||||||
|
ipv4.ICMPTypeExtendedEchoReply: parseExtendedEchoReply,
|
||||||
|
|
||||||
|
ipv6.ICMPTypeDestinationUnreachable: parseDstUnreach,
|
||||||
|
ipv6.ICMPTypePacketTooBig: parsePacketTooBig,
|
||||||
|
ipv6.ICMPTypeTimeExceeded: parseTimeExceeded,
|
||||||
|
ipv6.ICMPTypeParameterProblem: parseParamProb,
|
||||||
|
|
||||||
|
ipv6.ICMPTypeEchoRequest: parseEcho,
|
||||||
|
ipv6.ICMPTypeEchoReply: parseEcho,
|
||||||
|
ipv6.ICMPTypeExtendedEchoRequest: parseExtendedEchoRequest,
|
||||||
|
ipv6.ICMPTypeExtendedEchoReply: parseExtendedEchoReply,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseMessage parses b as an ICMP message.
|
||||||
|
// The provided proto must be either the ICMPv4 or ICMPv6 protocol
|
||||||
|
// number.
|
||||||
|
func ParseMessage(proto int, b []byte) (*Message, error) {
|
||||||
|
if len(b) < 4 {
|
||||||
|
return nil, errMessageTooShort
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
m := &Message{Code: int(b[1]), Checksum: int(binary.BigEndian.Uint16(b[2:4]))}
|
||||||
|
switch proto {
|
||||||
|
case iana.ProtocolICMP:
|
||||||
|
m.Type = ipv4.ICMPType(b[0])
|
||||||
|
case iana.ProtocolIPv6ICMP:
|
||||||
|
m.Type = ipv6.ICMPType(b[0])
|
||||||
|
default:
|
||||||
|
return nil, errInvalidProtocol
|
||||||
|
}
|
||||||
|
if fn, ok := parseFns[m.Type]; !ok {
|
||||||
|
m.Body, err = parseRawBody(proto, b[4:])
|
||||||
|
} else {
|
||||||
|
m.Body, err = fn(proto, m.Type, b[4:])
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package icmp
|
||||||
|
|
||||||
|
// A MessageBody represents an ICMP message body.
|
||||||
|
type MessageBody interface {
|
||||||
|
// Len returns the length of ICMP message body.
|
||||||
|
// The provided proto must be either the ICMPv4 or ICMPv6
|
||||||
|
// protocol number.
|
||||||
|
Len(proto int) int
|
||||||
|
|
||||||
|
// Marshal returns the binary encoding of ICMP message body.
|
||||||
|
// The provided proto must be either the ICMPv4 or ICMPv6
|
||||||
|
// protocol number.
|
||||||
|
Marshal(proto int) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A RawBody represents a raw message body.
|
||||||
|
//
|
||||||
|
// A raw message body is excluded from message processing and can be
|
||||||
|
// used to construct applications such as protocol conformance
|
||||||
|
// testing.
|
||||||
|
type RawBody struct {
|
||||||
|
Data []byte // data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len implements the Len method of MessageBody interface.
|
||||||
|
func (p *RawBody) Len(proto int) int {
|
||||||
|
if p == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return len(p.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal implements the Marshal method of MessageBody interface.
|
||||||
|
func (p *RawBody) Marshal(proto int) ([]byte, error) {
|
||||||
|
return p.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseRawBody parses b as an ICMP message body.
|
||||||
|
func parseRawBody(proto int, b []byte) (MessageBody, error) {
|
||||||
|
p := &RawBody{Data: make([]byte, len(b))}
|
||||||
|
copy(p.Data, b)
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A DefaultMessageBody represents the default message body.
|
||||||
|
//
|
||||||
|
// Deprecated: Use RawBody instead.
|
||||||
|
type DefaultMessageBody = RawBody
|
|
@ -0,0 +1,77 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package icmp
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
|
||||||
|
// MPLSLabel represents an MPLS label stack entry.
|
||||||
|
type MPLSLabel struct {
|
||||||
|
Label int // label value
|
||||||
|
TC int // traffic class; formerly experimental use
|
||||||
|
S bool // bottom of stack
|
||||||
|
TTL int // time to live
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
classMPLSLabelStack = 1
|
||||||
|
typeIncomingMPLSLabelStack = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// MPLSLabelStack represents an MPLS label stack.
|
||||||
|
type MPLSLabelStack struct {
|
||||||
|
Class int // extension object class number
|
||||||
|
Type int // extension object sub-type
|
||||||
|
Labels []MPLSLabel
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len implements the Len method of Extension interface.
|
||||||
|
func (ls *MPLSLabelStack) Len(proto int) int {
|
||||||
|
return 4 + (4 * len(ls.Labels))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal implements the Marshal method of Extension interface.
|
||||||
|
func (ls *MPLSLabelStack) Marshal(proto int) ([]byte, error) {
|
||||||
|
b := make([]byte, ls.Len(proto))
|
||||||
|
if err := ls.marshal(proto, b); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *MPLSLabelStack) marshal(proto int, b []byte) error {
|
||||||
|
l := ls.Len(proto)
|
||||||
|
binary.BigEndian.PutUint16(b[:2], uint16(l))
|
||||||
|
b[2], b[3] = classMPLSLabelStack, typeIncomingMPLSLabelStack
|
||||||
|
off := 4
|
||||||
|
for _, ll := range ls.Labels {
|
||||||
|
b[off], b[off+1], b[off+2] = byte(ll.Label>>12), byte(ll.Label>>4&0xff), byte(ll.Label<<4&0xf0)
|
||||||
|
b[off+2] |= byte(ll.TC << 1 & 0x0e)
|
||||||
|
if ll.S {
|
||||||
|
b[off+2] |= 0x1
|
||||||
|
}
|
||||||
|
b[off+3] = byte(ll.TTL)
|
||||||
|
off += 4
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseMPLSLabelStack(b []byte) (Extension, error) {
|
||||||
|
ls := &MPLSLabelStack{
|
||||||
|
Class: int(b[2]),
|
||||||
|
Type: int(b[3]),
|
||||||
|
}
|
||||||
|
for b = b[4:]; len(b) >= 4; b = b[4:] {
|
||||||
|
ll := MPLSLabel{
|
||||||
|
Label: int(b[0])<<12 | int(b[1])<<4 | int(b[2])>>4,
|
||||||
|
TC: int(b[2]&0x0e) >> 1,
|
||||||
|
TTL: int(b[3]),
|
||||||
|
}
|
||||||
|
if b[2]&0x1 != 0 {
|
||||||
|
ll.S = true
|
||||||
|
}
|
||||||
|
ls.Labels = append(ls.Labels, ll)
|
||||||
|
}
|
||||||
|
return ls, nil
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package icmp
|
||||||
|
|
||||||
|
import "golang.org/x/net/internal/iana"
|
||||||
|
|
||||||
|
// multipartMessageBodyDataLen takes b as an original datagram and
|
||||||
|
// exts as extensions, and returns a required length for message body
|
||||||
|
// and a required length for a padded original datagram in wire
|
||||||
|
// format.
|
||||||
|
func multipartMessageBodyDataLen(proto int, withOrigDgram bool, b []byte, exts []Extension) (bodyLen, dataLen int) {
|
||||||
|
bodyLen = 4 // length of leading octets
|
||||||
|
var extLen int
|
||||||
|
var rawExt bool // raw extension may contain an empty object
|
||||||
|
for _, ext := range exts {
|
||||||
|
extLen += ext.Len(proto)
|
||||||
|
if _, ok := ext.(*RawExtension); ok {
|
||||||
|
rawExt = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if extLen > 0 && withOrigDgram {
|
||||||
|
dataLen = multipartMessageOrigDatagramLen(proto, b)
|
||||||
|
} else {
|
||||||
|
dataLen = len(b)
|
||||||
|
}
|
||||||
|
if extLen > 0 || rawExt {
|
||||||
|
bodyLen += 4 // length of extension header
|
||||||
|
}
|
||||||
|
bodyLen += dataLen + extLen
|
||||||
|
return bodyLen, dataLen
|
||||||
|
}
|
||||||
|
|
||||||
|
// multipartMessageOrigDatagramLen takes b as an original datagram,
|
||||||
|
// and returns a required length for a padded orignal datagram in wire
|
||||||
|
// format.
|
||||||
|
func multipartMessageOrigDatagramLen(proto int, b []byte) int {
|
||||||
|
roundup := func(b []byte, align int) int {
|
||||||
|
// According to RFC 4884, the padded original datagram
|
||||||
|
// field must contain at least 128 octets.
|
||||||
|
if len(b) < 128 {
|
||||||
|
return 128
|
||||||
|
}
|
||||||
|
r := len(b)
|
||||||
|
return (r + align - 1) &^ (align - 1)
|
||||||
|
}
|
||||||
|
switch proto {
|
||||||
|
case iana.ProtocolICMP:
|
||||||
|
return roundup(b, 4)
|
||||||
|
case iana.ProtocolIPv6ICMP:
|
||||||
|
return roundup(b, 8)
|
||||||
|
default:
|
||||||
|
return len(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshalMultipartMessageBody takes data as an original datagram and
|
||||||
|
// exts as extesnsions, and returns a binary encoding of message body.
|
||||||
|
// It can be used for non-multipart message bodies when exts is nil.
|
||||||
|
func marshalMultipartMessageBody(proto int, withOrigDgram bool, data []byte, exts []Extension) ([]byte, error) {
|
||||||
|
bodyLen, dataLen := multipartMessageBodyDataLen(proto, withOrigDgram, data, exts)
|
||||||
|
b := make([]byte, bodyLen)
|
||||||
|
copy(b[4:], data)
|
||||||
|
if len(exts) > 0 {
|
||||||
|
b[4+dataLen] = byte(extensionVersion << 4)
|
||||||
|
off := 4 + dataLen + 4 // leading octets, data, extension header
|
||||||
|
for _, ext := range exts {
|
||||||
|
switch ext := ext.(type) {
|
||||||
|
case *MPLSLabelStack:
|
||||||
|
if err := ext.marshal(proto, b[off:]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
off += ext.Len(proto)
|
||||||
|
case *InterfaceInfo:
|
||||||
|
attrs, l := ext.attrsAndLen(proto)
|
||||||
|
if err := ext.marshal(proto, b[off:], attrs, l); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
off += ext.Len(proto)
|
||||||
|
case *InterfaceIdent:
|
||||||
|
if err := ext.marshal(proto, b[off:]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
off += ext.Len(proto)
|
||||||
|
case *RawExtension:
|
||||||
|
copy(b[off:], ext.Data)
|
||||||
|
off += ext.Len(proto)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s := checksum(b[4+dataLen:])
|
||||||
|
b[4+dataLen+2] ^= byte(s)
|
||||||
|
b[4+dataLen+3] ^= byte(s >> 8)
|
||||||
|
if withOrigDgram {
|
||||||
|
switch proto {
|
||||||
|
case iana.ProtocolICMP:
|
||||||
|
b[1] = byte(dataLen / 4)
|
||||||
|
case iana.ProtocolIPv6ICMP:
|
||||||
|
b[0] = byte(dataLen / 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseMultipartMessageBody parses b as either a non-multipart
|
||||||
|
// message body or a multipart message body.
|
||||||
|
func parseMultipartMessageBody(proto int, typ Type, b []byte) ([]byte, []Extension, error) {
|
||||||
|
var l int
|
||||||
|
switch proto {
|
||||||
|
case iana.ProtocolICMP:
|
||||||
|
l = 4 * int(b[1])
|
||||||
|
case iana.ProtocolIPv6ICMP:
|
||||||
|
l = 8 * int(b[0])
|
||||||
|
}
|
||||||
|
if len(b) == 4 {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
exts, l, err := parseExtensions(typ, b[4:], l)
|
||||||
|
if err != nil {
|
||||||
|
l = len(b) - 4
|
||||||
|
}
|
||||||
|
var data []byte
|
||||||
|
if l > 0 {
|
||||||
|
data = make([]byte, l)
|
||||||
|
copy(data, b[4:])
|
||||||
|
}
|
||||||
|
return data, exts, nil
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package icmp
|
||||||
|
|
||||||
|
import "encoding/binary"
|
||||||
|
|
||||||
|
// A PacketTooBig represents an ICMP packet too big message body.
|
||||||
|
type PacketTooBig struct {
|
||||||
|
MTU int // maximum transmission unit of the nexthop link
|
||||||
|
Data []byte // data, known as original datagram field
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len implements the Len method of MessageBody interface.
|
||||||
|
func (p *PacketTooBig) Len(proto int) int {
|
||||||
|
if p == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 4 + len(p.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal implements the Marshal method of MessageBody interface.
|
||||||
|
func (p *PacketTooBig) Marshal(proto int) ([]byte, error) {
|
||||||
|
b := make([]byte, 4+len(p.Data))
|
||||||
|
binary.BigEndian.PutUint32(b[:4], uint32(p.MTU))
|
||||||
|
copy(b[4:], p.Data)
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parsePacketTooBig parses b as an ICMP packet too big message body.
|
||||||
|
func parsePacketTooBig(proto int, _ Type, b []byte) (MessageBody, error) {
|
||||||
|
bodyLen := len(b)
|
||||||
|
if bodyLen < 4 {
|
||||||
|
return nil, errMessageTooShort
|
||||||
|
}
|
||||||
|
p := &PacketTooBig{MTU: int(binary.BigEndian.Uint32(b[:4]))}
|
||||||
|
if bodyLen > 4 {
|
||||||
|
p.Data = make([]byte, bodyLen-4)
|
||||||
|
copy(p.Data, b[4:])
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package icmp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"golang.org/x/net/internal/iana"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A ParamProb represents an ICMP parameter problem message body.
|
||||||
|
type ParamProb struct {
|
||||||
|
Pointer uintptr // offset within the data where the error was detected
|
||||||
|
Data []byte // data, known as original datagram field
|
||||||
|
Extensions []Extension // extensions
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len implements the Len method of MessageBody interface.
|
||||||
|
func (p *ParamProb) Len(proto int) int {
|
||||||
|
if p == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
l, _ := multipartMessageBodyDataLen(proto, true, p.Data, p.Extensions)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal implements the Marshal method of MessageBody interface.
|
||||||
|
func (p *ParamProb) Marshal(proto int) ([]byte, error) {
|
||||||
|
switch proto {
|
||||||
|
case iana.ProtocolICMP:
|
||||||
|
if !validExtensions(ipv4.ICMPTypeParameterProblem, p.Extensions) {
|
||||||
|
return nil, errInvalidExtension
|
||||||
|
}
|
||||||
|
b, err := marshalMultipartMessageBody(proto, true, p.Data, p.Extensions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b[0] = byte(p.Pointer)
|
||||||
|
return b, nil
|
||||||
|
case iana.ProtocolIPv6ICMP:
|
||||||
|
b := make([]byte, p.Len(proto))
|
||||||
|
binary.BigEndian.PutUint32(b[:4], uint32(p.Pointer))
|
||||||
|
copy(b[4:], p.Data)
|
||||||
|
return b, nil
|
||||||
|
default:
|
||||||
|
return nil, errInvalidProtocol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseParamProb parses b as an ICMP parameter problem message body.
|
||||||
|
func parseParamProb(proto int, typ Type, b []byte) (MessageBody, error) {
|
||||||
|
if len(b) < 4 {
|
||||||
|
return nil, errMessageTooShort
|
||||||
|
}
|
||||||
|
p := &ParamProb{}
|
||||||
|
if proto == iana.ProtocolIPv6ICMP {
|
||||||
|
p.Pointer = uintptr(binary.BigEndian.Uint32(b[:4]))
|
||||||
|
p.Data = make([]byte, len(b)-4)
|
||||||
|
copy(p.Data, b[4:])
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
p.Pointer = uintptr(b[0])
|
||||||
|
var err error
|
||||||
|
p.Data, p.Extensions, err = parseMultipartMessageBody(proto, typ, b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue