Android Ping (isReachable)소스 분석

 2010.3.9 softgear

 네트워크에서 가장 많이 사용하는 기능 중 하나가 Ping이다. JAVA에서는 InetAddress클래스의 isReachable()함수로 구현되어 있다. 원래 이 함수는 ICMP를 써서 구현되어 있는 것이 었는데, Android에서는 TCP를 사용하는 것으로 변경되었다. 원래 ICMP 패킷을 전송하기 위해 raw소켓을 사용해야 하는데, root 권한이 필요하다. 따라서 보안상 위험한 ICMP 전송/수신 루틴 대신에 TCP를 사용하도록 수정한 것으로 보인다.

 Android SDK에서 isReachble()메소드를 사용하여 PING과 같은 기능을 하려면 다음과 같은 예제로 가능하다. isReachable()내의 인수는 int형으로 ms 단위의 타임아웃시간을 나타낸다.

               try {

                   InetAddress ia = InetAddress.getByName("192.168.123.254");

                   if(ia.isReachable(3000)==true) {

                   tv.setText("Reachable in 3 sec");

                   tv.append("\naaaa");

                   } else {

                   tv.setText("Unreachable in 3 sec");

                   }

               } catch (UnknownHostException e){

                     tv.setText("UnknownHost Exception");

               } catch (IOException e) {

                     tv.setText("IO Exception : " + e.toString());

               } catch (IllegalArgumentException e) {

                     tv.setText("IllegalArgumentExceptio");

               }

 Adroid isReachable() 함수는 다음 소스파일에 구현되어 있다.

(소스 다운로드 방법은 http://source.android.com/download 참고)

dalvik/libcore/luni/src/main/java/java/net/InetAddress.java

 isReachble 메소드의 호출관계는 다음과 같다. 인터페이스가 여러개일 경우, isReachableByMultiThread()를 호출한다.

public boolean isReachble(int timeout)

  public boolean isReachable(NetworkInterface netif, final int ttl, final int timeout)

      ┗ private boolean isReachableByTCP(InetAddress dest, InetAddress source,

            int timeout)

 isReachableByTCP() 함수는 다음과 같이 구현되어 있다.

private boolean isReachableByTCP(InetAddress dest, InetAddress source,

            int timeout) throws IOException {

        FileDescriptor fd = new FileDescriptor();

        // define traffic only for parameter

        int traffic = 0;

        boolean reached = false;

        NETIMPL.createStreamSocket(fd, NetUtil.preferIPv4Stack());

        try {

            if (null != source) {

                NETIMPL.bind(fd, source, 0);

            }

            NETIMPL.connectStreamWithTimeoutSocket(fd, 7, timeout, traffic,

                    dest);

            reached = true;

        } catch (IOException e) {

            if (ERRMSG_CONNECTION_REFUSED.equals(e.getMessage())) {

                // Connection refused means the IP is reachable

                reached = true;

            }

        }

 

        NETIMPL.socketClose(fd);

 

        return reached;

    }

 결국 NETIMPL.connectStreamWithTimeoutSocket() 메소드를 이용하고 있다. 여기에 두번째 인수로 들어간 7 ECHO 포트이다. 이 함수는 다음 java 파일에서 선언하고 있는 것처럼 native (C코드)로 연결된다.

 ./dalvik/libcore/luni/src/main/java/org/apache/harmony/luni/platform/OSNetworkSystem.java

static native void connectStreamWithTimeoutSocketImpl(FileDescriptor aFD,

            int aport, int timeout, int trafficClass, InetAddress inetAddress)

            throws IOException;

 즉, JNI를 통해 c코드로 연결되며, 해당 소스는 다음과 같다.

 ./dalvik/libcore/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp

해당함수는, osNetworkSystem_connectStreamWithTimeoutSocketImpl() 이다

static void osNetworkSystem_connectStreamWithTimeoutSocketImpl(JNIEnv* env,

        jclass clazz, jobject fileDescriptor, jint remotePort, jint timeout,

        jint trafficClass, jobject inetAddr)

 지면상 소스코드는 생략한다. 여기서 핵심 동작은 sockConnectWithTimeout() 함수를 호출하는 것이다. sockConnectWithTimeout()함수는 전통적인 Unix의 소켓 프로그램과 거의 같다. 내부적으로 doConnect()함수를 호출하며, doConnect()함수는 connect() 함수를 호출한다. 특이한 것은 osNetworkSystem_connectStreamWithTimeoutSocketImpl() 함수에, 만약 AdbNetworking 을 사용하도록 되어 있으면 ADB redirect하도록 되어 있다.

 결과적으로, 안드로이드 InetAddress.isReachable() 메소드는 TCP ECHO 포트로 연결을 시도하여, 연결에 성공하거나, Refused를 받았을 경우에는 Reachable로 판단하고, 그외의 Timeout등의 에러에 대해서는 Unreachble로 판단하도록 구현되어 있다.

 문서끝.