-
Notifications
You must be signed in to change notification settings - Fork 39
Update IP parsing based on netlink address attribute format #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9bd6155
4fbd8da
afed845
e63ad1c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -5,6 +5,7 @@ package ipvs | |||
| import ( | ||||
| "bytes" | ||||
| "encoding/binary" | ||||
| "errors" | ||||
| "fmt" | ||||
| "net" | ||||
| "os/exec" | ||||
|
|
@@ -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 { | ||||
| 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) | ||||
|
|
@@ -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 | ||||
|
|
@@ -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) { | ||||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? Line 156 in e63ad1c
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. scratch my comment, this #19 (comment) has a point :)
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||||
|
|
||||
| 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") | ||
| } | ||
| }) | ||
| } | ||
| } |
There was a problem hiding this comment.
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.