iOSでもARPしたい！

#はじめに
iOSでもARPしたい！
そう感じられたことはありませんか？
…と思ってネットでいろいろ見ていたのですが、日本語での情報があまり見られなかったので、少し書いてみることにしました。

自分でパケットを作って、ARPしてみるのもいいのかもしれませんが、今回はFreeBSDの`arp`コマンドのソースコードを用いて、お手軽に行ってみようと思います。

海外の情報だと、Appleが利用しているFreeBSDのソースコードを利用するという感じでした。
コピーライトが2つ載っていて、ソースコードの又貸し状態です。

#`arp`のソースコード
まず、FreeBSDのSVNからソースコードを入手する必要があります。
必要なのは、`arp.c`と`route.h`です。
svn派ではなくgit派なので、httpでダウンロードします。
URLは次の2つになります。
https://svnweb.freebsd.org/base/stable/10/usr.sbin/arp/arp.c?view=co
https://svnweb.freebsd.org/base/stable/10/sys/net/route.h?view=co

#不要な部分の削除
実際の`arp`コマンドはARPテーブルへの追加や削除が行えますが、実機でそれを行ったら、リジェクト直行便な気がしますので、関連する部分を念のために削除します。
また、標準出力ではなく、`struct ether_addr`として取得したいので、出力用の関数なども削ってしまします。
これで、必要なヘッダーファイルを大幅に減らせます。

結局、残す関数は
`static struct sockaddr_in *getaddr(char *host)`
`static int search(u_long addr, action_fn *action)`
の2つになりました。
そして`static`を外します。
また、search関数で`struct ether_addr`のポインタを渡すように宣言と定義を変更して
`struct sockaddr_in *getaddr(char *host)`
`int search(u_long addr, struct ether_addr *hwaddr)`
となります。

#定義の移動
関数を外部から使いたいので、`arp.h`を作成してそちらにいくつかの定義を移します。

```arp.h
#include "TargetConditionals.h"

#include <net/ethernet.h>
#include <net/if_dl.h>
#if TARGET_OS_SIMULATOR
#include <net/route.h>
#else
#include "route.h"
#endif

typedef void (action_fn)(struct sockaddr_dl *sdl,
        struct sockaddr_in *s_in, struct rt_msghdr *rtm);

struct sockaddr_in *getaddr(char *host);
int search(u_long addr, struct ether_addr *hwaddr);
```

実は、route.hはiOS SimulatorのSDKには含まれており、自前で用意するとコンパイルが通らなくなるので、定数で分岐します。
分岐に用いる`TARGET_OS_SIMULATOR`は`TargetConditionals.h`で定義されているので、忘れずに`#include`してあげてください。

#ソースコードの修正
`arp.h`で、先ほどダウンロードした`route.h`を`#include`してますが、iOSには無いヘッダーファイルを参照しているので、修正します。
まず

```route.h
#include <sys/counter.h>
```

を

```route.h
#include <sys/socket.h>
#include <sys/types.h>
```

に変更します。
続いて、中ほどにある

```route.h
#include <net/radix.h>
```

を削除します。不要です。

次に、`arp.c`に戻り、不要な`#include`と定義を削除します。
残す`#include`は

```arp.c
#include <sys/sysctl.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
```

の6つになりました。
必要な定義は`arp.h`に移しましたので、ほかの定義はばっさり全削除です。
スマートです。

また、iOS SimulatorのSDKには`SA_SIZE`の定義がないと怒られるので、FreeBSDの`route.h`を参考にして`arp.c`に追加しておきます。

```arp.c
#ifndef SA_SIZE
#define SA_SIZE(sa) \
    ((!(sa) || ((struct sockaddr *)(sa))->sa_len == 0) ? \
    sizeof(long) : \
    1 + ((((struct sockaddr *)(sa))->sa_len - 1) | (sizeof(long) - 1)))
#endif
```

最後に、関数本体の修正を行います。
まず、エラー出力を取り除きます。
`warnx`関数の呼び出しを削除し、`err`関数の呼び出しを`return 0`などに置換します。
インタフェースの指定などはしないので`search`関数から、

```arp.c
if (rifname && if_indextoname(sdl->sdl_index, ifname) &&
    strcmp(ifname, rifname))
    continue;
```

のコードと`char ifname[IF_NAMESIZE];`の定義を削除します。
また、`search`関数では`struct ether_addr`を返すので、

```arp.c
(*action)(sdl, sin2, rtm);
```

を

```arp.c
memcpy(hwaddr, LLADDR(sdl), sdl->sdl_alen);
```

と修正します。

#使い方
あとは、ARPしたいコードで、関数定義を行った`arp.h`を`#include`して呼び出すだけです。
ついでに、文字列から`in_addr_t`を生成してくれる`inet_addr`関数を定義した`arpa/inet.h`を`#include`すると便利です。

IPアドレスが格納された文字列変数`ip`から、MACアドレスを格納した文字列変数`mac`を取得するコードは次のようになります。

```i_want_to_arp_on_ios.m
struct ether_addr hw_addr;
if (!search(inet_addr([ip cStringUsingEncoding:NSASCIIStringEncoding]), &hw_addr)) {
    return nil;
}
NSMutableString *mac = [[NSMutableString alloc] init];
for (int i = 0; i < sizeof(hw_addr.octet); i++) {
    if (i > 0) {
        [mac appendString:@":"];
    }
    [mac appendFormat:@"%02X", hw_addr.octet[i]];
}
```

`arp.c`には関数`getaddr`も残してあるので、ホスト名を解決してMACアドレスを参照するといったこともできます。

#さいごに
FreeBSDの4条項BSDライセンスに注意して利用してください。
この記事で示したコードはご自由に使っていただいて大丈夫です。ただし、一切の責任を取りかねますのでご了承ください。