序
今回はAndroidで動くLinuxライクな環境であるUserLAndを使ってみます。
出来ることが少なすぎるし、ここ2年ほど更新がなく、個人的には実用に耐えない環境だと思いますが、端末がスマホしかない!というとき、特にBluetoothな携帯キーボードがあったりすると便利なので入れてあるソフトです。今日はこれで何かが動かないときどうして動かないのかを調べてみます。ターゲットはipコマンドになります。
※Android14環境で確認した結果です。
※ssh経由でPCからのCLI操作を前提とするので、あまり端末自体の設定はしません。
インストールからPCからのリモートssh接続まで
- PlayストアからUserLAndをインストールして起動
- Ubuntuを選択
- Minimalを選択
- Terminalを選択→環境構築完了し端末画面が出るのを待つ
- Android設定の端末情報などでIPアドレスを調べる
- UserLAndのFilesystemsタブにあるAppsを長押しし、Editを選択した先にあるパスワードを調べる
- PCなど操作しやすいところから、
ssh -p 2022 userland@IPアドレス
としてEnter - パスワードを聞かれるので調べたパスワードを入力する
これでリモートからAndroidのUserLAndを操作出来ます。
Ubuntu環境を最新までアップデートする
とりあえず環境を最新まで更新します。
sudo apt-get update && sudo apt-get upgrade -y
ipコマンドを入れて現象を再現する
sudo apt install -y iproute2
ip a
sudo ip a
userland@localhost:~$ ip a
Cannot bind netlink socket: Permission denied
userland@localhost:~$ sudo ip a
Cannot bind netlink socket: Permission denied
userland@localhost:~$
現象が再現出来ました。
現象の原因を調査する
ソースコードを取得してビルドする
今回参考にしたのはココ
ソースコード取得やビルドや編集に必要なコマンドを入れておく
vimのインストール
設定ファイルとか見たいのでまずインストール
userland@localhost:~$ sudo apt install -y vim
iproute2のリポジトリ確認
userland@localhost:~$ apt info iproute2
Package: iproute2
Version: 5.15.0-1ubuntu2
Priority: important
Section: net
Origin: Ubuntu
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Original-Maintainer: Alexander Wirt <formorer@debian.org>
Bugs: https://bugs.launchpad.net/ubuntu/+filebug
Installed-Size: 2843 kB
Provides: arpd
Depends: debconf (>= 0.5) | debconf-2.0, libbpf0 (>= 1:0.2), libbsd0 (>= 0.0), libc6 (>= 2.34), libcap2 (>= 1:2.10), libdb5.3, libelf1 (>= 0.131), libmnl0 (>= 1.0.3-4~), libselinux1 (>= 3.1~), libxtables12 (>= 1.6.0+snapshot20161117), libcap2-bin
Recommends: libatm1 (>= 2.4.1-17~)
Suggests: iproute2-doc
Conflicts: arpd, iproute (<< 20130000-1)
Replaces: iproute
Homepage: https://wiki.linuxfoundation.org/networking/iproute2
Task: minimal, server-minimal
Download-Size: 1080 kB
APT-Manual-Installed: yes
APT-Sources: http://ports.ubuntu.com/ubuntu-ports jammy/main arm64 Packages
Description: networking and traffic control tools
userland@localhost:~$
APT-Sources: http://ports.ubuntu.com/ubuntu-ports jammy/main arm64 Packages
という行が知りたかったところです。
vimを使ってaptのsource-listを更新
--- sources.list 2023-01-26 02:34:23.000000000 +0000
+++ sources2.list 2024-08-20 00:31:19.149730615 +0000
@@ -1,7 +1,7 @@
# See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to
# newer versions of the distribution.
deb http://ports.ubuntu.com/ubuntu-ports/ jammy main restricted
-# deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy main restricted
+deb-src http://ports.ubuntu.com/ubuntu-ports/ jammy main restricted
## Major bug fix updates produced after the final release of the
## distribution.
ビルドに必要なパッケージをインストール
sudo apt update
mkdir BUILD
cd BUILD
-
sudo apt build-dep iproute2
# これはiproute2の固有のビルドに必要なパッケージ sudo apt install build-essential devscripts
iproute2のソースを取得してビルド
apt source iproute2
cd iproute2-5.15.0/
debuild -us -uc -b
-
find . -type f -name 'ip'
でビルドしたipコマンドを見つける -
./ip/ip a
で現象の再現確認
これで原因調査環境が整いました。
原因調査
エラーメッセージを検索してみる
ソースコードがあるので、メッセージを検索してみました。
find . -type f -name '*.c' -o -name '*.h' | xargs grep 'Cannot bind netlink socket'
userland@localhost:~/BUILD/iproute2-5.15.0$ find . -type f -name '*.c' -o -name '*.h' | xargs grep 'Cannot bind netlink socket'
./lib/libnetlink.c: perror("Cannot bind netlink socket");
いきなり見つかりました。当該ソースはこんな感じです。
int rtnl_open_byproto(struct rtnl_handle *rth, unsigned int subscriptions,
int protocol)
{
socklen_t addr_len;
int sndbuf = 32768;
int one = 1;
memset(rth, 0, sizeof(*rth));
rth->proto = protocol;
rth->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
if (rth->fd < 0) {
perror("Cannot open netlink socket");
return -1;
}
if (setsockopt(rth->fd, SOL_SOCKET, SO_SNDBUF,
&sndbuf, sizeof(sndbuf)) < 0) {
perror("SO_SNDBUF");
return -1;
}
if (setsockopt(rth->fd, SOL_SOCKET, SO_RCVBUF,
&rcvbuf, sizeof(rcvbuf)) < 0) {
perror("SO_RCVBUF");
return -1;
}
/* Older kernels may no support extended ACK reporting */
setsockopt(rth->fd, SOL_NETLINK, NETLINK_EXT_ACK,
&one, sizeof(one));
memset(&rth->local, 0, sizeof(rth->local));
rth->local.nl_family = AF_NETLINK;
rth->local.nl_groups = subscriptions;
if (bind(rth->fd, (struct sockaddr *)&rth->local,
sizeof(rth->local)) < 0) {
perror("Cannot bind netlink socket");
return -1;
}
addr_len = sizeof(rth->local);
if (getsockname(rth->fd, (struct sockaddr *)&rth->local,
&addr_len) < 0) {
perror("Cannot getsockname");
return -1;
}
if (addr_len != sizeof(rth->local)) {
fprintf(stderr, "Wrong address length %d\n", addr_len);
return -1;
}
if (rth->local.nl_family != AF_NETLINK) {
fprintf(stderr, "Wrong address family %d\n",
rth->local.nl_family);
return -1;
}
rth->seq = time(NULL);
return 0;
}
本当にbindシステムコールで失敗していたことが分かります。IPアドレスを調べるのになぜ?という疑問が湧きますが、調べたところ、これはAF_NETLINKが示すとおり、NETLINK_ROUTE ソケットを通してカーネルのルーティングテーブルを読んだり変更したりするためのもので、実はIPコマンドのIPアドレス取得の根幹をなすLinuxの機能でした。
つまりここだけスキップして通しても結局IPアドレスは取得できないということです。
なぜbindに失敗しているのか?
The following is a list of the ways that apps are affected by this change:
- NetworkInterface.getHardwareAddress() returns null for every interface.
- Apps cannot use the bind() function on NETLINK_ROUTE sockets.
- The ip command does not return information about interfaces.
- Apps cannot send RTM_GETLINK messages.
Android11(API30)、約3年前の2021年頃からこうなってるそうです。正直もう少しやり方があったんじゃないかと思います。UserLAndのC/C++からは現状回避策すらない(termuxはある)ので、対応不可能です(補足)。
まとめ
- 自分のIPを知るという非常に簡単なことがAndroidのUserLAndでは不可能
補足
C/C++からはgetifaddrs()
が使えるように読めるのですが、、、
#include <iostream>
#include <sys/types.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <linux/if_link.h>
using namespace std;
int main() {
struct ifaddrs *ifaddr, *ifa;
int family, s, n;
char host[NI_MAXHOST];
if (getifaddrs(&ifaddr) == -1) {
perror("getifaddrs");
exit(EXIT_FAILURE);
}
for (ifa = ifaddr, n = 0; ifa != NULL; ifa = ifa->ifa_next, n++) {
if (ifa->ifa_addr == NULL)
continue;
family = ifa->ifa_addr->sa_family;
printf("%-8s %s (%d)\n",
ifa->ifa_name,
(family == AF_PACKET) ? "AF_PACKET" :
(family == AF_INET) ? "AF_INET" :
(family == AF_INET6) ? "AF_INET6" : "???",
family);
if (family == AF_INET || family == AF_INET6) {
s = getnameinfo(ifa->ifa_addr,
(family == AF_INET) ? sizeof(struct sockaddr_in) :
sizeof(struct sockaddr_in6),
host, NI_MAXHOST,
NULL, 0, NI_NUMERICHOST);
if (s != 0) {
printf("getnameinfo() failed: %s\n", gai_strerror(s));
exit(EXIT_FAILURE);
}
printf("\t\taddress: <%s>\n", host);
} else if (family == AF_PACKET && ifa->ifa_data != NULL) {
struct rtnl_link_stats *stats = reinterpret_cast<rtnl_link_stats*>(ifa->ifa_data);
printf("\t\ttx_packets = %10u; rx_packets = %10u\n"
"\t\ttx_bytes = %10u; rx_bytes = %10u\n",
stats->tx_packets, stats->rx_packets,
stats->tx_bytes, stats->rx_bytes);
}
}
freeifaddrs(ifaddr);
exit(EXIT_SUCCESS);
}
userland@localhost:~$ make hoge
g++ hoge.cpp -o hoge
userland@localhost:~$ ./hoge
getifaddrs: Permission denied
userland@localhost:~$
のように失敗する。
ただし、termuxでは以下のように成功した。
~ $ ./hoge
wlan0 AF_INET6 (10)
address: <xxxxx>
wlan0 AF_INET6 (10)
address: <xxxxx>
wlan0 AF_INET6 (10)
address: <xxxxx>
ccmni0 AF_INET6 (10)
address: <xxxxx>
ccmni0 AF_INET6 (10)
address: <xxxxx>
dummy0 AF_INET6 (10)
address: <xxxxx>
lo AF_INET6 (10)
address: <xxxxx>
wlan0 AF_INET (2)
address: <xxxxx>
lo AF_INET (2)
address: <xxxxx>
~ $
原因はUserLAndがUbuntuが提供するC標準ライブラリを使用するのに対して、termuxはAndroidのC標準ライブラリを使用するから、なようです。termuxの方が良くも悪くもnativeな動きになるので、動くと…。