iT邦幫忙

2022 iThome 鐵人賽

DAY 22
0

本文目標:

  • 分析 UPF 資料層實作
  • UPF 控制層與資料層互動
  • UPF 資料層如何處理 N3(上行)以及 N6(下行)流量

本文以 GTP5G 當前版本 v0.7.3(commit hash: 4e76e682886cac7871dc8c3f724e622fc92c7ef5)作為範例程式碼,原則上前後版本的架構與邏輯並不會相差太多,但還請讀者參照原始程式碼時盡量以該版本為主。

Basics

Abbreviations

  • ndo: network device operations
  • PDR (packet detection rule): matching an incoming packet
  • PDI (packet detection information): fields for packet matching
  • FAR (forwarding action rule): packet forwarding
  • BAR (buffering action rule): traffic buffering (used when radio link released, paging, handovering…)
  • QER (QoS enforcement rule): QoS management
  • URR (usage reporting rule): accounting

sk_buff

https://blog.51cto.com/weiguozhihui/1586777

proc

proc 可以讓我們在 user space 取得與 kernel module 有關的資訊,舉例來說:

ianchen0119@ubuntu:~$ ls /proc/gtp5g/
dbg  far  pdr  qer

可以得知在 /proc/gtp5g/ 底下共有四個 proc,接著,我們可以查看 dbg 的內容得知 gtp5g 的執行狀態:

ianchen0119@ubuntu:~$ cat /proc/gtp5g/dbg
gtp5g kerenl debug level range: 0~4
         0 -> Logging
         1 -> Error(default)
         2 -> Warning
         3 -> Information
         4 -> Trace
Current: 1

generic netlink

Rtnetlink

Rtnetlink allows the kernel's routing tables to be read and
altered. It is used within the kernel to communicate between
various subsystems, though this usage is not documented here, and
for communication with user-space programs. Network routes, IP
addresses, link parameters, neighbor setups, queueing
disciplines, traffic classes and packet classifiers may all be
controlled through NETLINK_ROUTE sockets. It is based on netlink
messages; see netlink(7) for more information.
-- linux man page

pernet

There is a way to get notified by the network core when a new network namespace is created or destroyed. For example, as a device driver developer or some other kernel code developer your module wants to get notified by the network core when a new network namespace created or destroyed.
-- what is net_generic function in linux include/net/net_namespace.h?

進入正題

目前 gtp5g 經過 refactor 後,檔案數量與部分原始程式碼可能與本文提到的地方不盡相同,還請讀者們多包涵。

gtp5g 使用 late_initcall() 註冊 gtp5g_init(),後者會執行以下行為:

1. 註冊 rtnetlink device 相關的操作

/* Source: /src/gtpu/link.c */

struct rtnl_link_ops gtp5g_link_ops __read_mostly = {
    .kind         = "gtp5g",
    .maxtype      = IFLA_GTP5G_MAX,
    .policy       = gtp5g_policy,
    .priv_size    = sizeof(struct gtp5g_dev),
    .setup        = gtp5g_link_setup,
    .validate     = gtp5g_validate,
    .newlink      = gtp5g_newlink,
    .dellink      = gtp5g_dellink,
    .get_size     = gtp5g_get_size,
    .fill_info    = gtp5g_fill_info,
};

這樣一來,當 go-upf 透過 rtnetlink socket 建立新的 device 時,上面被註冊的函式就會被調用:

// 該函式由 NewDriver() 呼叫
func OpenGtp5gLink(mux *nl.Mux, addr string, mtu uint32, log *logrus.Entry) (*Gtp5gLink, error) {
	g := &Gtp5gLink{
		log: log,
	}

	g.mux = mux

	rtconn, err := nl.Open(syscall.NETLINK_ROUTE)
	if err != nil {
		return nil, errors.Wrap(err, "open")
	}
	g.rtconn = rtconn
	g.client = nl.NewClient(rtconn, mux)

	laddr, err := net.ResolveUDPAddr("udp4", addr)
	if err != nil {
		g.Close()
		return nil, errors.Wrap(err, "resolve addr")
	}
	conn, err := net.ListenUDP("udp4", laddr)
	if err != nil {
		g.Close()
		return nil, errors.Wrap(err, "listen")
	}
	g.conn = conn

	// TODO: Duplicate fd
	f, err := conn.File()
	if err != nil {
		g.Close()
		return nil, errors.Wrap(err, "file")
	}
	g.f = f

	linkinfo := &nl.Attr{
		Type: syscall.IFLA_LINKINFO,
		Value: nl.AttrList{
			{
				Type:  rtnllink.IFLA_INFO_KIND,
				Value: nl.AttrString("gtp5g"),
			},
			{
				Type: rtnllink.IFLA_INFO_DATA,
				Value: nl.AttrList{
					{
						Type:  gtp5gnl.IFLA_FD1,
						Value: nl.AttrU32(f.Fd()),
					},
					{
						Type:  gtp5gnl.IFLA_HASHSIZE,
						Value: nl.AttrU32(131072),
					},
				},
			},
		},
	}
	attrs := []*nl.Attr{linkinfo}

	if mtu != 0 {
		attrs = append(attrs, &nl.Attr{
			Type:  syscall.IFLA_MTU,
			Value: nl.AttrU32(mtu),
		})
	}

	err = rtnllink.Create(g.client, "upfgtp", attrs...)
	if err != nil {
		g.Close()
		return nil, errors.Wrap(err, "create")
	}
	err = rtnllink.Up(g.client, "upfgtp")
	if err != nil {
		g.Close()
		return nil, errors.Wrap(err, "up")
	}
	link, err := gtp5gnl.GetLink("upfgtp")
	if err != nil {
		g.Close()
		return nil, errors.Wrap(err, "get link")
	}
	g.link = link
	return g, nil
}

這邊需要特別注意的是上方 go-upf 的 OpenGtp5gLink() 函式會將代表 N3 的 UDP Socket f.Fd() 作為 IFLA_LINKINFO 訊息的參數傳至 kernel space 的 gtp5g kernel module。

NewDriver() 除了初始化一個用於接收上下行封包的 gtp5g device 之外,還需要設定該裝置的對應路由:

func NewDriver(wg *sync.WaitGroup, cfg *factory.Config) (Driver, error) {
	cfgGtpu := cfg.Gtpu
	if cfgGtpu == nil {
		return nil, errors.Errorf("no Gtpu config")
	}

	logger.MainLog.Infof("starting Gtpu Forwarder [%s]", cfgGtpu.Forwarder)
	if cfgGtpu.Forwarder == "gtp5g" {
		var gtpuAddr string
		var mtu uint32
		for _, ifInfo := range cfgGtpu.IfList {
			mtu = ifInfo.MTU
			gtpuAddr = fmt.Sprintf("%s:%d", ifInfo.Addr, factory.UpfGtpDefaultPort)
			logger.MainLog.Infof("GTP Address: %q", gtpuAddr)
			break
		}
		if gtpuAddr == "" {
			return nil, errors.Errorf("not found GTP address")
		}
		driver, err := OpenGtp5g(wg, gtpuAddr, mtu)
		if err != nil {
			return nil, errors.Wrap(err, "open Gtp5g")
		}

		link := driver.Link()
		for _, dnn := range cfg.DnnList {
			_, dst, err := net.ParseCIDR(dnn.Cidr)
			if err != nil {
				logger.MainLog.Errorln(err)
				continue
			}
			err = link.RouteAdd(dst)
			if err != nil {
				driver.Close()
				return nil, err
			}
		}
		return driver, nil
	}
	return nil, errors.Errorf("not support forwarder:%q", cfgGtpu.Forwarder)
}

將 UPF 對應到的 DNN 轉為適當的資料格式(*net.IPNet)後,傳入 RouteAdd() 新增對應的路由:

func (g *Gtp5gLink) RouteAdd(dst *net.IPNet) error {
	r := &rtnlroute.Request{
		Header: rtnlroute.Header{
			Table:    syscall.RT_TABLE_MAIN,
			Scope:    syscall.RT_SCOPE_UNIVERSE,
			Protocol: syscall.RTPROT_STATIC,
			Type:     syscall.RTN_UNICAST,
		},
	}
	err := r.AddDst(dst)
	if err != nil {
		return err
	}
	err = r.AddIfName(g.link.Name)
	if err != nil {
		return err
	}
	return rtnlroute.Create(g.client, r)
}

如此一來,UPF 便能夠正確的將下行封包送往對應的路由。

2. 註冊 gerneric netlink family

struct genl_family gtp5g_genl_family __ro_after_init = {
    .name       = "gtp5g",
    .version    = 0,
    .hdrsize    = 0,
    .maxattr    = GTP5G_ATTR_MAX,
    .netnsok    = true,
    .module     = THIS_MODULE,
    .ops        = gtp5g_genl_ops,
    .n_ops      = ARRAY_SIZE(gtp5g_genl_ops),
    .mcgrps     = gtp5g_genl_mcgrps,
    .n_mcgrps   = ARRAY_SIZE(gtp5g_genl_mcgrps),
};

其中的 ops gtp5g_genl_ops 就包含了 PDR、FAR、QER、URR 的查詢、新增、刪除操作。

3. 呼叫 register_pernet_subsys 註冊 pernet (namespace)

4. 建立 proc

proc_gtp5g = proc_mkdir("gtp5g", NULL);
    if (!proc_gtp5g) {
        GTP5G_ERR(NULL, "Failed to create /proc/gtp5g\n");
        goto unreg_pernet;
	}

    proc_gtp5g_dbg = proc_create("dbg", (S_IFREG | S_IRUGO | S_IWUGO),
        proc_gtp5g, &proc_gtp5g_dbg_ops);
    if (!proc_gtp5g_dbg) {
        GTP5G_ERR(NULL, "Failed to create /proc/gtp5g/dbg\n");
        goto remove_gtp5g_proc;
	}

    proc_gtp5g_pdr = proc_create("pdr", (S_IFREG | S_IRUGO | S_IWUGO),
        proc_gtp5g, &proc_gtp5g_pdr_ops);
    if (!proc_gtp5g_pdr) {
        GTP5G_ERR(NULL, "Failed to create /proc/gtp5g/pdr\n");
        goto remove_dbg_proc;
	}

    proc_gtp5g_far = proc_create("far", (S_IFREG | S_IRUGO | S_IWUGO),
        proc_gtp5g, &proc_gtp5g_far_ops);
    if (!proc_gtp5g_far) {
        GTP5G_ERR(NULL, "Failed to create /proc/gtp5g/far\n");
        goto remove_pdr_proc;
	}

    proc_gtp5g_qer = proc_create("qer", (S_IFREG | S_IRUGO | S_IWUGO), 
        proc_gtp5g, &proc_gtp5g_qer_ops);
    if (!proc_gtp5g_qer) {
        GTP5G_ERR(NULL, "Failed to create /proc/gtp5g/qer\n");
        goto remove_far_proc;
	}

看完 gtp5g_init(),我們可以確定 gtp5g 是由三個子項組成,分別是:

  • 與 network device 相關的 gtp5g_netdev_ops
  • 與 rtnetlink 相關的 gtp5g_link_ops
  • 與 genl 相關的 gtp5g_genl_ops

其他的 structured data 以及 member function 都會被上述的子元件使用到,除非有開發/除錯需求,可以先忽略不看。

1. rtnetlink

rtnl 相關的 hook 與函式:



每個 Hook 的定義都可以在 linux kernel 的原始程式碼當中找到:

.kind

Identifier,gtp5g 的 Identifier 為 gtp5g

.maxtype

Highest device specific netlink attribute number.

.policy

Netlink policy for device specific attribute validation.

.priv_size

sizeof net_device private space.

.setup

net_device setup function.

gtp5g 中的 net device 會在 gtp5g_link_setup 時設定:

最基本的 Network Device Driver 的寫法就是 allocate network device 後再賦予 hook function struct net_device_ops,最後將該 network device 註冊到 kernel 中,Kernel 就可以調用該 Network device。
以 gtp5g 為例,這邊註冊的 gtp5g_netdev_ops 會用來處理來自 N6 的 downlink packets。

.validate

Optional validation function for netlink/changelink parameters.

.newlink

Function for configuring and registering a new device.

static int gtp5g_newlink(struct net *src_net, struct net_device *dev,
    struct nlattr *tb[], struct nlattr *data[],
    struct netlink_ext_ack *extack)
{
    struct gtp5g_dev *gtp;
    struct gtp5g_net *gn;
    struct sock *sk;
    unsigned int role = GTP5G_ROLE_UPF;
    u32 fd1;
    int hashsize, err;

    gtp = netdev_priv(dev);

    if (!data[IFLA_GTP5G_FD1]) {
        GTP5G_ERR(NULL, "Failed to create a new link\n");
        return -EINVAL;
    }
    // 取得 go-upf 傳給 gtp5g 的 udp socket fd
    fd1 = nla_get_u32(data[IFLA_GTP5G_FD1]);
    
    sk = gtp5g_encap_enable(fd1, UDP_ENCAP_GTP1U, gtp);
    if (IS_ERR(sk))
        return PTR_ERR(sk);
    gtp->sk1u = sk;
    
    if (data[IFLA_GTP5G_ROLE]) {
        role = nla_get_u32(data[IFLA_GTP5G_ROLE]);
        if (role > GTP5G_ROLE_RAN) {
            if (sk)
                gtp5g_encap_disable(sk);
            return -EINVAL;
        }
    }
    gtp->role = role;
    
    if (!data[IFLA_GTP5G_PDR_HASHSIZE])
        hashsize = 1024;
    else
        hashsize = nla_get_u32(data[IFLA_GTP5G_PDR_HASHSIZE]);

    err = dev_hashtable_new(gtp, hashsize);
    if (err < 0) {
        gtp5g_encap_disable(gtp->sk1u);
        GTP5G_ERR(dev, "Failed to create a hash table\n");
        goto out_encap;
    }

    err = register_netdevice(dev);
    if (err < 0) {
        netdev_dbg(dev, "failed to register new netdev %d\n", err);
        gtp5g_hashtable_free(gtp);
        gtp5g_encap_disable(gtp->sk1u);
        goto out_hashtable;
    }

    gn = net_generic(dev_net(dev), GTP5G_NET_ID());
    list_add_rcu(&gtp->list, &gn->gtp5g_dev_list);
    list_add_rcu(&gtp->proc_list, get_proc_gtp5g_dev_list_head());

    GTP5G_LOG(dev, "Registered a new 5G GTP interface\n");
    return 0;
out_hashtable:
    gtp5g_hashtable_free(gtp);
out_encap:
    gtp5g_encap_disable(gtp->sk1u);
    return err;
}

上述函式主要負責兩個重點:

  • 綁定 udp socket:為了能夠收到來自 n3 的封包
  • 註冊 gtp5g net device

綁定 udp socket 的工作會藉由呼叫 gtp5g_encap_enable() 完成:

struct sock *gtp5g_encap_enable(int fd, int type, struct gtp5g_dev *gtp){
    struct udp_tunnel_sock_cfg tuncfg = {NULL};
    struct socket *sock;
    struct sock *sk;
    int err;

    GTP5G_LOG(NULL, "enable gtp5g for the fd(%d) type(%d)\n", fd, type);

    sock = sockfd_lookup(fd, &err);
    if (!sock) {
        GTP5G_ERR(NULL, "Failed to find the socket fd(%d)\n", fd);
        return NULL;
    }

    if (sock->sk->sk_protocol != IPPROTO_UDP) {
        GTP5G_ERR(NULL, "socket fd(%d) is not a UDP\n", fd);
        sk = ERR_PTR(-EINVAL);
        goto out_sock;
    }

    lock_sock(sock->sk);
    if (sock->sk->sk_user_data) {
        GTP5G_ERR(NULL, "Failed to set sk_user_datat of socket fd(%d)\n", fd);
        sk = ERR_PTR(-EBUSY);
        goto out_sock;
    }

    sk = sock->sk;
    sock_hold(sk);

    tuncfg.sk_user_data = gtp;
    tuncfg.encap_type = type;
    tuncfg.encap_rcv = gtp5g_encap_recv;
    tuncfg.encap_destroy = gtp5g_encap_disable_locked;

    setup_udp_tunnel_sock(sock_net(sock->sk), sock, &tuncfg);

out_sock:
    release_sock(sock->sk);
    sockfd_put(sock);
    return sk;
}

上方函式在最後設定了代表 N3 的 udp tunnel:

tuncfg.sk_user_data = gtp;
tuncfg.encap_type = type;
tuncfg.encap_rcv = gtp5g_encap_recv;
tuncfg.encap_destroy = gtp5g_encap_disable_locked;

setup_udp_tunnel_sock(sock_net(sock->sk), sock, &tuncfg);

根據 tuncfg 的設定,我們得知所有傳入 N3 的封包都會交由 gtp5g_encap_recv() 處理:

static int gtp5g_encap_recv(struct sock *sk, struct sk_buff *skb)
{
    struct gtp5g_dev *gtp;
    int ret = 0;

    gtp = rcu_dereference_sk_user_data(sk);
    if (!gtp) {
        return 1;
    }

    switch (udp_sk(sk)->encap_type) {
    case UDP_ENCAP_GTP1U:
        ret = gtp1u_udp_encap_recv(gtp, skb);
        break;
    default:
        ret = -1; // Should not happen
    }

    switch (ret) {
    case 1:
        GTP5G_ERR(gtp->dev, "Pass up to the process\n");
        break;
    case 0:
        break;
    case -1:
        GTP5G_ERR(gtp->dev, "GTP packet has been dropped\n");
        kfree_skb(skb);
        ret = 0;
        break;
    }

    return ret;
}

gtp5g_encap_recv() 會判斷該封包的協定是否為 GTP1U,如果是會交由 gtp1u_udp_encap_recv() 進一步處理,否則會直接丟棄該封包。

.dellink

Function to remove a device.

.get_size

Function to calculate required room for dumping device specific netlink attributes

.fill_info

Function to dump device specific netlink attributes.

2. network device

gtp5g_netdev_ops 定義 gtp5g 如何處理來自 Internet(N6 interface)的封包,這些我們定義好的 operations 會在 gtp5g link device 初始化時指派給該裝置:

static void gtp5g_link_setup(struct net_device *dev)
{
    dev->netdev_ops = &gtp5g_netdev_ops;
    dev->needs_free_netdev = true;

    dev->hard_header_len = 0;
    dev->addr_len = 0;
    dev->mtu = ETH_DATA_LEN -
        (sizeof(struct iphdr) +
         sizeof(struct udphdr) +
         sizeof(struct gtpv1_hdr));

    /* Zero header length. */
    dev->type = ARPHRD_NONE;
    dev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;

    dev->priv_flags |= IFF_NO_QUEUE;
    dev->features |= NETIF_F_LLTX;
    netif_keep_dst(dev);

    /* TODO: Modify the headroom size based on
     * what are the extension header going to support
     * */
    dev->needed_headroom = LL_MAX_HEADER +
        sizeof(struct iphdr) +
        sizeof(struct udphdr) +
        sizeof(struct gtpv1_hdr);
}

而 gtp5g_netdev_ops 的相關函式如下:

const struct net_device_ops gtp5g_netdev_ops = {
    .ndo_init           = gtp5g_dev_init,
    .ndo_uninit         = gtp5g_dev_uninit,
    .ndo_start_xmit     = gtp5g_dev_xmit,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 11, 0)
    .ndo_get_stats64    = dev_get_tstats64,
#else
    .ndo_get_stats64    = ip_tunnel_get_stats64,
#endif
};

每個 Hook 的定義都可以在 linux kernel 的原始程式碼當中找到:

.ndo_init

This function is called once when a network device is registered.
The network device can use this for any late stage initialization or semantic validation. It can fail with an error code which will be propagated back to register_netdev.

static int gtp5g_dev_init(struct net_device *dev)
{
    /* netdev_priv() 用來得到 net device 的 private data */
    struct gtp5g_dev *gtp = netdev_priv(dev);

    gtp->dev = dev;

    dev->tstats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);
    if (!dev->tstats) {
        GTP5G_ERR(dev, "Failled to allocate stats\n");
        return -ENOMEM;
    }

    return 0;
}

gtp5g_dev 是我們自定義的 structured data:

struct gtp5g_dev {
    struct list_head        list;

    struct sock             *sk1u;

    struct net_device       *dev;

    unsigned int            role;

    unsigned int            hash_size;
    struct hlist_head       *pdr_id_hash;
    struct hlist_head       *far_id_hash;
    struct hlist_head       *qer_id_hash;

    struct hlist_head       *i_teid_hash;      // Used for GTP-U packet detect
    struct hlist_head       *addr_hash;        // Used for IPv4 packet detect

    /* IEs list related to PDR */
    struct hlist_head       *related_far_hash;     // PDR list waiting the FAR to handle
    struct hlist_head       *related_qer_hash;     // PDR list waiting the QER to handle

    /* Used by proc interface */
    struct list_head        proc_list;
};

一個 gtp5g_dev 可以視為一個 linked list 的 head,它記錄了:

  • 對應的 network device
  • 對應的 socket
  • 相關的 pdr 與 qer

.ndo_uninit

This function is called when device is unregistered or when registration fails. It is not called if init fails.

static void gtp5g_dev_uninit(struct net_device *dev)
{
    struct gtp5g_dev *gtp = netdev_priv(dev);

    gtp5g_encap_disable(gtp);
    free_percpu(dev->tstats);
}

用來關掉 gtp5g_dev 紀錄的 socket。

.ndo_start_xmit

Called when a packet needs to be transmitted.
Returns NETDEV_TX_OK.
Can return NETDEV_TX_BUSY, but you should stop the queue before that can happen;
it's for obsolete devices and weird corner cases, but the stack really does a non-trivial amount of useless work if you return NETDEV_TX_BUSY.
Required; cannot be NULL.

/**
 * Entry function for Downlink packets
 * */
static netdev_tx_t gtp5g_dev_xmit(struct sk_buff *skb, struct net_device *dev)
{
    unsigned int proto = ntohs(skb->protocol);
    struct gtp5g_pktinfo pktinfo;
    int ret = 0;

    /* Ensure there is sufficient headroom */
    if (skb_cow_head(skb, dev->needed_headroom)) {
        goto tx_err;
    }

    skb_reset_inner_headers(skb);

    /* PDR lookups in gtp5g_build_skb_*() need rcu read-side lock. 
     * */
    rcu_read_lock();
    switch (proto) {
    case ETH_P_IP:
        ret = gtp5g_handle_skb_ipv4(skb, dev, &pktinfo);
        break;
    default:
        ret = -EOPNOTSUPP;
    }
    rcu_read_unlock();

    if (ret < 0)
        goto tx_err;

    if (ret == FAR_ACTION_FORW)
        gtp5g_xmit_skb_ipv4(skb, &pktinfo);

    return NETDEV_TX_OK;

tx_err:
    dev->stats.tx_errors++;
    dev_kfree_skb(skb);
    return NETDEV_TX_OK;
}
  • skb_cow_head(skb, dev->needed_headroom)
    如果 sk_buffer 小於 needed_headroom,代表 buffer 沒有足夠的空間填入資料,傳送會在這邊回傳失敗。
  • skb_reset_inner_headers() 用於清除原本 packet 的 headers。
  • gtp5g_handle_skb_ipv4() 會根據 destination ip 查詢 PDR,如果有順利找到,就會繼續檢查 FAR 來決定怎麼處理封包:
far = pdr->far;
    if (far) {
        // One and only one of the DROP, FORW and BUFF flags shall be set to 1.
        // The NOCP flag may only be set if the BUFF flag is set.
        // The DUPL flag may be set with any of the DROP, FORW, BUFF and NOCP flags.
        switch (far->action & FAR_ACTION_MASK) {
        case FAR_ACTION_DROP:
            return gtp5g_drop_skb_ipv4(skb, dev, pdr);
        case FAR_ACTION_FORW:
            return gtp5g_fwd_skb_ipv4(skb, dev, pktinfo, pdr);
        case FAR_ACTION_BUFF:
            /* gtp5g_buf_skb_ipv4() 會將封包透過 socket 送給 UPF */
            return gtp5g_buf_skb_ipv4(skb, dev, pdr);
        default:
            GTP5G_ERR(dev, "Unspec apply action(%u) in FAR(%u) and related to PDR(%u)",
                far->action, far->id, pdr->id);
        }
    }
  • 如果確定 Action 為 FAR_ACTION_FORWgtp5g_xmit_skb_ipv4() 會被呼叫:
static void gtp5g_xmit_skb_ipv4(struct sk_buff *skb, struct gtp5g_pktinfo *pktinfo)
{
    //GTP5G_ERR(pktinfo->dev, "gtp -> IP src: %pI4 dst: %pI4\n",
    //           &pktinfo->iph->saddr, &pktinfo->iph->daddr);
    udp_tunnel_xmit_skb(pktinfo->rt, 
        pktinfo->sk,
        skb,
        pktinfo->fl4.saddr,
        pktinfo->fl4.daddr,
        pktinfo->iph->tos,
        ip4_dst_hoplimit(&pktinfo->rt->dst),
        0,
        pktinfo->gtph_port, 
        pktinfo->gtph_port,
        true, 
        true);
}

這樣一來,Packet 的轉送就完成了!

.ndo_get_stats64

Called when a user wants to get the network device usage statistics. Drivers must do one of the following:

  1. Define @ndo_get_stats64 to fill in a zero-initialised rtnl_link_stats64 structure passed by the caller.
  2. Define @ndo_get_stats to update a net_device_stats structure
    (which should normally be dev->stats) and return a pointer to it. The structure may be changed asynchronously only if each field is written atomically.
  3. Update dev->stats asynchronously and atomically, and define neither operation.

3. genl

以下 operations 已經在 gtp5g_init() 註冊 genl_family 時一併註冊:

static const struct genl_ops gtp5g_genl_ops[] = {
    {
        .cmd = GTP5G_CMD_ADD_PDR,
        // .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
        .doit = gtp5g_genl_add_pdr,
        // .policy = gtp5g_genl_pdr_policy,
        .flags = GENL_ADMIN_PERM,
    },
    {
        .cmd = GTP5G_CMD_DEL_PDR,
        // .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
        .doit = gtp5g_genl_del_pdr,
        // .policy = gtp5g_genl_pdr_policy,
        .flags = GENL_ADMIN_PERM,
    },
    {
        .cmd = GTP5G_CMD_GET_PDR,
        // .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
        .doit = gtp5g_genl_get_pdr,
        .dumpit = gtp5g_genl_dump_pdr,
        // .policy = gtp5g_genl_pdr_policy,
        .flags = GENL_ADMIN_PERM,
    },
    {
        .cmd = GTP5G_CMD_ADD_FAR,
        // .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
        .doit = gtp5g_genl_add_far,
        // .policy = gtp5g_genl_far_policy,
        .flags = GENL_ADMIN_PERM,
    },
    {
        .cmd = GTP5G_CMD_DEL_FAR,
        // .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
        .doit = gtp5g_genl_del_far,
        // .policy = gtp5g_genl_far_policy,
        .flags = GENL_ADMIN_PERM,
    },
    {
        .cmd = GTP5G_CMD_GET_FAR,
        // .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
        .doit = gtp5g_genl_get_far,
        .dumpit = gtp5g_genl_dump_far,
        // .policy = gtp5g_genl_far_policy,
        .flags = GENL_ADMIN_PERM,
    },
    {
        .cmd = GTP5G_CMD_ADD_QER,
        // .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
        .doit = gtp5g_genl_add_qer,
        // .policy = gtp5g_genl_qer_policy,
        .flags = GENL_ADMIN_PERM,
    },
    {
        .cmd = GTP5G_CMD_DEL_QER,
        // .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
        .doit = gtp5g_genl_del_qer,
        // .policy = gtp5g_genl_qer_policy,
        .flags = GENL_ADMIN_PERM,
    },
    {
        .cmd = GTP5G_CMD_GET_QER,
        // .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP,
        .doit = gtp5g_genl_get_qer,
        .dumpit = gtp5g_genl_dump_qer,
        // .policy = gtp5g_genl_qer_policy,
        .flags = GENL_ADMIN_PERM,
    },

};

每一個 cmd 對應到的 function 都跟 patcket rule 有關,大致流程為:

  • 呼叫 gtp5g_find_dev(sock_net(skb->sk), info->attrs) 找到對應的 gtp5g_dev。
  • 如果有找到,就把 genl info 當中的資料拿出來並且塞到正確的位置。

4. pernet (network namespace)

https://blog.csdn.net/sidemap/article/details/102880341

在先前提到的 module initializer (gtp5g_init) 有註冊 register_pernet_subsys(),而 linux pernet 允許我們保留一些空間當作 private data 使用:

static struct pernet_operations gtp5g_net_ops = {
    .init    = gtp5g_net_init,
    .exit    = gtp5g_net_exit,
    .id      = &gtp5g_net_id,
    .size    = sizeof(struct gtp5g_net),
};
  • id 可供之後查找 private data 使用。
  • private data 的 size 為 sizeof(struct gtp5g_net)

gtp5g_net 可以視為用來存放 gtp5g_dev_list 的 list head:

struct gtp5g_net {
    struct list_head gtp5g_dev_list;
};

了解 pernet 以及 private data 以後,我們再回到 gtp5g 的原始程式碼中就可以發現:當我們需要查找 gtp5g device 之前都會先呼叫 struct gtp5g_net *gn = net_generic(net, gtp5g_net_id); 取得指向 gtp5g_net 的 base addr。

相關資源


上一篇
以 Dummy module 為例來學習 device driver 開發
下一篇
DevOps 與核心網路
系列文
5G 核心網路與雲原生開發之亂彈阿翔36
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言