mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Add IPVS netlink support
This PR adds netlink support to manipulate ipvs configuration. Signed-off-by: Jana Radhakrishnan <mrjana@docker.com>
This commit is contained in:
parent
ac18cc4b8f
commit
4b549ce428
4 changed files with 791 additions and 0 deletions
130
libnetwork/ipvs/constants.go
Normal file
130
libnetwork/ipvs/constants.go
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package ipvs
|
||||||
|
|
||||||
|
const (
|
||||||
|
genlCtrlID = 0x10
|
||||||
|
)
|
||||||
|
|
||||||
|
// GENL control commands
|
||||||
|
const (
|
||||||
|
genlCtrlCmdUnspec uint8 = iota
|
||||||
|
genlCtrlCmdNewFamily
|
||||||
|
genlCtrlCmdDelFamily
|
||||||
|
genlCtrlCmdGetFamily
|
||||||
|
)
|
||||||
|
|
||||||
|
// GENL family attributes
|
||||||
|
const (
|
||||||
|
genlCtrlAttrUnspec int = iota
|
||||||
|
genlCtrlAttrFamilyID
|
||||||
|
genlCtrlAttrFamilyName
|
||||||
|
)
|
||||||
|
|
||||||
|
// IPVS genl commands
|
||||||
|
const (
|
||||||
|
ipvsCmdUnspec uint8 = iota
|
||||||
|
ipvsCmdNewService
|
||||||
|
ipvsCmdSetService
|
||||||
|
ipvsCmdDelService
|
||||||
|
ipvsCmdGetService
|
||||||
|
ipvsCmdNewDest
|
||||||
|
ipvsCmdSetDest
|
||||||
|
ipvsCmdDelDest
|
||||||
|
ipvsCmdGetDest
|
||||||
|
ipvsCmdNewDaemon
|
||||||
|
ipvsCmdDelDaemon
|
||||||
|
ipvsCmdGetDaemon
|
||||||
|
ipvsCmdSetConfig
|
||||||
|
ipvsCmdGetConfig
|
||||||
|
ipvsCmdSetInfo
|
||||||
|
ipvsCmdGetInfo
|
||||||
|
ipvsCmdZero
|
||||||
|
ipvsCmdFlush
|
||||||
|
)
|
||||||
|
|
||||||
|
// Attributes used in the first level of commands
|
||||||
|
const (
|
||||||
|
ipvsCmdAttrUnspec int = iota
|
||||||
|
ipvsCmdAttrService
|
||||||
|
ipvsCmdAttrDest
|
||||||
|
ipvsCmdAttrDaemon
|
||||||
|
ipvsCmdAttrTimeoutTCP
|
||||||
|
ipvsCmdAttrTimeoutTCPFin
|
||||||
|
ipvsCmdAttrTimeoutUDP
|
||||||
|
)
|
||||||
|
|
||||||
|
// Attributes used to describe a service. Used inside nested attribute
|
||||||
|
// ipvsCmdAttrService
|
||||||
|
const (
|
||||||
|
ipvsSvcAttrUnspec int = iota
|
||||||
|
ipvsSvcAttrAddressFamily
|
||||||
|
ipvsSvcAttrProtocol
|
||||||
|
ipvsSvcAttrAddress
|
||||||
|
ipvsSvcAttrPort
|
||||||
|
ipvsSvcAttrFWMark
|
||||||
|
ipvsSvcAttrSchedName
|
||||||
|
ipvsSvcAttrFlags
|
||||||
|
ipvsSvcAttrTimeout
|
||||||
|
ipvsSvcAttrNetmask
|
||||||
|
ipvsSvcAttrStats
|
||||||
|
ipvsSvcAttrPEName
|
||||||
|
)
|
||||||
|
|
||||||
|
// Attributes used to describe a destination (real server). Used
|
||||||
|
// inside nested attribute ipvsCmdAttrDest.
|
||||||
|
const (
|
||||||
|
ipvsDestAttrUnspec int = iota
|
||||||
|
ipvsDestAttrAddress
|
||||||
|
ipvsDestAttrPort
|
||||||
|
ipvsDestAttrForwardingMethod
|
||||||
|
ipvsDestAttrWeight
|
||||||
|
ipvsDestAttrUpperThreshold
|
||||||
|
ipvsDestAttrLowerThreshold
|
||||||
|
ipvsDestAttrActiveConnections
|
||||||
|
ipvsDestAttrInactiveConnections
|
||||||
|
ipvsDestAttrPersistentConnections
|
||||||
|
ipvsDestAttrStats
|
||||||
|
)
|
||||||
|
|
||||||
|
// Destination forwarding methods
|
||||||
|
const (
|
||||||
|
// ConnectionFlagFwdmask indicates the mask in the connection
|
||||||
|
// flags which is used by forwarding method bits.
|
||||||
|
ConnectionFlagFwdMask = 0x0007
|
||||||
|
|
||||||
|
// ConnectionFlagMasq is used for masquerade forwarding method.
|
||||||
|
ConnectionFlagMasq = 0x0000
|
||||||
|
|
||||||
|
// ConnectionFlagLocalNode is used for local node forwarding
|
||||||
|
// method.
|
||||||
|
ConnectionFlagLocalNode = 0x0001
|
||||||
|
|
||||||
|
// ConnectionFlagTunnel is used for tunnel mode forwarding
|
||||||
|
// method.
|
||||||
|
ConnectionFlagTunnel = 0x0002
|
||||||
|
|
||||||
|
// ConnectionFlagDirectRoute is used for direct routing
|
||||||
|
// forwarding method.
|
||||||
|
ConnectionFlagDirectRoute = 0x0003
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RoundRobin distributes jobs equally amongst the available
|
||||||
|
// real servers.
|
||||||
|
RoundRobin = "rr"
|
||||||
|
|
||||||
|
// LeastConnection assigns more jobs to real servers with
|
||||||
|
// fewer active jobs.
|
||||||
|
LeastConnection = "lc"
|
||||||
|
|
||||||
|
// DestinationHashing assigns jobs to servers through looking
|
||||||
|
// up a statically assigned hash table by their destination IP
|
||||||
|
// addresses.
|
||||||
|
DestinationHashing = "dh"
|
||||||
|
|
||||||
|
// SourceHashing assigns jobs to servers through looking up
|
||||||
|
// a statically assigned hash table by their source IP
|
||||||
|
// addresses.
|
||||||
|
SourceHashing = "sh"
|
||||||
|
)
|
113
libnetwork/ipvs/ipvs.go
Normal file
113
libnetwork/ipvs/ipvs.go
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package ipvs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/vishvananda/netlink/nl"
|
||||||
|
"github.com/vishvananda/netns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service defines an IPVS service in its entirety.
|
||||||
|
type Service struct {
|
||||||
|
// Virtual service address.
|
||||||
|
Address net.IP
|
||||||
|
Protocol uint16
|
||||||
|
Port uint16
|
||||||
|
FWMark uint32 // Firewall mark of the service.
|
||||||
|
|
||||||
|
// Virtual service options.
|
||||||
|
SchedName string
|
||||||
|
Flags uint32
|
||||||
|
Timeout uint32
|
||||||
|
Netmask uint32
|
||||||
|
AddressFamily uint16
|
||||||
|
PEName string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destination defines an IPVS destination (real server) in its
|
||||||
|
// entirety.
|
||||||
|
type Destination struct {
|
||||||
|
Address net.IP
|
||||||
|
Port uint16
|
||||||
|
Weight int
|
||||||
|
ConnectionFlags uint32
|
||||||
|
AddressFamily uint16
|
||||||
|
UpperThreshold uint32
|
||||||
|
LowerThreshold uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle provides a namespace specific ipvs handle to program ipvs
|
||||||
|
// rules.
|
||||||
|
type Handle struct {
|
||||||
|
sock *nl.NetlinkSocket
|
||||||
|
}
|
||||||
|
|
||||||
|
// New provides a new ipvs handle in the namespace pointed to by the
|
||||||
|
// passed path. It will return a valid handle or an error in case an
|
||||||
|
// error occured while creating the handle.
|
||||||
|
func New(path string) (*Handle, error) {
|
||||||
|
setup()
|
||||||
|
|
||||||
|
n := netns.None()
|
||||||
|
if path != "" {
|
||||||
|
var err error
|
||||||
|
n, err = netns.GetFromPath(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sock, err := nl.GetNetlinkSocketAt(n, netns.None(), syscall.NETLINK_GENERIC)
|
||||||
|
if err != nil {
|
||||||
|
n.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Handle{sock: sock}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the ipvs handle. The handle is invalid after Close
|
||||||
|
// returns.
|
||||||
|
func (i *Handle) Close() {
|
||||||
|
if i.sock != nil {
|
||||||
|
i.sock.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService creates a new ipvs service in the passed handle.
|
||||||
|
func (i *Handle) NewService(s *Service) error {
|
||||||
|
return i.doCmd(s, nil, ipvsCmdNewService)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateService updates an already existing service in the passed
|
||||||
|
// handle.
|
||||||
|
func (i *Handle) UpdateService(s *Service) error {
|
||||||
|
return i.doCmd(s, nil, ipvsCmdSetService)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelService deletes an already existing service in the passed
|
||||||
|
// handle.
|
||||||
|
func (i *Handle) DelService(s *Service) error {
|
||||||
|
return i.doCmd(s, nil, ipvsCmdDelService)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDestination creates an new real server in the passed ipvs
|
||||||
|
// service which should already be existing in the passed handle.
|
||||||
|
func (i *Handle) NewDestination(s *Service, d *Destination) error {
|
||||||
|
return i.doCmd(s, d, ipvsCmdNewDest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDestination updates an already existing real server in the
|
||||||
|
// passed ipvs service in the passed handle.
|
||||||
|
func (i *Handle) UpdateDestination(s *Service, d *Destination) error {
|
||||||
|
return i.doCmd(s, d, ipvsCmdSetDest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelDestination deletes an already existing real server in the
|
||||||
|
// passed ipvs service in the passed handle.
|
||||||
|
func (i *Handle) DelDestination(s *Service, d *Destination) error {
|
||||||
|
return i.doCmd(s, d, ipvsCmdDelDest)
|
||||||
|
}
|
321
libnetwork/ipvs/ipvs_test.go
Normal file
321
libnetwork/ipvs/ipvs_test.go
Normal file
|
@ -0,0 +1,321 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package ipvs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/libnetwork/testutils"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
"github.com/vishvananda/netlink/nl"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
schedMethods = []string{
|
||||||
|
RoundRobin,
|
||||||
|
LeastConnection,
|
||||||
|
DestinationHashing,
|
||||||
|
SourceHashing,
|
||||||
|
}
|
||||||
|
|
||||||
|
protocols = []string{
|
||||||
|
"TCP",
|
||||||
|
"UDP",
|
||||||
|
"FWM",
|
||||||
|
}
|
||||||
|
|
||||||
|
fwdMethods = []uint32{
|
||||||
|
ConnectionFlagMasq,
|
||||||
|
ConnectionFlagTunnel,
|
||||||
|
ConnectionFlagDirectRoute,
|
||||||
|
}
|
||||||
|
|
||||||
|
fwdMethodStrings = []string{
|
||||||
|
"Masq",
|
||||||
|
"Tunnel",
|
||||||
|
"Route",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkDestination(t *testing.T, checkPresent bool, protocol, serviceAddress, realAddress, fwdMethod string) {
|
||||||
|
var (
|
||||||
|
realServerStart bool
|
||||||
|
realServers []string
|
||||||
|
)
|
||||||
|
|
||||||
|
out, err := exec.Command("ipvsadm", "-Ln").CombinedOutput()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, o := range strings.Split(string(out), "\n") {
|
||||||
|
cmpStr := serviceAddress
|
||||||
|
if protocol == "FWM" {
|
||||||
|
cmpStr = " " + cmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(o, cmpStr) {
|
||||||
|
realServerStart = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if realServerStart {
|
||||||
|
if !strings.Contains(o, "->") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
realServers = append(realServers, o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range realServers {
|
||||||
|
if strings.Contains(r, realAddress) {
|
||||||
|
parts := strings.Fields(r)
|
||||||
|
assert.Equal(t, fwdMethod, parts[2])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkPresent {
|
||||||
|
t.Fatalf("Did not find the destination %s fwdMethod %s in ipvs output", realAddress, fwdMethod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkService(t *testing.T, checkPresent bool, protocol, schedMethod, serviceAddress string) {
|
||||||
|
out, err := exec.Command("ipvsadm", "-Ln").CombinedOutput()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, o := range strings.Split(string(out), "\n") {
|
||||||
|
cmpStr := serviceAddress
|
||||||
|
if protocol == "FWM" {
|
||||||
|
cmpStr = " " + cmpStr
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(o, cmpStr) {
|
||||||
|
parts := strings.Split(o, " ")
|
||||||
|
assert.Equal(t, protocol, parts[0])
|
||||||
|
assert.Equal(t, serviceAddress, parts[2])
|
||||||
|
assert.Equal(t, schedMethod, parts[3])
|
||||||
|
|
||||||
|
if !checkPresent {
|
||||||
|
t.Fatalf("Did not expect the service %s in ipvs output", serviceAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if checkPresent {
|
||||||
|
t.Fatalf("Did not find the service %s in ipvs output", serviceAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFamily(t *testing.T) {
|
||||||
|
if testutils.RunningOnCircleCI() {
|
||||||
|
t.Skipf("Skipping as not supported on CIRCLE CI kernel")
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := getIPVSFamily()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.NotEqual(t, 0, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestService(t *testing.T) {
|
||||||
|
if testutils.RunningOnCircleCI() {
|
||||||
|
t.Skipf("Skipping as not supported on CIRCLE CI kernel")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer testutils.SetupTestOSContext(t)()
|
||||||
|
|
||||||
|
i, err := New("")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, protocol := range protocols {
|
||||||
|
for _, schedMethod := range schedMethods {
|
||||||
|
var serviceAddress string
|
||||||
|
|
||||||
|
s := Service{
|
||||||
|
AddressFamily: nl.FAMILY_V4,
|
||||||
|
SchedName: schedMethod,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch protocol {
|
||||||
|
case "FWM":
|
||||||
|
s.FWMark = 1234
|
||||||
|
serviceAddress = fmt.Sprintf("%d", 1234)
|
||||||
|
case "TCP":
|
||||||
|
s.Protocol = syscall.IPPROTO_TCP
|
||||||
|
s.Port = 80
|
||||||
|
s.Address = net.ParseIP("1.2.3.4")
|
||||||
|
s.Netmask = 0xFFFFFFFF
|
||||||
|
serviceAddress = "1.2.3.4:80"
|
||||||
|
case "UDP":
|
||||||
|
s.Protocol = syscall.IPPROTO_UDP
|
||||||
|
s.Port = 53
|
||||||
|
s.Address = net.ParseIP("2.3.4.5")
|
||||||
|
serviceAddress = "2.3.4.5:53"
|
||||||
|
}
|
||||||
|
|
||||||
|
err := i.NewService(&s)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
checkService(t, true, protocol, schedMethod, serviceAddress)
|
||||||
|
var lastMethod string
|
||||||
|
for _, updateSchedMethod := range schedMethods {
|
||||||
|
if updateSchedMethod == schedMethod {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
s.SchedName = updateSchedMethod
|
||||||
|
err = i.UpdateService(&s)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
checkService(t, true, protocol, updateSchedMethod, serviceAddress)
|
||||||
|
lastMethod = updateSchedMethod
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.DelService(&s)
|
||||||
|
checkService(t, false, protocol, lastMethod, serviceAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDummyInterface(t *testing.T) {
|
||||||
|
if testutils.RunningOnCircleCI() {
|
||||||
|
t.Skipf("Skipping as not supported on CIRCLE CI kernel")
|
||||||
|
}
|
||||||
|
|
||||||
|
dummy := &netlink.Dummy{
|
||||||
|
LinkAttrs: netlink.LinkAttrs{
|
||||||
|
Name: "dummy",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := netlink.LinkAdd(dummy)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
dummyLink, err := netlink.LinkByName("dummy")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ip, ipNet, err := net.ParseCIDR("10.1.1.1/24")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ipNet.IP = ip
|
||||||
|
|
||||||
|
ipAddr := &netlink.Addr{IPNet: ipNet, Label: ""}
|
||||||
|
err = netlink.AddrAdd(dummyLink, ipAddr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDestination(t *testing.T) {
|
||||||
|
defer testutils.SetupTestOSContext(t)()
|
||||||
|
|
||||||
|
createDummyInterface(t)
|
||||||
|
i, err := New("")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, protocol := range []string{"TCP"} {
|
||||||
|
var serviceAddress string
|
||||||
|
|
||||||
|
s := Service{
|
||||||
|
AddressFamily: nl.FAMILY_V4,
|
||||||
|
SchedName: RoundRobin,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch protocol {
|
||||||
|
case "FWM":
|
||||||
|
s.FWMark = 1234
|
||||||
|
serviceAddress = fmt.Sprintf("%d", 1234)
|
||||||
|
case "TCP":
|
||||||
|
s.Protocol = syscall.IPPROTO_TCP
|
||||||
|
s.Port = 80
|
||||||
|
s.Address = net.ParseIP("1.2.3.4")
|
||||||
|
s.Netmask = 0xFFFFFFFF
|
||||||
|
serviceAddress = "1.2.3.4:80"
|
||||||
|
case "UDP":
|
||||||
|
s.Protocol = syscall.IPPROTO_UDP
|
||||||
|
s.Port = 53
|
||||||
|
s.Address = net.ParseIP("2.3.4.5")
|
||||||
|
serviceAddress = "2.3.4.5:53"
|
||||||
|
}
|
||||||
|
|
||||||
|
err := i.NewService(&s)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
checkService(t, true, protocol, RoundRobin, serviceAddress)
|
||||||
|
|
||||||
|
s.SchedName = ""
|
||||||
|
for j, fwdMethod := range fwdMethods {
|
||||||
|
d1 := Destination{
|
||||||
|
AddressFamily: nl.FAMILY_V4,
|
||||||
|
Address: net.ParseIP("10.1.1.2"),
|
||||||
|
Port: 5000,
|
||||||
|
Weight: 1,
|
||||||
|
ConnectionFlags: fwdMethod,
|
||||||
|
}
|
||||||
|
|
||||||
|
realAddress := "10.1.1.2:5000"
|
||||||
|
err := i.NewDestination(&s, &d1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
checkDestination(t, true, protocol, serviceAddress, realAddress, fwdMethodStrings[j])
|
||||||
|
d2 := Destination{
|
||||||
|
AddressFamily: nl.FAMILY_V4,
|
||||||
|
Address: net.ParseIP("10.1.1.3"),
|
||||||
|
Port: 5000,
|
||||||
|
Weight: 1,
|
||||||
|
ConnectionFlags: fwdMethod,
|
||||||
|
}
|
||||||
|
|
||||||
|
realAddress = "10.1.1.3:5000"
|
||||||
|
err = i.NewDestination(&s, &d2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
checkDestination(t, true, protocol, serviceAddress, realAddress, fwdMethodStrings[j])
|
||||||
|
|
||||||
|
d3 := Destination{
|
||||||
|
AddressFamily: nl.FAMILY_V4,
|
||||||
|
Address: net.ParseIP("10.1.1.4"),
|
||||||
|
Port: 5000,
|
||||||
|
Weight: 1,
|
||||||
|
ConnectionFlags: fwdMethod,
|
||||||
|
}
|
||||||
|
|
||||||
|
realAddress = "10.1.1.4:5000"
|
||||||
|
err = i.NewDestination(&s, &d3)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
checkDestination(t, true, protocol, serviceAddress, realAddress, fwdMethodStrings[j])
|
||||||
|
|
||||||
|
for m, updateFwdMethod := range fwdMethods {
|
||||||
|
if updateFwdMethod == fwdMethod {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
d1.ConnectionFlags = updateFwdMethod
|
||||||
|
realAddress = "10.1.1.2:5000"
|
||||||
|
err = i.UpdateDestination(&s, &d1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
checkDestination(t, true, protocol, serviceAddress, realAddress, fwdMethodStrings[m])
|
||||||
|
|
||||||
|
d2.ConnectionFlags = updateFwdMethod
|
||||||
|
realAddress = "10.1.1.3:5000"
|
||||||
|
err = i.UpdateDestination(&s, &d2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
checkDestination(t, true, protocol, serviceAddress, realAddress, fwdMethodStrings[m])
|
||||||
|
|
||||||
|
d3.ConnectionFlags = updateFwdMethod
|
||||||
|
realAddress = "10.1.1.4:5000"
|
||||||
|
err = i.UpdateDestination(&s, &d3)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
checkDestination(t, true, protocol, serviceAddress, realAddress, fwdMethodStrings[m])
|
||||||
|
}
|
||||||
|
|
||||||
|
err = i.DelDestination(&s, &d1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = i.DelDestination(&s, &d2)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = i.DelDestination(&s, &d3)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
227
libnetwork/ipvs/netlink.go
Normal file
227
libnetwork/ipvs/netlink.go
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package ipvs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/vishvananda/netlink/nl"
|
||||||
|
"github.com/vishvananda/netns"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
native = nl.NativeEndian()
|
||||||
|
ipvsFamily int
|
||||||
|
ipvsOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
type genlMsgHdr struct {
|
||||||
|
cmd uint8
|
||||||
|
version uint8
|
||||||
|
reserved uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type ipvsFlags struct {
|
||||||
|
flags uint32
|
||||||
|
mask uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func deserializeGenlMsg(b []byte) (hdr *genlMsgHdr) {
|
||||||
|
return (*genlMsgHdr)(unsafe.Pointer(&b[0:unsafe.Sizeof(*hdr)][0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hdr *genlMsgHdr) Serialize() []byte {
|
||||||
|
return (*(*[unsafe.Sizeof(*hdr)]byte)(unsafe.Pointer(hdr)))[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hdr *genlMsgHdr) Len() int {
|
||||||
|
return int(unsafe.Sizeof(*hdr))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ipvsFlags) Serialize() []byte {
|
||||||
|
return (*(*[unsafe.Sizeof(*f)]byte)(unsafe.Pointer(f)))[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *ipvsFlags) Len() int {
|
||||||
|
return int(unsafe.Sizeof(*f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup() {
|
||||||
|
ipvsOnce.Do(func() {
|
||||||
|
var err error
|
||||||
|
ipvsFamily, err = getIPVSFamily()
|
||||||
|
if err != nil {
|
||||||
|
panic("could not get ipvs family")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func fillService(s *Service) nl.NetlinkRequestData {
|
||||||
|
cmdAttr := nl.NewRtAttr(ipvsCmdAttrService, nil)
|
||||||
|
nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrAddressFamily, nl.Uint16Attr(s.AddressFamily))
|
||||||
|
if s.FWMark != 0 {
|
||||||
|
nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrFWMark, nl.Uint32Attr(s.FWMark))
|
||||||
|
} else {
|
||||||
|
nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrProtocol, nl.Uint16Attr(s.Protocol))
|
||||||
|
nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrAddress, rawIPData(s.Address))
|
||||||
|
|
||||||
|
// Port needs to be in network byte order.
|
||||||
|
portBuf := new(bytes.Buffer)
|
||||||
|
binary.Write(portBuf, binary.BigEndian, s.Port)
|
||||||
|
nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrPort, portBuf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrSchedName, nl.ZeroTerminated(s.SchedName))
|
||||||
|
if s.PEName != "" {
|
||||||
|
nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrPEName, nl.ZeroTerminated(s.PEName))
|
||||||
|
}
|
||||||
|
|
||||||
|
f := &ipvsFlags{
|
||||||
|
flags: s.Flags,
|
||||||
|
mask: 0xFFFFFFFF,
|
||||||
|
}
|
||||||
|
nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrFlags, f.Serialize())
|
||||||
|
nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrTimeout, nl.Uint32Attr(s.Timeout))
|
||||||
|
nl.NewRtAttrChild(cmdAttr, ipvsSvcAttrNetmask, nl.Uint32Attr(s.Netmask))
|
||||||
|
return cmdAttr
|
||||||
|
}
|
||||||
|
|
||||||
|
func fillDestinaton(d *Destination) nl.NetlinkRequestData {
|
||||||
|
cmdAttr := nl.NewRtAttr(ipvsCmdAttrDest, nil)
|
||||||
|
|
||||||
|
nl.NewRtAttrChild(cmdAttr, ipvsDestAttrAddress, rawIPData(d.Address))
|
||||||
|
// Port needs to be in network byte order.
|
||||||
|
portBuf := new(bytes.Buffer)
|
||||||
|
binary.Write(portBuf, binary.BigEndian, d.Port)
|
||||||
|
nl.NewRtAttrChild(cmdAttr, ipvsDestAttrPort, portBuf.Bytes())
|
||||||
|
|
||||||
|
nl.NewRtAttrChild(cmdAttr, ipvsDestAttrForwardingMethod, nl.Uint32Attr(d.ConnectionFlags&ConnectionFlagFwdMask))
|
||||||
|
nl.NewRtAttrChild(cmdAttr, ipvsDestAttrWeight, nl.Uint32Attr(uint32(d.Weight)))
|
||||||
|
nl.NewRtAttrChild(cmdAttr, ipvsDestAttrUpperThreshold, nl.Uint32Attr(d.UpperThreshold))
|
||||||
|
nl.NewRtAttrChild(cmdAttr, ipvsDestAttrLowerThreshold, nl.Uint32Attr(d.LowerThreshold))
|
||||||
|
|
||||||
|
return cmdAttr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Handle) doCmd(s *Service, d *Destination, cmd uint8) error {
|
||||||
|
req := newIPVSRequest(cmd)
|
||||||
|
req.AddData(fillService(s))
|
||||||
|
|
||||||
|
if d != nil {
|
||||||
|
req.AddData(fillDestinaton(d))
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := execute(i.sock, req, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIPVSFamily() (int, error) {
|
||||||
|
sock, err := nl.GetNetlinkSocketAt(netns.None(), netns.None(), syscall.NETLINK_GENERIC)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := newGenlRequest(genlCtrlID, genlCtrlCmdGetFamily)
|
||||||
|
req.AddData(nl.NewRtAttr(genlCtrlAttrFamilyName, nl.ZeroTerminated("IPVS")))
|
||||||
|
|
||||||
|
msgs, err := execute(sock, req, 0)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range msgs {
|
||||||
|
hdr := deserializeGenlMsg(m)
|
||||||
|
attrs, err := nl.ParseRouteAttr(m[hdr.Len():])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, attr := range attrs {
|
||||||
|
switch int(attr.Attr.Type) {
|
||||||
|
case genlCtrlAttrFamilyID:
|
||||||
|
return int(native.Uint16(attr.Value[0:2])), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, fmt.Errorf("no family id in the netlink response")
|
||||||
|
}
|
||||||
|
|
||||||
|
func rawIPData(ip net.IP) []byte {
|
||||||
|
family := nl.GetIPFamily(ip)
|
||||||
|
if family == nl.FAMILY_V4 {
|
||||||
|
return ip.To4()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
||||||
|
func newIPVSRequest(cmd uint8) *nl.NetlinkRequest {
|
||||||
|
return newGenlRequest(ipvsFamily, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGenlRequest(familyID int, cmd uint8) *nl.NetlinkRequest {
|
||||||
|
req := nl.NewNetlinkRequest(familyID, syscall.NLM_F_ACK)
|
||||||
|
req.AddData(&genlMsgHdr{cmd: cmd, version: 1})
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
pid, err := s.GetPid()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var res [][]byte
|
||||||
|
|
||||||
|
done:
|
||||||
|
for {
|
||||||
|
msgs, err := s.Receive()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, m := range msgs {
|
||||||
|
if m.Header.Seq != req.Seq {
|
||||||
|
return nil, fmt.Errorf("Wrong Seq nr %d, expected %d", m.Header.Seq, req.Seq)
|
||||||
|
}
|
||||||
|
if m.Header.Pid != pid {
|
||||||
|
return nil, fmt.Errorf("Wrong pid %d, expected %d", m.Header.Pid, pid)
|
||||||
|
}
|
||||||
|
if m.Header.Type == syscall.NLMSG_DONE {
|
||||||
|
break done
|
||||||
|
}
|
||||||
|
if m.Header.Type == syscall.NLMSG_ERROR {
|
||||||
|
error := int32(native.Uint32(m.Data[0:4]))
|
||||||
|
if error == 0 {
|
||||||
|
break done
|
||||||
|
}
|
||||||
|
return nil, syscall.Errno(-error)
|
||||||
|
}
|
||||||
|
if resType != 0 && m.Header.Type != resType {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
res = append(res, m.Data)
|
||||||
|
if m.Header.Flags&syscall.NLM_F_MULTI == 0 {
|
||||||
|
break done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
Loading…
Reference in a new issue