Merge pull request #35677 from fcrisciani/netdb-debug-tool

Netdb debug tool
This commit is contained in:
Sebastiaan van Stijn 2017-12-06 16:30:11 -08:00 committed by GitHub
commit 58dec54d1f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 1722 additions and 461 deletions

View file

@ -59,6 +59,8 @@ func installCommonConfigFlags(conf *config.Config, flags *pflag.FlagSet) {
flags.IntVar(&maxConcurrentDownloads, "max-concurrent-downloads", config.DefaultMaxConcurrentDownloads, "Set the max concurrent downloads for each pull")
flags.IntVar(&maxConcurrentUploads, "max-concurrent-uploads", config.DefaultMaxConcurrentUploads, "Set the max concurrent uploads for each push")
flags.IntVar(&conf.ShutdownTimeout, "shutdown-timeout", defaultShutdownTimeout, "Set the default shutdown timeout")
flags.IntVar(&conf.NetworkDiagnosticPort, "network-diagnostic-port", 0, "TCP port number of the network diagnostic server")
flags.MarkHidden("network-diagnostic-port")
flags.StringVar(&conf.SwarmDefaultAdvertiseAddr, "swarm-default-advertise-addr", "", "Set default address or interface for swarm advertised address")
flags.BoolVar(&conf.Experimental, "experimental", false, "Enable experimental features")

View file

@ -85,26 +85,27 @@ type CommonTLSOptions struct {
// It includes json tags to deserialize configuration from a file
// using the same names that the flags in the command line use.
type CommonConfig struct {
AuthzMiddleware *authorization.Middleware `json:"-"`
AuthorizationPlugins []string `json:"authorization-plugins,omitempty"` // AuthorizationPlugins holds list of authorization plugins
AutoRestart bool `json:"-"`
Context map[string][]string `json:"-"`
DisableBridge bool `json:"-"`
DNS []string `json:"dns,omitempty"`
DNSOptions []string `json:"dns-opts,omitempty"`
DNSSearch []string `json:"dns-search,omitempty"`
ExecOptions []string `json:"exec-opts,omitempty"`
GraphDriver string `json:"storage-driver,omitempty"`
GraphOptions []string `json:"storage-opts,omitempty"`
Labels []string `json:"labels,omitempty"`
Mtu int `json:"mtu,omitempty"`
Pidfile string `json:"pidfile,omitempty"`
RawLogs bool `json:"raw-logs,omitempty"`
RootDeprecated string `json:"graph,omitempty"`
Root string `json:"data-root,omitempty"`
ExecRoot string `json:"exec-root,omitempty"`
SocketGroup string `json:"group,omitempty"`
CorsHeaders string `json:"api-cors-header,omitempty"`
AuthzMiddleware *authorization.Middleware `json:"-"`
AuthorizationPlugins []string `json:"authorization-plugins,omitempty"` // AuthorizationPlugins holds list of authorization plugins
AutoRestart bool `json:"-"`
Context map[string][]string `json:"-"`
DisableBridge bool `json:"-"`
DNS []string `json:"dns,omitempty"`
DNSOptions []string `json:"dns-opts,omitempty"`
DNSSearch []string `json:"dns-search,omitempty"`
ExecOptions []string `json:"exec-opts,omitempty"`
GraphDriver string `json:"storage-driver,omitempty"`
GraphOptions []string `json:"storage-opts,omitempty"`
Labels []string `json:"labels,omitempty"`
Mtu int `json:"mtu,omitempty"`
NetworkDiagnosticPort int `json:"network-diagnostic-port,omitempty"`
Pidfile string `json:"pidfile,omitempty"`
RawLogs bool `json:"raw-logs,omitempty"`
RootDeprecated string `json:"graph,omitempty"`
Root string `json:"data-root,omitempty"`
ExecRoot string `json:"exec-root,omitempty"`
SocketGroup string `json:"group,omitempty"`
CorsHeaders string `json:"api-cors-header,omitempty"`
// TrustKeyPath is used to generate the daemon ID and for signing schema 1 manifests
// when pushing to a registry which does not support schema 2. This field is marked as

View file

@ -61,6 +61,9 @@ func (daemon *Daemon) Reload(conf *config.Config) (err error) {
if err := daemon.reloadLiveRestore(conf, attributes); err != nil {
return err
}
if err := daemon.reloadNetworkDiagnosticPort(conf, attributes); err != nil {
return err
}
return nil
}
@ -308,3 +311,18 @@ func (daemon *Daemon) reloadLiveRestore(conf *config.Config, attributes map[stri
attributes["live-restore"] = fmt.Sprintf("%t", daemon.configStore.LiveRestoreEnabled)
return nil
}
// reloadNetworkDiagnosticPort updates the network controller starting the diagnose mode if the config is valid
func (daemon *Daemon) reloadNetworkDiagnosticPort(conf *config.Config, attributes map[string]string) error {
if conf == nil || daemon.netController == nil {
return nil
}
// Enable the network diagnose if the flag is set with a valid port withing the range
if conf.IsValueSet("network-diagnostic-port") && conf.NetworkDiagnosticPort > 0 && conf.NetworkDiagnosticPort < 65536 {
logrus.Warnf("Calling the diagnostic start with %d", conf.NetworkDiagnosticPort)
daemon.netController.StartDiagnose(conf.NetworkDiagnosticPort)
} else {
daemon.netController.StopDiagnose()
}
return nil
}

View file

@ -10,6 +10,7 @@ import (
"github.com/docker/docker/pkg/discovery"
_ "github.com/docker/docker/pkg/discovery/memory"
"github.com/docker/docker/registry"
"github.com/docker/libnetwork"
"github.com/stretchr/testify/assert"
)
@ -479,3 +480,71 @@ func TestDaemonDiscoveryReloadOnlyClusterAdvertise(t *testing.T) {
t.Fatal(e)
}
}
func TestDaemonReloadNetworkDiagnosticPort(t *testing.T) {
daemon := &Daemon{}
daemon.configStore = &config.Config{}
valuesSet := make(map[string]interface{})
valuesSet["network-diagnostic-port"] = 2000
enableConfig := &config.Config{
CommonConfig: config.CommonConfig{
NetworkDiagnosticPort: 2000,
ValuesSet: valuesSet,
},
}
disableConfig := &config.Config{
CommonConfig: config.CommonConfig{},
}
netOptions, err := daemon.networkOptions(enableConfig, nil, nil)
if err != nil {
t.Fatal(err)
}
controller, err := libnetwork.New(netOptions...)
if err != nil {
t.Fatal(err)
}
daemon.netController = controller
// Enable/Disable the server for some iterations
for i := 0; i < 10; i++ {
enableConfig.CommonConfig.NetworkDiagnosticPort++
if err := daemon.Reload(enableConfig); err != nil {
t.Fatal(err)
}
// Check that the diagnose is enabled
if !daemon.netController.IsDiagnoseEnabled() {
t.Fatalf("diagnosed should be enable")
}
// Reload
if err := daemon.Reload(disableConfig); err != nil {
t.Fatal(err)
}
// Check that the diagnose is disabled
if daemon.netController.IsDiagnoseEnabled() {
t.Fatalf("diagnosed should be disable")
}
}
enableConfig.CommonConfig.NetworkDiagnosticPort++
// 2 times the enable should not create problems
if err := daemon.Reload(enableConfig); err != nil {
t.Fatal(err)
}
// Check that the diagnose is enabled
if !daemon.netController.IsDiagnoseEnabled() {
t.Fatalf("diagnosed should be enable")
}
// Check that another reload does not cause issues
if err := daemon.Reload(enableConfig); err != nil {
t.Fatal(err)
}
// Check that the diagnose is enable
if !daemon.netController.IsDiagnoseEnabled() {
t.Fatalf("diagnosed should be enable")
}
}

View file

@ -30,7 +30,7 @@ github.com/moby/buildkit aaff9d591ef128560018433fe61beb802e149de8
github.com/tonistiigi/fsutil dea3a0da73aee887fc02142d995be764106ac5e2
#get libnetwork packages
github.com/docker/libnetwork 64ae58878fc8f95e4a167499d654e13fa36abdc7
github.com/docker/libnetwork 9bca9a4a220b158cc94402e0f8c2c7714eb6f503
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80
github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec
@ -42,7 +42,7 @@ github.com/hashicorp/go-multierror fcdddc395df1ddf4247c69bd436e84cfa0733f7e
github.com/hashicorp/serf 598c54895cc5a7b1a24a398d635e8c0ea0959870
github.com/docker/libkv 1d8431073ae03cdaedb198a89722f3aab6d418ef
github.com/vishvananda/netns 604eaf189ee867d8c147fafc28def2394e878d25
github.com/vishvananda/netlink bd6d5de5ccef2d66b0a26177928d0d8895d7f969
github.com/vishvananda/netlink b2de5d10e38ecce8607e6b438b6d174f389a004e
github.com/BurntSushi/toml f706d00e3de6abe700c994cdd545a1a4915af060
github.com/samuel/go-zookeeper d0e0d8e11f318e000a8cc434616d69e329edc374
github.com/deckarep/golang-set ef32fa3046d9f249d399f98ebaf9be944430fd1d

View file

@ -293,11 +293,13 @@ func (c *controller) agentInit(listenAddr, bindAddrOrInterface, advertiseAddr, d
c.Config().Daemon.NetworkControlPlaneMTU, netDBConf.PacketBufferSize)
}
nDB, err := networkdb.New(netDBConf)
if err != nil {
return err
}
// Register the diagnose handlers
c.DiagnoseServer.RegisterHandler(nDB, networkdb.NetDbPaths2Func)
var cancelList []func()
ch, cancel := nDB.Watch(libnetworkEPTable, "", "")
cancelList = append(cancelList, cancel)
@ -436,7 +438,7 @@ func (n *network) Services() map[string]ServiceInfo {
for eid, value := range entries {
var epRec EndpointRecord
nid := n.ID()
if err := proto.Unmarshal(value.([]byte), &epRec); err != nil {
if err := proto.Unmarshal(value.Value, &epRec); err != nil {
logrus.Errorf("Unmarshal of libnetworkEPTable failed for endpoint %s in network %s, %v", eid, nid, err)
continue
}
@ -461,7 +463,7 @@ func (n *network) Services() map[string]ServiceInfo {
}
entries := agent.networkDB.GetTableByNetwork(table.name, n.id)
for key, value := range entries {
epID, info := d.DecodeTableEntry(table.name, key, value.([]byte))
epID, info := d.DecodeTableEntry(table.name, key, value.Value)
if ep, ok := eps[epID]; !ok {
logrus.Errorf("Inconsistent driver and libnetwork state for endpoint %s", epID)
} else {

View file

@ -60,6 +60,7 @@ import (
"github.com/docker/libnetwork/cluster"
"github.com/docker/libnetwork/config"
"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/diagnose"
"github.com/docker/libnetwork/discoverapi"
"github.com/docker/libnetwork/driverapi"
"github.com/docker/libnetwork/drvregistry"
@ -133,6 +134,13 @@ type NetworkController interface {
// SetKeys configures the encryption key for gossip and overlay data path
SetKeys(keys []*types.EncryptionKey) error
// StartDiagnose start the network diagnose mode
StartDiagnose(port int)
// StopDiagnose start the network diagnose mode
StopDiagnose()
// IsDiagnoseEnabled returns true if the diagnose is enabled
IsDiagnoseEnabled() bool
}
// NetworkWalker is a client provided function which will be used to walk the Networks.
@ -167,6 +175,7 @@ type controller struct {
agentStopDone chan struct{}
keys []*types.EncryptionKey
clusterConfigAvailable bool
DiagnoseServer *diagnose.Server
sync.Mutex
}
@ -185,7 +194,9 @@ func New(cfgOptions ...config.Option) (NetworkController, error) {
serviceBindings: make(map[serviceKey]*service),
agentInitDone: make(chan struct{}),
networkLocker: locker.New(),
DiagnoseServer: diagnose.New(),
}
c.DiagnoseServer.Init()
if err := c.initStores(); err != nil {
return nil, err
@ -1291,3 +1302,28 @@ func (c *controller) Stop() {
c.stopExternalKeyListener()
osl.GC()
}
// StartDiagnose start the network diagnose mode
func (c *controller) StartDiagnose(port int) {
c.Lock()
if !c.DiagnoseServer.IsDebugEnable() {
c.DiagnoseServer.EnableDebug("127.0.0.1", port)
}
c.Unlock()
}
// StopDiagnose start the network diagnose mode
func (c *controller) StopDiagnose() {
c.Lock()
if c.DiagnoseServer.IsDebugEnable() {
c.DiagnoseServer.DisableDebug()
}
c.Unlock()
}
// IsDiagnoseEnabled returns true if the diagnose is enabled
func (c *controller) IsDiagnoseEnabled() bool {
c.Lock()
defer c.Unlock()
return c.DiagnoseServer.IsDebugEnable()
}

View file

@ -1,133 +0,0 @@
package diagnose
import (
"fmt"
"net"
"net/http"
"sync"
"github.com/sirupsen/logrus"
)
// HTTPHandlerFunc TODO
type HTTPHandlerFunc func(interface{}, http.ResponseWriter, *http.Request)
type httpHandlerCustom struct {
ctx interface{}
F func(interface{}, http.ResponseWriter, *http.Request)
}
// ServeHTTP TODO
func (h httpHandlerCustom) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.F(h.ctx, w, r)
}
var diagPaths2Func = map[string]HTTPHandlerFunc{
"/": notImplemented,
"/help": help,
"/ready": ready,
}
// Server when the debug is enabled exposes a
// This data structure is protected by the Agent mutex so does not require and additional mutex here
type Server struct {
sk net.Listener
port int
mux *http.ServeMux
registeredHanders []string
sync.Mutex
}
// Init TODO
func (n *Server) Init() {
n.mux = http.NewServeMux()
// Register local handlers
n.RegisterHandler(n, diagPaths2Func)
}
// RegisterHandler TODO
func (n *Server) RegisterHandler(ctx interface{}, hdlrs map[string]HTTPHandlerFunc) {
n.Lock()
defer n.Unlock()
for path, fun := range hdlrs {
n.mux.Handle(path, httpHandlerCustom{ctx, fun})
n.registeredHanders = append(n.registeredHanders, path)
}
}
// EnableDebug opens a TCP socket to debug the passed network DB
func (n *Server) EnableDebug(ip string, port int) {
n.Lock()
defer n.Unlock()
n.port = port
logrus.SetLevel(logrus.DebugLevel)
if n.sk != nil {
logrus.Infof("The server is already up and running")
return
}
logrus.Infof("Starting the server listening on %d for commands", port)
// // Create the socket
// var err error
// n.sk, err = net.Listen("tcp", listeningAddr)
// if err != nil {
// log.Fatal(err)
// }
//
// go func() {
// http.Serve(n.sk, n.mux)
// }()
http.ListenAndServe(fmt.Sprintf(":%d", port), n.mux)
}
// DisableDebug stop the dubug and closes the tcp socket
func (n *Server) DisableDebug() {
n.Lock()
defer n.Unlock()
n.sk.Close()
n.sk = nil
}
// IsDebugEnable returns true when the debug is enabled
func (n *Server) IsDebugEnable() bool {
n.Lock()
defer n.Unlock()
return n.sk != nil
}
func notImplemented(ctx interface{}, w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "URL path: %s no method implemented check /help\n", r.URL.Path)
}
func help(ctx interface{}, w http.ResponseWriter, r *http.Request) {
n, ok := ctx.(*Server)
if ok {
for _, path := range n.registeredHanders {
fmt.Fprintf(w, "%s\n", path)
}
}
}
func ready(ctx interface{}, w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "OK\n")
}
// DebugHTTPForm TODO
func DebugHTTPForm(r *http.Request) {
r.ParseForm()
for k, v := range r.Form {
logrus.Debugf("Form[%q] = %q\n", k, v)
}
}
// HTTPReplyError TODO
func HTTPReplyError(w http.ResponseWriter, message, usage string) {
fmt.Fprintf(w, "%s\n", message)
if usage != "" {
fmt.Fprintf(w, "Usage: %s\n", usage)
}
}

228
vendor/github.com/docker/libnetwork/diagnose/server.go generated vendored Normal file
View file

@ -0,0 +1,228 @@
package diagnose
import (
"context"
"encoding/json"
"fmt"
"net/http"
"sync"
"sync/atomic"
stackdump "github.com/docker/docker/pkg/signal"
"github.com/docker/libnetwork/common"
"github.com/sirupsen/logrus"
)
// HTTPHandlerFunc TODO
type HTTPHandlerFunc func(interface{}, http.ResponseWriter, *http.Request)
type httpHandlerCustom struct {
ctx interface{}
F func(interface{}, http.ResponseWriter, *http.Request)
}
// ServeHTTP TODO
func (h httpHandlerCustom) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.F(h.ctx, w, r)
}
var diagPaths2Func = map[string]HTTPHandlerFunc{
"/": notImplemented,
"/help": help,
"/ready": ready,
"/stackdump": stackTrace,
}
// Server when the debug is enabled exposes a
// This data structure is protected by the Agent mutex so does not require and additional mutex here
type Server struct {
enable int32
srv *http.Server
port int
mux *http.ServeMux
registeredHanders map[string]bool
sync.Mutex
}
// New creates a new diagnose server
func New() *Server {
return &Server{
registeredHanders: make(map[string]bool),
}
}
// Init initialize the mux for the http handling and register the base hooks
func (s *Server) Init() {
s.mux = http.NewServeMux()
// Register local handlers
s.RegisterHandler(s, diagPaths2Func)
}
// RegisterHandler allows to register new handlers to the mux and to a specific path
func (s *Server) RegisterHandler(ctx interface{}, hdlrs map[string]HTTPHandlerFunc) {
s.Lock()
defer s.Unlock()
for path, fun := range hdlrs {
if _, ok := s.registeredHanders[path]; ok {
continue
}
s.mux.Handle(path, httpHandlerCustom{ctx, fun})
s.registeredHanders[path] = true
}
}
// ServeHTTP this is the method called bu the ListenAndServe, and is needed to allow us to
// use our custom mux
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.mux.ServeHTTP(w, r)
}
// EnableDebug opens a TCP socket to debug the passed network DB
func (s *Server) EnableDebug(ip string, port int) {
s.Lock()
defer s.Unlock()
s.port = port
if s.enable == 1 {
logrus.Info("The server is already up and running")
return
}
logrus.Infof("Starting the diagnose server listening on %d for commands", port)
srv := &http.Server{Addr: fmt.Sprintf("127.0.0.1:%d", port), Handler: s}
s.srv = srv
s.enable = 1
go func(n *Server) {
// Ingore ErrServerClosed that is returned on the Shutdown call
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logrus.Errorf("ListenAndServe error: %s", err)
atomic.SwapInt32(&n.enable, 0)
}
}(s)
}
// DisableDebug stop the dubug and closes the tcp socket
func (s *Server) DisableDebug() {
s.Lock()
defer s.Unlock()
s.srv.Shutdown(context.Background())
s.srv = nil
s.enable = 0
logrus.Info("Disabling the diagnose server")
}
// IsDebugEnable returns true when the debug is enabled
func (s *Server) IsDebugEnable() bool {
s.Lock()
defer s.Unlock()
return s.enable == 1
}
func notImplemented(ctx interface{}, w http.ResponseWriter, r *http.Request) {
r.ParseForm()
_, json := ParseHTTPFormOptions(r)
rsp := WrongCommand("not implemented", fmt.Sprintf("URL path: %s no method implemented check /help\n", r.URL.Path))
// audit logs
log := logrus.WithFields(logrus.Fields{"component": "diagnose", "remoteIP": r.RemoteAddr, "method": common.CallerName(0), "url": r.URL.String()})
log.Info("command not implemented done")
HTTPReply(w, rsp, json)
}
func help(ctx interface{}, w http.ResponseWriter, r *http.Request) {
r.ParseForm()
_, json := ParseHTTPFormOptions(r)
// audit logs
log := logrus.WithFields(logrus.Fields{"component": "diagnose", "remoteIP": r.RemoteAddr, "method": common.CallerName(0), "url": r.URL.String()})
log.Info("help done")
n, ok := ctx.(*Server)
var result string
if ok {
for path := range n.registeredHanders {
result += fmt.Sprintf("%s\n", path)
}
HTTPReply(w, CommandSucceed(&StringCmd{Info: result}), json)
}
}
func ready(ctx interface{}, w http.ResponseWriter, r *http.Request) {
r.ParseForm()
_, json := ParseHTTPFormOptions(r)
// audit logs
log := logrus.WithFields(logrus.Fields{"component": "diagnose", "remoteIP": r.RemoteAddr, "method": common.CallerName(0), "url": r.URL.String()})
log.Info("ready done")
HTTPReply(w, CommandSucceed(&StringCmd{Info: "OK"}), json)
}
func stackTrace(ctx interface{}, w http.ResponseWriter, r *http.Request) {
r.ParseForm()
_, json := ParseHTTPFormOptions(r)
// audit logs
log := logrus.WithFields(logrus.Fields{"component": "diagnose", "remoteIP": r.RemoteAddr, "method": common.CallerName(0), "url": r.URL.String()})
log.Info("stack trace")
path, err := stackdump.DumpStacks("/tmp/")
if err != nil {
log.WithError(err).Error("failed to write goroutines dump")
HTTPReply(w, FailCommand(err), json)
} else {
log.Info("stack trace done")
HTTPReply(w, CommandSucceed(&StringCmd{Info: fmt.Sprintf("goroutine stacks written to %s", path)}), json)
}
}
// DebugHTTPForm helper to print the form url parameters
func DebugHTTPForm(r *http.Request) {
for k, v := range r.Form {
logrus.Debugf("Form[%q] = %q\n", k, v)
}
}
// JSONOutput contains details on JSON output printing
type JSONOutput struct {
enable bool
prettyPrint bool
}
// ParseHTTPFormOptions easily parse the JSON printing options
func ParseHTTPFormOptions(r *http.Request) (bool, *JSONOutput) {
_, unsafe := r.Form["unsafe"]
v, json := r.Form["json"]
var pretty bool
if len(v) > 0 {
pretty = v[0] == "pretty"
}
return unsafe, &JSONOutput{enable: json, prettyPrint: pretty}
}
// HTTPReply helper function that takes care of sending the message out
func HTTPReply(w http.ResponseWriter, r *HTTPResult, j *JSONOutput) (int, error) {
var response []byte
if j.enable {
w.Header().Set("Content-Type", "application/json")
var err error
if j.prettyPrint {
response, err = json.MarshalIndent(r, "", " ")
if err != nil {
response, _ = json.MarshalIndent(FailCommand(err), "", " ")
}
} else {
response, err = json.Marshal(r)
if err != nil {
response, _ = json.Marshal(FailCommand(err))
}
}
} else {
response = []byte(r.String())
}
return fmt.Fprint(w, string(response))
}

122
vendor/github.com/docker/libnetwork/diagnose/types.go generated vendored Normal file
View file

@ -0,0 +1,122 @@
package diagnose
import "fmt"
// StringInterface interface that has to be implemented by messages
type StringInterface interface {
String() string
}
// CommandSucceed creates a success message
func CommandSucceed(result StringInterface) *HTTPResult {
return &HTTPResult{
Message: "OK",
Details: result,
}
}
// FailCommand creates a failure message with error
func FailCommand(err error) *HTTPResult {
return &HTTPResult{
Message: "FAIL",
Details: &ErrorCmd{Error: err.Error()},
}
}
// WrongCommand creates a wrong command response
func WrongCommand(message, usage string) *HTTPResult {
return &HTTPResult{
Message: message,
Details: &UsageCmd{Usage: usage},
}
}
// HTTPResult Diagnose Server HTTP result operation
type HTTPResult struct {
Message string `json:"message"`
Details StringInterface `json:"details"`
}
func (h *HTTPResult) String() string {
rsp := h.Message
if h.Details != nil {
rsp += "\n" + h.Details.String()
}
return rsp
}
// UsageCmd command with usage field
type UsageCmd struct {
Usage string `json:"usage"`
}
func (u *UsageCmd) String() string {
return "Usage: " + u.Usage
}
// StringCmd command with info string
type StringCmd struct {
Info string `json:"info"`
}
func (s *StringCmd) String() string {
return s.Info
}
// ErrorCmd command with error
type ErrorCmd struct {
Error string `json:"error"`
}
func (e *ErrorCmd) String() string {
return "Error: " + e.Error
}
// TableObj network db table object
type TableObj struct {
Length int `json:"size"`
Elements []StringInterface `json:"entries"`
}
func (t *TableObj) String() string {
output := fmt.Sprintf("total entries: %d\n", t.Length)
for _, e := range t.Elements {
output += e.String()
}
return output
}
// PeerEntryObj entry in the networkdb peer table
type PeerEntryObj struct {
Index int `json:"-"`
Name string `json:"-=name"`
IP string `json:"ip"`
}
func (p *PeerEntryObj) String() string {
return fmt.Sprintf("%d) %s -> %s\n", p.Index, p.Name, p.IP)
}
// TableEntryObj network db table entry object
type TableEntryObj struct {
Index int `json:"-"`
Key string `json:"key"`
Value string `json:"value"`
Owner string `json:"owner"`
}
func (t *TableEntryObj) String() string {
return fmt.Sprintf("%d) k:`%s` -> v:`%s` owner:`%s`\n", t.Index, t.Key, t.Value, t.Owner)
}
// TableEndpointsResult fully typed message for proper unmarshaling on the client side
type TableEndpointsResult struct {
TableObj
Elements []TableEntryObj `json:"entries"`
}
// TablePeersResult fully typed message for proper unmarshaling on the client side
type TablePeersResult struct {
TableObj
Elements []PeerEntryObj `json:"entries"`
}

View file

@ -42,6 +42,14 @@ const (
DefaultGatewayV6AuxKey = "DefaultGatewayIPv6"
)
type defaultBridgeNetworkConflict struct {
ID string
}
func (d defaultBridgeNetworkConflict) Error() string {
return fmt.Sprintf("Stale default bridge network %s", d.ID)
}
type iptableCleanFunc func() error
type iptablesCleanFuncs []iptableCleanFunc
@ -137,6 +145,7 @@ type driver struct {
networks map[string]*bridgeNetwork
store datastore.DataStore
nlh *netlink.Handle
configNetwork sync.Mutex
sync.Mutex
}
@ -322,41 +331,6 @@ func (n *bridgeNetwork) isolateNetwork(others []*bridgeNetwork, enable bool) err
return nil
}
// Checks whether this network's configuration for the network with this id conflicts with any of the passed networks
func (c *networkConfiguration) conflictsWithNetworks(id string, others []*bridgeNetwork) error {
for _, nw := range others {
nw.Lock()
nwID := nw.id
nwConfig := nw.config
nwBridge := nw.bridge
nw.Unlock()
if nwID == id {
continue
}
// Verify the name (which may have been set by newInterface()) does not conflict with
// existing bridge interfaces. Ironically the system chosen name gets stored in the config...
// Basically we are checking if the two original configs were both empty.
if nwConfig.BridgeName == c.BridgeName {
return types.ForbiddenErrorf("conflicts with network %s (%s) by bridge name", nwID, nwConfig.BridgeName)
}
// If this network config specifies the AddressIPv4, we need
// to make sure it does not conflict with any previously allocated
// bridges. This could not be completely caught by the config conflict
// check, because networks which config does not specify the AddressIPv4
// get their address and subnet selected by the driver (see electBridgeIPv4())
if c.AddressIPv4 != nil && nwBridge.bridgeIPv4 != nil {
if nwBridge.bridgeIPv4.Contains(c.AddressIPv4.IP) ||
c.AddressIPv4.Contains(nwBridge.bridgeIPv4.IP) {
return types.ForbiddenErrorf("conflicts with network %s (%s) by ip network", nwID, nwConfig.BridgeName)
}
}
}
return nil
}
func (d *driver) configure(option map[string]interface{}) error {
var (
config *configuration
@ -602,11 +576,27 @@ func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo d
return err
}
err = config.processIPAM(id, ipV4Data, ipV6Data)
if err != nil {
if err = config.processIPAM(id, ipV4Data, ipV6Data); err != nil {
return err
}
// start the critical section, from this point onward we are dealing with the list of networks
// so to be consistent we cannot allow that the list changes
d.configNetwork.Lock()
defer d.configNetwork.Unlock()
// check network conflicts
if err = d.checkConflict(config); err != nil {
nerr, ok := err.(defaultBridgeNetworkConflict)
if !ok {
return err
}
// Got a conflict with a stale default network, clean that up and continue
logrus.Warn(nerr)
d.deleteNetwork(nerr.ID)
}
// there is no conflict, now create the network
if err = d.createNetwork(config); err != nil {
return err
}
@ -614,33 +604,47 @@ func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo d
return d.storeUpdate(config)
}
func (d *driver) checkConflict(config *networkConfiguration) error {
networkList := d.getNetworks()
for _, nw := range networkList {
nw.Lock()
nwConfig := nw.config
nw.Unlock()
if err := nwConfig.Conflicts(config); err != nil {
if config.DefaultBridge {
// We encountered and identified a stale default network
// We must delete it as libnetwork is the source of truth
// The default network being created must be the only one
// This can happen only from docker 1.12 on ward
logrus.Infof("Found stale default bridge network %s (%s)", nwConfig.ID, nwConfig.BridgeName)
return defaultBridgeNetworkConflict{nwConfig.ID}
}
return types.ForbiddenErrorf("cannot create network %s (%s): conflicts with network %s (%s): %s",
config.ID, config.BridgeName, nwConfig.ID, nwConfig.BridgeName, err.Error())
}
}
return nil
}
func (d *driver) createNetwork(config *networkConfiguration) error {
var err error
defer osl.InitOSContext()()
networkList := d.getNetworks()
for i, nw := range networkList {
nw.Lock()
nwConfig := nw.config
nw.Unlock()
if err := nwConfig.Conflicts(config); err != nil {
if config.DefaultBridge {
// We encountered and identified a stale default network
// We must delete it as libnetwork is the source of thruth
// The default network being created must be the only one
// This can happen only from docker 1.12 on ward
logrus.Infof("Removing stale default bridge network %s (%s)", nwConfig.ID, nwConfig.BridgeName)
if err := d.DeleteNetwork(nwConfig.ID); err != nil {
logrus.Warnf("Failed to remove stale default network: %s (%s): %v. Will remove from store.", nwConfig.ID, nwConfig.BridgeName, err)
d.storeDelete(nwConfig)
}
networkList = append(networkList[:i], networkList[i+1:]...)
} else {
return types.ForbiddenErrorf("cannot create network %s (%s): conflicts with network %s (%s): %s",
config.ID, config.BridgeName, nwConfig.ID, nwConfig.BridgeName, err.Error())
}
}
// Initialize handle when needed
d.Lock()
if d.nlh == nil {
d.nlh = ns.NlHandle()
}
d.Unlock()
// Create or retrieve the bridge L3 interface
bridgeIface, err := newInterface(d.nlh, config)
if err != nil {
return err
}
// Create and set network handler in driver
@ -649,6 +653,7 @@ func (d *driver) createNetwork(config *networkConfiguration) error {
endpoints: make(map[string]*bridgeEndpoint),
config: config,
portMapper: portmapper.New(d.config.UserlandProxyPath),
bridge: bridgeIface,
driver: d,
}
@ -665,35 +670,15 @@ func (d *driver) createNetwork(config *networkConfiguration) error {
}
}()
// Initialize handle when needed
d.Lock()
if d.nlh == nil {
d.nlh = ns.NlHandle()
}
d.Unlock()
// Create or retrieve the bridge L3 interface
bridgeIface, err := newInterface(d.nlh, config)
if err != nil {
return err
}
network.bridge = bridgeIface
// Verify the network configuration does not conflict with previously installed
// networks. This step is needed now because driver might have now set the bridge
// name on this config struct. And because we need to check for possible address
// conflicts, so we need to check against operationa lnetworks.
if err = config.conflictsWithNetworks(config.ID, networkList); err != nil {
return err
}
// Add inter-network communication rules.
setupNetworkIsolationRules := func(config *networkConfiguration, i *bridgeInterface) error {
if err := network.isolateNetwork(networkList, true); err != nil {
if err := network.isolateNetwork(networkList, false); err != nil {
if err = network.isolateNetwork(networkList, false); err != nil {
logrus.Warnf("Failed on removing the inter-network iptables rules on cleanup: %v", err)
}
return err
}
// register the cleanup function
network.registerIptCleanFunc(func() error {
nwList := d.getNetworks()
return network.isolateNetwork(nwList, false)
@ -767,10 +752,17 @@ func (d *driver) createNetwork(config *networkConfiguration) error {
}
func (d *driver) DeleteNetwork(nid string) error {
d.configNetwork.Lock()
defer d.configNetwork.Unlock()
return d.deleteNetwork(nid)
}
func (d *driver) deleteNetwork(nid string) error {
var err error
defer osl.InitOSContext()()
// Get network handler and remove it from driver
d.Lock()
n, ok := d.networks[nid]
@ -814,12 +806,6 @@ func (d *driver) DeleteNetwork(nid string) error {
}
}()
// Sanity check
if n == nil {
err = driverapi.ErrNoNetwork(nid)
return err
}
switch config.BridgeIfaceCreator {
case ifaceCreatedByLibnetwork, ifaceCreatorUnknown:
// We only delete the bridge if it was created by the bridge driver and

View file

@ -9,7 +9,7 @@ func (n *bridgeNetwork) setupFirewalld(config *networkConfiguration, i *bridgeIn
d.Unlock()
// Sanity check.
if driverConfig.EnableIPTables == false {
if !driverConfig.EnableIPTables {
return IPTableCfgError(config.BridgeName)
}

View file

@ -696,6 +696,12 @@ func (n *network) initSandbox(restore bool) error {
var nlSock *nl.NetlinkSocket
sbox.InvokeFunc(func() {
nlSock, err = nl.Subscribe(syscall.NETLINK_ROUTE, syscall.RTNLGRP_NEIGH)
if err != nil {
return
}
// set the receive timeout to not remain stuck on the RecvFrom if the fd gets closed
tv := syscall.NsecToTimeval(soTimeout.Nanoseconds())
err = nlSock.SetReceiveTimeout(&tv)
})
n.setNetlinkSocket(nlSock)
@ -721,6 +727,11 @@ func (n *network) watchMiss(nlSock *nl.NetlinkSocket) {
// The netlink socket got closed, simply exit to not leak this goroutine
return
}
// When the receive timeout expires the receive will return EAGAIN
if err == syscall.EAGAIN {
// we continue here to avoid spam for timeouts
continue
}
logrus.Errorf("Failed to receive from netlink: %v ", err)
continue
}

View file

@ -5,12 +5,19 @@ package ipvs
import (
"net"
"syscall"
"time"
"fmt"
"github.com/vishvananda/netlink/nl"
"github.com/vishvananda/netns"
)
const (
netlinkRecvSocketsTimeout = 3 * time.Second
netlinkSendSocketTimeout = 30 * time.Second
)
// Service defines an IPVS service in its entirety.
type Service struct {
// Virtual service address.
@ -82,6 +89,15 @@ func New(path string) (*Handle, error) {
if err != nil {
return nil, err
}
// Add operation timeout to avoid deadlocks
tv := syscall.NsecToTimeval(netlinkSendSocketTimeout.Nanoseconds())
if err := sock.SetSendTimeout(&tv); err != nil {
return nil, err
}
tv = syscall.NsecToTimeval(netlinkRecvSocketsTimeout.Nanoseconds())
if err := sock.SetReceiveTimeout(&tv); err != nil {
return nil, err
}
return &Handle{sock: sock}, nil
}

View file

@ -203,10 +203,6 @@ func newGenlRequest(familyID int, cmd uint8) *nl.NetlinkRequest {
}
func execute(s *nl.NetlinkSocket, req *nl.NetlinkRequest, resType uint16) ([][]byte, error) {
var (
err error
)
if err := s.Send(req); err != nil {
return nil, err
}
@ -222,6 +218,13 @@ done:
for {
msgs, err := s.Receive()
if err != nil {
if s.GetFd() == -1 {
return nil, fmt.Errorf("Socket got closed on receive")
}
if err == syscall.EAGAIN {
// timeout fired
continue
}
return nil, err
}
for _, m := range msgs {

View file

@ -72,7 +72,7 @@ func (e *eventDelegate) NotifyLeave(mn *memberlist.Node) {
// If the node instead left because was going down, then it makes sense to just delete all its state
e.nDB.Lock()
defer e.nDB.Unlock()
e.nDB.deleteNetworkEntriesForNode(mn.Name)
e.nDB.deleteNodeFromNetworks(mn.Name)
e.nDB.deleteNodeTableEntries(mn.Name)
if n, ok := e.nDB.nodes[mn.Name]; ok {
delete(e.nDB.nodes, mn.Name)

View file

@ -401,17 +401,23 @@ func (nDB *NetworkDB) UpdateEntry(tname, nid, key string, value []byte) error {
return nil
}
// TableElem elem
type TableElem struct {
Value []byte
owner string
}
// GetTableByNetwork walks the networkdb by the give table and network id and
// returns a map of keys and values
func (nDB *NetworkDB) GetTableByNetwork(tname, nid string) map[string]interface{} {
entries := make(map[string]interface{})
func (nDB *NetworkDB) GetTableByNetwork(tname, nid string) map[string]*TableElem {
entries := make(map[string]*TableElem)
nDB.indexes[byTable].WalkPrefix(fmt.Sprintf("/%s/%s", tname, nid), func(k string, v interface{}) bool {
entry := v.(*entry)
if entry.deleting {
return false
}
key := k[strings.LastIndex(k, "/")+1:]
entries[key] = entry.value
entries[key] = &TableElem{Value: entry.value, owner: entry.node}
return false
})
return entries
@ -445,7 +451,7 @@ func (nDB *NetworkDB) DeleteEntry(tname, nid, key string) error {
return nil
}
func (nDB *NetworkDB) deleteNetworkEntriesForNode(deletedNode string) {
func (nDB *NetworkDB) deleteNodeFromNetworks(deletedNode string) {
for nid, nodes := range nDB.networkNodes {
updatedNodes := make([]string, 0, len(nodes))
for _, node := range nodes {
@ -547,7 +553,9 @@ func (nDB *NetworkDB) deleteNodeTableEntries(node string) {
nDB.deleteEntry(nid, tname, key)
nDB.broadcaster.Write(makeEvent(opDelete, tname, nid, key, oldEntry.value))
if !oldEntry.deleting {
nDB.broadcaster.Write(makeEvent(opDelete, tname, nid, key, oldEntry.value))
}
return false
})
}

View file

@ -1,17 +1,19 @@
package networkdb
import (
"encoding/base64"
"fmt"
"net/http"
"strings"
stackdump "github.com/docker/docker/pkg/signal"
"github.com/docker/libnetwork/common"
"github.com/docker/libnetwork/diagnose"
"github.com/sirupsen/logrus"
)
const (
missingParameter = "missing parameter"
dbNotAvailable = "database not available"
)
// NetDbPaths2Func TODO
@ -26,14 +28,21 @@ var NetDbPaths2Func = map[string]diagnose.HTTPHandlerFunc{
"/deleteentry": dbDeleteEntry,
"/getentry": dbGetEntry,
"/gettable": dbGetTable,
"/dump": dbStackTrace,
}
func dbJoin(ctx interface{}, w http.ResponseWriter, r *http.Request) {
r.ParseForm()
diagnose.DebugHTTPForm(r)
_, json := diagnose.ParseHTTPFormOptions(r)
// audit logs
log := logrus.WithFields(logrus.Fields{"component": "diagnose", "remoteIP": r.RemoteAddr, "method": common.CallerName(0), "url": r.URL.String()})
log.Info("join cluster")
if len(r.Form["members"]) < 1 {
diagnose.HTTPReplyError(w, missingParameter, fmt.Sprintf("%s?members=ip1,ip2,...", r.URL.Path))
rsp := diagnose.WrongCommand(missingParameter, fmt.Sprintf("%s?members=ip1,ip2,...", r.URL.Path))
log.Error("join cluster failed, wrong input")
diagnose.HTTPReply(w, rsp, json)
return
}
@ -41,51 +50,88 @@ func dbJoin(ctx interface{}, w http.ResponseWriter, r *http.Request) {
if ok {
err := nDB.Join(strings.Split(r.Form["members"][0], ","))
if err != nil {
fmt.Fprintf(w, "%s error in the DB join %s\n", r.URL.Path, err)
rsp := diagnose.FailCommand(fmt.Errorf("%s error in the DB join %s", r.URL.Path, err))
log.WithError(err).Error("join cluster failed")
diagnose.HTTPReply(w, rsp, json)
return
}
fmt.Fprintf(w, "OK\n")
log.Info("join cluster done")
diagnose.HTTPReply(w, diagnose.CommandSucceed(nil), json)
return
}
diagnose.HTTPReply(w, diagnose.FailCommand(fmt.Errorf("%s", dbNotAvailable)), json)
}
func dbPeers(ctx interface{}, w http.ResponseWriter, r *http.Request) {
r.ParseForm()
diagnose.DebugHTTPForm(r)
_, json := diagnose.ParseHTTPFormOptions(r)
// audit logs
log := logrus.WithFields(logrus.Fields{"component": "diagnose", "remoteIP": r.RemoteAddr, "method": common.CallerName(0), "url": r.URL.String()})
log.Info("network peers")
if len(r.Form["nid"]) < 1 {
diagnose.HTTPReplyError(w, missingParameter, fmt.Sprintf("%s?nid=test", r.URL.Path))
rsp := diagnose.WrongCommand(missingParameter, fmt.Sprintf("%s?nid=test", r.URL.Path))
log.Error("network peers failed, wrong input")
diagnose.HTTPReply(w, rsp, json)
return
}
nDB, ok := ctx.(*NetworkDB)
if ok {
peers := nDB.Peers(r.Form["nid"][0])
fmt.Fprintf(w, "Network:%s Total peers: %d\n", r.Form["nid"], len(peers))
rsp := &diagnose.TableObj{Length: len(peers)}
for i, peerInfo := range peers {
fmt.Fprintf(w, "%d) %s -> %s\n", i, peerInfo.Name, peerInfo.IP)
rsp.Elements = append(rsp.Elements, &diagnose.PeerEntryObj{Index: i, Name: peerInfo.Name, IP: peerInfo.IP})
}
log.WithField("response", fmt.Sprintf("%+v", rsp)).Info("network peers done")
diagnose.HTTPReply(w, diagnose.CommandSucceed(rsp), json)
return
}
diagnose.HTTPReply(w, diagnose.FailCommand(fmt.Errorf("%s", dbNotAvailable)), json)
}
func dbClusterPeers(ctx interface{}, w http.ResponseWriter, r *http.Request) {
r.ParseForm()
diagnose.DebugHTTPForm(r)
_, json := diagnose.ParseHTTPFormOptions(r)
// audit logs
log := logrus.WithFields(logrus.Fields{"component": "diagnose", "remoteIP": r.RemoteAddr, "method": common.CallerName(0), "url": r.URL.String()})
log.Info("cluster peers")
nDB, ok := ctx.(*NetworkDB)
if ok {
peers := nDB.ClusterPeers()
fmt.Fprintf(w, "Total peers: %d\n", len(peers))
rsp := &diagnose.TableObj{Length: len(peers)}
for i, peerInfo := range peers {
fmt.Fprintf(w, "%d) %s -> %s\n", i, peerInfo.Name, peerInfo.IP)
rsp.Elements = append(rsp.Elements, &diagnose.PeerEntryObj{Index: i, Name: peerInfo.Name, IP: peerInfo.IP})
}
log.WithField("response", fmt.Sprintf("%+v", rsp)).Info("cluster peers done")
diagnose.HTTPReply(w, diagnose.CommandSucceed(rsp), json)
return
}
diagnose.HTTPReply(w, diagnose.FailCommand(fmt.Errorf("%s", dbNotAvailable)), json)
}
func dbCreateEntry(ctx interface{}, w http.ResponseWriter, r *http.Request) {
r.ParseForm()
diagnose.DebugHTTPForm(r)
unsafe, json := diagnose.ParseHTTPFormOptions(r)
// audit logs
log := logrus.WithFields(logrus.Fields{"component": "diagnose", "remoteIP": r.RemoteAddr, "method": common.CallerName(0), "url": r.URL.String()})
log.Info("create entry")
if len(r.Form["tname"]) < 1 ||
len(r.Form["nid"]) < 1 ||
len(r.Form["key"]) < 1 ||
len(r.Form["value"]) < 1 {
diagnose.HTTPReplyError(w, missingParameter, fmt.Sprintf("%s?tname=table_name&nid=network_id&key=k&value=v", r.URL.Path))
rsp := diagnose.WrongCommand(missingParameter, fmt.Sprintf("%s?tname=table_name&nid=network_id&key=k&value=v", r.URL.Path))
log.Error("create entry failed, wrong input")
diagnose.HTTPReply(w, rsp, json)
return
}
@ -93,25 +139,48 @@ func dbCreateEntry(ctx interface{}, w http.ResponseWriter, r *http.Request) {
nid := r.Form["nid"][0]
key := r.Form["key"][0]
value := r.Form["value"][0]
decodedValue := []byte(value)
if !unsafe {
var err error
decodedValue, err = base64.StdEncoding.DecodeString(value)
if err != nil {
log.WithError(err).Error("create entry failed")
diagnose.HTTPReply(w, diagnose.FailCommand(err), json)
return
}
}
nDB, ok := ctx.(*NetworkDB)
if ok {
if err := nDB.CreateEntry(tname, nid, key, []byte(value)); err != nil {
diagnose.HTTPReplyError(w, err.Error(), "")
if err := nDB.CreateEntry(tname, nid, key, decodedValue); err != nil {
rsp := diagnose.FailCommand(err)
diagnose.HTTPReply(w, rsp, json)
log.WithError(err).Error("create entry failed")
return
}
fmt.Fprintf(w, "OK\n")
log.Info("create entry done")
diagnose.HTTPReply(w, diagnose.CommandSucceed(nil), json)
return
}
diagnose.HTTPReply(w, diagnose.FailCommand(fmt.Errorf("%s", dbNotAvailable)), json)
}
func dbUpdateEntry(ctx interface{}, w http.ResponseWriter, r *http.Request) {
r.ParseForm()
diagnose.DebugHTTPForm(r)
unsafe, json := diagnose.ParseHTTPFormOptions(r)
// audit logs
log := logrus.WithFields(logrus.Fields{"component": "diagnose", "remoteIP": r.RemoteAddr, "method": common.CallerName(0), "url": r.URL.String()})
log.Info("update entry")
if len(r.Form["tname"]) < 1 ||
len(r.Form["nid"]) < 1 ||
len(r.Form["key"]) < 1 ||
len(r.Form["value"]) < 1 {
diagnose.HTTPReplyError(w, missingParameter, fmt.Sprintf("%s?tname=table_name&nid=network_id&key=k&value=v", r.URL.Path))
rsp := diagnose.WrongCommand(missingParameter, fmt.Sprintf("%s?tname=table_name&nid=network_id&key=k&value=v", r.URL.Path))
log.Error("update entry failed, wrong input")
diagnose.HTTPReply(w, rsp, json)
return
}
@ -119,24 +188,46 @@ func dbUpdateEntry(ctx interface{}, w http.ResponseWriter, r *http.Request) {
nid := r.Form["nid"][0]
key := r.Form["key"][0]
value := r.Form["value"][0]
decodedValue := []byte(value)
if !unsafe {
var err error
decodedValue, err = base64.StdEncoding.DecodeString(value)
if err != nil {
log.WithError(err).Error("update entry failed")
diagnose.HTTPReply(w, diagnose.FailCommand(err), json)
return
}
}
nDB, ok := ctx.(*NetworkDB)
if ok {
if err := nDB.UpdateEntry(tname, nid, key, []byte(value)); err != nil {
diagnose.HTTPReplyError(w, err.Error(), "")
if err := nDB.UpdateEntry(tname, nid, key, decodedValue); err != nil {
log.WithError(err).Error("update entry failed")
diagnose.HTTPReply(w, diagnose.FailCommand(err), json)
return
}
fmt.Fprintf(w, "OK\n")
log.Info("update entry done")
diagnose.HTTPReply(w, diagnose.CommandSucceed(nil), json)
return
}
diagnose.HTTPReply(w, diagnose.FailCommand(fmt.Errorf("%s", dbNotAvailable)), json)
}
func dbDeleteEntry(ctx interface{}, w http.ResponseWriter, r *http.Request) {
r.ParseForm()
diagnose.DebugHTTPForm(r)
_, json := diagnose.ParseHTTPFormOptions(r)
// audit logs
log := logrus.WithFields(logrus.Fields{"component": "diagnose", "remoteIP": r.RemoteAddr, "method": common.CallerName(0), "url": r.URL.String()})
log.Info("delete entry")
if len(r.Form["tname"]) < 1 ||
len(r.Form["nid"]) < 1 ||
len(r.Form["key"]) < 1 {
diagnose.HTTPReplyError(w, missingParameter, fmt.Sprintf("%s?tname=table_name&nid=network_id&key=k", r.URL.Path))
rsp := diagnose.WrongCommand(missingParameter, fmt.Sprintf("%s?tname=table_name&nid=network_id&key=k", r.URL.Path))
log.Error("delete entry failed, wrong input")
diagnose.HTTPReply(w, rsp, json)
return
}
@ -148,20 +239,32 @@ func dbDeleteEntry(ctx interface{}, w http.ResponseWriter, r *http.Request) {
if ok {
err := nDB.DeleteEntry(tname, nid, key)
if err != nil {
diagnose.HTTPReplyError(w, err.Error(), "")
log.WithError(err).Error("delete entry failed")
diagnose.HTTPReply(w, diagnose.FailCommand(err), json)
return
}
fmt.Fprintf(w, "OK\n")
log.Info("delete entry done")
diagnose.HTTPReply(w, diagnose.CommandSucceed(nil), json)
return
}
diagnose.HTTPReply(w, diagnose.FailCommand(fmt.Errorf("%s", dbNotAvailable)), json)
}
func dbGetEntry(ctx interface{}, w http.ResponseWriter, r *http.Request) {
r.ParseForm()
diagnose.DebugHTTPForm(r)
unsafe, json := diagnose.ParseHTTPFormOptions(r)
// audit logs
log := logrus.WithFields(logrus.Fields{"component": "diagnose", "remoteIP": r.RemoteAddr, "method": common.CallerName(0), "url": r.URL.String()})
log.Info("get entry")
if len(r.Form["tname"]) < 1 ||
len(r.Form["nid"]) < 1 ||
len(r.Form["key"]) < 1 {
diagnose.HTTPReplyError(w, missingParameter, fmt.Sprintf("%s?tname=table_name&nid=network_id&key=k", r.URL.Path))
rsp := diagnose.WrongCommand(missingParameter, fmt.Sprintf("%s?tname=table_name&nid=network_id&key=k", r.URL.Path))
log.Error("get entry failed, wrong input")
diagnose.HTTPReply(w, rsp, json)
return
}
@ -173,18 +276,39 @@ func dbGetEntry(ctx interface{}, w http.ResponseWriter, r *http.Request) {
if ok {
value, err := nDB.GetEntry(tname, nid, key)
if err != nil {
diagnose.HTTPReplyError(w, err.Error(), "")
log.WithError(err).Error("get entry failed")
diagnose.HTTPReply(w, diagnose.FailCommand(err), json)
return
}
fmt.Fprintf(w, "key:`%s` value:`%s`\n", key, string(value))
var encodedValue string
if unsafe {
encodedValue = string(value)
} else {
encodedValue = base64.StdEncoding.EncodeToString(value)
}
rsp := &diagnose.TableEntryObj{Key: key, Value: encodedValue}
log.WithField("response", fmt.Sprintf("%+v", rsp)).Info("update entry done")
diagnose.HTTPReply(w, diagnose.CommandSucceed(rsp), json)
return
}
diagnose.HTTPReply(w, diagnose.FailCommand(fmt.Errorf("%s", dbNotAvailable)), json)
}
func dbJoinNetwork(ctx interface{}, w http.ResponseWriter, r *http.Request) {
r.ParseForm()
diagnose.DebugHTTPForm(r)
_, json := diagnose.ParseHTTPFormOptions(r)
// audit logs
log := logrus.WithFields(logrus.Fields{"component": "diagnose", "remoteIP": r.RemoteAddr, "method": common.CallerName(0), "url": r.URL.String()})
log.Info("join network")
if len(r.Form["nid"]) < 1 {
diagnose.HTTPReplyError(w, missingParameter, fmt.Sprintf("%s?nid=network_id", r.URL.Path))
rsp := diagnose.WrongCommand(missingParameter, fmt.Sprintf("%s?nid=network_id", r.URL.Path))
log.Error("join network failed, wrong input")
diagnose.HTTPReply(w, rsp, json)
return
}
@ -193,18 +317,30 @@ func dbJoinNetwork(ctx interface{}, w http.ResponseWriter, r *http.Request) {
nDB, ok := ctx.(*NetworkDB)
if ok {
if err := nDB.JoinNetwork(nid); err != nil {
diagnose.HTTPReplyError(w, err.Error(), "")
log.WithError(err).Error("join network failed")
diagnose.HTTPReply(w, diagnose.FailCommand(err), json)
return
}
fmt.Fprintf(w, "OK\n")
log.Info("join network done")
diagnose.HTTPReply(w, diagnose.CommandSucceed(nil), json)
return
}
diagnose.HTTPReply(w, diagnose.FailCommand(fmt.Errorf("%s", dbNotAvailable)), json)
}
func dbLeaveNetwork(ctx interface{}, w http.ResponseWriter, r *http.Request) {
r.ParseForm()
diagnose.DebugHTTPForm(r)
_, json := diagnose.ParseHTTPFormOptions(r)
// audit logs
log := logrus.WithFields(logrus.Fields{"component": "diagnose", "remoteIP": r.RemoteAddr, "method": common.CallerName(0), "url": r.URL.String()})
log.Info("leave network")
if len(r.Form["nid"]) < 1 {
diagnose.HTTPReplyError(w, missingParameter, fmt.Sprintf("%s?nid=network_id", r.URL.Path))
rsp := diagnose.WrongCommand(missingParameter, fmt.Sprintf("%s?nid=network_id", r.URL.Path))
log.Error("leave network failed, wrong input")
diagnose.HTTPReply(w, rsp, json)
return
}
@ -213,19 +349,31 @@ func dbLeaveNetwork(ctx interface{}, w http.ResponseWriter, r *http.Request) {
nDB, ok := ctx.(*NetworkDB)
if ok {
if err := nDB.LeaveNetwork(nid); err != nil {
diagnose.HTTPReplyError(w, err.Error(), "")
log.WithError(err).Error("leave network failed")
diagnose.HTTPReply(w, diagnose.FailCommand(err), json)
return
}
fmt.Fprintf(w, "OK\n")
log.Info("leave network done")
diagnose.HTTPReply(w, diagnose.CommandSucceed(nil), json)
return
}
diagnose.HTTPReply(w, diagnose.FailCommand(fmt.Errorf("%s", dbNotAvailable)), json)
}
func dbGetTable(ctx interface{}, w http.ResponseWriter, r *http.Request) {
r.ParseForm()
diagnose.DebugHTTPForm(r)
unsafe, json := diagnose.ParseHTTPFormOptions(r)
// audit logs
log := logrus.WithFields(logrus.Fields{"component": "diagnose", "remoteIP": r.RemoteAddr, "method": common.CallerName(0), "url": r.URL.String()})
log.Info("get table")
if len(r.Form["tname"]) < 1 ||
len(r.Form["nid"]) < 1 {
diagnose.HTTPReplyError(w, missingParameter, fmt.Sprintf("%s?tname=table_name&nid=network_id", r.URL.Path))
rsp := diagnose.WrongCommand(missingParameter, fmt.Sprintf("%s?tname=table_name&nid=network_id", r.URL.Path))
log.Error("get table failed, wrong input")
diagnose.HTTPReply(w, rsp, json)
return
}
@ -235,20 +383,26 @@ func dbGetTable(ctx interface{}, w http.ResponseWriter, r *http.Request) {
nDB, ok := ctx.(*NetworkDB)
if ok {
table := nDB.GetTableByNetwork(tname, nid)
fmt.Fprintf(w, "total elements: %d\n", len(table))
i := 0
rsp := &diagnose.TableObj{Length: len(table)}
var i = 0
for k, v := range table {
fmt.Fprintf(w, "%d) k:`%s` -> v:`%s`\n", i, k, string(v.([]byte)))
i++
var encodedValue string
if unsafe {
encodedValue = string(v.Value)
} else {
encodedValue = base64.StdEncoding.EncodeToString(v.Value)
}
rsp.Elements = append(rsp.Elements,
&diagnose.TableEntryObj{
Index: i,
Key: k,
Value: encodedValue,
Owner: v.owner,
})
}
log.WithField("response", fmt.Sprintf("%+v", rsp)).Info("get table done")
diagnose.HTTPReply(w, diagnose.CommandSucceed(rsp), json)
return
}
}
func dbStackTrace(ctx interface{}, w http.ResponseWriter, r *http.Request) {
path, err := stackdump.DumpStacks("/tmp/")
if err != nil {
logrus.WithError(err).Error("failed to write goroutines dump")
} else {
fmt.Fprintf(w, "goroutine stacks written to %s", path)
}
diagnose.HTTPReply(w, diagnose.FailCommand(fmt.Errorf("%s", dbNotAvailable)), json)
}

View file

@ -45,7 +45,7 @@ github.com/sirupsen/logrus v1.0.3
github.com/stretchr/testify dab07ac62d4905d3e48d17dc549c684ac3b7c15a
github.com/syndtr/gocapability db04d3cc01c8b54962a58ec7e491717d06cfcc16
github.com/ugorji/go f1f1a805ed361a0e078bb537e4ea78cd37dcf065
github.com/vishvananda/netlink bd6d5de5ccef2d66b0a26177928d0d8895d7f969
github.com/vishvananda/netlink b2de5d10e38ecce8607e6b438b6d174f389a004e
github.com/vishvananda/netns 604eaf189ee867d8c147fafc28def2394e878d25
golang.org/x/crypto 558b6879de74bc843225cde5686419267ff707ca
golang.org/x/net 7dcfb8076726a3fdd9353b6b8a1f1b6be6811bd6

View file

@ -38,15 +38,18 @@ Add a new bridge and add eth1 into it:
package main
import (
"net"
"fmt"
"github.com/vishvananda/netlink"
)
func main() {
la := netlink.NewLinkAttrs()
la.Name = "foo"
mybridge := &netlink.Bridge{la}}
_ := netlink.LinkAdd(mybridge)
mybridge := &netlink.Bridge{LinkAttrs: la}
err := netlink.LinkAdd(mybridge)
if err != nil {
fmt.Printf("could not add %s: %v\n", la.Name, err)
}
eth1, _ := netlink.LinkByName("eth1")
netlink.LinkSetMaster(eth1, mybridge)
}
@ -63,7 +66,6 @@ Add a new ip address to loopback:
package main
import (
"net"
"github.com/vishvananda/netlink"
)

View file

@ -2,7 +2,6 @@ package netlink
import (
"fmt"
"log"
"net"
"strings"
"syscall"
@ -65,7 +64,7 @@ func (h *Handle) addrHandle(link Link, addr *Addr, req *nl.NetlinkRequest) error
msg := nl.NewIfAddrmsg(family)
msg.Index = uint32(base.Index)
msg.Scope = uint8(addr.Scope)
prefixlen, _ := addr.Mask.Size()
prefixlen, masklen := addr.Mask.Size()
msg.Prefixlen = uint8(prefixlen)
req.AddData(msg)
@ -103,9 +102,14 @@ func (h *Handle) addrHandle(link Link, addr *Addr, req *nl.NetlinkRequest) error
}
}
if addr.Broadcast != nil {
req.AddData(nl.NewRtAttr(syscall.IFA_BROADCAST, addr.Broadcast))
if addr.Broadcast == nil {
calcBroadcast := make(net.IP, masklen/8)
for i := range localAddrData {
calcBroadcast[i] = localAddrData[i] | ^addr.Mask[i]
}
addr.Broadcast = calcBroadcast
}
req.AddData(nl.NewRtAttr(syscall.IFA_BROADCAST, addr.Broadcast))
if addr.Label != "" {
labelData := nl.NewRtAttr(syscall.IFA_LABEL, nl.ZeroTerminated(addr.Label))
@ -232,16 +236,34 @@ type AddrUpdate struct {
// AddrSubscribe takes a chan down which notifications will be sent
// when addresses change. Close the 'done' chan to stop subscription.
func AddrSubscribe(ch chan<- AddrUpdate, done <-chan struct{}) error {
return addrSubscribe(netns.None(), netns.None(), ch, done)
return addrSubscribeAt(netns.None(), netns.None(), ch, done, nil)
}
// AddrSubscribeAt works like AddrSubscribe plus it allows the caller
// to choose the network namespace in which to subscribe (ns).
func AddrSubscribeAt(ns netns.NsHandle, ch chan<- AddrUpdate, done <-chan struct{}) error {
return addrSubscribe(ns, netns.None(), ch, done)
return addrSubscribeAt(ns, netns.None(), ch, done, nil)
}
func addrSubscribe(newNs, curNs netns.NsHandle, ch chan<- AddrUpdate, done <-chan struct{}) error {
// AddrSubscribeOptions contains a set of options to use with
// AddrSubscribeWithOptions.
type AddrSubscribeOptions struct {
Namespace *netns.NsHandle
ErrorCallback func(error)
}
// AddrSubscribeWithOptions work like AddrSubscribe but enable to
// provide additional options to modify the behavior. Currently, the
// namespace can be provided as well as an error callback.
func AddrSubscribeWithOptions(ch chan<- AddrUpdate, done <-chan struct{}, options AddrSubscribeOptions) error {
if options.Namespace == nil {
none := netns.None()
options.Namespace = &none
}
return addrSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback)
}
func addrSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- AddrUpdate, done <-chan struct{}, cberr func(error)) error {
s, err := nl.SubscribeAt(newNs, curNs, syscall.NETLINK_ROUTE, syscall.RTNLGRP_IPV4_IFADDR, syscall.RTNLGRP_IPV6_IFADDR)
if err != nil {
return err
@ -257,20 +279,26 @@ func addrSubscribe(newNs, curNs netns.NsHandle, ch chan<- AddrUpdate, done <-cha
for {
msgs, err := s.Receive()
if err != nil {
log.Printf("netlink.AddrSubscribe: Receive() error: %v", err)
if cberr != nil {
cberr(err)
}
return
}
for _, m := range msgs {
msgType := m.Header.Type
if msgType != syscall.RTM_NEWADDR && msgType != syscall.RTM_DELADDR {
log.Printf("netlink.AddrSubscribe: bad message type: %d", msgType)
continue
if cberr != nil {
cberr(fmt.Errorf("bad message type: %d", msgType))
}
return
}
addr, _, ifindex, err := parseAddr(m.Data)
if err != nil {
log.Printf("netlink.AddrSubscribe: could not parse address: %v", err)
continue
if cberr != nil {
cberr(fmt.Errorf("could not parse address: %v", err))
}
return
}
ch <- AddrUpdate{LinkAddress: *addr.IPNet,

115
vendor/github.com/vishvananda/netlink/bridge_linux.go generated vendored Normal file
View file

@ -0,0 +1,115 @@
package netlink
import (
"fmt"
"syscall"
"github.com/vishvananda/netlink/nl"
)
// BridgeVlanList gets a map of device id to bridge vlan infos.
// Equivalent to: `bridge vlan show`
func BridgeVlanList() (map[int32][]*nl.BridgeVlanInfo, error) {
return pkgHandle.BridgeVlanList()
}
// BridgeVlanList gets a map of device id to bridge vlan infos.
// Equivalent to: `bridge vlan show`
func (h *Handle) BridgeVlanList() (map[int32][]*nl.BridgeVlanInfo, error) {
req := h.newNetlinkRequest(syscall.RTM_GETLINK, syscall.NLM_F_DUMP)
msg := nl.NewIfInfomsg(syscall.AF_BRIDGE)
req.AddData(msg)
req.AddData(nl.NewRtAttr(nl.IFLA_EXT_MASK, nl.Uint32Attr(uint32(nl.RTEXT_FILTER_BRVLAN))))
msgs, err := req.Execute(syscall.NETLINK_ROUTE, syscall.RTM_NEWLINK)
if err != nil {
return nil, err
}
ret := make(map[int32][]*nl.BridgeVlanInfo)
for _, m := range msgs {
msg := nl.DeserializeIfInfomsg(m)
attrs, err := nl.ParseRouteAttr(m[msg.Len():])
if err != nil {
return nil, err
}
for _, attr := range attrs {
switch attr.Attr.Type {
case nl.IFLA_AF_SPEC:
//nested attr
nestAttrs, err := nl.ParseRouteAttr(attr.Value)
if err != nil {
return nil, fmt.Errorf("failed to parse nested attr %v", err)
}
for _, nestAttr := range nestAttrs {
switch nestAttr.Attr.Type {
case nl.IFLA_BRIDGE_VLAN_INFO:
vlanInfo := nl.DeserializeBridgeVlanInfo(nestAttr.Value)
ret[msg.Index] = append(ret[msg.Index], vlanInfo)
}
}
}
}
}
return ret, nil
}
// BridgeVlanAdd adds a new vlan filter entry
// Equivalent to: `bridge vlan add dev DEV vid VID [ pvid ] [ untagged ] [ self ] [ master ]`
func BridgeVlanAdd(link Link, vid uint16, pvid, untagged, self, master bool) error {
return pkgHandle.BridgeVlanAdd(link, vid, pvid, untagged, self, master)
}
// BridgeVlanAdd adds a new vlan filter entry
// Equivalent to: `bridge vlan add dev DEV vid VID [ pvid ] [ untagged ] [ self ] [ master ]`
func (h *Handle) BridgeVlanAdd(link Link, vid uint16, pvid, untagged, self, master bool) error {
return h.bridgeVlanModify(syscall.RTM_SETLINK, link, vid, pvid, untagged, self, master)
}
// BridgeVlanDel adds a new vlan filter entry
// Equivalent to: `bridge vlan del dev DEV vid VID [ pvid ] [ untagged ] [ self ] [ master ]`
func BridgeVlanDel(link Link, vid uint16, pvid, untagged, self, master bool) error {
return pkgHandle.BridgeVlanDel(link, vid, pvid, untagged, self, master)
}
// BridgeVlanDel adds a new vlan filter entry
// Equivalent to: `bridge vlan del dev DEV vid VID [ pvid ] [ untagged ] [ self ] [ master ]`
func (h *Handle) BridgeVlanDel(link Link, vid uint16, pvid, untagged, self, master bool) error {
return h.bridgeVlanModify(syscall.RTM_DELLINK, link, vid, pvid, untagged, self, master)
}
func (h *Handle) bridgeVlanModify(cmd int, link Link, vid uint16, pvid, untagged, self, master bool) error {
base := link.Attrs()
h.ensureIndex(base)
req := h.newNetlinkRequest(cmd, syscall.NLM_F_ACK)
msg := nl.NewIfInfomsg(syscall.AF_BRIDGE)
msg.Index = int32(base.Index)
req.AddData(msg)
br := nl.NewRtAttr(nl.IFLA_AF_SPEC, nil)
var flags uint16
if self {
flags |= nl.BRIDGE_FLAGS_SELF
}
if master {
flags |= nl.BRIDGE_FLAGS_MASTER
}
if flags > 0 {
nl.NewRtAttrChild(br, nl.IFLA_BRIDGE_FLAGS, nl.Uint16Attr(flags))
}
vlanInfo := &nl.BridgeVlanInfo{Vid: vid}
if pvid {
vlanInfo.Flags |= nl.BRIDGE_VLAN_INFO_PVID
}
if untagged {
vlanInfo.Flags |= nl.BRIDGE_VLAN_INFO_UNTAGGED
}
nl.NewRtAttrChild(br, nl.IFLA_BRIDGE_VLAN_INFO, vlanInfo.Serialize())
req.AddData(br)
_, err := req.Execute(syscall.NETLINK_ROUTE, 0)
if err != nil {
return err
}
return nil
}

View file

@ -22,7 +22,11 @@ const (
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/netfilter/nfnetlink.h -> #define NFNL_SUBSYS_CTNETLINK_EXP 2
ConntrackExpectTable = 2
)
const (
// For Parsing Mark
TCP_PROTO = 6
UDP_PROTO = 17
)
const (
// backward compatibility with golang 1.6 which does not have io.SeekCurrent
seekCurrent = 1
@ -56,7 +60,7 @@ func ConntrackTableFlush(table ConntrackTableType) error {
// ConntrackDeleteFilter deletes entries on the specified table on the base of the filter
// conntrack -D [table] parameters Delete conntrack or expectation
func ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter *ConntrackFilter) (uint, error) {
func ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter CustomConntrackFilter) (uint, error) {
return pkgHandle.ConntrackDeleteFilter(table, family, filter)
}
@ -88,7 +92,7 @@ func (h *Handle) ConntrackTableFlush(table ConntrackTableType) error {
// ConntrackDeleteFilter deletes entries on the specified table on the base of the filter using the netlink handle passed
// conntrack -D [table] parameters Delete conntrack or expectation
func (h *Handle) ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter *ConntrackFilter) (uint, error) {
func (h *Handle) ConntrackDeleteFilter(table ConntrackTableType, family InetFamily, filter CustomConntrackFilter) (uint, error) {
res, err := h.dumpConntrackTable(table, family)
if err != nil {
return 0, err
@ -142,15 +146,16 @@ type ConntrackFlow struct {
FamilyType uint8
Forward ipTuple
Reverse ipTuple
Mark uint32
}
func (s *ConntrackFlow) String() string {
// conntrack cmd output:
// udp 17 src=127.0.0.1 dst=127.0.0.1 sport=4001 dport=1234 [UNREPLIED] src=127.0.0.1 dst=127.0.0.1 sport=1234 dport=4001
return fmt.Sprintf("%s\t%d src=%s dst=%s sport=%d dport=%d\tsrc=%s dst=%s sport=%d dport=%d",
// udp 17 src=127.0.0.1 dst=127.0.0.1 sport=4001 dport=1234 [UNREPLIED] src=127.0.0.1 dst=127.0.0.1 sport=1234 dport=4001 mark=0
return fmt.Sprintf("%s\t%d src=%s dst=%s sport=%d dport=%d\tsrc=%s dst=%s sport=%d dport=%d mark=%d",
nl.L4ProtoMap[s.Forward.Protocol], s.Forward.Protocol,
s.Forward.SrcIP.String(), s.Forward.DstIP.String(), s.Forward.SrcPort, s.Forward.DstPort,
s.Reverse.SrcIP.String(), s.Reverse.DstIP.String(), s.Reverse.SrcPort, s.Reverse.DstPort)
s.Reverse.SrcIP.String(), s.Reverse.DstIP.String(), s.Reverse.SrcPort, s.Reverse.DstPort, s.Mark)
}
// This method parse the ip tuple structure
@ -160,7 +165,7 @@ func (s *ConntrackFlow) String() string {
// <len, NLA_F_NESTED|nl.CTA_TUPLE_PROTO, 1 byte for the protocol, 3 bytes of padding>
// <len, CTA_PROTO_SRC_PORT, 2 bytes for the source port, 2 bytes of padding>
// <len, CTA_PROTO_DST_PORT, 2 bytes for the source port, 2 bytes of padding>
func parseIpTuple(reader *bytes.Reader, tpl *ipTuple) {
func parseIpTuple(reader *bytes.Reader, tpl *ipTuple) uint8 {
for i := 0; i < 2; i++ {
_, t, _, v := parseNfAttrTLV(reader)
switch t {
@ -189,6 +194,7 @@ func parseIpTuple(reader *bytes.Reader, tpl *ipTuple) {
// Skip some padding 2 byte
reader.Seek(2, seekCurrent)
}
return tpl.Protocol
}
func parseNfAttrTLV(r *bytes.Reader) (isNested bool, attrType, len uint16, value []byte) {
@ -216,6 +222,7 @@ func parseBERaw16(r *bytes.Reader, v *uint16) {
func parseRawData(data []byte) *ConntrackFlow {
s := &ConntrackFlow{}
var proto uint8
// First there is the Nfgenmsg header
// consume only the family field
reader := bytes.NewReader(data)
@ -234,7 +241,7 @@ func parseRawData(data []byte) *ConntrackFlow {
nested, t, l := parseNfAttrTL(reader)
if nested && t == nl.CTA_TUPLE_ORIG {
if nested, t, _ = parseNfAttrTL(reader); nested && t == nl.CTA_TUPLE_IP {
parseIpTuple(reader, &s.Forward)
proto = parseIpTuple(reader, &s.Forward)
}
} else if nested && t == nl.CTA_TUPLE_REPLY {
if nested, t, _ = parseNfAttrTL(reader); nested && t == nl.CTA_TUPLE_IP {
@ -248,7 +255,19 @@ func parseRawData(data []byte) *ConntrackFlow {
}
}
}
if proto == TCP_PROTO {
reader.Seek(64, seekCurrent)
_, t, _, v := parseNfAttrTLV(reader)
if t == nl.CTA_MARK {
s.Mark = uint32(v[3])
}
} else if proto == UDP_PROTO {
reader.Seek(16, seekCurrent)
_, t, _, v := parseNfAttrTLV(reader)
if t == nl.CTA_MARK {
s.Mark = uint32(v[3])
}
}
return s
}
@ -290,6 +309,12 @@ const (
ConntrackNatAnyIP // -any-nat ip Source or destination NAT ip
)
type CustomConntrackFilter interface {
// MatchConntrackFlow applies the filter to the flow and returns true if the flow matches
// the filter or false otherwise
MatchConntrackFlow(flow *ConntrackFlow) bool
}
type ConntrackFilter struct {
ipFilter map[ConntrackFilterType]net.IP
}
@ -342,3 +367,5 @@ func (f *ConntrackFilter) MatchConntrackFlow(flow *ConntrackFlow) bool {
return match
}
var _ CustomConntrackFilter = (*ConntrackFilter)(nil)

View file

@ -2,8 +2,6 @@ package netlink
import (
"fmt"
"github.com/vishvananda/netlink/nl"
)
type Filter interface {
@ -184,14 +182,6 @@ func NewMirredAction(redirIndex int) *MirredAction {
}
}
// Constants used in TcU32Sel.Flags.
const (
TC_U32_TERMINAL = nl.TC_U32_TERMINAL
TC_U32_OFFSET = nl.TC_U32_OFFSET
TC_U32_VAROFFSET = nl.TC_U32_VAROFFSET
TC_U32_EAT = nl.TC_U32_EAT
)
// Sel of the U32 filters that contains multiple TcU32Key. This is the copy
// and the frontend representation of nl.TcU32Sel. It is serialized into canonical
// nl.TcU32Sel with the appropriate endianness.

View file

@ -11,6 +11,14 @@ import (
"github.com/vishvananda/netlink/nl"
)
// Constants used in TcU32Sel.Flags.
const (
TC_U32_TERMINAL = nl.TC_U32_TERMINAL
TC_U32_OFFSET = nl.TC_U32_OFFSET
TC_U32_VAROFFSET = nl.TC_U32_VAROFFSET
TC_U32_EAT = nl.TC_U32_EAT
)
// Fw filter filters on firewall marks
// NOTE: this is in filter_linux because it refers to nl.TcPolice which
// is defined in nl/tc_linux.go
@ -128,9 +136,11 @@ func (h *Handle) FilterAdd(filter Filter) error {
req.AddData(nl.NewRtAttr(nl.TCA_KIND, nl.ZeroTerminated(filter.Type())))
options := nl.NewRtAttr(nl.TCA_OPTIONS, nil)
if u32, ok := filter.(*U32); ok {
switch filter := filter.(type) {
case *U32:
// Convert TcU32Sel into nl.TcU32Sel as it is without copy.
sel := (*nl.TcU32Sel)(unsafe.Pointer(u32.Sel))
sel := (*nl.TcU32Sel)(unsafe.Pointer(filter.Sel))
if sel == nil {
// match all
sel = &nl.TcU32Sel{
@ -158,56 +168,56 @@ func (h *Handle) FilterAdd(filter Filter) error {
}
sel.Nkeys = uint8(len(sel.Keys))
nl.NewRtAttrChild(options, nl.TCA_U32_SEL, sel.Serialize())
if u32.ClassId != 0 {
nl.NewRtAttrChild(options, nl.TCA_U32_CLASSID, nl.Uint32Attr(u32.ClassId))
if filter.ClassId != 0 {
nl.NewRtAttrChild(options, nl.TCA_U32_CLASSID, nl.Uint32Attr(filter.ClassId))
}
actionsAttr := nl.NewRtAttrChild(options, nl.TCA_U32_ACT, nil)
// backwards compatibility
if u32.RedirIndex != 0 {
u32.Actions = append([]Action{NewMirredAction(u32.RedirIndex)}, u32.Actions...)
if filter.RedirIndex != 0 {
filter.Actions = append([]Action{NewMirredAction(filter.RedirIndex)}, filter.Actions...)
}
if err := EncodeActions(actionsAttr, u32.Actions); err != nil {
if err := EncodeActions(actionsAttr, filter.Actions); err != nil {
return err
}
} else if fw, ok := filter.(*Fw); ok {
if fw.Mask != 0 {
case *Fw:
if filter.Mask != 0 {
b := make([]byte, 4)
native.PutUint32(b, fw.Mask)
native.PutUint32(b, filter.Mask)
nl.NewRtAttrChild(options, nl.TCA_FW_MASK, b)
}
if fw.InDev != "" {
nl.NewRtAttrChild(options, nl.TCA_FW_INDEV, nl.ZeroTerminated(fw.InDev))
if filter.InDev != "" {
nl.NewRtAttrChild(options, nl.TCA_FW_INDEV, nl.ZeroTerminated(filter.InDev))
}
if (fw.Police != nl.TcPolice{}) {
if (filter.Police != nl.TcPolice{}) {
police := nl.NewRtAttrChild(options, nl.TCA_FW_POLICE, nil)
nl.NewRtAttrChild(police, nl.TCA_POLICE_TBF, fw.Police.Serialize())
if (fw.Police.Rate != nl.TcRateSpec{}) {
payload := SerializeRtab(fw.Rtab)
nl.NewRtAttrChild(police, nl.TCA_POLICE_TBF, filter.Police.Serialize())
if (filter.Police.Rate != nl.TcRateSpec{}) {
payload := SerializeRtab(filter.Rtab)
nl.NewRtAttrChild(police, nl.TCA_POLICE_RATE, payload)
}
if (fw.Police.PeakRate != nl.TcRateSpec{}) {
payload := SerializeRtab(fw.Ptab)
if (filter.Police.PeakRate != nl.TcRateSpec{}) {
payload := SerializeRtab(filter.Ptab)
nl.NewRtAttrChild(police, nl.TCA_POLICE_PEAKRATE, payload)
}
}
if fw.ClassId != 0 {
if filter.ClassId != 0 {
b := make([]byte, 4)
native.PutUint32(b, fw.ClassId)
native.PutUint32(b, filter.ClassId)
nl.NewRtAttrChild(options, nl.TCA_FW_CLASSID, b)
}
} else if bpf, ok := filter.(*BpfFilter); ok {
case *BpfFilter:
var bpfFlags uint32
if bpf.ClassId != 0 {
nl.NewRtAttrChild(options, nl.TCA_BPF_CLASSID, nl.Uint32Attr(bpf.ClassId))
if filter.ClassId != 0 {
nl.NewRtAttrChild(options, nl.TCA_BPF_CLASSID, nl.Uint32Attr(filter.ClassId))
}
if bpf.Fd >= 0 {
nl.NewRtAttrChild(options, nl.TCA_BPF_FD, nl.Uint32Attr((uint32(bpf.Fd))))
if filter.Fd >= 0 {
nl.NewRtAttrChild(options, nl.TCA_BPF_FD, nl.Uint32Attr((uint32(filter.Fd))))
}
if bpf.Name != "" {
nl.NewRtAttrChild(options, nl.TCA_BPF_NAME, nl.ZeroTerminated(bpf.Name))
if filter.Name != "" {
nl.NewRtAttrChild(options, nl.TCA_BPF_NAME, nl.ZeroTerminated(filter.Name))
}
if bpf.DirectAction {
if filter.DirectAction {
bpfFlags |= nl.TCA_BPF_FLAG_ACT_DIRECT
}
nl.NewRtAttrChild(options, nl.TCA_BPF_FLAGS, nl.Uint32Attr(bpfFlags))

View file

@ -45,12 +45,27 @@ func (h *Handle) SetSocketTimeout(to time.Duration) error {
}
tv := syscall.NsecToTimeval(to.Nanoseconds())
for _, sh := range h.sockets {
fd := sh.Socket.GetFd()
err := syscall.SetsockoptTimeval(fd, syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, &tv)
if err != nil {
if err := sh.Socket.SetSendTimeout(&tv); err != nil {
return err
}
err = syscall.SetsockoptTimeval(fd, syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, &tv)
if err := sh.Socket.SetReceiveTimeout(&tv); err != nil {
return err
}
}
return nil
}
// SetSocketReceiveBufferSize sets the receive buffer size for each
// socket in the netlink handle. The maximum value is capped by
// /proc/sys/net/core/rmem_max.
func (h *Handle) SetSocketReceiveBufferSize(size int, force bool) error {
opt := syscall.SO_RCVBUF
if force {
opt = syscall.SO_RCVBUFFORCE
}
for _, sh := range h.sockets {
fd := sh.Socket.GetFd()
err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, opt, size)
if err != nil {
return err
}
@ -58,6 +73,24 @@ func (h *Handle) SetSocketTimeout(to time.Duration) error {
return nil
}
// GetSocketReceiveBufferSize gets the receiver buffer size for each
// socket in the netlink handle. The retrieved value should be the
// double to the one set for SetSocketReceiveBufferSize.
func (h *Handle) GetSocketReceiveBufferSize() ([]int, error) {
results := make([]int, len(h.sockets))
i := 0
for _, sh := range h.sockets {
fd := sh.Socket.GetFd()
size, err := syscall.GetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_RCVBUF)
if err != nil {
return nil, err
}
results[i] = size
i++
}
return results, nil
}
// NewHandle returns a netlink handle on the network namespace
// specified by ns. If ns=netns.None(), current network namespace
// will be assumed

View file

@ -145,6 +145,10 @@ func (h *Handle) LinkSetFlood(link Link, mode bool) error {
return ErrNotImplemented
}
func (h *Handle) LinkSetTxQLen(link Link, qlen int) error {
return ErrNotImplemented
}
func (h *Handle) setProtinfoAttr(link Link, mode bool, attr int) error {
return ErrNotImplemented
}

View file

@ -37,6 +37,7 @@ type LinkAttrs struct {
EncapType string
Protinfo *Protinfo
OperState LinkOperState
NetNsID int
}
// LinkOperState represents the values of the IFLA_OPERSTATE link
@ -171,6 +172,7 @@ type LinkXdp struct {
Fd int
Attached bool
Flags uint32
ProgId uint32
}
// Device links cannot be created via netlink. These links
@ -339,6 +341,7 @@ type Vxlan struct {
UDPCSum bool
NoAge bool
GBP bool
FlowBased bool
Age int
Limit int
Port int
@ -684,6 +687,7 @@ type Gretap struct {
EncapType uint16
EncapFlags uint16
Link uint32
FlowBased bool
}
func (gretap *Gretap) Attrs() *LinkAttrs {
@ -729,6 +733,28 @@ func (iptun *Vti) Type() string {
return "vti"
}
type Gretun struct {
LinkAttrs
Link uint32
IFlags uint16
OFlags uint16
IKey uint32
OKey uint32
Local net.IP
Remote net.IP
Ttl uint8
Tos uint8
PMtuDisc uint8
}
func (gretun *Gretun) Attrs() *LinkAttrs {
return &gretun.LinkAttrs
}
func (gretun *Gretun) Type() string {
return "gre"
}
type Vrf struct {
LinkAttrs
Table uint32

View file

@ -379,6 +379,74 @@ func (h *Handle) LinkSetVfTxRate(link Link, vf, rate int) error {
return err
}
// LinkSetVfSpoofchk enables/disables spoof check on a vf for the link.
// Equivalent to: `ip link set $link vf $vf spoofchk $check`
func LinkSetVfSpoofchk(link Link, vf int, check bool) error {
return pkgHandle.LinkSetVfSpoofchk(link, vf, check)
}
// LinkSetVfSpookfchk enables/disables spoof check on a vf for the link.
// Equivalent to: `ip link set $link vf $vf spoofchk $check`
func (h *Handle) LinkSetVfSpoofchk(link Link, vf int, check bool) error {
var setting uint32
base := link.Attrs()
h.ensureIndex(base)
req := h.newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
msg := nl.NewIfInfomsg(syscall.AF_UNSPEC)
msg.Index = int32(base.Index)
req.AddData(msg)
data := nl.NewRtAttr(nl.IFLA_VFINFO_LIST, nil)
info := nl.NewRtAttrChild(data, nl.IFLA_VF_INFO, nil)
if check {
setting = 1
}
vfmsg := nl.VfSpoofchk{
Vf: uint32(vf),
Setting: setting,
}
nl.NewRtAttrChild(info, nl.IFLA_VF_SPOOFCHK, vfmsg.Serialize())
req.AddData(data)
_, err := req.Execute(syscall.NETLINK_ROUTE, 0)
return err
}
// LinkSetVfTrust enables/disables trust state on a vf for the link.
// Equivalent to: `ip link set $link vf $vf trust $state`
func LinkSetVfTrust(link Link, vf int, state bool) error {
return pkgHandle.LinkSetVfTrust(link, vf, state)
}
// LinkSetVfTrust enables/disables trust state on a vf for the link.
// Equivalent to: `ip link set $link vf $vf trust $state`
func (h *Handle) LinkSetVfTrust(link Link, vf int, state bool) error {
var setting uint32
base := link.Attrs()
h.ensureIndex(base)
req := h.newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
msg := nl.NewIfInfomsg(syscall.AF_UNSPEC)
msg.Index = int32(base.Index)
req.AddData(msg)
data := nl.NewRtAttr(nl.IFLA_VFINFO_LIST, nil)
info := nl.NewRtAttrChild(data, nl.IFLA_VF_INFO, nil)
if state {
setting = 1
}
vfmsg := nl.VfTrust{
Vf: uint32(vf),
Setting: setting,
}
nl.NewRtAttrChild(info, nl.IFLA_VF_TRUST, vfmsg.Serialize())
req.AddData(data)
_, err := req.Execute(syscall.NETLINK_ROUTE, 0)
return err
}
// LinkSetMaster sets the master of the link device.
// Equivalent to: `ip link set $link master $master`
func LinkSetMaster(link Link, master *Bridge) error {
@ -500,6 +568,12 @@ func (h *Handle) LinkSetNsFd(link Link, fd int) error {
// LinkSetXdpFd adds a bpf function to the driver. The fd must be a bpf
// program loaded with bpf(type=BPF_PROG_TYPE_XDP)
func LinkSetXdpFd(link Link, fd int) error {
return LinkSetXdpFdWithFlags(link, fd, 0)
}
// LinkSetXdpFdWithFlags adds a bpf function to the driver with the given
// options. The fd must be a bpf program loaded with bpf(type=BPF_PROG_TYPE_XDP)
func LinkSetXdpFdWithFlags(link Link, fd, flags int) error {
base := link.Attrs()
ensureIndex(base)
req := nl.NewNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
@ -508,7 +582,7 @@ func LinkSetXdpFd(link Link, fd int) error {
msg.Index = int32(base.Index)
req.AddData(msg)
addXdpAttrs(&LinkXdp{Fd: fd}, req)
addXdpAttrs(&LinkXdp{Fd: fd, Flags: uint32(flags)}, req)
_, err := req.Execute(syscall.NETLINK_ROUTE, 0)
return err
@ -528,7 +602,13 @@ type vxlanPortRange struct {
func addVxlanAttrs(vxlan *Vxlan, linkInfo *nl.RtAttr) {
data := nl.NewRtAttrChild(linkInfo, nl.IFLA_INFO_DATA, nil)
if vxlan.FlowBased {
vxlan.VxlanId = 0
}
nl.NewRtAttrChild(data, nl.IFLA_VXLAN_ID, nl.Uint32Attr(uint32(vxlan.VxlanId)))
if vxlan.VtepDevIndex != 0 {
nl.NewRtAttrChild(data, nl.IFLA_VXLAN_LINK, nl.Uint32Attr(uint32(vxlan.VtepDevIndex)))
}
@ -569,6 +649,9 @@ func addVxlanAttrs(vxlan *Vxlan, linkInfo *nl.RtAttr) {
if vxlan.GBP {
nl.NewRtAttrChild(data, nl.IFLA_VXLAN_GBP, []byte{})
}
if vxlan.FlowBased {
nl.NewRtAttrChild(data, nl.IFLA_VXLAN_FLOWBASED, boolAttr(vxlan.FlowBased))
}
if vxlan.NoAge {
nl.NewRtAttrChild(data, nl.IFLA_VXLAN_AGEING, nl.Uint32Attr(0))
} else if vxlan.Age > 0 {
@ -818,16 +901,17 @@ func (h *Handle) linkModify(link Link, flags int) error {
linkInfo := nl.NewRtAttr(syscall.IFLA_LINKINFO, nil)
nl.NewRtAttrChild(linkInfo, nl.IFLA_INFO_KIND, nl.NonZeroTerminated(link.Type()))
if vlan, ok := link.(*Vlan); ok {
switch link := link.(type) {
case *Vlan:
b := make([]byte, 2)
native.PutUint16(b, uint16(vlan.VlanId))
native.PutUint16(b, uint16(link.VlanId))
data := nl.NewRtAttrChild(linkInfo, nl.IFLA_INFO_DATA, nil)
nl.NewRtAttrChild(data, nl.IFLA_VLAN_ID, b)
} else if veth, ok := link.(*Veth); ok {
case *Veth:
data := nl.NewRtAttrChild(linkInfo, nl.IFLA_INFO_DATA, nil)
peer := nl.NewRtAttrChild(data, nl.VETH_INFO_PEER, nil)
nl.NewIfInfomsgChild(peer, syscall.AF_UNSPEC)
nl.NewRtAttrChild(peer, syscall.IFLA_IFNAME, nl.ZeroTerminated(veth.PeerName))
nl.NewRtAttrChild(peer, syscall.IFLA_IFNAME, nl.ZeroTerminated(link.PeerName))
if base.TxQLen >= 0 {
nl.NewRtAttrChild(peer, syscall.IFLA_TXQLEN, nl.Uint32Attr(uint32(base.TxQLen)))
}
@ -835,35 +919,37 @@ func (h *Handle) linkModify(link Link, flags int) error {
nl.NewRtAttrChild(peer, syscall.IFLA_MTU, nl.Uint32Attr(uint32(base.MTU)))
}
} else if vxlan, ok := link.(*Vxlan); ok {
addVxlanAttrs(vxlan, linkInfo)
} else if bond, ok := link.(*Bond); ok {
addBondAttrs(bond, linkInfo)
} else if ipv, ok := link.(*IPVlan); ok {
case *Vxlan:
addVxlanAttrs(link, linkInfo)
case *Bond:
addBondAttrs(link, linkInfo)
case *IPVlan:
data := nl.NewRtAttrChild(linkInfo, nl.IFLA_INFO_DATA, nil)
nl.NewRtAttrChild(data, nl.IFLA_IPVLAN_MODE, nl.Uint16Attr(uint16(ipv.Mode)))
} else if macv, ok := link.(*Macvlan); ok {
if macv.Mode != MACVLAN_MODE_DEFAULT {
nl.NewRtAttrChild(data, nl.IFLA_IPVLAN_MODE, nl.Uint16Attr(uint16(link.Mode)))
case *Macvlan:
if link.Mode != MACVLAN_MODE_DEFAULT {
data := nl.NewRtAttrChild(linkInfo, nl.IFLA_INFO_DATA, nil)
nl.NewRtAttrChild(data, nl.IFLA_MACVLAN_MODE, nl.Uint32Attr(macvlanModes[macv.Mode]))
nl.NewRtAttrChild(data, nl.IFLA_MACVLAN_MODE, nl.Uint32Attr(macvlanModes[link.Mode]))
}
} else if macv, ok := link.(*Macvtap); ok {
if macv.Mode != MACVLAN_MODE_DEFAULT {
case *Macvtap:
if link.Mode != MACVLAN_MODE_DEFAULT {
data := nl.NewRtAttrChild(linkInfo, nl.IFLA_INFO_DATA, nil)
nl.NewRtAttrChild(data, nl.IFLA_MACVLAN_MODE, nl.Uint32Attr(macvlanModes[macv.Mode]))
nl.NewRtAttrChild(data, nl.IFLA_MACVLAN_MODE, nl.Uint32Attr(macvlanModes[link.Mode]))
}
} else if gretap, ok := link.(*Gretap); ok {
addGretapAttrs(gretap, linkInfo)
} else if iptun, ok := link.(*Iptun); ok {
addIptunAttrs(iptun, linkInfo)
} else if vti, ok := link.(*Vti); ok {
addVtiAttrs(vti, linkInfo)
} else if vrf, ok := link.(*Vrf); ok {
addVrfAttrs(vrf, linkInfo)
} else if bridge, ok := link.(*Bridge); ok {
addBridgeAttrs(bridge, linkInfo)
} else if gtp, ok := link.(*GTP); ok {
addGTPAttrs(gtp, linkInfo)
case *Gretap:
addGretapAttrs(link, linkInfo)
case *Iptun:
addIptunAttrs(link, linkInfo)
case *Gretun:
addGretunAttrs(link, linkInfo)
case *Vti:
addVtiAttrs(link, linkInfo)
case *Vrf:
addVrfAttrs(link, linkInfo)
case *Bridge:
addBridgeAttrs(link, linkInfo)
case *GTP:
addGTPAttrs(link, linkInfo)
}
req.AddData(linkInfo)
@ -1093,6 +1179,8 @@ func LinkDeserialize(hdr *syscall.NlMsghdr, m []byte) (Link, error) {
link = &Gretap{}
case "ipip":
link = &Iptun{}
case "gre":
link = &Gretun{}
case "vti":
link = &Vti{}
case "vrf":
@ -1124,6 +1212,8 @@ func LinkDeserialize(hdr *syscall.NlMsghdr, m []byte) (Link, error) {
parseGretapData(link, data)
case "ipip":
parseIptunData(link, data)
case "gre":
parseGretunData(link, data)
case "vti":
parseVtiData(link, data)
case "vrf":
@ -1178,6 +1268,8 @@ func LinkDeserialize(hdr *syscall.NlMsghdr, m []byte) (Link, error) {
}
case syscall.IFLA_OPERSTATE:
base.OperState = LinkOperState(uint8(attr.Value[0]))
case nl.IFLA_LINK_NETNSID:
base.NetNsID = int(native.Uint32(attr.Value[0:4]))
}
}
@ -1239,16 +1331,34 @@ type LinkUpdate struct {
// LinkSubscribe takes a chan down which notifications will be sent
// when links change. Close the 'done' chan to stop subscription.
func LinkSubscribe(ch chan<- LinkUpdate, done <-chan struct{}) error {
return linkSubscribe(netns.None(), netns.None(), ch, done)
return linkSubscribeAt(netns.None(), netns.None(), ch, done, nil)
}
// LinkSubscribeAt works like LinkSubscribe plus it allows the caller
// to choose the network namespace in which to subscribe (ns).
func LinkSubscribeAt(ns netns.NsHandle, ch chan<- LinkUpdate, done <-chan struct{}) error {
return linkSubscribe(ns, netns.None(), ch, done)
return linkSubscribeAt(ns, netns.None(), ch, done, nil)
}
func linkSubscribe(newNs, curNs netns.NsHandle, ch chan<- LinkUpdate, done <-chan struct{}) error {
// LinkSubscribeOptions contains a set of options to use with
// LinkSubscribeWithOptions.
type LinkSubscribeOptions struct {
Namespace *netns.NsHandle
ErrorCallback func(error)
}
// LinkSubscribeWithOptions work like LinkSubscribe but enable to
// provide additional options to modify the behavior. Currently, the
// namespace can be provided as well as an error callback.
func LinkSubscribeWithOptions(ch chan<- LinkUpdate, done <-chan struct{}, options LinkSubscribeOptions) error {
if options.Namespace == nil {
none := netns.None()
options.Namespace = &none
}
return linkSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback)
}
func linkSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- LinkUpdate, done <-chan struct{}, cberr func(error)) error {
s, err := nl.SubscribeAt(newNs, curNs, syscall.NETLINK_ROUTE, syscall.RTNLGRP_LINK)
if err != nil {
return err
@ -1264,12 +1374,18 @@ func linkSubscribe(newNs, curNs netns.NsHandle, ch chan<- LinkUpdate, done <-cha
for {
msgs, err := s.Receive()
if err != nil {
if cberr != nil {
cberr(err)
}
return
}
for _, m := range msgs {
ifmsg := nl.DeserializeIfInfomsg(m.Data)
link, err := LinkDeserialize(&m.Header, m.Data)
if err != nil {
if cberr != nil {
cberr(err)
}
return
}
ch <- LinkUpdate{IfInfomsg: *ifmsg, Header: m.Header, Link: link}
@ -1363,6 +1479,33 @@ func (h *Handle) setProtinfoAttr(link Link, mode bool, attr int) error {
return nil
}
// LinkSetTxQLen sets the transaction queue length for the link.
// Equivalent to: `ip link set $link txqlen $qlen`
func LinkSetTxQLen(link Link, qlen int) error {
return pkgHandle.LinkSetTxQLen(link, qlen)
}
// LinkSetTxQLen sets the transaction queue length for the link.
// Equivalent to: `ip link set $link txqlen $qlen`
func (h *Handle) LinkSetTxQLen(link Link, qlen int) error {
base := link.Attrs()
h.ensureIndex(base)
req := h.newNetlinkRequest(syscall.RTM_SETLINK, syscall.NLM_F_ACK)
msg := nl.NewIfInfomsg(syscall.AF_UNSPEC)
msg.Index = int32(base.Index)
req.AddData(msg)
b := make([]byte, 4)
native.PutUint32(b, uint32(qlen))
data := nl.NewRtAttr(syscall.IFLA_TXQLEN, b)
req.AddData(data)
_, err := req.Execute(syscall.NETLINK_ROUTE, 0)
return err
}
func parseVlanData(link Link, data []syscall.NetlinkRouteAttr) {
vlan := link.(*Vlan)
for _, datum := range data {
@ -1407,6 +1550,8 @@ func parseVxlanData(link Link, data []syscall.NetlinkRouteAttr) {
vxlan.UDPCSum = int8(datum.Value[0]) != 0
case nl.IFLA_VXLAN_GBP:
vxlan.GBP = true
case nl.IFLA_VXLAN_FLOWBASED:
vxlan.FlowBased = int8(datum.Value[0]) != 0
case nl.IFLA_VXLAN_AGEING:
vxlan.Age = int(native.Uint32(datum.Value[0:4]))
vxlan.NoAge = vxlan.Age == 0
@ -1547,6 +1692,12 @@ func linkFlags(rawFlags uint32) net.Flags {
func addGretapAttrs(gretap *Gretap, linkInfo *nl.RtAttr) {
data := nl.NewRtAttrChild(linkInfo, nl.IFLA_INFO_DATA, nil)
if gretap.FlowBased {
// In flow based mode, no other attributes need to be configured
nl.NewRtAttrChild(data, nl.IFLA_GRE_COLLECT_METADATA, boolAttr(gretap.FlowBased))
return
}
ip := gretap.Local.To4()
if ip != nil {
nl.NewRtAttrChild(data, nl.IFLA_GRE_LOCAL, []byte(ip))
@ -1613,6 +1764,69 @@ func parseGretapData(link Link, data []syscall.NetlinkRouteAttr) {
gre.EncapType = native.Uint16(datum.Value[0:2])
case nl.IFLA_GRE_ENCAP_FLAGS:
gre.EncapFlags = native.Uint16(datum.Value[0:2])
case nl.IFLA_GRE_COLLECT_METADATA:
gre.FlowBased = int8(datum.Value[0]) != 0
}
}
}
func addGretunAttrs(gre *Gretun, linkInfo *nl.RtAttr) {
data := nl.NewRtAttrChild(linkInfo, nl.IFLA_INFO_DATA, nil)
ip := gre.Local.To4()
if ip != nil {
nl.NewRtAttrChild(data, nl.IFLA_GRE_LOCAL, []byte(ip))
}
ip = gre.Remote.To4()
if ip != nil {
nl.NewRtAttrChild(data, nl.IFLA_GRE_REMOTE, []byte(ip))
}
if gre.IKey != 0 {
nl.NewRtAttrChild(data, nl.IFLA_GRE_IKEY, htonl(gre.IKey))
gre.IFlags |= uint16(nl.GRE_KEY)
}
if gre.OKey != 0 {
nl.NewRtAttrChild(data, nl.IFLA_GRE_OKEY, htonl(gre.OKey))
gre.OFlags |= uint16(nl.GRE_KEY)
}
nl.NewRtAttrChild(data, nl.IFLA_GRE_IFLAGS, htons(gre.IFlags))
nl.NewRtAttrChild(data, nl.IFLA_GRE_OFLAGS, htons(gre.OFlags))
if gre.Link != 0 {
nl.NewRtAttrChild(data, nl.IFLA_GRE_LINK, nl.Uint32Attr(gre.Link))
}
nl.NewRtAttrChild(data, nl.IFLA_GRE_PMTUDISC, nl.Uint8Attr(gre.PMtuDisc))
nl.NewRtAttrChild(data, nl.IFLA_GRE_TTL, nl.Uint8Attr(gre.Ttl))
nl.NewRtAttrChild(data, nl.IFLA_GRE_TOS, nl.Uint8Attr(gre.Tos))
}
func parseGretunData(link Link, data []syscall.NetlinkRouteAttr) {
gre := link.(*Gretun)
for _, datum := range data {
switch datum.Attr.Type {
case nl.IFLA_GRE_OKEY:
gre.IKey = ntohl(datum.Value[0:4])
case nl.IFLA_GRE_IKEY:
gre.OKey = ntohl(datum.Value[0:4])
case nl.IFLA_GRE_LOCAL:
gre.Local = net.IP(datum.Value[0:4])
case nl.IFLA_GRE_REMOTE:
gre.Remote = net.IP(datum.Value[0:4])
case nl.IFLA_GRE_IFLAGS:
gre.IFlags = ntohs(datum.Value[0:2])
case nl.IFLA_GRE_OFLAGS:
gre.OFlags = ntohs(datum.Value[0:2])
case nl.IFLA_GRE_TTL:
gre.Ttl = uint8(datum.Value[0])
case nl.IFLA_GRE_TOS:
gre.Tos = uint8(datum.Value[0])
case nl.IFLA_GRE_PMTUDISC:
gre.PMtuDisc = uint8(datum.Value[0])
}
}
}
@ -1630,8 +1844,10 @@ func addXdpAttrs(xdp *LinkXdp, req *nl.NetlinkRequest) {
b := make([]byte, 4)
native.PutUint32(b, uint32(xdp.Fd))
nl.NewRtAttrChild(attrs, nl.IFLA_XDP_FD, b)
native.PutUint32(b, xdp.Flags)
nl.NewRtAttrChild(attrs, nl.IFLA_XDP_FLAGS, b)
if xdp.Flags != 0 {
native.PutUint32(b, xdp.Flags)
nl.NewRtAttrChild(attrs, nl.IFLA_XDP_FLAGS, b)
}
req.AddData(attrs)
}
@ -1649,6 +1865,8 @@ func parseLinkXdp(data []byte) (*LinkXdp, error) {
xdp.Attached = attr.Value[0] != 0
case nl.IFLA_XDP_FLAGS:
xdp.Flags = native.Uint32(attr.Value[0:4])
case nl.IFLA_XDP_PROG_ID:
xdp.ProgId = native.Uint32(attr.Value[0:4])
}
}
return xdp, nil

View file

@ -14,6 +14,7 @@ type Neigh struct {
Flags int
IP net.IP
HardwareAddr net.HardwareAddr
LLIPAddr net.IP //Used in the case of NHRP
}
// String returns $ip/$hwaddr $label

View file

@ -128,6 +128,7 @@ func (h *Handle) NeighDel(neigh *Neigh) error {
func neighHandle(neigh *Neigh, req *nl.NetlinkRequest) error {
var family int
if neigh.Family > 0 {
family = neigh.Family
} else {
@ -151,7 +152,10 @@ func neighHandle(neigh *Neigh, req *nl.NetlinkRequest) error {
dstData := nl.NewRtAttr(NDA_DST, ipData)
req.AddData(dstData)
if neigh.Flags != NTF_PROXY || neigh.HardwareAddr != nil {
if neigh.LLIPAddr != nil {
llIPData := nl.NewRtAttr(NDA_LLADDR, neigh.LLIPAddr.To4())
req.AddData(llIPData)
} else if neigh.Flags != NTF_PROXY || neigh.HardwareAddr != nil {
hwData := nl.NewRtAttr(NDA_LLADDR, []byte(neigh.HardwareAddr))
req.AddData(hwData)
}
@ -237,12 +241,33 @@ func NeighDeserialize(m []byte) (*Neigh, error) {
return nil, err
}
// This should be cached for perfomance
// once per table dump
link, err := LinkByIndex(neigh.LinkIndex)
if err != nil {
return nil, err
}
encapType := link.Attrs().EncapType
for _, attr := range attrs {
switch attr.Attr.Type {
case NDA_DST:
neigh.IP = net.IP(attr.Value)
case NDA_LLADDR:
neigh.HardwareAddr = net.HardwareAddr(attr.Value)
// BUG: Is this a bug in the netlink library?
// #define RTA_LENGTH(len) (RTA_ALIGN(sizeof(struct rtattr)) + (len))
// #define RTA_PAYLOAD(rta) ((int)((rta)->rta_len) - RTA_LENGTH(0))
attrLen := attr.Attr.Len - syscall.SizeofRtAttr
if attrLen == 4 && (encapType == "ipip" ||
encapType == "sit" ||
encapType == "gre") {
neigh.LLIPAddr = net.IP(attr.Value)
} else if attrLen == 16 &&
encapType == "tunnel6" {
neigh.IP = net.IP(attr.Value)
} else {
neigh.HardwareAddr = net.HardwareAddr(attr.Value)
}
}
}

View file

@ -108,6 +108,10 @@ func LinkSetFlood(link Link, mode bool) error {
return ErrNotImplemented
}
func LinkSetTxQLen(link Link, qlen int) error {
return ErrNotImplemented
}
func LinkAdd(link Link) error {
return ErrNotImplemented
}

View file

@ -0,0 +1,74 @@
package nl
import (
"fmt"
"unsafe"
)
const (
SizeofBridgeVlanInfo = 0x04
)
/* Bridge Flags */
const (
BRIDGE_FLAGS_MASTER = iota /* Bridge command to/from master */
BRIDGE_FLAGS_SELF /* Bridge command to/from lowerdev */
)
/* Bridge management nested attributes
* [IFLA_AF_SPEC] = {
* [IFLA_BRIDGE_FLAGS]
* [IFLA_BRIDGE_MODE]
* [IFLA_BRIDGE_VLAN_INFO]
* }
*/
const (
IFLA_BRIDGE_FLAGS = iota
IFLA_BRIDGE_MODE
IFLA_BRIDGE_VLAN_INFO
)
const (
BRIDGE_VLAN_INFO_MASTER = 1 << iota
BRIDGE_VLAN_INFO_PVID
BRIDGE_VLAN_INFO_UNTAGGED
BRIDGE_VLAN_INFO_RANGE_BEGIN
BRIDGE_VLAN_INFO_RANGE_END
)
// struct bridge_vlan_info {
// __u16 flags;
// __u16 vid;
// };
type BridgeVlanInfo struct {
Flags uint16
Vid uint16
}
func (b *BridgeVlanInfo) Serialize() []byte {
return (*(*[SizeofBridgeVlanInfo]byte)(unsafe.Pointer(b)))[:]
}
func DeserializeBridgeVlanInfo(b []byte) *BridgeVlanInfo {
return (*BridgeVlanInfo)(unsafe.Pointer(&b[0:SizeofBridgeVlanInfo][0]))
}
func (b *BridgeVlanInfo) PortVID() bool {
return b.Flags&BRIDGE_VLAN_INFO_PVID > 0
}
func (b *BridgeVlanInfo) EngressUntag() bool {
return b.Flags&BRIDGE_VLAN_INFO_UNTAGGED > 0
}
func (b *BridgeVlanInfo) String() string {
return fmt.Sprintf("%+v", *b)
}
/* New extended info filters for IFLA_EXT_MASK */
const (
RTEXT_FILTER_VF = 1 << iota
RTEXT_FILTER_BRVLAN
RTEXT_FILTER_BRVLAN_COMPRESSED
)

View file

@ -79,8 +79,8 @@ const (
CTA_TUPLE_ORIG = 1
CTA_TUPLE_REPLY = 2
CTA_STATUS = 3
CTA_TIMEOUT = 8
CTA_MARK = 9
CTA_TIMEOUT = 7
CTA_MARK = 8
CTA_PROTOINFO = 4
)

View file

@ -231,7 +231,8 @@ const (
* on/off switch
*/
IFLA_VF_STATS /* network device statistics */
IFLA_VF_MAX = IFLA_VF_STATS
IFLA_VF_TRUST /* Trust state of VF */
IFLA_VF_MAX = IFLA_VF_TRUST
)
const (
@ -259,6 +260,7 @@ const (
SizeofVfSpoofchk = 0x08
SizeofVfLinkState = 0x08
SizeofVfRssQueryEn = 0x08
SizeofVfTrust = 0x08
)
// struct ifla_vf_mac {
@ -419,12 +421,42 @@ func (msg *VfRssQueryEn) Serialize() []byte {
return (*(*[SizeofVfRssQueryEn]byte)(unsafe.Pointer(msg)))[:]
}
// struct ifla_vf_trust {
// __u32 vf;
// __u32 setting;
// };
type VfTrust struct {
Vf uint32
Setting uint32
}
func (msg *VfTrust) Len() int {
return SizeofVfTrust
}
func DeserializeVfTrust(b []byte) *VfTrust {
return (*VfTrust)(unsafe.Pointer(&b[0:SizeofVfTrust][0]))
}
func (msg *VfTrust) Serialize() []byte {
return (*(*[SizeofVfTrust]byte)(unsafe.Pointer(msg)))[:]
}
const (
XDP_FLAGS_UPDATE_IF_NOEXIST = 1 << iota
XDP_FLAGS_SKB_MODE
XDP_FLAGS_DRV_MODE
XDP_FLAGS_MASK = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_SKB_MODE | XDP_FLAGS_DRV_MODE
)
const (
IFLA_XDP_UNSPEC = iota
IFLA_XDP_FD /* fd of xdp program to attach, or -1 to remove */
IFLA_XDP_ATTACHED /* read-only bool indicating if prog is attached */
IFLA_XDP_FLAGS /* xdp prog related flags */
IFLA_XDP_MAX = IFLA_XDP_FLAGS
IFLA_XDP_PROG_ID /* xdp prog id */
IFLA_XDP_MAX = IFLA_XDP_PROG_ID
)
const (

View file

@ -621,6 +621,20 @@ func (s *NetlinkSocket) Receive() ([]syscall.NetlinkMessage, error) {
return syscall.ParseNetlinkMessage(rb)
}
// SetSendTimeout allows to set a send timeout on the socket
func (s *NetlinkSocket) SetSendTimeout(timeout *syscall.Timeval) error {
// Set a send timeout of SOCKET_SEND_TIMEOUT, this will allow the Send to periodically unblock and avoid that a routine
// remains stuck on a send on a closed fd
return syscall.SetsockoptTimeval(int(s.fd), syscall.SOL_SOCKET, syscall.SO_SNDTIMEO, timeout)
}
// SetReceiveTimeout allows to set a receive timeout on the socket
func (s *NetlinkSocket) SetReceiveTimeout(timeout *syscall.Timeval) error {
// Set a read timeout of SOCKET_READ_TIMEOUT, this will allow the Read to periodically unblock and avoid that a routine
// remains stuck on a recvmsg on a closed fd
return syscall.SetsockoptTimeval(int(s.fd), syscall.SOL_SOCKET, syscall.SO_RCVTIMEO, timeout)
}
func (s *NetlinkSocket) GetPid() (uint32, error) {
fd := int(atomic.LoadInt32(&s.fd))
lsa, err := syscall.Getsockname(fd)

View file

@ -160,71 +160,73 @@ func qdiscPayload(req *nl.NetlinkRequest, qdisc Qdisc) error {
req.AddData(nl.NewRtAttr(nl.TCA_KIND, nl.ZeroTerminated(qdisc.Type())))
options := nl.NewRtAttr(nl.TCA_OPTIONS, nil)
if prio, ok := qdisc.(*Prio); ok {
switch qdisc := qdisc.(type) {
case *Prio:
tcmap := nl.TcPrioMap{
Bands: int32(prio.Bands),
Priomap: prio.PriorityMap,
Bands: int32(qdisc.Bands),
Priomap: qdisc.PriorityMap,
}
options = nl.NewRtAttr(nl.TCA_OPTIONS, tcmap.Serialize())
} else if tbf, ok := qdisc.(*Tbf); ok {
case *Tbf:
opt := nl.TcTbfQopt{}
opt.Rate.Rate = uint32(tbf.Rate)
opt.Peakrate.Rate = uint32(tbf.Peakrate)
opt.Limit = tbf.Limit
opt.Buffer = tbf.Buffer
opt.Rate.Rate = uint32(qdisc.Rate)
opt.Peakrate.Rate = uint32(qdisc.Peakrate)
opt.Limit = qdisc.Limit
opt.Buffer = qdisc.Buffer
nl.NewRtAttrChild(options, nl.TCA_TBF_PARMS, opt.Serialize())
if tbf.Rate >= uint64(1<<32) {
nl.NewRtAttrChild(options, nl.TCA_TBF_RATE64, nl.Uint64Attr(tbf.Rate))
if qdisc.Rate >= uint64(1<<32) {
nl.NewRtAttrChild(options, nl.TCA_TBF_RATE64, nl.Uint64Attr(qdisc.Rate))
}
if tbf.Peakrate >= uint64(1<<32) {
nl.NewRtAttrChild(options, nl.TCA_TBF_PRATE64, nl.Uint64Attr(tbf.Peakrate))
if qdisc.Peakrate >= uint64(1<<32) {
nl.NewRtAttrChild(options, nl.TCA_TBF_PRATE64, nl.Uint64Attr(qdisc.Peakrate))
}
if tbf.Peakrate > 0 {
nl.NewRtAttrChild(options, nl.TCA_TBF_PBURST, nl.Uint32Attr(tbf.Minburst))
if qdisc.Peakrate > 0 {
nl.NewRtAttrChild(options, nl.TCA_TBF_PBURST, nl.Uint32Attr(qdisc.Minburst))
}
} else if htb, ok := qdisc.(*Htb); ok {
case *Htb:
opt := nl.TcHtbGlob{}
opt.Version = htb.Version
opt.Rate2Quantum = htb.Rate2Quantum
opt.Defcls = htb.Defcls
opt.Version = qdisc.Version
opt.Rate2Quantum = qdisc.Rate2Quantum
opt.Defcls = qdisc.Defcls
// TODO: Handle Debug properly. For now default to 0
opt.Debug = htb.Debug
opt.DirectPkts = htb.DirectPkts
opt.Debug = qdisc.Debug
opt.DirectPkts = qdisc.DirectPkts
nl.NewRtAttrChild(options, nl.TCA_HTB_INIT, opt.Serialize())
// nl.NewRtAttrChild(options, nl.TCA_HTB_DIRECT_QLEN, opt.Serialize())
} else if netem, ok := qdisc.(*Netem); ok {
case *Netem:
opt := nl.TcNetemQopt{}
opt.Latency = netem.Latency
opt.Limit = netem.Limit
opt.Loss = netem.Loss
opt.Gap = netem.Gap
opt.Duplicate = netem.Duplicate
opt.Jitter = netem.Jitter
opt.Latency = qdisc.Latency
opt.Limit = qdisc.Limit
opt.Loss = qdisc.Loss
opt.Gap = qdisc.Gap
opt.Duplicate = qdisc.Duplicate
opt.Jitter = qdisc.Jitter
options = nl.NewRtAttr(nl.TCA_OPTIONS, opt.Serialize())
// Correlation
corr := nl.TcNetemCorr{}
corr.DelayCorr = netem.DelayCorr
corr.LossCorr = netem.LossCorr
corr.DupCorr = netem.DuplicateCorr
corr.DelayCorr = qdisc.DelayCorr
corr.LossCorr = qdisc.LossCorr
corr.DupCorr = qdisc.DuplicateCorr
if corr.DelayCorr > 0 || corr.LossCorr > 0 || corr.DupCorr > 0 {
nl.NewRtAttrChild(options, nl.TCA_NETEM_CORR, corr.Serialize())
}
// Corruption
corruption := nl.TcNetemCorrupt{}
corruption.Probability = netem.CorruptProb
corruption.Correlation = netem.CorruptCorr
corruption.Probability = qdisc.CorruptProb
corruption.Correlation = qdisc.CorruptCorr
if corruption.Probability > 0 {
nl.NewRtAttrChild(options, nl.TCA_NETEM_CORRUPT, corruption.Serialize())
}
// Reorder
reorder := nl.TcNetemReorder{}
reorder.Probability = netem.ReorderProb
reorder.Correlation = netem.ReorderCorr
reorder.Probability = qdisc.ReorderProb
reorder.Correlation = qdisc.ReorderCorr
if reorder.Probability > 0 {
nl.NewRtAttrChild(options, nl.TCA_NETEM_REORDER, reorder.Serialize())
}
} else if _, ok := qdisc.(*Ingress); ok {
case *Ingress:
// ingress filters must use the proper handle
if qdisc.Attrs().Parent != HANDLE_INGRESS {
return fmt.Errorf("Ingress filters must set Parent to HANDLE_INGRESS")

View file

@ -16,6 +16,7 @@ type Destination interface {
Decode([]byte) error
Encode() ([]byte, error)
String() string
Equal(Destination) bool
}
type Encap interface {
@ -23,6 +24,7 @@ type Encap interface {
Decode([]byte) error
Encode() ([]byte, error)
String() string
Equal(Encap) bool
}
// Route represents a netlink route.
@ -72,6 +74,25 @@ func (r Route) String() string {
return fmt.Sprintf("{%s}", strings.Join(elems, " "))
}
func (r Route) Equal(x Route) bool {
return r.LinkIndex == x.LinkIndex &&
r.ILinkIndex == x.ILinkIndex &&
r.Scope == x.Scope &&
ipNetEqual(r.Dst, x.Dst) &&
r.Src.Equal(x.Src) &&
r.Gw.Equal(x.Gw) &&
nexthopInfoSlice(r.MultiPath).Equal(x.MultiPath) &&
r.Protocol == x.Protocol &&
r.Priority == x.Priority &&
r.Table == x.Table &&
r.Type == x.Type &&
r.Tos == x.Tos &&
r.Flags == x.Flags &&
(r.MPLSDst == x.MPLSDst || (r.MPLSDst != nil && x.MPLSDst != nil && *r.MPLSDst == *x.MPLSDst)) &&
(r.NewDst == x.NewDst || (r.NewDst != nil && r.NewDst.Equal(x.NewDst))) &&
(r.Encap == x.Encap || (r.Encap != nil && r.Encap.Equal(x.Encap)))
}
func (r *Route) SetFlag(flag NextHopFlag) {
r.Flags |= int(flag)
}
@ -110,7 +131,46 @@ func (n *NexthopInfo) String() string {
elems = append(elems, fmt.Sprintf("Encap: %s", n.Encap))
}
elems = append(elems, fmt.Sprintf("Weight: %d", n.Hops+1))
elems = append(elems, fmt.Sprintf("Gw: %d", n.Gw))
elems = append(elems, fmt.Sprintf("Gw: %s", n.Gw))
elems = append(elems, fmt.Sprintf("Flags: %s", n.ListFlags()))
return fmt.Sprintf("{%s}", strings.Join(elems, " "))
}
func (n NexthopInfo) Equal(x NexthopInfo) bool {
return n.LinkIndex == x.LinkIndex &&
n.Hops == x.Hops &&
n.Gw.Equal(x.Gw) &&
n.Flags == x.Flags &&
(n.NewDst == x.NewDst || (n.NewDst != nil && n.NewDst.Equal(x.NewDst))) &&
(n.Encap == x.Encap || (n.Encap != nil && n.Encap.Equal(x.Encap)))
}
type nexthopInfoSlice []*NexthopInfo
func (n nexthopInfoSlice) Equal(x []*NexthopInfo) bool {
if len(n) != len(x) {
return false
}
for i := range n {
if n[i] == nil || x[i] == nil {
return false
}
if !n[i].Equal(*x[i]) {
return false
}
}
return true
}
// ipNetEqual returns true iff both IPNet are equal
func ipNetEqual(ipn1 *net.IPNet, ipn2 *net.IPNet) bool {
if ipn1 == ipn2 {
return true
}
if ipn1 == nil || ipn2 == nil {
return false
}
m1, _ := ipn1.Mask.Size()
m2, _ := ipn2.Mask.Size()
return m1 == m2 && ipn1.IP.Equal(ipn2.IP)
}

View file

@ -86,6 +86,34 @@ func (d *MPLSDestination) String() string {
return strings.Join(s, "/")
}
func (d *MPLSDestination) Equal(x Destination) bool {
o, ok := x.(*MPLSDestination)
if !ok {
return false
}
if d == nil && o == nil {
return true
}
if d == nil || o == nil {
return false
}
if d.Labels == nil && o.Labels == nil {
return true
}
if d.Labels == nil || o.Labels == nil {
return false
}
if len(d.Labels) != len(o.Labels) {
return false
}
for i := range d.Labels {
if d.Labels[i] != o.Labels[i] {
return false
}
}
return true
}
type MPLSEncap struct {
Labels []int
}
@ -129,6 +157,34 @@ func (e *MPLSEncap) String() string {
return strings.Join(s, "/")
}
func (e *MPLSEncap) Equal(x Encap) bool {
o, ok := x.(*MPLSEncap)
if !ok {
return false
}
if e == nil && o == nil {
return true
}
if e == nil || o == nil {
return false
}
if e.Labels == nil && o.Labels == nil {
return true
}
if e.Labels == nil || o.Labels == nil {
return false
}
if len(e.Labels) != len(o.Labels) {
return false
}
for i := range e.Labels {
if e.Labels[i] != o.Labels[i] {
return false
}
}
return true
}
// RouteAdd will add a route to the system.
// Equivalent to: `ip route add $route`
func RouteAdd(route *Route) error {
@ -421,19 +477,8 @@ func (h *Handle) RouteListFiltered(family int, filter *Route, filterMask uint64)
continue
case filterMask&RT_FILTER_DST != 0:
if filter.MPLSDst == nil || route.MPLSDst == nil || (*filter.MPLSDst) != (*route.MPLSDst) {
if filter.Dst == nil {
if route.Dst != nil {
continue
}
} else {
if route.Dst == nil {
continue
}
aMaskLen, aMaskBits := route.Dst.Mask.Size()
bMaskLen, bMaskBits := filter.Dst.Mask.Size()
if !(route.Dst.IP.Equal(filter.Dst.IP) && aMaskLen == bMaskLen && aMaskBits == bMaskBits) {
continue
}
if !ipNetEqual(route.Dst, filter.Dst) {
continue
}
}
}
@ -633,16 +678,34 @@ func (h *Handle) RouteGet(destination net.IP) ([]Route, error) {
// RouteSubscribe takes a chan down which notifications will be sent
// when routes are added or deleted. Close the 'done' chan to stop subscription.
func RouteSubscribe(ch chan<- RouteUpdate, done <-chan struct{}) error {
return routeSubscribeAt(netns.None(), netns.None(), ch, done)
return routeSubscribeAt(netns.None(), netns.None(), ch, done, nil)
}
// RouteSubscribeAt works like RouteSubscribe plus it allows the caller
// to choose the network namespace in which to subscribe (ns).
func RouteSubscribeAt(ns netns.NsHandle, ch chan<- RouteUpdate, done <-chan struct{}) error {
return routeSubscribeAt(ns, netns.None(), ch, done)
return routeSubscribeAt(ns, netns.None(), ch, done, nil)
}
func routeSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- RouteUpdate, done <-chan struct{}) error {
// RouteSubscribeOptions contains a set of options to use with
// RouteSubscribeWithOptions.
type RouteSubscribeOptions struct {
Namespace *netns.NsHandle
ErrorCallback func(error)
}
// RouteSubscribeWithOptions work like RouteSubscribe but enable to
// provide additional options to modify the behavior. Currently, the
// namespace can be provided as well as an error callback.
func RouteSubscribeWithOptions(ch chan<- RouteUpdate, done <-chan struct{}, options RouteSubscribeOptions) error {
if options.Namespace == nil {
none := netns.None()
options.Namespace = &none
}
return routeSubscribeAt(*options.Namespace, netns.None(), ch, done, options.ErrorCallback)
}
func routeSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- RouteUpdate, done <-chan struct{}, cberr func(error)) error {
s, err := nl.SubscribeAt(newNs, curNs, syscall.NETLINK_ROUTE, syscall.RTNLGRP_IPV4_ROUTE, syscall.RTNLGRP_IPV6_ROUTE)
if err != nil {
return err
@ -658,11 +721,17 @@ func routeSubscribeAt(newNs, curNs netns.NsHandle, ch chan<- RouteUpdate, done <
for {
msgs, err := s.Receive()
if err != nil {
if cberr != nil {
cberr(err)
}
return
}
for _, m := range msgs {
route, err := deserializeRoute(m.Data)
if err != nil {
if cberr != nil {
cberr(err)
}
return
}
ch <- RouteUpdate{Type: m.Header.Type, Route: route}

View file

@ -8,6 +8,7 @@ import (
// Rule represents a netlink rule.
type Rule struct {
Priority int
Family int
Table int
Mark int
Mask int

View file

@ -37,6 +37,9 @@ func (h *Handle) RuleDel(rule *Rule) error {
func ruleHandle(rule *Rule, req *nl.NetlinkRequest) error {
msg := nl.NewRtMsg()
msg.Family = syscall.AF_INET
if rule.Family != 0 {
msg.Family = uint8(rule.Family)
}
var dstFamily uint8
var rtAttrs []*nl.RtAttr