Add Support for Virtual XFRM Interfaces

XFRM interfaces are available in Linux Kernel 4.19+

When an IF_ID is applied to a XFRM policy and state, the corresponding
traffic will be sent through the virtual interface with the same IF_ID.
This commit is contained in:
Matt Ellison 2019-01-02 17:56:57 -05:00 committed by Alessandro Boch
parent 48a75e0e38
commit 1e2e7ab670
11 changed files with 192 additions and 32 deletions

17
link.go
View File

@ -848,11 +848,26 @@ func (gtp *GTP) Type() string {
return "gtp"
}
// Virtual XFRM Interfaces
// Named "xfrmi" to prevent confusion with XFRM objects
type Xfrmi struct {
LinkAttrs
Ifid uint32
}
func (xfrm *Xfrmi) Attrs() *LinkAttrs {
return &xfrm.LinkAttrs
}
func (xfrm *Xfrmi) Type() string {
return "xfrm"
}
// iproute2 supported devices;
// vlan | veth | vcan | dummy | ifb | macvlan | macvtap |
// bridge | bond | ipoib | ip6tnl | ipip | sit | vxlan |
// gre | gretap | ip6gre | ip6gretap | vti | vti6 | nlmon |
// bond_slave | ipvlan
// bond_slave | ipvlan | xfrm
// LinkNotFoundError wraps the various not found errors when
// getting/reading links. This is intended for better error

View File

@ -1190,6 +1190,8 @@ func (h *Handle) linkModify(link Link, flags int) error {
addBridgeAttrs(link, linkInfo)
case *GTP:
addGTPAttrs(link, linkInfo)
case *Xfrmi:
addXfrmiAttrs(link, linkInfo)
}
req.AddData(linkInfo)
@ -1441,6 +1443,8 @@ func LinkDeserialize(hdr *unix.NlMsghdr, m []byte) (Link, error) {
link = &Vrf{}
case "gtp":
link = &GTP{}
case "xfrm":
link = &Xfrmi{}
default:
link = &GenericLink{LinkType: linkType}
}
@ -1482,6 +1486,8 @@ func LinkDeserialize(hdr *unix.NlMsghdr, m []byte) (Link, error) {
parseBridgeData(link, data)
case "gtp":
parseGTPData(link, data)
case "xfrm":
parseXfrmiData(link, data)
}
}
}
@ -2488,6 +2494,25 @@ func parseVfInfo(data []syscall.NetlinkRouteAttr, id int) VfInfo {
return vf
}
func addXfrmiAttrs(xfrmi *Xfrmi, linkInfo *nl.RtAttr) {
data := linkInfo.AddRtAttr(nl.IFLA_INFO_DATA, nil)
data.AddRtAttr(nl.IFLA_XFRM_LINK, nl.Uint32Attr(uint32(xfrmi.ParentIndex)))
data.AddRtAttr(nl.IFLA_XFRM_IF_ID, nl.Uint32Attr(xfrmi.Ifid))
}
func parseXfrmiData(link Link, data []syscall.NetlinkRouteAttr) {
xfrmi := link.(*Xfrmi)
for _, datum := range data {
switch datum.Attr.Type {
case nl.IFLA_XFRM_LINK:
xfrmi.ParentIndex = int(native.Uint32(datum.Value))
case nl.IFLA_XFRM_IF_ID:
xfrmi.Ifid = native.Uint32(datum.Value)
}
}
}
// LinkSetBondSlave add slave to bond link via ioctl interface.
func LinkSetBondSlave(link Link, master *Bond) error {
fd, err := getSocketUDP()

View File

@ -201,6 +201,14 @@ func testLinkAddDel(t *testing.T, link Link) {
compareGretun(t, gretun, other)
}
if xfrmi, ok := link.(*Xfrmi); ok {
other, ok := result.(*Xfrmi)
if !ok {
t.Fatal("Result of create is not a xfrmi")
}
compareXfrmi(t, xfrmi, other)
}
if err = LinkDel(link); err != nil {
t.Fatal(err)
}
@ -409,6 +417,12 @@ func compareVxlan(t *testing.T, expected, actual *Vxlan) {
}
}
func compareXfrmi(t *testing.T, expected, actual *Xfrmi) {
if expected.Ifid != actual.Ifid {
t.Fatal("Xfrmi.Ifid doesn't match")
}
}
func TestLinkAddDelWithIndex(t *testing.T) {
tearDown := setUpNetlinkTest(t)
defer tearDown()
@ -1746,6 +1760,27 @@ func TestLinkAddDelGTP(t *testing.T) {
testLinkAddDel(t, gtp)
}
func TestLinkAddDelXfrmi(t *testing.T) {
minKernelRequired(t, 4, 19)
defer setUpNetlinkTest(t)()
lo, _ := LinkByName("lo")
testLinkAddDel(t, &Xfrmi{
LinkAttrs: LinkAttrs{Name: "xfrm123", ParentIndex: lo.Attrs().Index},
Ifid: 123})
}
func TestLinkAddDelXfrmiNoId(t *testing.T) {
minKernelRequired(t, 4, 19)
defer setUpNetlinkTest(t)()
lo, _ := LinkByName("lo")
testLinkAddDel(t, &Xfrmi{
LinkAttrs: LinkAttrs{Name: "xfrm0", ParentIndex: lo.Attrs().Index}})
}
func TestLinkByNameWhenLinkIsNotFound(t *testing.T) {
_, err := LinkByName("iammissing")
if err == nil {

View File

@ -573,3 +573,11 @@ const (
GTP_ROLE_GGSN = iota
GTP_ROLE_SGSN
)
const (
IFLA_XFRM_UNSPEC = iota
IFLA_XFRM_LINK
IFLA_XFRM_IF_ID
IFLA_XFRM_MAX = iota - 1
)

View File

@ -50,32 +50,40 @@ const (
// Attribute types
const (
/* Netlink message attributes. */
XFRMA_UNSPEC = 0x00
XFRMA_ALG_AUTH = 0x01 /* struct xfrm_algo */
XFRMA_ALG_CRYPT = 0x02 /* struct xfrm_algo */
XFRMA_ALG_COMP = 0x03 /* struct xfrm_algo */
XFRMA_ENCAP = 0x04 /* struct xfrm_algo + struct xfrm_encap_tmpl */
XFRMA_TMPL = 0x05 /* 1 or more struct xfrm_user_tmpl */
XFRMA_SA = 0x06 /* struct xfrm_usersa_info */
XFRMA_POLICY = 0x07 /* struct xfrm_userpolicy_info */
XFRMA_SEC_CTX = 0x08 /* struct xfrm_sec_ctx */
XFRMA_LTIME_VAL = 0x09
XFRMA_REPLAY_VAL = 0x0a
XFRMA_REPLAY_THRESH = 0x0b
XFRMA_ETIMER_THRESH = 0x0c
XFRMA_SRCADDR = 0x0d /* xfrm_address_t */
XFRMA_COADDR = 0x0e /* xfrm_address_t */
XFRMA_LASTUSED = 0x0f /* unsigned long */
XFRMA_POLICY_TYPE = 0x10 /* struct xfrm_userpolicy_type */
XFRMA_MIGRATE = 0x11
XFRMA_ALG_AEAD = 0x12 /* struct xfrm_algo_aead */
XFRMA_KMADDRESS = 0x13 /* struct xfrm_user_kmaddress */
XFRMA_ALG_AUTH_TRUNC = 0x14 /* struct xfrm_algo_auth */
XFRMA_MARK = 0x15 /* struct xfrm_mark */
XFRMA_TFCPAD = 0x16 /* __u32 */
XFRMA_REPLAY_ESN_VAL = 0x17 /* struct xfrm_replay_esn */
XFRMA_SA_EXTRA_FLAGS = 0x18 /* __u32 */
XFRMA_MAX = 0x18
XFRMA_UNSPEC = iota
XFRMA_ALG_AUTH /* struct xfrm_algo */
XFRMA_ALG_CRYPT /* struct xfrm_algo */
XFRMA_ALG_COMP /* struct xfrm_algo */
XFRMA_ENCAP /* struct xfrm_algo + struct xfrm_encap_tmpl */
XFRMA_TMPL /* 1 or more struct xfrm_user_tmpl */
XFRMA_SA /* struct xfrm_usersa_info */
XFRMA_POLICY /* struct xfrm_userpolicy_info */
XFRMA_SEC_CTX /* struct xfrm_sec_ctx */
XFRMA_LTIME_VAL
XFRMA_REPLAY_VAL
XFRMA_REPLAY_THRESH
XFRMA_ETIMER_THRESH
XFRMA_SRCADDR /* xfrm_address_t */
XFRMA_COADDR /* xfrm_address_t */
XFRMA_LASTUSED /* unsigned long */
XFRMA_POLICY_TYPE /* struct xfrm_userpolicy_type */
XFRMA_MIGRATE
XFRMA_ALG_AEAD /* struct xfrm_algo_aead */
XFRMA_KMADDRESS /* struct xfrm_user_kmaddress */
XFRMA_ALG_AUTH_TRUNC /* struct xfrm_algo_auth */
XFRMA_MARK /* struct xfrm_mark */
XFRMA_TFCPAD /* __u32 */
XFRMA_REPLAY_ESN_VAL /* struct xfrm_replay_esn */
XFRMA_SA_EXTRA_FLAGS /* __u32 */
XFRMA_PROTO /* __u8 */
XFRMA_ADDRESS_FILTER /* struct xfrm_address_filter */
XFRMA_PAD
XFRMA_OFFLOAD_DEV /* struct xfrm_state_offload */
XFRMA_SET_MARK /* __u32 */
XFRMA_SET_MARK_MASK /* __u32 */
XFRMA_IF_ID /* __u32 */
XFRMA_MAX = iota - 1
)
const (

View File

@ -85,11 +85,12 @@ type XfrmPolicy struct {
Index int
Action PolicyAction
Ifindex int
Ifid int
Mark *XfrmMark
Tmpls []XfrmPolicyTmpl
}
func (p XfrmPolicy) String() string {
return fmt.Sprintf("{Dst: %v, Src: %v, Proto: %s, DstPort: %d, SrcPort: %d, Dir: %s, Priority: %d, Index: %d, Action: %s, Ifindex: %d, Mark: %s, Tmpls: %s}",
p.Dst, p.Src, p.Proto, p.DstPort, p.SrcPort, p.Dir, p.Priority, p.Index, p.Action, p.Ifindex, p.Mark, p.Tmpls)
return fmt.Sprintf("{Dst: %v, Src: %v, Proto: %s, DstPort: %d, SrcPort: %d, Dir: %s, Priority: %d, Index: %d, Action: %s, Ifindex: %d, Ifid: %d, Mark: %s, Tmpls: %s}",
p.Dst, p.Src, p.Proto, p.DstPort, p.SrcPort, p.Dir, p.Priority, p.Index, p.Action, p.Ifindex, p.Ifid, p.Mark, p.Tmpls)
}

View File

@ -92,6 +92,9 @@ func (h *Handle) xfrmPolicyAddOrUpdate(policy *XfrmPolicy, nlProto int) error {
req.AddData(out)
}
ifId := nl.NewRtAttr(nl.XFRMA_IF_ID, nl.Uint32Attr(uint32(policy.Ifid)))
req.AddData(ifId)
_, err := req.Execute(unix.NETLINK_XFRM, 0)
return err
}
@ -185,6 +188,9 @@ func (h *Handle) xfrmPolicyGetOrDelete(policy *XfrmPolicy, nlProto int) (*XfrmPo
req.AddData(out)
}
ifId := nl.NewRtAttr(nl.XFRMA_IF_ID, nl.Uint32Attr(uint32(policy.Ifid)))
req.AddData(ifId)
resType := nl.XFRM_MSG_NEWPOLICY
if nlProto == nl.XFRM_MSG_DELPOLICY {
resType = 0
@ -248,6 +254,8 @@ func parseXfrmPolicy(m []byte, family int) (*XfrmPolicy, error) {
policy.Mark = new(XfrmMark)
policy.Mark.Value = mark.Value
policy.Mark.Mask = mark.Mask
case nl.XFRMA_IF_ID:
policy.Ifid = int(native.Uint32(attr.Value))
}
}

View File

@ -35,6 +35,10 @@ func TestXfrmPolicyAddUpdateDel(t *testing.T) {
t.Fatalf("default policy has a non-zero interface index.\nGot %d", policies[0].Ifindex)
}
if policies[0].Ifid != 0 {
t.Fatalf("default policy has non-zero if_id.\nGot %d", policies[0].Ifid)
}
if policies[0].Action != XFRM_POLICY_ALLOW {
t.Fatalf("default policy has non-allow action.\nGot %s", policies[0].Action)
}
@ -161,6 +165,31 @@ func TestXfrmPolicyBlockWithIfindex(t *testing.T) {
}
}
func TestXfrmPolicyWithIfid(t *testing.T) {
minKernelRequired(t, 4, 19)
defer setUpNetlinkTest(t)()
pol := getPolicy()
pol.Ifid = 54321
if err := XfrmPolicyAdd(pol); err != nil {
t.Fatal(err)
}
policies, err := XfrmPolicyList(FAMILY_ALL)
if err != nil {
t.Fatal(err)
}
if len(policies) != 1 {
t.Fatalf("unexpected number of policies: %d", len(policies))
}
if !comparePolicies(pol, &policies[0]) {
t.Fatalf("unexpected policy returned.\nExpected: %v.\nGot %v", pol, policies[0])
}
if err = XfrmPolicyDel(&policies[0]); err != nil {
t.Fatal(err)
}
}
func comparePolicies(a, b *XfrmPolicy) bool {
if a == b {
return true
@ -173,7 +202,7 @@ func comparePolicies(a, b *XfrmPolicy) bool {
compareIPNet(a.Src, b.Src) && compareIPNet(a.Dst, b.Dst) &&
a.Action == b.Action && a.Ifindex == b.Ifindex &&
a.Mark.Value == b.Mark.Value && a.Mark.Mask == b.Mark.Mask &&
compareTemplates(a.Tmpls, b.Tmpls)
a.Ifid == b.Ifid && compareTemplates(a.Tmpls, b.Tmpls)
}
func compareTemplates(a, b []XfrmPolicyTmpl) bool {

View File

@ -94,6 +94,7 @@ type XfrmState struct {
Limits XfrmStateLimits
Statistics XfrmStateStats
Mark *XfrmMark
Ifid int
Auth *XfrmStateAlgo
Crypt *XfrmStateAlgo
Aead *XfrmStateAlgo
@ -102,8 +103,8 @@ type XfrmState struct {
}
func (sa XfrmState) String() string {
return fmt.Sprintf("Dst: %v, Src: %v, Proto: %s, Mode: %s, SPI: 0x%x, ReqID: 0x%x, ReplayWindow: %d, Mark: %v, Auth: %v, Crypt: %v, Aead: %v, Encap: %v, ESN: %t",
sa.Dst, sa.Src, sa.Proto, sa.Mode, sa.Spi, sa.Reqid, sa.ReplayWindow, sa.Mark, sa.Auth, sa.Crypt, sa.Aead, sa.Encap, sa.ESN)
return fmt.Sprintf("Dst: %v, Src: %v, Proto: %s, Mode: %s, SPI: 0x%x, ReqID: 0x%x, ReplayWindow: %d, Mark: %v, Ifid: %d, Auth: %v, Crypt: %v, Aead: %v, Encap: %v, ESN: %t",
sa.Dst, sa.Src, sa.Proto, sa.Mode, sa.Spi, sa.Reqid, sa.ReplayWindow, sa.Mark, sa.Ifid, sa.Auth, sa.Crypt, sa.Aead, sa.Encap, sa.ESN)
}
func (sa XfrmState) Print(stats bool) string {
if !stats {

View File

@ -159,6 +159,9 @@ func (h *Handle) xfrmStateAddOrUpdate(state *XfrmState, nlProto int) error {
req.AddData(out)
}
ifId := nl.NewRtAttr(nl.XFRMA_IF_ID, nl.Uint32Attr(uint32(state.Ifid)))
req.AddData(ifId)
_, err := req.Execute(unix.NETLINK_XFRM, 0)
return err
}
@ -270,6 +273,9 @@ func (h *Handle) xfrmStateGetOrDelete(state *XfrmState, nlProto int) (*XfrmState
req.AddData(out)
}
ifId := nl.NewRtAttr(nl.XFRMA_IF_ID, nl.Uint32Attr(uint32(state.Ifid)))
req.AddData(ifId)
resType := nl.XFRM_MSG_NEWSA
if nlProto == nl.XFRM_MSG_DELSA {
resType = 0
@ -367,6 +373,8 @@ func parseXfrmState(m []byte, family int) (*XfrmState, error) {
state.Mark = new(XfrmMark)
state.Mark.Value = mark.Value
state.Mark.Mask = mark.Mask
case nl.XFRMA_IF_ID:
state.Ifid = int(native.Uint32(attr.Value))
}
}

View File

@ -201,6 +201,27 @@ func TestXfrmStateStats(t *testing.T) {
}
}
func TestXfrmStateWithIfid(t *testing.T) {
minKernelRequired(t, 4, 19)
defer setUpNetlinkTest(t)()
state := getBaseState()
state.Ifid = 54321
if err := XfrmStateAdd(state); err != nil {
t.Fatal(err)
}
s, err := XfrmStateGet(state)
if err != nil {
t.Fatal(err)
}
if !compareStates(state, s) {
t.Fatalf("unexpected state returned.\nExpected: %v.\nGot %v", state, s)
}
if err = XfrmStateDel(s); err != nil {
t.Fatal(err)
}
}
func getBaseState() *XfrmState {
return &XfrmState{
// Force 4 byte notation for the IPv4 addresses
@ -251,6 +272,7 @@ func compareStates(a, b *XfrmState) bool {
}
return a.Src.Equal(b.Src) && a.Dst.Equal(b.Dst) &&
a.Mode == b.Mode && a.Spi == b.Spi && a.Proto == b.Proto &&
a.Ifid == b.Ifid &&
compareAlgo(a.Auth, b.Auth) &&
compareAlgo(a.Crypt, b.Crypt) &&
compareAlgo(a.Aead, b.Aead) &&