本文目標:
本文以 GTP5G 當前版本 v0.7.3(commit hash:
4e76e682886cac7871dc8c3f724e622fc92c7ef5
)作為範例程式碼,原則上前後版本的架構與邏輯並不會相差太多,但還請讀者參照原始程式碼時盡量以該版本為主。
https://blog.51cto.com/weiguozhihui/1586777
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
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
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 是由三個子項組成,分別是:
其他的 structured data 以及 member function 都會被上述的子元件使用到,除非有開發/除錯需求,可以先忽略不看。
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(>p->list, &gn->gtp5g_dev_list);
list_add_rcu(>p->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 的工作會藉由呼叫 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.
gtp5g_netdev_ops 定義 gtp5g 如何處理來自 Internet(N6 interface)的封包,這些我們定義好的 operations 會在 gtp5g link device 初始化時指派給該裝置:
static void gtp5g_link_setup(struct net_device *dev)
{
dev->netdev_ops = >p5g_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,它記錄了:
.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)
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);
}
}
FAR_ACTION_FORW
,gtp5g_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:
以下 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。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 = >p5g_net_id,
.size = sizeof(struct gtp5g_net),
};
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。