星期二, 1月 17, 2012

ping in Android

上網找了一下,一般對於ping的建議是直接利用java內建的InetAddress.isReachable()來做,實際上在ping內部網路的伺服器時,是沒問題的,但是如果要ping位於外部網路的伺服器時,就會失敗而回傳False。

Android內部的實作是在libcore/luni/src/main/java/java/net/InetAddress.java裡,這裡就很簡單的建立socket,然後試著連到指定位址的port 7,如果可以連,或者是伺服器明確地拒絕,就視為伺服器存在,可以連線。這就解釋了為什麼無法ping位於外部網路的伺服器,因為ISP為了安全或是其他考量,而不允許。我分別以python與java寫了與Android實作相似的程式去實驗,的確都不行。

import sys
import socket

if len(sys.argv)<2:
    print( "Need at least 1 parameters." )
    print( "Usage: {0} host".format( sys.argv[0] ) )
    sys.exit(-1)

r = False
try:
    s = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
    s.settimeout(5)
    s.connect( (sys.argv[1], 7) )
    r = True
except socket.error, ex:
    if ex.errno==111:
        r = True
    else:
        print( ex )

if r:
    print( "{0} is reachable.".format( sys.argv[1] ) )
else:
    print( "{0} is NOT reachable.".format( sys.argv[1] ) )
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.io.IOException;

class Ping {
    public static void main(String[] args) {
        InetAddress in;
                    
        try {
            in = InetAddress.getByName(args[0]);
            boolean result = in.isReachable(5000);
            if (result) {
                System.out.println("Response OK");
            }
            else {
                System.out.println("Response fail");
            }
        } catch (UnknownHostException e) {
            System.out.println(e.getMessage());
        } catch (IOException e) {
            System.out.println(e.getMessage());
        }
    }
}

Java裡只能建立 stream(TCP) 或 dgram(UDP) 的socket,那麼只能用JNI,用C寫ping了,但經過實驗結果,發現會因為權限的關係而無法建立socket,原來要建立raw與IPPROTO_ICMP的socket,需要root權限。一般linux裡,非root使用者可以使用ping,是因為ping加上了setuid權限,才能使用。在Android裡,要不就是建立service,要不就是設法為ping加上setuid,否則是都無法使用的。

沒有留言: