> For the complete documentation index, see [llms.txt](https://man.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://man.bsdcn.org/man4/netgraph.4.md).

# netgraph(4)

`netgraph` — 基于图的内核网络子系统

## 名称

`netgraph`

## 描述

`netgraph` 系统为实现执行各种网络功能的内核对象提供了统一且模块化的系统。这些对象称为 *节点*，可以排列成任意复杂的图。节点具有 *钩子*，用于将两个节点连接在一起，形成图中的边。节点沿着边通信以处理数据、实现协议等。

`netgraph` 的目标是补充而非替换现有的内核网络基础设施。它提供：

* 组合协议和链路层驱动程序的灵活方式。
* 实现新协议的模块化方式。
* 内核实体间相互通信的通用框架。
* 合理快速的基于内核的实现。

### 节点与类型

`netgraph` 中最基本的概念是 *节点*。所有节点都实现了若干预定义方法，使其能够以明确定义的方式与其他节点交互。

每个节点都有一个 *类型*，这是在节点创建时确定的节点静态属性。节点的类型由唯一的 ASCII 类型名描述。类型意味着节点做什么以及它如何连接到其他节点。

用面向对象的语言来说，类型是类，节点是各自类的实例。所有节点类型都是通用节点类型的子类，因此继承某些通用功能和能力（例如，具有 ASCII 名称的能力）。

可以为节点分配全局唯一的 ASCII 名称，可用于引用该节点。名称不得包含字符 `.` 或 `:`，并且限制为 `NG_NODESIZ` 字符（包括终止 `NUL` 字符）。

每个节点实例都有一个唯一的 *ID 号*，以 32 位十六进制值表示。当节点没有分配 ASCII 名称时，可使用此值引用该节点。

### 钩子

节点通过连接一对 *钩子*（每个节点一个）连接到其他节点。数据在连接的钩子对之间双向流动。一个节点可以有任意数量的钩子，并可以为钩子分配任何含义。

钩子具有以下属性：

* 钩子具有 ASCII 名称，在该节点上的所有钩子中唯一（其他节点上的钩子可能具有相同名称）。名称不得包含字符 `.` 或 `:`，并且限制为 `NG_HOOKSIZ` 字符（包括终止 `NUL` 字符）。
* 钩子始终连接到另一个钩子。也就是说，钩子在连接时创建，通过删除任一钩子断开边会销毁两个钩子。
* 钩子可以设置为这样的状态：传入数据包始终由输入排队系统排队，而不是直接交付。当数据从中断处理程序发送且处理必须快速以免阻塞其他中断时，可以使用此功能。
* 钩子可以提供覆盖的接收数据和接收消息函数，应优先于通用的节点范围方法用于通过该钩子接收的数据和消息。

节点可以决定为某些钩子分配特殊含义。例如，连接到名为 `debug` 的钩子可能会触发节点开始向该钩子发送调试信息。

### 数据流

两种类型的信息在节点之间流动：数据消息和控制消息。数据消息沿着图中的边以 `mbuf chains` 形式传递，一次一条边。链中的第一个 `mbuf` 必须设置 `M_PKTHDR` 标志。每个节点决定如何处理通过其某个钩子接收的数据。

与数据一起，节点还可以接收控制消息。有通用和特定于类型的控制消息。控制消息具有通用头部格式，后跟特定于类型的数据，并且为提高效率是二进制结构。然而，节点类型也可以支持在二进制和 ASCII 格式之间转换特定于类型的数据，用于调试和人机界面目的（参见下文的 `NGM_ASCII2BINARY` 和 `NGM_BINARY2ASCII` 通用控制消息）。节点不需要支持这些转换。

有三种寻址控制消息的方式。如果连接两个节点有一系列边，则可以通过将相应的 ASCII 钩子名称序列指定为消息的目的地址来“源路由”消息（相对寻址）。如果目的地与源相邻，则源节点可以简单地指定（作为代码中的指针）应跨哪个钩子发送消息。否则，使用接收节点的全局 ASCII 名称（或等效的基于 ID 的名称）作为消息的目的地址（绝对寻址）。两种 ASCII 寻址方式可以组合，通过指定绝对起始节点和一系列钩子。内核外的控制程序只能使用 ASCII 寻址模式；直接指针的使用仅限于内核模块。

消息通常表示命令，随后在相反方向跟随回复消息。为方便此操作，控制消息的接收者会获得一个适合寻址回复的“返回地址”。

每个控制消息包含一个 32 位值，称为“typecookie”，指示消息的类型，即如何解释它。通常每种类型为其理解的消息定义唯一的 typecookie。然而，节点可以选择识别和实现多种类型的消息。

如果消息被传递到暗示它通过特定钩子到达该节点的地址（而非使用其 ID 或全局名称直接寻址），则该钩子会被标识给接收节点。这允许消息被重新路由或转发，如果节点决定这是必需的，方式与数据包在节点之间传递的方式大致相同。基本系统定义了一组用于流控和链路管理的标准消息，通常以这种方式传递。流控消息通常在与它们相关的数据相反的方向上传输。

### Netgraph 通常为函数式

为最小化延迟，大多数 `netgraph` 操作是函数式的。也就是说，数据和控件消息通过函数调用交付，而不是使用队列和邮箱。例如，如果节点 A 希望向相邻节点 B 发送数据 `mbuf`，它会调用通用 `netgraph` 数据交付函数。此函数反过来定位节点 B 并调用 B 的“接收数据”方法。但也有例外。

每个节点都有一个输入队列，某些操作可以被认为是 *写入者*，因为它们更改节点的状态。显然，在 SMP 世界中，如果在一个数据包通过节点时节点的状态被更改，那将很糟糕。为此，输入队列实现了 *读者/写入者* 语义，以便当节点中有写入者时，所有其他请求都会排队，而当有读者时，写入者和任何后续数据包都会排队。在没有理由排队数据的情况下，输入方法被直接调用，如上所述。

节点可以声明所有请求都应被视为写入者，或者通过特定钩子进入的请求应被视为写入者，甚至通过特定钩子离开或进入的数据包应始终排队，而不是直接交付（对于希望快速返回硬件的中断例程通常有用）。默认情况下，所有控制消息数据包都被视为写入者，除非在其定义中明确声明为读者。（参见

`#include <netgraph/ng_message.h>`

中的 `NGM_READONLY`）

虽然这种操作模式产生了良好的性能，但它对节点开发者有一些影响：

* 每当节点交付数据或控制消息时，节点可能需要允许在原始交付函数调用返回之前接收返回消息的可能性。
* `Netgraph` 提供节点之间的内部同步。数据始终在 *边缘节点* 处进入“图”。*边缘节点* 是在 `Netgraph` 和系统其他部分之间接口的节点。“边缘节点”的示例包括设备驱动程序、`socket , ether , tty` 和 `ksocket` 节点类型。在这些 *边缘节点* 中，调用线程直接执行节点中的代码，并从该代码调用 `Netgraph` 框架以跨图中的某些边交付数据。从执行的角度来看，调用线程将执行 `Netgraph` 框架方法，如果它可以获取锁来执行此操作，则执行下一个节点的输入方法。这会继续，直到数据被丢弃或排队等待某些设备或系统实体，或者线程无法获取下一个节点的锁。在这种情况下，数据被排队等待节点，执行回溯到原始调用实体。排队的数据将被锁的当前持有者在完成其操作时拾取和处理，或者由在有此类项目排队时激活的特殊 `Netgraph` 线程处理。
* 如果图包含循环，可能会发生无限循环。

到目前为止，这些问题在实践中未被证明有问题。

### 与内核其他部分的交互

节点可以与 `Netgraph` 子系统之外的内核其他组件有隐藏的交互，例如设备硬件、内核协议栈等。事实上，`Netgraph` 的好处之一是能够将不同的内核网络实体连接到一致的通信框架中。

一个示例是 `socket` 节点类型，它既是 `Netgraph` 节点，又是协议族 `PF_NETGRAPH` 中的 socket(2)。套接字节点允许用户进程参与 `Netgraph`。其他节点使用通常的方法与套接字节点通信，节点隐藏了它也向协作用户进程传递信息的事实。

另一个示例是向硬件呈现节点接口的设备驱动程序。

### 节点方法

通过调用以下节点方法，节点会收到以下操作的通知，并且可以接受或拒绝该操作（通过返回适当的错误代码）：

**创建** 新节点 调用该类型的构造函数。如果允许创建新节点，构造函数方法可以分配它需要的任何特殊资源。对于对应于硬件的节点，这通常在设备 attach 例程期间完成。通常也会在此处分配对应于设备名称的全局 ASCII 名称。

**创建** 新钩子 创建钩子并初步链接到节点，并告知节点将用于描述此钩子的名称。节点设置它需要的任何特殊数据结构，或者可以根据钩子的名称拒绝连接。

**成功** 连接两个钩子 在两端都接受了它们的钩子并且链接已建立后，节点有机会找出链路对面的对等方是谁，然后可以决定拒绝连接。拆卸是自动的。这也是节点决定是否将特定钩子（或其对等方）设置为 *排队* 模式的时间。

**销毁** 钩子 节点会收到断开连接的通知。节点可能认为某些钩子对操作至关重要，而其他钩子可牺牲：一个钩子的断开可能是可接受的事件，而对于另一个钩子则可能导致节点完全关闭。

**预关闭** 节点 此方法在真正关闭之前调用，真正关闭在下面讨论。在此方法中，节点完全可操作，可以向其对等方发送“再见”消息，或者可以像 [ng\_tee(4)](/man4/ng_tee.4.md) 节点类型那样将自己从链中排除并重新连接其对等方。

**关闭** 节点 此方法允许节点清理并确保此时需要执行的任何操作都得到执行。该方法由通用（即超类）节点析构函数调用，该析构函数将删除节点的通用组件。某些节点（通常与一块硬件关联）可能是 *持久* 的，即关闭会断开所有边并重置节点，但不会删除它。在这种情况下，关闭方法不应释放其资源，而应清理，然后调用 Fn NG\_NODE\_REVIVE 宏以向通用代码发出信号表示关闭已中止。在关闭由节点自身由于硬件移除或卸载（通过 Fn ng\_rmnode\_self）启动的情况下，它应设置 `NGF_REALLY_DIE` 标志以向其自身的关闭方法发出信号表示不应持久。

### 发送与接收数据

所有节点还支持另外两种方法：

**接收** 数据消息 此函数接收一个 `Netgraph` *可排队请求项*，通常称为 *item*。该 item 包含指向 `mbuf` 的指针。节点会收到 item 到达的钩子，并可在其处理决策中使用此信息。接收节点必须在完成或出错时始终 Fn NG\_FREE\_M `mbuf chain`，或将其传递给另一个节点（或内核模块），由后者负责释放它。类似地，如果不将 *item* 传递给另一个节点，则必须使用 Fn NG\_FREE\_ITEM 宏释放它。如果在释放时 item 仍然持有对 `mbufs` 的引用，则它们也会被适当地释放。因此，如果 `mbuf` 有可能独立于 item 被更改或释放，则非常重要的是使用 Fn NGI\_GET\_M 宏检索它，该宏还删除 item 内的引用。（否则将发生同一对象的多次释放。）如果只需检查 `mbufs` 的内容，则可以使用 Fn NGI\_M 宏读取和重写 item 内的 `mbuf` 指针。如果开发者需要随 `mbuf chain` 传递任何元信息，他应使用 [mbuf\_tags(9)](/man9/mbuf_tags.9.md) 框架。Bf -symbolic 注意，旧的 `Netgraph` 特定元数据格式现已过时。Ef 接收节点可以决定通过将数据排队到 `Netgraph` NETISR 系统中来延迟数据（见下文）。它通过在数据将到达的钩子的 flags 字中设置 `HK_QUEUE` 标志来实现此目的。基础设施会尊重该位并将数据排队以供稍后交付，而不是直接交付。节点可以决定在 *对等* 节点上设置该位，以便其自身的输出数据包被排队。节点可以选择为在特定钩子上接收的数据指定不同的接收数据函数，以简化编码。它使用 Fn NG\_HOOK\_SET\_RCVDATA hook fn 宏来执行此操作。该函数接收相同的参数，只是它将接收（且仅接收）来自该钩子的所有数据包。

**接收** 控制消息 当控制消息寻址到该节点时调用此方法。与接收数据一样，接收一个 *item*，其中包含指向控制消息的指针。可以使用 Fn NGI\_MSG 宏检查消息，或使用 Fn NGI\_GET\_MSG 从 item 中完全提取，该宏还删除 item 内的引用。如果在释放时（使用 Fn NG\_FREE\_ITEM 宏）item 仍然持有对消息的引用，则消息也会被适当地释放。如果引用已被删除，节点必须使用 Fn NG\_FREE\_MSG 宏自行释放消息。始终提供返回地址，给出发起消息的节点的地址，以便稍后随时发送回复消息。返回地址使用 Fn NGI\_RETADDR 宏从 *item* 中检索，类型为 `ng_ID_t`。所有控制消息和回复都使用 [malloc(9)](/man9/malloc.9.md) 类型 `M_NETGRAPH_MSG` 分配，然而使用 Fn NG\_MKMESSAGE 和 Fn NG\_MKRESPONSE 宏来分配和填写消息更为方便。必须使用 Fn NG\_FREE\_MSG 宏释放消息。如果消息通过特定钩子交付，则该钩子也将被告知，这允许使用诸如流控消息和状态更改消息之类的内容，其中节点可能希望将消息转发出另一个钩子而非到达的钩子。节点可以选择为在特定钩子上接收的消息指定不同的接收消息函数，以简化编码。它使用 Fn NG\_HOOK\_SET\_RCVMSG hook fn 宏来执行此操作。该函数接收相同的参数，只是它将接收（且仅接收）来自该钩子的所有消息。

大量使用了引用计数，以便释放所有引用的节点会自动释放，并且此行为已经过测试和调试，为“类型模块”编写者提供了一致且可信的框架。

### 寻址

`Netgraph` 框架提供了一种明确且易于使用的方法来专门寻址图中的任何单个节点。节点的命名独立于其类型，因为另一个节点或外部组件不需要知道节点的类型即可寻址它并发送通用消息类型。应选择节点和钩子名称以使地址有意义。

地址是绝对的或相对的。绝对地址以节点名称或 ID 开头，后跟冒号，后跟以句点分隔的钩子名称序列。这寻址通过从命名节点开始并遵循指定的钩子序列到达的节点。相对地址仅包含钩子名称序列，隐式地从本地节点开始钩子遍历。

节点名称有几种特殊可能性。名称 `.`（称为 `.:`）始终引用本地节点。此外，没有全局名称的节点可以通过其 ID 号寻址，方法是将 ID 号的十六进制表示括在方括号内。以下是有效的 `Netgraph` 地址示例：

```sh
.:
[3f]:
foo:
.:hook1
foo:hook1.hook2
[d80]:hook1
```

对于具有单条物理帧中继线路、两个活动逻辑 DLCI 通道（DLCI 16 上的 RFC 1490 帧和 DLCI 20 上的 PPP 帧）的站点，可能会创建以下节点集合：

```sh
[type SYNC ]                  [type FRAME]                 [type RFC1490]
[ "Frame1" ](uplink)<-->(data)[<un-named>](dlci16)<-->(mux)[<un-named>  ]
[    A     ]                  [    B     ](dlci20)<---+    [     C      ]
                                                      |
                                                      |      [ type PPP ]
                                                      +>(mux)[<un-named>]
                                                             [    D     ]
```

始终可以使用名称“`Frame1:uplink.dlci16`”从任何地方向节点 C 发送控制消息。在这种情况下，节点 C 还会被告知消息通过其钩子 `mux` 到达。类似地，“`Frame1:uplink.dlci20`”可以可靠地用于到达节点 D，节点 A 可以将节点 B 称为“`.:uplink`”或简单地“`uplink`”。反之，B 可以将 A 称为“`data`”。地址“`mux.data`”可由节点 C 和 D 用于寻址到节点 A 的消息。

注意，这仅适用于 *控制消息*。在每种情况下使用相对寻址模式时，接收者会被告知消息到达的钩子以及发起节点。这允许逐跳分发消息和状态信息。数据消息 *仅* 一次路由一跳，通过指定离开的钩子，每个节点做出下一个路由决策。因此，当 B 在钩子 `data` 上接收到帧时，它解码帧中继头部以确定 DLCI，然后将解包的帧转发到 C 或 D。

类似地，流控消息可以以与传出数据相反的方向路由。例如，来自“`Frame1:`”的“缓冲区几乎满”消息将传递给节点 B，B 可能决定向节点 C 和 D 都发送类似消息。节点将使用 *直接钩子指针* 寻址来路由消息。消息可能作为同步回复从“`Frame1:`”传送到 B，节省时间和周期。

### Netgraph 结构

结构定义在

`#include <netgraph/netgraph.h>`

（仅限节点感兴趣的内核结构）和

`#include <netgraph/ng_message.h>`

（用户程序也感兴趣的消息定义）中。

节点作者感兴趣的两种基本对象类型是 *节点* 和 *钩子*。这两个对象具有以下节点作者也感兴趣的属性。

```sh
if (NG_NODE_NAME(node)[0] != 'e0') ...
if (strcmp(NG_NODE_NAME(node), "fred") == 0) ...
```

**有效性** 驱动程序或中断例程可能想检查节点是否仍然有效。假定调用者持有对节点的引用，因此它不会被释放，但它可能已被禁用或以其他方式关闭。使用 Fn NG\_NODE\_IS\_VALID node 宏将返回此状态。最终，在无效节点中运行代码应该几乎是不可能的，但目前该工作尚未完成。

**节点** ID（`ng_ID_t`）此属性可使用宏 Fn NG\_NODE\_ID node 检索。

**节点** 名称 可选的全局唯一名称，`NUL` 终止字符串。如果此处有值，则为节点名称。

**节点** 相关的不透明 cookie 任何指针类型都可以放置在此处。宏 Fn NG\_NODE\_SET\_PRIVATE node value 和 Fn NG\_NODE\_PRIVATE node 分别设置和检索此属性。

**钩子数** Fn NG\_NODE\_NUMHOOKS node 宏用于检索此值。

**钩子** 节点可以具有多个钩子。提供了一种遍历方法以允许测试所有钩子的某些条件。Fn NG\_NODE\_FOREACH\_HOOK node fn arg rethook，其中 `fn` 是将为每个钩子调用的函数，形式为 Fn fn hook arg，返回 0 以终止搜索。如果搜索终止，则 `rethook` 将设置为搜索终止时的钩子。

**钩子** 相关的不透明 cookie 任何指针类型都可以放置在此处。宏 Fn NG\_HOOK\_SET\_PRIVATE hook value 和 Fn NG\_HOOK\_PRIVATE hook 分别设置和检索此属性。

**关联** 节点 宏 Fn NG\_HOOK\_NODE hook 查找关联的节点。

**对等** 钩子（`hook_p`）此连接对中的另一个钩子。Fn NG\_HOOK\_PEER hook 宏查找对等方。

**引用** Fn NG\_HOOK\_REF hook 和 Fn NG\_HOOK\_UNREF hook 宏分别递增和递减钩子引用计数。递减后，应始终假设钩子已被释放，除非你有另一个仍然有效的引用。

**覆盖** 接收函数 Fn NG\_HOOK\_SET\_RCVDATA hook fn 和 Fn NG\_HOOK\_SET\_RCVMSG hook fn 宏可用于设置覆盖方法，优先于通用接收数据和接收消息函数使用。要取消设置这些，使用宏将它们设置为 `NULL`。它们仅用于在设置它们的钩子上接收的数据和消息。

**`struct ng_node`** 节点作者应始终使用以下 `typedef` 声明其指针，并且永远不应实际声明该结构。Fd typedef struct ng\_node \*node\_p; 以下属性与节点关联，可以通过以下方式访问：

**`struct ng_hook`** 节点作者应始终使用以下 `typedef` 声明其钩子指针。Fd typedef struct ng\_hook \*hook\_p; 以下属性与钩子关联，可以通过以下方式访问：每个节点的名称、引用计数和钩子链表的维护由 `Netgraph` 子系统自动处理。通常，节点的私有信息包含指向节点或钩子结构的反向指针，这算作必须包含在节点引用计数中的新引用。当调用节点构造函数时，已经为此计算了一个引用，因此当节点被销毁时，它应记住对节点执行 Fn NG\_NODE\_UNREF。从钩子可以获得相应的节点，从节点可以遍历所有活动钩子。如何定义节点的当前示例始终可以在 `src/sys/netgraph/ng_sample.c` 中看到，应作为新节点编写者的起点。

### Netgraph 消息结构

控制消息具有以下结构：

```sh
#define NG_CMDSTRSIZ    32      /* 最大命令字符串（包括 null） */
struct ng_mesg {
  struct ng_msghdr {
    u_char      version;        /* 必须等于 NG_VERSION */
    u_char      spare;          /* 填充到 4 字节 */
    uint16_t    spare2;
    uint32_t    arglen;         /* cmd/resp 数据长度 */
    uint32_t    cmd;            /* 命令标识符 */
    uint32_t    flags;          /* 消息状态标志 */
    uint32_t    token;          /* 回复应具有相同的 token */
    uint32_t    typecookie;     /* 理解此消息的节点类型 */
    u_char      cmdstr[NG_CMDSTRSIZ];  /* cmd 字符串 +   */
  } header;
  char  data[];                 /* 实际数据的占位符 */
};
#define NG_ABI_VERSION  12              /* Netgraph 内核 ABI 版本 */
#define NG_VERSION      8               /* Netgraph 消息版本 */
#define NGF_ORIG        0x00000000      /* 消息是原始请求 */
#define NGF_RESP        0x00000001      /* 消息是响应 */
```

控制消息具有上面显示的固定头部，后跟取决于类型 cookie 和命令的可变长度数据部分。每个字段解释如下：

**`version`** 指示 `Netgraph` 消息协议本身的版本。当前版本为 `NG_VERSION`。

**`arglen`** 这是从 `data` 开始的任何额外参数的长度。

**`flags`** 指示这是命令还是响应控制消息。

**`token`** `token` 是发送方将回复消息与相应命令消息匹配的手段；回复始终具有相同的 token。

**`typecookie`** 相应节点类型的唯一 32 位值。如果节点不识别类型 cookie，它必须通过返回 Er EINVAL 来拒绝消息。每种类型都应有一个包含文件，定义命令、参数格式和自身消息的 cookie。typecookie 确保发送方和接收方都包含了相同的头文件；当头文件进行不兼容的更改时，typecookie *必须* 更改。生成唯一类型 cookie 的事实方法是取编写头文件时 Epoch 的秒数（即“`date` `-u` `+%s`”的输出）。有一个为 `generic` 节点类型预定义的 typecookie `NGM_GENERIC_COOKIE`，以及一组所有节点都理解的相应通用消息。这些消息的处理是自动的。

**`cmd`** 消息命令的标识符。这是特定于类型的，在与 typecookie 相同的头文件中定义。

**`cmdstr`** 用于 `command` 的简短人类可读版本的空间（仅用于调试目的）。

某些模块可能选择实现来自多个头文件的消息，从而识别多个类型 cookie。

### 控制消息 ASCII 形式

控制消息为提高效率采用二进制格式。然而，出于调试和人机界面目的，如果节点类型支持，控制消息可以转换为等效的 ASCII 形式或从其转换。ASCII 形式类似于二进制形式，但有两个例外：

* `cmdstr` 头部字段必须包含命令的 ASCII 名称，对应于 `cmd` 头部字段。
* 参数字段包含消息参数的 `NUL` 终止 ASCII 字符串版本。

通常，控制消息的参数字段可以是任何任意 C 数据类型。`Netgraph` 包含解析例程以支持以这种简单语法在 ASCII 中预定义的一些数据类型：

* 整数类型以 8、10 或 16 进制数字表示。
* 字符串用双引号括起，并遵守常规 C 语言反斜杠转义。
* IP 地址具有显而易见的形式。
* 数组用方括号括起，元素从索引零开始连续列出。元素可以具有可选的索引和等号（`=`）前缀。每当元素没有显式索引时，索引隐式为前一个元素的索引加一。
* 结构用花括号括起，每个字段以 `fieldname`=`value` 形式指定。
* 任何值等于其“默认值”的数组元素或结构字段都可以省略。对于整数类型，默认值通常为零；对于字符串类型，为空字符串。
* 数组元素和结构字段可以以任何顺序指定。

每种节点类型可以通过提供必要的解析和反向解析例程来定义自己的任意类型。为特定节点类型定义的 ASCII 形式记录在相应的手册页中。

### 通用控制消息

有许多标准的预定义消息适用于任何节点，因为它们由框架本身直接支持。这些消息定义在

`#include <netgraph/ng_message.h>`

中，以及消息的基本布局和其他类似信息。

**`NGM_CONNECT`** 使用两端提供的钩子名称连接到另一个节点。

**`NGM_MKPEER`** 构造给定类型的节点，然后使用提供的钩子名称连接到它。

**`NGM_SHUTDOWN`** 目标节点应断开与所有邻居的连接并关闭。持久节点（如代表物理硬件的节点）可能不会从节点命名空间中消失，而只是重置自身。节点必须断开其所有钩子。这可能导致邻居关闭自身，并可能级联关闭整个连接图。

**`NGM_NAME`** 为节点分配名称。节点可以没有名称而存在，这是使用 `NGM_MKPEER` 方法创建的节点的默认情况。此类节点只能相对寻址或通过其 ID 号寻址。

**`NGM_RMHOOK`** 请求节点断开与其一个邻居的钩子连接。两个节点都将调用其“disconnect”方法。任一节点可能因此选择完全关闭。

**`NGM_NODEINFO`** 要求目标节点描述自身。返回的四个字段是节点名称（如果有）、节点类型、节点 ID 和附加的钩子数。ID 是该节点唯一的内部编号。

**`NGM_LISTHOOKS`** 这返回 `NGM_NODEINFO` 给出的信息，但另外包括描述每个链路的字段数组，以及该链路远端节点的描述。

**`NGM_LISTNAMES`** 这返回一个节点描述数组（如 `NGM_NODEINFO`），其中数组的每个条目描述一个命名节点。将描述所有命名节点。

**`NGM_LISTNODES`** 这与 `NGM_LISTNAMES` 相同，只是无论是否命名都列出所有节点。

**`NGM_LISTTYPES`** 这返回当前安装的所有 `Netgraph` 类型的列表。

**`NGM_TEXT_STATUS`** 节点可以返回文本格式的状态消息。状态信息完全由节点类型确定。它是唯一需要在节点自身内提供任何支持的“通用”消息，因此节点可以选择不支持此消息。文本响应长度必须小于 `NG_TEXTRESPONSE` 字节（目前为 1024）。这可用于以人类可读形式返回一般状态信息。

**`NGM_BINARY2ASCII`** 此消息将二进制控制消息转换为其 ASCII 形式。要转换的整个控制消息包含在 `NGM_BINARY2ASCII` 消息本身的参数字段中。如果成功，回复将包含 ASCII 形式的相同控制消息。节点通常只知道如何翻译它自己理解的消息，因此 `NGM_BINARY2ASCII` 的目标节点通常与实际接收该消息的节点相同。

**`NGM_ASCII2BINARY`** `NGM_BINARY2ASCII` 的反向操作。要转换的控制消息（以 ASCII 形式）包含在 `NGM_ASCII2BINARY` 的参数部分中，只需填写 `flags , cmdstr` 和 `arglen` 头部字段，以及参数字段中参数的 `NUL` 终止字符串版本。如果成功，回复包含控制消息的二进制版本。

### 流控消息

除了相对于图影响节点的控制消息外，还定义了许多 *流控* 消息。目前这些消息 *不* 由系统自动处理，因此如果节点要在使用流控的图中使用，并且在这些消息的可能路径中，则需要处理它们。不理解这些消息的节点的默认操作应是将其传递给下一个节点。希望最终会有一些辅助函数来协助此操作。这些消息也定义在

`#include <netgraph/ng_message.h>`

中，并具有单独的 cookie `NG_FLOW_COOKIE` 以帮助识别它们。这里不会深入介绍它们。

## 初始化

基本 `Netgraph` 代码可以静态编译进内核，也可以通过 [kldload(8)](/man8/kldload.8.md) 作为 KLD 动态加载。在前一种情况下，在内核配置文件中包含

> `options NETGRAPH`

你也可以在内核编译中包含选定的节点类型，例如：

> `options NETGRAPH`

> `options NETGRAPH_SOCKET`

> `options NETGRAPH_ECHO`

加载 `Netgraph` 子系统后，可以随时通过 [kldload(8)](/man8/kldload.8.md) 作为 KLD 模块加载单个节点类型。此外，`Netgraph` 知道如何自动执行此操作；当请求创建未知类型 `type` 的新节点时，`Netgraph` 将尝试加载 KLD 模块 `ng_`<`type` >`.ko`。>

类型也可以在引导时安装，因为某些设备驱动程序可能希望将设备的每个实例导出为 `Netgraph` 节点。

通常，可以随时从内核中通过调用 Fn ng\_newtype 安装新类型，提供指向类型的 `struct ng_type` 结构的指针。

Fn NETGRAPH\_INIT 宏使用链接器集自动执行此过程。

## 现有节点类型

目前存在几种节点类型。每种都在自己的手册页中有完整文档：

**SOCKET** socket 类型在新的协议域 `PF_NETGRAPH` 中实现了两个新套接字。新套接字协议为 `NG_DATA` 和 `NG_CONTROL`，均为 `SOCK_DGRAM` 类型。通常，每个套接字节点关联一个。当两个套接字都关闭时，节点将关闭。`NG_DATA` 套接字用于发送和接收数据，而 `NG_CONTROL` 套接字用于发送和接收控制消息。数据和控件消息使用 sendto(2) 和 recvfrom(2) 系统调用传递，使用 `struct sockaddr_ng` 套接字地址。

**HOLE** 仅响应通用消息，是数据的“黑洞”。用于测试。始终接受新钩子。

**ECHO** 仅响应通用消息，并始终通过数据到达的钩子回显数据。将任何非通用消息作为其自身响应返回。用于测试。始终接受新钩子。

**TEE** 此节点用于“侦听”。它有 4 个钩子：`left , right , left2right` 和 `right2left`。从 `right` 进入的数据传递到 `left` 并复制到 `right2left`，从 `left` 进入的数据传递到 `right` 并复制到 `left2right`。从 `left2right` 进入的数据发送到 `right`，从 `right2left` 到 `left`。

**RFC1490** MUX 封装/解封装根据 RFC 1490 编码的帧。具有用于封装数据包的钩子（`downstream`）和每种协议（即 IP、PPP 等）的钩子。

**FRAME** RELAY MUX 封装/解封装帧中继帧。具有用于封装数据包的钩子（`downstream`）和每个 DLCI 的钩子。

**FRAME** RELAY LMI 自动处理帧中继“LMI”（链路管理接口）操作和数据包。自动探测并检测交换机使用的几种 LMI 标准。

**TTY** 此节点也是线路规程。它只是在 `mbuf` 帧和顺序串行数据之间转换，使 TTY 显示为 `Netgraph` 节点。它具有可编程的“热键”字符。

**ASYNC** 此节点根据 RFC 1662 封装和解封装异步帧。这与 TTY 节点类型结合使用，以支持通过异步串行线路的 PPP 链路。

**ETHERNET** 此节点附加到系统中的每个以太网接口。它允许从网络捕获原始以太网帧，以及从接口发送帧。

**INTERFACE** 此节点也是系统网络接口。它具有代表每个协议族（IP、IPv6）的钩子，并出现在 [ifconfig(8)](/man8/ifconfig.8.md) 的输出中。接口命名为“`ng0`”、“`ng1`”等。

**ONE2MANY** 此节点实现简单的循环复用器。例如，可用于使多个 LAN 端口协同工作以在两台机器之间获得更高速的链路。

**各种** PPP 相关节点 有一个完整的多链路 PPP 实现在 `Netgraph` 中运行。`net/mpd5` port 可以使用这些模块制作一个非常低延迟高容量的 PPP 系统。它还支持使用 PPTP 节点的 PPTP VPN。

**PPPOE** PPPoE 的服务器和客户端实现。与 ppp(8) 或 `net/mpd5` port 一起使用。

**BRIDGE** 此节点与以太网节点一起，允许实现非常灵活的桥接系统。

**KSOCKET** 此有趣节点对系统来说看起来像套接字，但将所有数据转移到 `Netgraph` 系统和从其转移以进行进一步处理。这允许从命令行几乎轻松实现诸如 UDP 隧道之类的事情。

有关更多节点类型，请参见本手册页末尾的部分。

## 注释

可以通过尝试向命名节点发送控制消息（例如 `NGM_NODEINFO`）来检查它是否存在。如果不存在，将返回 Er ENOENT。

所有数据消息都是设置了 `M_PKTHDR` 标志的 `mbuf chains`。

节点负责释放它们分配的内容。有三个例外：

* 通过数据链路发送的 `Mbufs` 永远不应由发送方释放。在出错的情况下，应视为已释放。
* 使用 Fn NG\_SEND\_MSG\_\* 系列宏之一发送的消息由接收方释放。与上述情况一样，与消息关联的地址由分配它们的任何对象释放，因此如果接收方想保留该信息，应复制它们。
* 控制消息和数据都通过 `Netgraph` *item* 交付和排队。item 必须使用 Fn NG\_FREE\_ITEM item 释放或传递给另一个节点。

## 文件

**netgraph/netgraph.h** 仅供 `Netgraph` 节点在内核内使用的定义。

**netgraph/ng\_message.h** 任何需要处理 `Netgraph` 消息的文件所需的定义。

**netgraph/ng\_socket.h** 使用 `Netgraph` `socket` 类型节点所需的定义。

**netgraph/ng\_<`type`** >`.h`> 使用 `Netgraph` `type` 节点所需的定义，包括类型 cookie 定义。

**`/boot/kernel/netgraph.ko`** `Netgraph` 子系统可加载 KLD 模块。

**`/boot/kernel/ng_`** <`type` >`.ko`> 节点类型 `type` 的可加载 KLD 模块。

**`src/sys/netgraph/ng_sample.c`** `Netgraph` 节点骨架。将此作为新节点类型的起点。

## 用户模式支持

有一个库用于支持希望与 `Netgraph` 系统交互的用户模式程序。详见 netgraph(3)。

有两个用户模式支持程序 ngctl(8) 和 nghook(8) 可用于协助手动配置和调试。

有一些有用的技术用于调试新节点类型。首先，在用户模式中首先实现新节点类型使调试更容易。`tee` 节点类型也用于调试，特别是与 ngctl(8) 和 nghook(8) 结合使用时。

还可以查看 **`/usr/share/examples/netgraph`** 以获取使用 `Netgraph` 解决的几个常见网络问题的解决方案。

## 参见

socket(2), netgraph(3), [ng\_async(4)](/man4/ng_async.4.md), [ng\_bluetooth(4)](/man4/ng_bluetooth.4.md), [ng\_bpf(4)](/man4/ng_bpf.4.md), [ng\_bridge(4)](/man4/ng_bridge.4.md), [ng\_btsocket(4)](/man4/ng_btsocket.4.md), [ng\_car(4)](/man4/ng_car.4.md), [ng\_cisco(4)](/man4/ng_cisco.4.md), [ng\_device(4)](/man4/ng_device.4.md), [ng\_echo(4)](/man4/ng_echo.4.md), [ng\_eiface(4)](/man4/ng_eiface.4.md), [ng\_etf(4)](/man4/ng_etf.4.md), [ng\_ether(4)](/man4/ng_ether.4.md), [ng\_frame\_relay(4)](/man4/ng_frame_relay.4.md), [ng\_gif(4)](/man4/ng_gif.4.md), [ng\_gif\_demux(4)](/man4/ng_gif_demux.4.md), [ng\_hci(4)](/man4/ng_hci.4.md), [ng\_hole(4)](/man4/ng_hole.4.md), [ng\_hub(4)](/man4/ng_hub.4.md), [ng\_iface(4)](/man4/ng_iface.4.md), [ng\_ip\_input(4)](/man4/ng_ip_input.4.md), [ng\_ipfw(4)](/man4/ng_ipfw.4.md), [ng\_ksocket(4)](/man4/ng_ksocket.4.md), [ng\_l2cap(4)](/man4/ng_l2cap.4.md), [ng\_l2tp(4)](/man4/ng_l2tp.4.md), [ng\_lmi(4)](/man4/ng_lmi.4.md), [ng\_mppc(4)](/man4/ng_mppc.4.md), [ng\_nat(4)](/man4/ng_nat.4.md), [ng\_netflow(4)](/man4/ng_netflow.4.md), [ng\_one2many(4)](/man4/ng_one2many.4.md), [ng\_patch(4)](/man4/ng_patch.4.md), [ng\_ppp(4)](/man4/ng_ppp.4.md), [ng\_pppoe(4)](/man4/ng_pppoe.4.md), [ng\_pptpgre(4)](/man4/ng_pptpgre.4.md), [ng\_rfc1490(4)](/man4/ng_rfc1490.4.md), [ng\_socket(4)](/man4/ng_socket.4.md), [ng\_split(4)](/man4/ng_split.4.md), [ng\_tee(4)](/man4/ng_tee.4.md), [ng\_tty(4)](/man4/ng_tty.4.md), [ng\_ubt(4)](/man4/ng_ubt.4.md), [ng\_UI(4)](/man4/ng_ui.4.md), [ng\_vjc(4)](/man4/ng_vjc.4.md), [ng\_vlan(4)](/man4/ng_vlan.4.md), ngctl(8), nghook(8)

## 历史

`Netgraph` 系统由 Whistle Communications, Inc. 在为 Whistle InterJet 定制的 FreeBSD 2.2 版本中设计和首次实现。它首次在 FreeBSD 3.4 的主树中亮相。

## 作者

Julian Elischer <julian@FreeBSD.org>，以及 Archie Cobbs <archie@FreeBSD.org> 的贡献。
