From 58c32ae2d72285baf322aaff05213c2fba8fb8a2 Mon Sep 17 00:00:00 2001 From: Wataru Ishida Date: Fri, 5 May 2017 09:02:53 +0000 Subject: [PATCH] Add support for GPRS Tunnelling Protocol(GTP) Signed-off-by: Wataru Ishida --- gtp_linux.go | 238 ++++++++++++++++++++++++++++++++++++++++++ gtp_test.go | 116 ++++++++++++++++++++ link.go | 16 +++ link_linux.go | 32 ++++++ link_test.go | 33 ++++++ netlink_test.go | 25 +++++ nl/genetlink_linux.go | 25 +++++ nl/link_linux.go | 13 +++ 8 files changed, 498 insertions(+) create mode 100644 gtp_linux.go create mode 100644 gtp_test.go diff --git a/gtp_linux.go b/gtp_linux.go new file mode 100644 index 0000000..7331303 --- /dev/null +++ b/gtp_linux.go @@ -0,0 +1,238 @@ +package netlink + +import ( + "fmt" + "net" + "strings" + "syscall" + + "github.com/vishvananda/netlink/nl" +) + +type PDP struct { + Version uint32 + TID uint64 + PeerAddress net.IP + MSAddress net.IP + Flow uint16 + NetNSFD uint32 + ITEI uint32 + OTEI uint32 +} + +func (pdp *PDP) String() string { + elems := []string{} + elems = append(elems, fmt.Sprintf("Version: %d", pdp.Version)) + if pdp.Version == 0 { + elems = append(elems, fmt.Sprintf("TID: %d", pdp.TID)) + } else if pdp.Version == 1 { + elems = append(elems, fmt.Sprintf("TEI: %d/%d", pdp.ITEI, pdp.OTEI)) + } + elems = append(elems, fmt.Sprintf("MS-Address: %s", pdp.MSAddress)) + elems = append(elems, fmt.Sprintf("Peer-Address: %s", pdp.PeerAddress)) + return fmt.Sprintf("{%s}", strings.Join(elems, " ")) +} + +func (p *PDP) parseAttributes(attrs []syscall.NetlinkRouteAttr) error { + for _, a := range attrs { + switch a.Attr.Type { + case nl.GENL_GTP_ATTR_VERSION: + p.Version = native.Uint32(a.Value) + case nl.GENL_GTP_ATTR_TID: + p.TID = native.Uint64(a.Value) + case nl.GENL_GTP_ATTR_PEER_ADDRESS: + p.PeerAddress = net.IP(a.Value) + case nl.GENL_GTP_ATTR_MS_ADDRESS: + p.MSAddress = net.IP(a.Value) + case nl.GENL_GTP_ATTR_FLOW: + p.Flow = native.Uint16(a.Value) + case nl.GENL_GTP_ATTR_NET_NS_FD: + p.NetNSFD = native.Uint32(a.Value) + case nl.GENL_GTP_ATTR_I_TEI: + p.ITEI = native.Uint32(a.Value) + case nl.GENL_GTP_ATTR_O_TEI: + p.OTEI = native.Uint32(a.Value) + } + } + return nil +} + +func parsePDP(msgs [][]byte) ([]*PDP, error) { + pdps := make([]*PDP, 0, len(msgs)) + for _, m := range msgs { + attrs, err := nl.ParseRouteAttr(m[nl.SizeofGenlmsg:]) + if err != nil { + return nil, err + } + pdp := &PDP{} + if err := pdp.parseAttributes(attrs); err != nil { + return nil, err + } + pdps = append(pdps, pdp) + } + return pdps, nil +} + +func (h *Handle) GTPPDPList() ([]*PDP, error) { + f, err := h.GenlFamilyGet(nl.GENL_GTP_NAME) + if err != nil { + return nil, err + } + msg := &nl.Genlmsg{ + Command: nl.GENL_GTP_CMD_GETPDP, + Version: nl.GENL_GTP_VERSION, + } + req := h.newNetlinkRequest(int(f.ID), syscall.NLM_F_DUMP) + req.AddData(msg) + msgs, err := req.Execute(syscall.NETLINK_GENERIC, 0) + if err != nil { + return nil, err + } + return parsePDP(msgs) +} + +func GTPPDPList() ([]*PDP, error) { + return pkgHandle.GTPPDPList() +} + +func gtpPDPGet(req *nl.NetlinkRequest) (*PDP, error) { + msgs, err := req.Execute(syscall.NETLINK_GENERIC, 0) + if err != nil { + return nil, err + } + pdps, err := parsePDP(msgs) + if err != nil { + return nil, err + } + if len(pdps) != 1 { + return nil, fmt.Errorf("invalid reqponse for GENL_GTP_CMD_GETPDP") + } + return pdps[0], nil +} + +func (h *Handle) GTPPDPByTID(link Link, tid int) (*PDP, error) { + f, err := h.GenlFamilyGet(nl.GENL_GTP_NAME) + if err != nil { + return nil, err + } + msg := &nl.Genlmsg{ + Command: nl.GENL_GTP_CMD_GETPDP, + Version: nl.GENL_GTP_VERSION, + } + req := h.newNetlinkRequest(int(f.ID), 0) + req.AddData(msg) + req.AddData(nl.NewRtAttr(nl.GENL_GTP_ATTR_VERSION, nl.Uint32Attr(0))) + req.AddData(nl.NewRtAttr(nl.GENL_GTP_ATTR_LINK, nl.Uint32Attr(uint32(link.Attrs().Index)))) + req.AddData(nl.NewRtAttr(nl.GENL_GTP_ATTR_TID, nl.Uint64Attr(uint64(tid)))) + return gtpPDPGet(req) +} + +func GTPPDPByTID(link Link, tid int) (*PDP, error) { + return pkgHandle.GTPPDPByTID(link, tid) +} + +func (h *Handle) GTPPDPByITEI(link Link, itei int) (*PDP, error) { + f, err := h.GenlFamilyGet(nl.GENL_GTP_NAME) + if err != nil { + return nil, err + } + msg := &nl.Genlmsg{ + Command: nl.GENL_GTP_CMD_GETPDP, + Version: nl.GENL_GTP_VERSION, + } + req := h.newNetlinkRequest(int(f.ID), 0) + req.AddData(msg) + req.AddData(nl.NewRtAttr(nl.GENL_GTP_ATTR_VERSION, nl.Uint32Attr(1))) + req.AddData(nl.NewRtAttr(nl.GENL_GTP_ATTR_LINK, nl.Uint32Attr(uint32(link.Attrs().Index)))) + req.AddData(nl.NewRtAttr(nl.GENL_GTP_ATTR_I_TEI, nl.Uint32Attr(uint32(itei)))) + return gtpPDPGet(req) +} + +func GTPPDPByITEI(link Link, itei int) (*PDP, error) { + return pkgHandle.GTPPDPByITEI(link, itei) +} + +func (h *Handle) GTPPDPByMSAddress(link Link, addr net.IP) (*PDP, error) { + f, err := h.GenlFamilyGet(nl.GENL_GTP_NAME) + if err != nil { + return nil, err + } + msg := &nl.Genlmsg{ + Command: nl.GENL_GTP_CMD_GETPDP, + Version: nl.GENL_GTP_VERSION, + } + req := h.newNetlinkRequest(int(f.ID), 0) + req.AddData(msg) + req.AddData(nl.NewRtAttr(nl.GENL_GTP_ATTR_VERSION, nl.Uint32Attr(0))) + req.AddData(nl.NewRtAttr(nl.GENL_GTP_ATTR_LINK, nl.Uint32Attr(uint32(link.Attrs().Index)))) + req.AddData(nl.NewRtAttr(nl.GENL_GTP_ATTR_MS_ADDRESS, []byte(addr.To4()))) + return gtpPDPGet(req) +} + +func GTPPDPByMSAddress(link Link, addr net.IP) (*PDP, error) { + return pkgHandle.GTPPDPByMSAddress(link, addr) +} + +func (h *Handle) GTPPDPAdd(link Link, pdp *PDP) error { + f, err := h.GenlFamilyGet(nl.GENL_GTP_NAME) + if err != nil { + return err + } + msg := &nl.Genlmsg{ + Command: nl.GENL_GTP_CMD_NEWPDP, + Version: nl.GENL_GTP_VERSION, + } + req := h.newNetlinkRequest(int(f.ID), syscall.NLM_F_EXCL|syscall.NLM_F_ACK) + req.AddData(msg) + req.AddData(nl.NewRtAttr(nl.GENL_GTP_ATTR_VERSION, nl.Uint32Attr(pdp.Version))) + req.AddData(nl.NewRtAttr(nl.GENL_GTP_ATTR_LINK, nl.Uint32Attr(uint32(link.Attrs().Index)))) + req.AddData(nl.NewRtAttr(nl.GENL_GTP_ATTR_PEER_ADDRESS, []byte(pdp.PeerAddress.To4()))) + req.AddData(nl.NewRtAttr(nl.GENL_GTP_ATTR_MS_ADDRESS, []byte(pdp.MSAddress.To4()))) + + switch pdp.Version { + case 0: + req.AddData(nl.NewRtAttr(nl.GENL_GTP_ATTR_TID, nl.Uint64Attr(pdp.TID))) + req.AddData(nl.NewRtAttr(nl.GENL_GTP_ATTR_FLOW, nl.Uint16Attr(pdp.Flow))) + case 1: + req.AddData(nl.NewRtAttr(nl.GENL_GTP_ATTR_I_TEI, nl.Uint32Attr(pdp.ITEI))) + req.AddData(nl.NewRtAttr(nl.GENL_GTP_ATTR_O_TEI, nl.Uint32Attr(pdp.OTEI))) + default: + return fmt.Errorf("unsupported GTP version: %d", pdp.Version) + } + _, err = req.Execute(syscall.NETLINK_GENERIC, 0) + return err +} + +func GTPPDPAdd(link Link, pdp *PDP) error { + return pkgHandle.GTPPDPAdd(link, pdp) +} + +func (h *Handle) GTPPDPDel(link Link, pdp *PDP) error { + f, err := h.GenlFamilyGet(nl.GENL_GTP_NAME) + if err != nil { + return err + } + msg := &nl.Genlmsg{ + Command: nl.GENL_GTP_CMD_DELPDP, + Version: nl.GENL_GTP_VERSION, + } + req := h.newNetlinkRequest(int(f.ID), syscall.NLM_F_EXCL|syscall.NLM_F_ACK) + req.AddData(msg) + req.AddData(nl.NewRtAttr(nl.GENL_GTP_ATTR_VERSION, nl.Uint32Attr(pdp.Version))) + req.AddData(nl.NewRtAttr(nl.GENL_GTP_ATTR_LINK, nl.Uint32Attr(uint32(link.Attrs().Index)))) + + switch pdp.Version { + case 0: + req.AddData(nl.NewRtAttr(nl.GENL_GTP_ATTR_TID, nl.Uint64Attr(pdp.TID))) + case 1: + req.AddData(nl.NewRtAttr(nl.GENL_GTP_ATTR_I_TEI, nl.Uint32Attr(pdp.ITEI))) + default: + return fmt.Errorf("unsupported GTP version: %d", pdp.Version) + } + _, err = req.Execute(syscall.NETLINK_GENERIC, 0) + return err +} + +func GTPPDPDel(link Link, pdp *PDP) error { + return pkgHandle.GTPPDPDel(link, pdp) +} diff --git a/gtp_test.go b/gtp_test.go new file mode 100644 index 0000000..4b94b6b --- /dev/null +++ b/gtp_test.go @@ -0,0 +1,116 @@ +// +build linux + +package netlink + +import ( + "net" + "testing" +) + +func TestPDPv0AddDel(t *testing.T) { + tearDown := setUpNetlinkTestWithKModule(t, "gtp") + defer tearDown() + + if err := LinkAdd(testGTPLink(t)); err != nil { + t.Fatal(err) + } + link, err := LinkByName("gtp0") + if err != nil { + t.Fatal(err) + } + err = GTPPDPAdd(link, &PDP{ + PeerAddress: net.ParseIP("1.1.1.1"), + MSAddress: net.ParseIP("2.2.2.2"), + TID: 10, + }) + if err != nil { + t.Fatal(err) + } + list, err := GTPPDPList() + if err != nil { + t.Fatal(err) + } + if len(list) != 1 { + t.Fatal("Failed to add v0 PDP") + } + pdp, err := GTPPDPByMSAddress(link, net.ParseIP("2.2.2.2")) + if err != nil { + t.Fatal(err) + } + if pdp == nil { + t.Fatal("failed to get v0 PDP by MS address") + } + pdp, err = GTPPDPByTID(link, 10) + if err != nil { + t.Fatal(err) + } + if pdp == nil { + t.Fatal("failed to get v0 PDP by TID") + } + err = GTPPDPDel(link, &PDP{TID: 10}) + if err != nil { + t.Fatal(err) + } + list, err = GTPPDPList() + if err != nil { + t.Fatal(err) + } + if len(list) != 0 { + t.Fatal("Failed to delete v0 PDP") + } +} + +func TestPDPv1AddDel(t *testing.T) { + tearDown := setUpNetlinkTestWithKModule(t, "gtp") + defer tearDown() + + if err := LinkAdd(testGTPLink(t)); err != nil { + t.Fatal(err) + } + link, err := LinkByName("gtp0") + if err != nil { + t.Fatal(err) + } + err = GTPPDPAdd(link, &PDP{ + PeerAddress: net.ParseIP("1.1.1.1"), + MSAddress: net.ParseIP("2.2.2.2"), + Version: 1, + ITEI: 10, + OTEI: 10, + }) + if err != nil { + t.Fatal(err) + } + list, err := GTPPDPList() + if err != nil { + t.Fatal(err) + } + if len(list) != 1 { + t.Fatal("Failed to add v1 PDP") + } + pdp, err := GTPPDPByMSAddress(link, net.ParseIP("2.2.2.2")) + if err != nil { + t.Fatal(err) + } + if pdp == nil { + t.Fatal("failed to get v1 PDP by MS address") + } + pdp, err = GTPPDPByITEI(link, 10) + if err != nil { + t.Fatal(err) + } + if pdp == nil { + t.Fatal("failed to get v1 PDP by ITEI") + } + err = GTPPDPDel(link, &PDP{Version: 1, ITEI: 10}) + if err != nil { + t.Fatal(err) + } + list, err = GTPPDPList() + if err != nil { + t.Fatal(err) + } + if len(list) != 0 { + t.Fatal("Failed to delete v1 PDP") + } +} diff --git a/link.go b/link.go index 9e78796..877f6e4 100644 --- a/link.go +++ b/link.go @@ -734,6 +734,22 @@ func (vrf *Vrf) Type() string { return "vrf" } +type GTP struct { + LinkAttrs + FD0 int + FD1 int + Role int + PDPHashsize int +} + +func (gtp *GTP) Attrs() *LinkAttrs { + return >p.LinkAttrs +} + +func (gtp *GTP) Type() string { + return "gtp" +} + // iproute2 supported devices; // vlan | veth | vcan | dummy | ifb | macvlan | macvtap | // bridge | bond | ipoib | ip6tnl | ipip | sit | vxlan | diff --git a/link_linux.go b/link_linux.go index 40ccc92..7cf3e10 100644 --- a/link_linux.go +++ b/link_linux.go @@ -846,6 +846,8 @@ func (h *Handle) linkModify(link Link, flags int) error { addVrfAttrs(vrf, linkInfo) } else if bridge, ok := link.(*Bridge); ok { addBridgeAttrs(bridge, linkInfo) + } else if gtp, ok := link.(*GTP); ok { + addGTPAttrs(gtp, linkInfo) } req.AddData(linkInfo) @@ -1079,6 +1081,8 @@ func LinkDeserialize(hdr *syscall.NlMsghdr, m []byte) (Link, error) { link = &Vti{} case "vrf": link = &Vrf{} + case "gtp": + link = >P{} default: link = &GenericLink{LinkType: linkType} } @@ -1110,6 +1114,8 @@ func LinkDeserialize(hdr *syscall.NlMsghdr, m []byte) (Link, error) { parseVrfData(link, data) case "bridge": parseBridgeData(link, data) + case "gtp": + parseGTPData(link, data) } } } @@ -1740,3 +1746,29 @@ func parseBridgeData(bridge Link, data []syscall.NetlinkRouteAttr) { } } } + +func addGTPAttrs(gtp *GTP, linkInfo *nl.RtAttr) { + data := nl.NewRtAttrChild(linkInfo, nl.IFLA_INFO_DATA, nil) + nl.NewRtAttrChild(data, nl.IFLA_GTP_FD0, nl.Uint32Attr(uint32(gtp.FD0))) + nl.NewRtAttrChild(data, nl.IFLA_GTP_FD1, nl.Uint32Attr(uint32(gtp.FD1))) + nl.NewRtAttrChild(data, nl.IFLA_GTP_PDP_HASHSIZE, nl.Uint32Attr(131072)) + if gtp.Role != nl.GTP_ROLE_GGSN { + nl.NewRtAttrChild(data, nl.IFLA_GTP_ROLE, nl.Uint32Attr(uint32(gtp.Role))) + } +} + +func parseGTPData(link Link, data []syscall.NetlinkRouteAttr) { + gtp := link.(*GTP) + for _, datum := range data { + switch datum.Attr.Type { + case nl.IFLA_GTP_FD0: + gtp.FD0 = int(native.Uint32(datum.Value)) + case nl.IFLA_GTP_FD1: + gtp.FD1 = int(native.Uint32(datum.Value)) + case nl.IFLA_GTP_PDP_HASHSIZE: + gtp.PDPHashsize = int(native.Uint32(datum.Value)) + case nl.IFLA_GTP_ROLE: + gtp.Role = int(native.Uint32(datum.Value)) + } + } +} diff --git a/link_test.go b/link_test.go index 3aa64a0..86ffda1 100644 --- a/link_test.go +++ b/link_test.go @@ -1247,3 +1247,36 @@ func TestLinkSubscribeWithProtinfo(t *testing.T) { t.Fatal(err) } } + +func testGTPLink(t *testing.T) *GTP { + conn1, err := net.ListenUDP("udp", &net.UDPAddr{ + IP: net.ParseIP("0.0.0.0"), + Port: 3386, + }) + if err != nil { + t.Fatal(err) + } + conn2, err := net.ListenUDP("udp", &net.UDPAddr{ + IP: net.ParseIP("0.0.0.0"), + Port: 2152, + }) + if err != nil { + t.Fatal(err) + } + fd1, _ := conn1.File() + fd2, _ := conn2.File() + return >P{ + LinkAttrs: LinkAttrs{ + Name: "gtp0", + }, + FD0: int(fd1.Fd()), + FD1: int(fd2.Fd()), + } +} + +func TestLinkAddDelGTP(t *testing.T) { + tearDown := setUpNetlinkTestWithKModule(t, "gtp") + defer tearDown() + gtp := testGTPLink(t) + testLinkAddDel(t, gtp) +} diff --git a/netlink_test.go b/netlink_test.go index 5037b7f..7e35e40 100644 --- a/netlink_test.go +++ b/netlink_test.go @@ -1,9 +1,12 @@ package netlink import ( + "fmt" + "io/ioutil" "log" "os" "runtime" + "strings" "testing" "github.com/vishvananda/netns" @@ -56,3 +59,25 @@ func setUpMPLSNetlinkTest(t *testing.T) tearDownNetlinkTest { setUpF("/proc/sys/net/mpls/conf/lo/input", "1") return f } + +func setUpNetlinkTestWithKModule(t *testing.T, name string) tearDownNetlinkTest { + file, err := ioutil.ReadFile("/proc/modules") + if err != nil { + t.Fatal("Failed to open /proc/modules", err) + } + found := false + for _, line := range strings.Split(string(file), "\n") { + n := strings.Split(line, " ")[0] + if n == name { + found = true + break + } + + } + if !found { + msg := fmt.Sprintf("Skipped test because it requres kmodule %s.", name) + log.Println(msg) + t.Skip(msg) + } + return setUpNetlinkTest(t) +} diff --git a/nl/genetlink_linux.go b/nl/genetlink_linux.go index 006625c..81b46f2 100644 --- a/nl/genetlink_linux.go +++ b/nl/genetlink_linux.go @@ -46,6 +46,31 @@ const ( GENL_CTRL_ATTR_MCAST_GRP_ID ) +const ( + GENL_GTP_VERSION = 0 + GENL_GTP_NAME = "gtp" +) + +const ( + GENL_GTP_CMD_NEWPDP = iota + GENL_GTP_CMD_DELPDP + GENL_GTP_CMD_GETPDP +) + +const ( + GENL_GTP_ATTR_UNSPEC = iota + GENL_GTP_ATTR_LINK + GENL_GTP_ATTR_VERSION + GENL_GTP_ATTR_TID + GENL_GTP_ATTR_PEER_ADDRESS + GENL_GTP_ATTR_MS_ADDRESS + GENL_GTP_ATTR_FLOW + GENL_GTP_ATTR_NET_NS_FD + GENL_GTP_ATTR_I_TEI + GENL_GTP_ATTR_O_TEI + GENL_GTP_ATTR_PAD +) + type Genlmsg struct { Command uint8 Version uint8 diff --git a/nl/link_linux.go b/nl/link_linux.go index 4143754..ece95d6 100644 --- a/nl/link_linux.go +++ b/nl/link_linux.go @@ -505,3 +505,16 @@ const ( IFLA_BR_MCAST_MLD_VERSION IFLA_BR_MAX = IFLA_BR_MCAST_MLD_VERSION ) + +const ( + IFLA_GTP_UNSPEC = iota + IFLA_GTP_FD0 + IFLA_GTP_FD1 + IFLA_GTP_PDP_HASHSIZE + IFLA_GTP_ROLE +) + +const ( + GTP_ROLE_GGSN = iota + GTP_ROLE_SGSN +)