Linuxカーネルで処理できるEjectプロトコルを作ってみる

Linuxカーネルでinet_add_protocol()という関数を使うとオレオレプロトコルを簡単に追加できるので遊んでみました。
以下が作ったカーネルモジュールです。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kmod.h>
#include <net/protocol.h>

MODULE_DESCRIPTION("simple Eject protocol");
MODULE_AUTHOR("masami256");
MODULE_LICENSE("GPL");

#define IPPROTO_NET_EJECT 123
int 
eject_handler(struct sk_buff *skb)
{
	if (skb->data && !strncmp(skb->data, "eject", 5)) {
		char *argv[5] = {"/bin/sh", "-c", "/usr/bin/eject", "cdrom", NULL};
		char *envp[1] = {NULL};
		printk(KERN_INFO "Execute /usr/bin/eject cdrom\n");
		call_usermodehelper("/bin/sh", argv, envp, UMH_NO_WAIT);
	} else {
		printk(KERN_INFO "protocol receve unknown data\n");
	}

	kfree_skb(skb);
	return 0;
}

const struct net_protocol net_eject_protocol = {
	.handler = eject_handler,
	.no_policy = 1,
	.netns_ok = 1
};

static int
net_eject_init(void)
{
	int ret = inet_add_protocol(&net_eject_protocol, IPPROTO_NET_EJECT);
	if (!ret)
		printk(KERN_INFO "Add protocol success\n");

	return ret;
}

static void
net_eject_cleanup(void)
{
	inet_del_protocol(&net_eject_protocol, IPPROTO_NET_EJECT);
	printk(KERN_INFO "module unloaded\n");
}

module_init(net_eject_init);
module_exit(net_eject_cleanup);

あとはデータファイルを適当な名前で作って以下の内容にしておきます。

eject

ここではpayloadとしておきます。
そして、sendipコマンドで実行します。

sudo sendip -p ipv4 -is 0 -f payload -ip 123 192.168.1.21

すると192.168.1.21のCDドライブが開きます。

inet_add_protocol()はipv4ならnet/ipv4/protocol.cに定義されています。これを見るとプロトコルは最大256要素のハッシュテーブルみたいです。
ファイルを見るとこんな感じになっています。

 31 const struct net_protocol __rcu *inet_protos[MAX_INET_PROTOS] __read_mostly;
 32 const struct net_offload __rcu *inet_offloads[MAX_INET_PROTOS] __read_mostly;
 33 
 34 /*
 35  *      Add a protocol handler to the hash tables
 36  */
 37 
 38 int inet_add_protocol(const struct net_protocol *prot, unsigned char protocol)
 39 {
 40         if (!prot->netns_ok) {
 41                 pr_err("Protocol %u is not namespace aware, cannot register.\n",
 42                         protocol);
 43                 return -EINVAL;
 44         }
 45 
 46         return !cmpxchg((const struct net_protocol **)&inet_protos[protocol],
 47                         NULL, prot) ? 0 : -1;
 48 }

Ejectはプロトコルの番号は123にしました。この数値はどっから見つけたかというとuapi/linux/in.hにあった定義を見て123が空いてたのでそれにしてますw

 25 enum {
 26   IPPROTO_IP = 0,               /* Dummy protocol for TCP               */
 27 #define IPPROTO_IP              IPPROTO_IP
 28   IPPROTO_ICMP = 1,             /* Internet Control Message Protocol    */
 29 #define IPPROTO_ICMP            IPPROTO_ICMP
 30   IPPROTO_IGMP = 2,             /* Internet Group Management Protocol   */
 31 #define IPPROTO_IGMP            IPPROTO_IGMP
 32   IPPROTO_IPIP = 4,             /* IPIP tunnels (older KA9Q tunnels use 94) */
 33 #define IPPROTO_IPIP            IPPROTO_IPIP
 34   IPPROTO_TCP = 6,              /* Transmission Control Protocol        */
 35 #define IPPROTO_TCP             IPPROTO_TCP
 36   IPPROTO_EGP = 8,              /* Exterior Gateway Protocol            */
 37 #define IPPROTO_EGP             IPPROTO_EGP
 38   IPPROTO_PUP = 12,             /* PUP protocol                         */
 39 #define IPPROTO_PUP             IPPROTO_PUP
 40   IPPROTO_UDP = 17,             /* User Datagram Protocol               */
 41 #define IPPROTO_UDP             IPPROTO_UDP
 42   IPPROTO_IDP = 22,             /* XNS IDP protocol                     */
 43 #define IPPROTO_IDP             IPPROTO_IDP
 44   IPPROTO_TP = 29,              /* SO Transport Protocol Class 4        */
 45 #define IPPROTO_TP              IPPROTO_TP
 46   IPPROTO_DCCP = 33,            /* Datagram Congestion Control Protocol */
 47 #define IPPROTO_DCCP            IPPROTO_DCCP
 48   IPPROTO_IPV6 = 41,            /* IPv6-in-IPv4 tunnelling              */
 49 #define IPPROTO_IPV6            IPPROTO_IPV6
 50   IPPROTO_RSVP = 46,            /* RSVP Protocol                        */
 51 #define IPPROTO_RSVP            IPPROTO_RSVP
 52   IPPROTO_GRE = 47,             /* Cisco GRE tunnels (rfc 1701,1702)    */
 53 #define IPPROTO_GRE             IPPROTO_GRE
 54   IPPROTO_ESP = 50,             /* Encapsulation Security Payload protocol */
 55 #define IPPROTO_ESP             IPPROTO_ESP
 56   IPPROTO_AH = 51,              /* Authentication Header protocol       */
 57 #define IPPROTO_AH              IPPROTO_AH
 58   IPPROTO_MTP = 92,             /* Multicast Transport Protocol         */
 59 #define IPPROTO_MTP             IPPROTO_MTP
 60   IPPROTO_BEETPH = 94,          /* IP option pseudo header for BEET     */
 61 #define IPPROTO_BEETPH          IPPROTO_BEETPH
 62   IPPROTO_ENCAP = 98,           /* Encapsulation Header                 */
 63 #define IPPROTO_ENCAP           IPPROTO_ENCAP
 64   IPPROTO_PIM = 103,            /* Protocol Independent Multicast       */
 65 #define IPPROTO_PIM             IPPROTO_PIM
 66   IPPROTO_COMP = 108,           /* Compression Header Protocol          */
 67 #define IPPROTO_COMP            IPPROTO_COMP
 68   IPPROTO_SCTP = 132,           /* Stream Control Transport Protocol    */
 69 #define IPPROTO_SCTP            IPPROTO_SCTP
 70   IPPROTO_UDPLITE = 136,        /* UDP-Lite (RFC 3828)                  */
 71 #define IPPROTO_UDPLITE         IPPROTO_UDPLITE
 72   IPPROTO_RAW = 255,            /* Raw IP packets                       */
 73 #define IPPROTO_RAW             IPPROTO_RAW
 74   IPPROTO_MAX
 75 };

Ejectの実行にcall_usermodehelper()でユーザランドのコマンドを使っていて、カーネル内で完結していないのがスタイリッシュじゃないですね(´・ω・`)
リモートのPCで実行する場合はiptablesの設定も忘れずに。

プロのための Linuxシステム構築・運用技術 (Software Design plus)

プロのための Linuxシステム構築・運用技術 (Software Design plus)