Go言語でNetlinkを少し触った話
Go言語でNetlinkを少し触ったのでメモ。
具体的にはGo言語でNetlinkというネットワーク関連のライブラリを使ってStatic Routeを設定したりするサンプルを作ったりした。
Netlinkとは
調べた範囲だと、Linuxカーネルのサブシステムの1つで、ルーティングテーブルの管理などのネットワーク関連の設定などを行う際に利用されるもの、という理解をしている。
Netlinkは、Linuxカーネルとユーザ空間プロセス間の、またはカーネル内の通信を提供するためのIPC(Inter-process communication)メカニズムで、具体的にはNetlink用のソケットがありネットワーク設定を行ったりといったやり取りができるようになっているよう。
要はプログラムからNetlinkのソケットに接続して、Netlink用のメッセージフォーマットで通信することでやり取りができるという感じみたいだった。
軽くみた感じNetwork Managerやiproute2(ipコマンドなどが入ってるユーティリティ)などがネットワーク設定を行う際にも内部でNetlinkを使っているっぽい感じだった。
古くは?ioctlというデバイス固有の入出力操作や、通常のシステムコールでは表現できない操作を行うためのシステムコールを利用してネットワーク周りの設定などの操作を行っていたらしいが、その代替としてNetlinkが作られたらしい。
ChatGPTに質問した感じioctlは別にネットワーク用の操作をするためのものではないのでエラーハンドリングしにくかったりといった問題があったらしいけどソースについてはよくわからない。
(NetlinkのRFCを読めばこのあたりもわかるのかな?)
Netlinkの操作などを行う際、C言語から扱う場合はlibnlというライブラリを使用して行うようだった。
Netlinkのソケット通信はTCPなどは利用せず独自のメッセージフォーマットとなっているようで
の3つで構成されているよう。
このあたりの詳細はあまり確認できていないけどこちらのページが図が多くてイメージを掴みやすそうだった。
作ったサンプルプログラム
上記の通り基本Netlinkの操作などをする場合はlibnlライブラリを使ってC言語で実装していくことになると思うけど、Go言語からNetlinkの操作などを行うためのライブラリを作っている方がいるので、今回はこちらを使用してサンプルプログラムを作成した。
使用したライブラリはこちら
今回はStatic RouteをちょっとNetlinkからいじって見たかったのでそれようのサンプルプログラムを作成してみた。
作成したプログラムのヘルプとしてはこんな感じ。
$ netlink-gosample add --help
add static route
Usage:
  netlink-gosample add <dst> <gw> [flags]
Examples:
netlink-gosample add 192.168.8.0/21 192.168.7.254
Flags:
  -h, --help   help for add
$ netlink-gosample del --help
delete static route
Usage:
  netlink-gosample del <dst> [flags]
Examples:
netlink-gosample del 192.168.8.0/21
Flags:
  -h, --help   help for del
$ netlink-gosample list --help
list static routes
Usage:
  netlink-gosample list [flags]
Flags:
  -h, --help   help for list
$ netlink-gosample watch --help
watch static route events
Usage:
  netlink-gosample watch [flags]
Flags:
  -h, --help   help for watch
ヘルプを見ての通りではあると思うのだけど作成したのは4つのサブコマンドで
- Static Routeを追加
- Static Routeを削除
- Static Routeの一覧を表示(してるつもり)
- Static Routeの変更などのイベントを監視(してるつもり)
という操作ができるサブコマンドを作成した。
なので要は下記のコマンドの真似事みたいなことができる感じ。
$ ip route
$ ip route add <dst> via <gw>
$ ip route del <dst>
$ ip monitor
ライブラリの使い方
ライブラリの使い方としてはかなり簡単で、例えばStatic Routeを設定するのであれば下記のようなコードだけでできる。
(もうちょっとちゃんとしたサンプルとしてはこちら)
gw := net.ParseIP(o.gw)
_, dst, err := net.ParseCIDR(o.dst)
if err != nil {
  return err
}
route := &netlink.Route{
  Table: unix.RT_TABLE_UNSPEC,
  Dst:   dst,
  Gw:    gw,
}
err = netlink.RouteAdd(route)
if err != nil {
  return err
}
あとは下記のようなことをやるとStatic Routeの設定変更のイベントをハンドリングして処理ができるので、こういうのはipコマンドだと難しいのかなと思ったのでいざというときに知っておくとできることの幅が広がると思った。
ch := make(chan netlink.RouteUpdate)
done := make(chan struct{})
defer close(done)
if err := netlink.RouteSubscribe(ch, done); err != nil {
  return err
}
for {
  select {
  case update := <-ch:
    if update.Dst != nil && !update.Gw.Equal(emptyGw) {
      event := "unknown"
      if update.Type == unix.RTM_NEWROUTE {
        event = "added"
      } else if update.Type == unix.RTM_DELROUTE {
        event = "deleted"
      }
      fmt.Printf("event: %s dst: %s gw: %s\n", event, update.Dst, update.Gw)
    }
  case <-ctx.Done():
    return nil
  }
}
まとめ
https://github.com/vishvananda/netlink を使うと思った以上にシンプルにNetlinkを触るプログラムを書けそうだったので「案外勉強しながら触っていけるかも」と思える感じだった。
一方でlibnlなどを使用してC言語で記述する場合結構がんばらなきゃいけない感じなのかなという印象だった。
(この下にlibnlで書いたサンプルプログラムも乗せたけど結構長い)
また、Netlinkを使ってどんなことができたりするのかについては、個人的にはライブラリのテストコードが参考になって面白かった。
例えば今回のようなStatic Route関連であればこのあたりのコードが参考になった。
同じ作者の方がGoでNetwork Namespaceを操作するライブラリも書かれていたので今度こっちも試してみたい。
あとKubernetesのCNIプラグインでいうとWeave Netがvishvananda/netlinkを使ってNetlinkの操作を直接行っているようだったので、今度このあたりも勉強してみたいと思った。
Netlinkについては日本語の情報があまり見つからなかったのでChatGPTにめっちゃ質問しつつ調べ物してた。こういう情報調べてもなかなか情報が出てこない(と思ってる)状態だとChatGPTめっちゃ助かる。
参考リンク
- https://en.wikipedia.org/wiki/Netlink
- https://en.wikipedia.org/wiki/Ioctl
- https://ilyaletre.hatenablog.com/entry/2019/09/01/205432
- https://hana-shin.hatenablog.com/entry/2022/01/11/212403
- https://eniyo0.hatenablog.com/entry/2022/11/23/180135
- https://www.linuxjournal.com/article/7356
- https://www.man7.org/linux/man-pages/man7/netlink.7.html
- https://ja.manpages.org/netlink_route/7
- https://github.com/vishvananda/netns
- https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/about
- https://gitlab.freedesktop.org/NetworkManager/NetworkManager
- https://www.infradead.org/~tgr/libnl/
- https://www.infradead.org/~tgr/libnl/doc/core.html
- https://tex2e.github.io/rfc-translater/html/rfc3549.html
- https://github.com/weaveworks/weave
おまけ
ioctlを使ってStatic Routeを追加する
ioctlを使ってStatic Routeを追加する場合のサンプルプログラムとしては下記のようなものになる。
(コードはChatGPTに生成してもらいつつ直したりしたやつ)
コードはリポジトリにも上げてある。
やってることが簡単だからかそんなに大変そうでもなさそうな印象だった。
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/route.h>
int main() {
    int sockfd;
    struct rtentry route;
    struct sockaddr_in *addr;
    int err = 0;
    // Create a socket.
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket");
        return -1;
    }
    memset(&route, 0, sizeof(route));
    addr = (struct sockaddr_in*)&route.rt_gateway;
    addr->sin_family = AF_INET;
    inet_pton(AF_INET, "192.168.7.254", &addr->sin_addr);
    addr = (struct sockaddr_in*)&route.rt_dst;
    addr->sin_family = AF_INET;
    inet_pton(AF_INET, "192.168.8.0", &addr->sin_addr);
    addr = (struct sockaddr_in*)&route.rt_genmask;
    addr->sin_family = AF_INET;
    inet_pton(AF_INET, "255.255.248.0", &addr->sin_addr); // → 192.168.8.0/21
    route.rt_flags = RTF_UP | RTF_GATEWAY;
    route.rt_metric = 0;
    err = ioctl(sockfd, SIOCADDRT, &route);
    if (err) {
        perror("ioctl");
        return -1;
    }
    return 0;
}
libnlを使ってStatic Routeを追加する
一方でlibnlを使ってStatic Routeを追加する例はこんな感じだった。
(これもChatGPTに生成してもらいつつ直したりしたやつ)
コードはリポジトリにも上げてある。
ライブラリを使用してるのでもっとシンプルな感じになるのかと思ったけど、結構がんばらなきゃいけなさそうだったのが意外だった。
(C言語よくわからんので自分の技量の問題もあると思う)
#include <stdio.h>
#include <netlink/netlink.h>
#include <netlink/route/route.h>
#include <netlink/route/link.h>
#include <netlink/route/addr.h>
// デフォルトのゲートウェイとデスティネーションネットワークを定義
#define DEFAULT_GATEWAY "192.168.7.254"
#define DEST_NETWORK "192.168.8.0/21"
int main(int argc, char *argv[])
{
    struct nl_sock *sock;
    struct nl_cache *link_cache, *route_cache;
    struct rtnl_route *route;
    struct nl_addr *dest_addr;
    struct rtnl_nexthop *nh;
    struct rtnl_addr *addr;
    struct rtnl_link *link;
    struct nl_addr *gw_addr;
    // ソケットを作成
    sock = nl_socket_alloc();
    if (!sock) {
        printf("Failed to allocate netlink socket.\n");
        return -1;
    }
    // ソケットを接続
    if (nl_connect(sock, NETLINK_ROUTE)) {
        printf("Failed to connect netlink socket.\n");
        nl_socket_free(sock);
        return -1;
    }
    // リンクキャッシュの作成
    int err = rtnl_link_alloc_cache(sock, AF_UNSPEC, &link_cache);
    if (err < 0) {
        nl_perror(err, "Unable to allocate link cache");
        return -1;
    }
    // ルートキャッシュを作成
    if (rtnl_route_alloc_cache(sock, AF_INET, 0, &route_cache)) {
        printf("Failed to allocate route cache.\n");
        nl_cache_free(link_cache);
        nl_close(sock);
        nl_socket_free(sock);
        return -1;
    }
    // ルートを作成
    route = rtnl_route_alloc();
    if (!route) {
        printf("Failed to allocate route.\n");
        nl_cache_free(route_cache);
        nl_cache_free(link_cache);
        nl_close(sock);
        nl_socket_free(sock);
        return -1;
    }
    // デスティネーションアドレスを作成
    if (nl_addr_parse(DEST_NETWORK, AF_INET, &dest_addr)) {
        printf("Failed to parse destination address.\n");
        rtnl_route_put(route);
        nl_cache_free(route_cache);
        nl_cache_free(link_cache);
        nl_close(sock);
        nl_socket_free(sock);
        return -1;
    }
    
    rtnl_route_set_dst(route, dest_addr);
    // ネクストホップを作成
    nh = rtnl_route_nh_alloc();
    if (!nh) {
        printf("Failed to allocate next hop.\n");
        nl_addr_put(dest_addr);
        rtnl_route_put(route);
        nl_cache_free(route_cache);
        nl_cache_free(link_cache);
        nl_close(sock);
        nl_socket_free(sock);
        return -1;
    }
    // ゲートウェイアドレスを作成
    if (nl_addr_parse(DEFAULT_GATEWAY, AF_INET, &gw_addr)) {
        printf("Failed to parse gateway address.\n");
        rtnl_route_nh_free(nh);
        nl_addr_put(dest_addr);
        rtnl_route_put(route);
        nl_cache_free(route_cache);
        nl_cache_free(link_cache);
        nl_close(sock);
        nl_socket_free(sock);
        return -1;
    }
    
    rtnl_route_nh_set_gateway(nh, gw_addr);
    // 全てのリンクについて反復処理
    for (struct nl_object *obj = nl_cache_get_first(link_cache); obj; obj = nl_cache_get_next(obj)) {
        link = (struct rtnl_link *)obj;
        struct nl_addr *link_addr = rtnl_link_get_addr(link);
        // リンクのアドレスが設定されていない場合はスキップ
        if (!link_addr) {
            continue;
        }
        if (nl_addr_cmp_prefix(gw_addr, link_addr) == 0) {
            rtnl_route_nh_set_ifindex(nh, rtnl_link_get_ifindex(link));
            break;
        }
    }
    // ネクストホップをルートに追加
    rtnl_route_add_nexthop(route, nh);
    // ルートを追加
    if (rtnl_route_add(sock, route, NLM_F_CREATE)) {
        printf("Failed to add route.\n");
    } else {
        printf("Route added successfully.\n");
    }
    // リソースを開放
    nl_addr_put(gw_addr);
    nl_addr_put(dest_addr);
    rtnl_route_put(route);
    nl_cache_free(route_cache);
    nl_cache_free(link_cache);
    nl_close(sock);
    nl_socket_free(sock);
    return 0;
}
関連するかしないかわからないけどネットワーク関連で今度読んでみたい本




Discussion