Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion ipvs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ func checkDestination(t *testing.T, i *Handle, s *Service, d *Destination, check
assert.NilError(t, err)

for _, dst := range dstArray {
if dst.Address.Equal(d.Address) && dst.Port == d.Port && lookupFwMethod(dst.ConnectionFlags) == lookupFwMethod(d.ConnectionFlags) {
if dst.Address.Equal(d.Address) && dst.Port == d.Port &&
lookupFwMethod(dst.ConnectionFlags) == lookupFwMethod(d.ConnectionFlags) &&
dst.AddressFamily == d.AddressFamily {
dstFound = true
break
}
Expand Down
55 changes: 39 additions & 16 deletions netlink.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package ipvs
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"net"
"os/exec"
Expand Down Expand Up @@ -351,17 +352,6 @@ func assembleService(attrs []syscall.NetlinkRouteAttr) (*Service, error) {

}

// in older kernels (< 3.18), the svc address family attribute may not exist so we must
// assume it based on the svc address provided.
if s.AddressFamily == 0 {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially added this check cause it seemed harmless but given AddressFamily for IPVS services always existed I think it would be simpler to remove this.

addr := (net.IP)(addressBytes)
if addr.To4() != nil {
s.AddressFamily = syscall.AF_INET
} else {
s.AddressFamily = syscall.AF_INET6
}
}

// parse Address after parse AddressFamily incase of parseIP error
if addressBytes != nil {
ip, err := parseIP(addressBytes, s.AddressFamily)
Expand Down Expand Up @@ -472,12 +462,14 @@ func assembleDestination(attrs []syscall.NetlinkRouteAttr) (*Destination, error)
// in older kernels (< 3.18), the destination address family attribute doesn't exist so we must
// assume it based on the destination address provided.
if d.AddressFamily == 0 {
addr := (net.IP)(addressBytes)
if addr.To4() != nil {
d.AddressFamily = syscall.AF_INET
} else {
d.AddressFamily = syscall.AF_INET6
// we can't check the address family using net stdlib because netlink returns
// IPv4 addresses as the first 4 bytes in a []byte of length 16 where as
// stdlib expects it as the last 4 bytes.
addressFamily, err := getIPFamily(addressBytes)
if err != nil {
return nil, err
}
d.AddressFamily = addressFamily
}

// parse Address after parse AddressFamily incase of parseIP error
Expand All @@ -492,6 +484,37 @@ func assembleDestination(attrs []syscall.NetlinkRouteAttr) (*Destination, error)
return &d, nil
}

// getIPFamily parses the IP family based on raw data from netlink.
// For AF_INET, netlink will set the first 4 bytes with trailing zeros
// 10.0.0.1 -> [10 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0]
// For AF_INET6, the full 16 byte array is used:
// 2001:db8:3c4d:15::1a00 -> [32 1 13 184 60 77 0 21 0 0 0 0 0 0 26 0]
func getIPFamily(address []byte) (uint16, error) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aojea @uablrek can I get your review on this? Is this a safe way to determine the address family? It wouldn't account for addresses like 2600:: but that seems okay?

Can't use Go's net package because it expects IPv4 address to be last 4 bytes but netlink returns it as first 4 bytes.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry for my question but I'm not much familiar with this code, but assuming there is no nat64 or nat46, is not possible to obtain the IP family from the virtual IP or in another place and pass it as parameter, instead of using the destination?

ipvs/netlink.go

Line 156 in e63ad1c

func getIPVSFamily() (int, error) {

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scratch my comment, this #19 (comment) has a point :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah worth clarifying that newer kernels pass the address family attribute so this code path would never run. But for old kernels we need some way to figure out the address family so we know how to parse the address into a net.IP.

if len(address) == 4 {
return syscall.AF_INET, nil
}

if isZeros(address) {
return 0, errors.New("could not parse IP family from address data")
}

// assume IPv4 if first 4 bytes are non-zero but rest of the data is trailing zeros
if !isZeros(address[:4]) && isZeros(address[4:]) {
return syscall.AF_INET, nil
}

return syscall.AF_INET6, nil
}

func isZeros(b []byte) bool {
for i := 0; i < len(b); i++ {
if b[i] != 0 {
return false
}
}
return true
}

// parseDestination given a ipvs netlink response this function will respond with a valid destination entry, an error otherwise
func (i *Handle) parseDestination(msg []byte) (*Destination, error) {
var dst *Destination
Expand Down
55 changes: 55 additions & 0 deletions netlink_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// +build linux

package ipvs

import (
"errors"
"reflect"
"syscall"
"testing"
)

func Test_getIPFamily(t *testing.T) {
testcases := []struct {
name string
address []byte
expectedFamily uint16
expectedErr error
}{
{
name: "16 byte IPv4 10.0.0.1",
address: []byte{10, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
expectedFamily: syscall.AF_INET,
expectedErr: nil,
},
{
name: "16 byte IPv6 2001:db8:3c4d:15::1a00",
address: []byte{32, 1, 13, 184, 60, 77, 0, 21, 0, 0, 0, 0, 0, 0, 26, 0},
expectedFamily: syscall.AF_INET6,
expectedErr: nil,
},
{
name: "zero address",
address: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
expectedFamily: 0,
expectedErr: errors.New("could not parse IP family from address data"),
},
}

for _, testcase := range testcases {
t.Run(testcase.name, func(t *testing.T) {
family, err := getIPFamily(testcase.address)
if !reflect.DeepEqual(err, testcase.expectedErr) {
t.Logf("got err: %v", err)
t.Logf("expected err: %v", testcase.expectedErr)
t.Errorf("unexpected error")
}

if family != testcase.expectedFamily {
t.Logf("got IP family: %v", family)
t.Logf("expected IP family: %v", testcase.expectedFamily)
t.Errorf("unexpected IP family")
}
})
}
}