TCP/IP モデルのうちトランスポート層ではなく、インターネット層およびネットワークインターフェイス層のパケット (正確には PDU) を扱う低レイヤープログラミングの雑多なテクニックをまとめます。『ルーター自作でわかるパケットの流れ』などを参考にしています。バックアップ目的で書籍のサンプルコードをホスティングしました。
/usr/include/net/ethernet.h
/* Ethernet protocol ID's */
#define ETHERTYPE_PUP 0x0200 /* Xerox PUP */
#define ETHERTYPE_SPRITE 0x0500 /* Sprite */
#define ETHERTYPE_IP 0x0800 /* IP */
#define ETHERTYPE_ARP 0x0806 /* Address resolution */
#define ETHERTYPE_REVARP 0x8035 /* Reverse ARP */
#define ETHERTYPE_AT 0x809B /* AppleTalk protocol */
#define ETHERTYPE_AARP 0x80F3 /* AppleTalk ARP */
#define ETHERTYPE_VLAN 0x8100 /* IEEE 802.1Q VLAN tagging */
#define ETHERTYPE_IPX 0x8137 /* IPX */
#define ETHERTYPE_IPV6 0x86dd /* IP protocol version 6 */
#define ETHERTYPE_LOOPBACK 0x9000 /* used to test interfaces */
/usr/include/net/ethernet.h
struct ether_header
{
u_int8_t ether_dhost[ETH_ALEN]; /* destination eth addr */
u_int8_t ether_shost[ETH_ALEN]; /* source ether addr */
u_int16_t ether_type; /* packet type ID field */
} __attribute__ ((__packed__));
/usr/include/net/if_arp.h
/* ARP protocol opcodes. */
#define ARPOP_REQUEST 1 /* ARP request. */
#define ARPOP_REPLY 2 /* ARP reply. */
#define ARPOP_RREQUEST 3 /* RARP request. */
#define ARPOP_RREPLY 4 /* RARP reply. */
#define ARPOP_InREQUEST 8 /* InARP request. */
#define ARPOP_InREPLY 9 /* InARP reply. */
#define ARPOP_NAK 10 /* (ATM)ARP NAK. */
本 ARP パケットが下位層でどのようにカプセル化されるのか。イーサネットであれば ARPHRD_ETHER
となります。ちなみに、ARP パケットのプロトコルタイプ部には、本 ARP パケットがカプセル化している上位層のプロトコルの識別子が記載されます。先程紹介した /usr/include/net/ethernet.h の ETHERTYPE_IP
などが該当します。
/usr/include/net/if_arp.h
/* ARP protocol HARDWARE identifiers. */
#define ARPHRD_NETROM 0 /* From KA9Q: NET/ROM pseudo. */
#define ARPHRD_ETHER 1 /* Ethernet 10/100Mbps. */
#define ARPHRD_EETHER 2 /* Experimental Ethernet. */
#define ARPHRD_AX25 3 /* AX.25 Level 2. */
#define ARPHRD_PRONET 4 /* PROnet token ring. */
#define ARPHRD_CHAOS 5 /* Chaosnet. */
#define ARPHRD_IEEE802 6 /* IEEE 802.2 Ethernet/TR/TB. */
#define ARPHRD_ARCNET 7 /* ARCnet. */
#define ARPHRD_APPLETLK 8 /* APPLEtalk. */
#define ARPHRD_DLCI 15 /* Frame Relay DLCI. */
#define ARPHRD_ATM 19 /* ATM. */
#define ARPHRD_METRICOM 23 /* Metricom STRIP (new IANA id). */
#define ARPHRD_IEEE1394 24 /* IEEE 1394 IPv4 - RFC 2734. */
#define ARPHRD_EUI64 27 /* EUI-64. */
#define ARPHRD_INFINIBAND 32 /* InfiniBand. */
上位層および下位層のプロトコル次第で ARP パケット全体のサイズが変化するため、不変部分と可変部分に分けて定義されています。#if 0
の部分はスキップされるため定義されていないことに注意してください。
/usr/include/net/if_arp.h
/* See RFC 826 for protocol description. ARP packets are variable
in size; the arphdr structure defines the fixed-length portion.
Protocol type values are the same as those for 10 Mb/s Ethernet.
It is followed by the variable-sized fields ar_sha, arp_spa,
arp_tha and arp_tpa in that order, according to the lengths
specified. Field names used correspond to RFC 826. */
struct arphdr
{
unsigned short int ar_hrd; /* Format of hardware address. */
unsigned short int ar_pro; /* Format of protocol address. */
unsigned char ar_hln; /* Length of hardware address. */
unsigned char ar_pln; /* Length of protocol address. */
unsigned short int ar_op; /* ARP opcode (command). */
#if 0
/* Ethernet looks like this : This bit is variable sized
however... */
unsigned char __ar_sha[ETH_ALEN]; /* Sender hardware address. */
unsigned char __ar_sip[4]; /* Sender IP address. */
unsigned char __ar_tha[ETH_ALEN]; /* Target hardware address. */
unsigned char __ar_tip[4]; /* Target IP address. */
#endif
};
/usr/include/netinet/if_ether.h
/*
* Ethernet Address Resolution Protocol.
*
* See RFC 826 for protocol description. Structure below is adapted
* to resolving internet addresses. Field names used correspond to
* RFC 826.
*/
struct ether_arp {
struct arphdr ea_hdr; /* fixed-size header */
u_int8_t arp_sha[ETH_ALEN]; /* sender hardware address */
u_int8_t arp_spa[4]; /* sender protocol address */
u_int8_t arp_tha[ETH_ALEN]; /* target hardware address */
u_int8_t arp_tpa[4]; /* target protocol address */
};
#define arp_hrd ea_hdr.ar_hrd
#define arp_pro ea_hdr.ar_pro
#define arp_hln ea_hdr.ar_hln
#define arp_pln ea_hdr.ar_pln
#define arp_op ea_hdr.ar_op
/usr/include/netinet/ip.h
/*
* Definitions for Explicit Congestion Notification (ECN)
*
* Taken from RFC-3168, Section 5.
*/
#define IPTOS_ECN_MASK 0x03
#define IPTOS_ECN(x) ((x) & IPTOS_ECN_MASK)
#define IPTOS_ECN_NOT_ECT 0x00
#define IPTOS_ECN_ECT1 0x01
#define IPTOS_ECN_ECT0 0x02
#define IPTOS_ECN_CE 0x03
/*
* Definitions for IP differentiated services code points (DSCP)
*
* Taken from RFC-2597, Section 6 and RFC-2598, Section 2.3.
*/
#define IPTOS_DSCP_MASK 0xfc
#define IPTOS_DSCP(x) ((x) & IPTOS_DSCP_MASK)
#define IPTOS_DSCP_AF11 0x28
#define IPTOS_DSCP_AF12 0x30
#define IPTOS_DSCP_AF13 0x38
#define IPTOS_DSCP_AF21 0x48
#define IPTOS_DSCP_AF22 0x50
#define IPTOS_DSCP_AF23 0x58
#define IPTOS_DSCP_AF31 0x68
#define IPTOS_DSCP_AF32 0x70
#define IPTOS_DSCP_AF33 0x78
#define IPTOS_DSCP_AF41 0x88
#define IPTOS_DSCP_AF42 0x90
#define IPTOS_DSCP_AF43 0x98
#define IPTOS_DSCP_EF 0xb8
これは同ファイルで定義されているサービス種別部に代わるものです。
/*
* Definitions for IP type of service (ip_tos) [deprecated; use DSCP
* and CS definitions above instead.]
*/
#define IPTOS_TOS_MASK 0x1E
#define IPTOS_TOS(tos) ((tos) & IPTOS_TOS_MASK)
#define IPTOS_LOWDELAY 0x10
#define IPTOS_THROUGHPUT 0x08
#define IPTOS_RELIABILITY 0x04
#define IPTOS_LOWCOST 0x02
#define IPTOS_MINCOST IPTOS_LOWCOST
/usr/include/netinet/ip.h
#define IP_RF 0x8000 /* reserved fragment flag */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* more fragments flag */
/usr/include/netinet/ip.h
#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
/usr/include/netinet/ip.h
#define MAXTTL 255 /* maximum time to live (seconds) */
#define IPDEFTTL 64 /* default ttl, from RFC 1340 */
IP ヘッダとは直接関係ありませんが /usr/include/netinet/ip.h には MSS の最大値も定義されています。
#define IP_MSS 576 /* default maximum segment size */
/usr/include/netinet/in.h
(ip.h ではなく in.h であることに注意)
enum
{
IPPROTO_IP = 0, /* Dummy protocol for TCP. */
IPPROTO_HOPOPTS = 0, /* IPv6 Hop-by-Hop options. */
IPPROTO_ICMP = 1, /* Internet Control Message Protocol. */
IPPROTO_IGMP = 2, /* Internet Group Management Protocol. */
IPPROTO_IPIP = 4, /* IPIP tunnels (older KA9Q tunnels use 94). */
IPPROTO_TCP = 6, /* Transmission Control Protocol. */
IPPROTO_EGP = 8, /* Exterior Gateway Protocol. */
IPPROTO_PUP = 12, /* PUP protocol. */
IPPROTO_UDP = 17, /* User Datagram Protocol. */
IPPROTO_IDP = 22, /* XNS IDP protocol. */
IPPROTO_TP = 29, /* SO Transport Protocol Class 4. */
IPPROTO_DCCP = 33, /* Datagram Congestion Control Protocol. */
IPPROTO_IPV6 = 41, /* IPv6 header. */
IPPROTO_ROUTING = 43, /* IPv6 routing header. */
IPPROTO_FRAGMENT = 44, /* IPv6 fragmentation header. */
IPPROTO_RSVP = 46, /* Reservation Protocol. */
IPPROTO_GRE = 47, /* General Routing Encapsulation. */
IPPROTO_ESP = 50, /* encapsulating security payload. */
IPPROTO_AH = 51, /* authentication header. */
IPPROTO_ICMPV6 = 58, /* ICMPv6. */
IPPROTO_NONE = 59, /* IPv6 no next header. */
IPPROTO_DSTOPTS = 60, /* IPv6 destination options. */
IPPROTO_MTP = 92, /* Multicast Transport Protocol. */
IPPROTO_ENCAP = 98, /* Encapsulation Header. */
IPPROTO_PIM = 103, /* Protocol Independent Multicast. */
IPPROTO_COMP = 108, /* Compression Header Protocol. */
IPPROTO_SCTP = 132, /* Stream Control Transmission Protocol. */
IPPROTO_UDPLITE = 136, /* UDP-Lite protocol. */
IPPROTO_RAW = 255, /* Raw IP packets. */
IPPROTO_MAX
};
/usr/include/netinet/ip.h
struct iphdr
{
#if __BYTE_ORDER == _LITTLE_ENDIAN
unsigned int ihl:4;
unsigned int version:4;
#elif __BYTE_ORDER == __BIG_ENDIAN
unsigned int version:4;
unsigned int ihl:4;
#else
# error "Please fix <bits/endian.h>"
#endif
u_int8_t tos;
u_int16_t tot_len;
u_int16_t id;
u_int16_t frag_off;
u_int8_t ttl;
u_int8_t protocol;
u_int16_t check;
u_int32_t saddr;
u_int32_t daddr;
/*The options start here. */
};
/usr/include/netinet/ip_icmp.h
#define ICMP_ECHOREPLY 0 /* Echo Reply */
#define ICMP_DEST_UNREACH 3 /* Destination Unreachable */
#define ICMP_SOURCE_QUENCH 4 /* Source Quench */
#define ICMP_REDIRECT 5 /* Redirect (change route) */
#define ICMP_ECHO 8 /* Echo Request */
#define ICMP_TIME_EXCEEDED 11 /* Time Exceeded */
#define ICMP_PARAMETERPROB 12 /* Parameter Problem */
#define ICMP_TIMESTAMP 13 /* Timestamp Request */
#define ICMP_TIMESTAMPREPLY 14 /* Timestamp Reply */
#define ICMP_INFO_REQUEST 15 /* Information Request */
#define ICMP_INFO_REPLY 16 /* Information Reply */
#define ICMP_ADDRESS 17 /* Address Mask Request */
#define ICMP_ADDRESSREPLY 18 /* Address Mask Reply */
#define NR_ICMP_TYPES 18
/usr/include/netinet/ip_icmp.h
/* Codes for UNREACH. */
#define ICMP_NET_UNREACH 0 /* Network Unreachable */
#define ICMP_HOST_UNREACH 1 /* Host Unreachable */
#define ICMP_PROT_UNREACH 2 /* Protocol Unreachable */
#define ICMP_PORT_UNREACH 3 /* Port Unreachable */
#define ICMP_FRAG_NEEDED 4 /* Fragmentation Needed/DF set */
#define ICMP_SR_FAILED 5 /* Source Route failed */
#define ICMP_NET_UNKNOWN 6
#define ICMP_HOST_UNKNOWN 7
#define ICMP_HOST_ISOLATED 8
#define ICMP_NET_ANO 9
#define ICMP_HOST_ANO 10
#define ICMP_NET_UNR_TOS 11
#define ICMP_HOST_UNR_TOS 12
#define ICMP_PKT_FILTERED 13 /* Packet filtered */
#define ICMP_PREC_VIOLATION 14 /* Precedence violation */
#define ICMP_PREC_CUTOFF 15 /* Precedence cut off */
#define NR_ICMP_UNREACH 15 /* instead of hardcoding immediate value */
/* Codes for REDIRECT. */
#define ICMP_REDIR_NET 0 /* Redirect Net */
#define ICMP_REDIR_HOST 1 /* Redirect Host */
#define ICMP_REDIR_NETTOS 2 /* Redirect Net for TOS */
#define ICMP_REDIR_HOSTTOS 3 /* Redirect Host for TOS */
/* Codes for TIME_EXCEEDED. */
#define ICMP_EXC_TTL 0 /* TTL count exceeded */
#define ICMP_EXC_FRAGTIME 1 /* Fragment Reass time exceeded */
/usr/include/netinet/ip_icmp.h
struct icmphdr
{
u_int8_t type; /* message type */
u_int8_t code; /* type sub-code */
u_int16_t checksum;
...
};
または
struct icmp
{
u_int8_t icmp_type; /* type of message, see below */
u_int8_t icmp_code; /* type sub code */
u_int16_t icmp_cksum; /* ones complement checksum of struct */
...
};
サンプルコードを理解するための雑学です。
#include <stdio.h>
int main(int argc, char *argv[], char *envp[]) {
int i;
for(i = 0; envp[i] != NULL; ++i) {
printf("%d: [%s]\n", i, envp[i]);
}
return(0);
}
などとすると以下のように出力されます。
0: [HOSTNAME=vagrant]
1: [TERM=xterm]
2: [SHELL=/bin/bash]
3: [HISTSIZE=1000]
4: [SSH_CLIENT=10.0.2.2 8153 22]
5: [SSH_TTY=/dev/pts/0]
6: [S_TIME_DEF_TIME=UTC]
7: [USER=vagrant]
...
標準インクルードパスには /usr/include が含まれています。そのため
/usr/include/net/ethernet.h
は以下のようにして読み込めます。
#include <net/ethernet.h>
#include <linux/if.h>
とすると以下の内容が定義されます。
/*
* Interface request structure used for socket
* ioctl's. All interface ioctl's must have parameter
* definitions which begin with ifr_name. The
* remainder may be interface specific.
*/
struct ifreq
{
#define IFHWADDRLEN 6
union
{
char ifrn_name[IFNAMSIZ]; /* if name, e.g. "en0" */
} ifr_ifrn;
union {
struct sockaddr ifru_addr;
struct sockaddr ifru_dstaddr;
struct sockaddr ifru_broadaddr;
struct sockaddr ifru_netmask;
struct sockaddr ifru_hwaddr;
short ifru_flags;
int ifru_ivalue;
int ifru_mtu;
struct ifmap ifru_map;
char ifru_slave[IFNAMSIZ]; /* Just fits the size */
char ifru_newname[IFNAMSIZ];
void * ifru_data;
struct if_settings ifru_settings;
} ifr_ifru;
};
#define ifr_name ifr_ifrn.ifrn_name /* interface name */
#define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */
#define ifr_addr ifr_ifru.ifru_addr /* address */
#define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */
#define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */
#define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */
#define ifr_flags ifr_ifru.ifru_flags /* flags */
#define ifr_metric ifr_ifru.ifru_ivalue /* metric */
#define ifr_mtu ifr_ifru.ifru_mtu /* mtu */
#define ifr_map ifr_ifru.ifru_map /* device map */
#define ifr_slave ifr_ifru.ifru_slave /* slave device */
#define ifr_data ifr_ifru.ifru_data /* for use by interface */
#define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */
#define ifr_bandwidth ifr_ifru.ifru_ivalue /* link bandwidth */
#define ifr_qlen ifr_ifru.ifru_ivalue /* Queue length */
#define ifr_newname ifr_ifru.ifru_newname /* New name */
#define ifr_settings ifr_ifru.ifru_settings /* Device/proto settings*/
#include <netpacket/packet.h>
とすると以下の内容が定義されます。
struct sockaddr_ll
{
unsigned short int sll_family;
unsigned short int sll_protocol;
int sll_ifindex;
unsigned short int sll_hatype;
unsigned char sll_pkttype;
unsigned char sll_halen;
unsigned char sll_addr[8];
};
#include <sys/socket.h>
とすると #include <bits/socket.h>
が間接的に実行されて以下の内容が定義されます。
/* Protocol families. */
#define PF_INET 2 /* IP protocol family. */
#define PF_INET6 10 /* IP version 6. */
#define PF_PACKET 17 /* Packet family. */
/* Types of sockets. */
SOCK_STREAM = 1, /* Sequenced, reliable, connection-based
byte streams. */
SOCK_DGRAM = 2, /* Connectionless, unreliable datagrams
of fixed maximum length. */
SOCK_RAW = 3, /* Raw protocol interface. */
#include <net/ethernet.h>
とすると #include <linux/if_ether.h>
が間接的に実行されて以下の内容が定義されます。
#define ETH_P_IP 0x0800 /* Internet Protocol packet */
#define ETH_P_ALL 0x0003 /* Every packet (be careful!!!) */
#include <sys/ioctl.h>
とすると #include <bits/ioctls.h>
が間接的に実行されて以下の内容が定義されます。
/* Socket configuration controls. */
#define SIOCGIFFLAGS 0x8913 /* get flags */
#define SIOCSIFFLAGS 0x8914 /* set flags */
多バイトデータをやりとりする際にはバイトオーダが問題になります。例えばビッグエンディアンのマシンとリトルエンディアンのマシンはそのまま多バイトデータを扱う通信ができません。そこでネットワーク通信のパケットに含めるデータはすべてビッグエンディアンに変換して送信するという決まりがあります。この場合のビッグエンディアンを特にネットワークバイトオーダとよびます。変換前のマシンのエンディアンをホストバイトオーダとよびます。#include <arpa/inet.h>
には以下の変換用関数が定義されています。
例えば ETH_P_IP
や ETH_P_ALL
は 2 バイトなので htons
および ntohs
を使用します。
#include <sys/ioctl.h>
とすると間接的に #include <bits/ioctls.h>
が実行されて以下の内容が定義されます。
/* Socket configuration controls. */
#define SIOCGIFNAME 0x8910 /* get iface name */
#define SIOCSIFLINK 0x8911 /* set iface channel */
#define SIOCGIFCONF 0x8912 /* get iface list */
#define SIOCGIFFLAGS 0x8913 /* get flags */
#define SIOCSIFFLAGS 0x8914 /* set flags */
#define SIOCGIFADDR 0x8915 /* get PA address */
#define SIOCSIFADDR 0x8916 /* set PA address */
#define SIOCGIFDSTADDR 0x8917 /* get remote PA address */
#define SIOCSIFDSTADDR 0x8918 /* set remote PA address */
#define SIOCGIFBRDADDR 0x8919 /* get broadcast PA address */
#define SIOCSIFBRDADDR 0x891a /* set broadcast PA address */
#define SIOCGIFNETMASK 0x891b /* get network PA mask */
#define SIOCSIFNETMASK 0x891c /* set network PA mask */
#define SIOCGIFMETRIC 0x891d /* get metric */
#define SIOCSIFMETRIC 0x891e /* set metric */
#define SIOCGIFMEM 0x891f /* get memory address (BSD) */
#define SIOCSIFMEM 0x8920 /* set memory address (BSD) */
#define SIOCGIFMTU 0x8921 /* get MTU size */
#define SIOCSIFMTU 0x8922 /* set MTU size */
#define SIOCSIFNAME 0x8923 /* set interface name */
#define SIOCSIFHWADDR 0x8924 /* set hardware address */
#define SIOCGIFENCAP 0x8925 /* get/set encapsulations */
#define SIOCSIFENCAP 0x8926
#define SIOCGIFHWADDR 0x8927 /* Get hardware address */
#define SIOCGIFSLAVE 0x8929 /* Driver slaving support */
#define SIOCSIFSLAVE 0x8930
#define SIOCADDMULTI 0x8931 /* Multicast address lists */
#define SIOCDELMULTI 0x8932
#define SIOCGIFINDEX 0x8933 /* name -> if_index mapping */
#define SIOGIFINDEX SIOCGIFINDEX /* misprint compatibility :-) */
#define SIOCSIFPFLAGS 0x8934 /* set/get extended flags set */
#define SIOCGIFPFLAGS 0x8935
#define SIOCDIFADDR 0x8936 /* delete PA address */
#define SIOCSIFHWBROADCAST 0x8937 /* set hardware broadcast addr */
#define SIOCGIFCOUNT 0x8938 /* get number of devices */
これらは #include <sys/ioctl.h>
で定義されるデバイスを操作する関数 ioctl の第二引数として使用されます。
という意味があります。デバイスの情報を取得する場合は Get で設定する場合は Set を使用します。例えば
int soc;
soc = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
で取得した soc および
struct ifreq ifreq;
として定義した結果格納用の変数を用いて ioctl は以下のように使用します。
ioctl(soc, SIOCGIFINDEX, &ifreq)
#include <sys/socket.h>
で定義される bind 関数はソケットにデバイスを割り当てるために使用します。ちなみに TCP および UDP のソケットに対して bind を使用すると IP やポートを割り当てることができます。
int soc;
soc = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
struct sockaddr_ll sa;
sa.sll_family = PF_PACKET; // プロトコルファミリー
sa.sll_protocol = htons(ETH_P_ALL); // プロトコル
sa.sll_ifindex = ifreq.ifr_ifindex; // インターフェースのインデックス
bind(soc, (struct sockaddr *)&sa, sizeof(sa))
#include <linux/if.h>
で以下の内容が定義されます。
/* Standard interface flags (netdevice->flags). */
#define IFF_UP 0x1 /* interface is up */
#define IFF_BROADCAST 0x2 /* broadcast address valid */
#define IFF_DEBUG 0x4 /* turn on debugging */
#define IFF_LOOPBACK 0x8 /* is a loopback net */
#define IFF_POINTOPOINT 0x10 /* interface is has p-p link */
#define IFF_NOTRAILERS 0x20 /* avoid use of trailers */
#define IFF_RUNNING 0x40 /* interface RFC2863 OPER_UP */
#define IFF_NOARP 0x80 /* no ARP protocol */
#define IFF_PROMISC 0x100 /* receive all packets */
#define IFF_ALLMULTI 0x200 /* receive all multicast packets*/
ioctl を利用してビットフラグを操作するために利用します。
ioctl(soc, SIOCGIFFLAGS, &ifreq);
ifreq.ifr_flags = ifreq.ifr_flags|IFF_PROMISC;
ioctl(soc, SIOCSIFFLAGS, &ifreq);