ルート情報を取得するクラス(JNI)

折角ルート情報を取得するクラスを作ったので、これをJavaのクラスとしても利用できるようにJNIで実装してみました。
いつもの様に
RotueInfo.java

package net;

import java.util.Vector;

public class RouteInfo {

    private int             index = 0;
    private String          dest = null;
    private String          mask = null;
    private String          gateway = null;
    private int             type = 0;
    private int             metric = 0;
    private boolean         defaultGw = false;
    private boolean         primaryGw = false;

    static {
        System.loadLibrary("jninet");
        init();
    }

    public RouteInfo() {
    }

    public String getDest() {
        return dest;
    }

    public String getMask() {
        return mask;
    }

    public String getGateway() {
        return gateway;
    }

    public int getIndex() {
        return index;
    }

    public int getType() {
        return type;
    }

    public int getMetric() {
        return metric;
    }

    public boolean isDefaultGateway() {
        return defaultGw;
    }

    public boolean isPrimaryGateway() {
        return primaryGw;
    }

    public boolean isSameRouteInfo(String dest, String mask, String gateway) {
        if ((dest == null) || (!dest.equals(this.dest)))
            return false;
        if ((mask == null) || (!mask.equals(this.mask)))
            return false;
        if ((gateway == null) || (!gateway.equals(this.gateway)))
            return false;
        return true;
    }

    public static RouteInfo getBestRoute(String dest, String mask, Vector<RouteInfo> table) {
        RouteInfo[] routeTable = null;
        if ((table != null) && (table.size() > 0)) {
            routeTable = new RouteInfo[table.size()];
            for (int i = 0; i < table.size(); i++) {
                routeTable[i] = table.get(i);
            }
        }
        return getBestRoute(dest, mask, routeTable);
    }

    private static native void init();
    public static native RouteInfo[] getRouteList();
    public static native RouteInfo getBestRoute(String dest, String mask, RouteInfo[] table);
}

を作ってコンパイルした後で、javahを使ってヘッダファイル作成します。


それから、JNIの実装として
jnirouteinfo.h

#ifndef _JNI_ROUTEINFO_CALL
#define _JNI_ROUTEINFO_CALL

#include <windows.h>

#include "routeinfo.h"
using namespace penguin;

#include "net_RouteInfo.h"

jclass      ri_class;           /* RouterInfo */
jmethodID   ri_ctor;            /* RouterInfo() */
jfieldID    ri_indexID;         /* RouterInfo.index */
jfieldID    ri_destID;          /* RouterInfo.dest */
jfieldID    ri_maskID;          /* RouterInfo.mask */
jfieldID    ri_gatewayID;       /* RouterInfo.gateway */
jfieldID    ri_typeID;          /* RouterInfo.type */
jfieldID    ri_metricID;        /* RouterInfo.metric */
jfieldID    ri_defaultGwID;     /* RouterInfo.defaultGw */
jfieldID    ri_primaryGwID;     /* RouterInfo.primaryGw */

#endif  //  _JNI_ROUTEINFO_CALL


jnirouteinfo.cpp

#include "jnirouteinfo.h"
#include "jninet.h"

jobject load_class(JNIEnv *env, RouteInfo info) {
    jobject netifObj = env->NewObject(ri_class, ri_ctor);

    env->SetIntField(netifObj, ri_indexID, info.getIndex());
    env->SetObjectField(netifObj, ri_destID, stringToJString(env, info.getDest()));
    env->SetObjectField(netifObj, ri_maskID, stringToJString(env, info.getMask()));
    env->SetObjectField(netifObj, ri_gatewayID, stringToJString(env, info.getGateway()));
    env->SetIntField(netifObj, ri_typeID, info.getType());
    env->SetIntField(netifObj, ri_metricID, info.getMetric());
    env->SetBooleanField(netifObj, ri_defaultGwID, info.isDefaultGateway());
    env->SetBooleanField(netifObj, ri_primaryGwID, info.isPrimaryGateway());

    return netifObj;
}

RouteInfo load_value(JNIEnv *env, jobject rt_info) {

    unsigned long   index = env->GetIntField(rt_info, ri_indexID);
    String          dest = jStringToString(env, (jstring)env->GetObjectField(rt_info, ri_destID));
    String          mask = jStringToString(env, (jstring)env->GetObjectField(rt_info, ri_maskID));
    String          gateway = jStringToString(env, (jstring)env->GetObjectField(rt_info, ri_gatewayID));;
    unsigned long   type = env->GetIntField(rt_info, ri_typeID);
    unsigned long   metric = env->GetIntField(rt_info, ri_metricID);
    bool            defaultGw = (env->GetBooleanField(rt_info, ri_defaultGwID) != 0) ? true : false;

    RouteInfo route_entry = RouteInfo(dest, mask, gateway, index, metric, defaultGw);
    return route_entry;
}

JNIEXPORT void JNICALL Java_net_RouteInfo_init(JNIEnv *env, jclass cls) {
    ri_class = (jclass)env->NewGlobalRef(cls);
    ri_indexID = env->GetFieldID(ri_class, "index", "I");
    ri_destID = env->GetFieldID(ri_class, "dest", "Ljava/lang/String;");
    ri_maskID = env->GetFieldID(ri_class, "mask", "Ljava/lang/String;");
    ri_gatewayID = env->GetFieldID(ri_class, "gateway", "Ljava/lang/String;");
    ri_typeID = env->GetFieldID(ri_class, "type", "I");
    ri_metricID = env->GetFieldID(ri_class, "metric", "I");
    ri_defaultGwID = env->GetFieldID(ri_class, "defaultGw", "Z");
    ri_primaryGwID = env->GetFieldID(ri_class, "primaryGw", "Z");
    ri_ctor = env->GetMethodID(ri_class, "<init>", "()V");
}

JNIEXPORT jobjectArray JNICALL Java_net_RouteInfo_getRouteList(JNIEnv *env, jclass cls) {
    jobjectArray rt_list = NULL;

    route_list list = RouteInfo::getRouteList();
    if (!list.empty()) {
        jsize rt_count = (jsize)list.size();
        if (rt_count > 0) {
            rt_list = env->NewObjectArray(rt_count, ri_class, NULL);
            for (jsize i = 0; i < rt_count; i++) {
                RouteInfo info = list.at(i);
                jobject one_route = load_class(env, info);
                env->SetObjectArrayElement(rt_list, i, one_route);
            }
        }
        list.clear();
    }
    return rt_list;
}

JNIEXPORT jobject JNICALL Java_net_RouteInfo_getBestRoute(JNIEnv *env, jclass cls, jstring dest, jstring mask, jobjectArray table) {
    jobject one_route = NULL;

    String dest_value = jStringToString(env, dest);
    String mask_value = jStringToString(env, mask);

    route_list route_table;
    if (table != NULL) {
        jsize len = env->GetArrayLength(table);
        for (jsize i = 0; i < len; i++) {
            jobject rt_entry = env->GetObjectArrayElement(table, i);
            RouteInfo entry = load_value(env, rt_entry);
            route_table.push_back(entry);
        }
    }
    RouteInfo info = RouteInfo::getBestRoute(dest_value, mask_value, route_table);
    if (info.getIndex() >= 0) {
        one_route = load_class(env, info);
    }

    return one_route;
}

を実装します。


JNIのライブラリ(dll)をjninetで前のJNIライブラリと一つにまとめてしまったので、jStringToString()関数をそのままaddrinfo.cppからコピーして使うと、リンク時に同じ名前の関数が存在するということでエラーになってしまうため、
stringToJString()関数と共に、jninet.cppに移動して実装しました。
ちなみにjninet.cppは

String jStringToString(JNIEnv *env, jstring j_string) {
    const jchar *words = env->GetStringChars(j_string, NULL);
    _bstr_t orginal = _bstr_t((wchar_t *)words);
    env->ReleaseStringChars(j_string, words);
    String c_string = String((_TCHAR *)orginal);
    return c_string;
}

jstring stringToJString(JNIEnv *env, String c_string) {
    _bstr_t value = _bstr_t((_TCHAR *)c_string.c_str());
    jstring j_string = env->NewString((const jchar *)((wchar_t *)value), (jsize)value.length());
    return j_string;
}

が実装されています。
jStringToString()関数とstringToJString()関数は、以前作ったjniifinfoでも利用可能なので、後日そちらも修正しておきます。

ルートテーブルの取得するクラスの作成

インターフェース情報を取得できたので、同じIPHELPAPIを使ってルーティング情報を取得するクラスを作ることにしました。
ルーティング情報は、"route print"をコマンド実行して結果の文字列を処理して取得しないとファイルの一覧のようにWindowsAPIでは取得できなかったので、今後の利用も考慮して作ることに・・・。


C++の基本プログラムとして、RouteInfoクラスを作成することにしました。
RouteInfo.h

#ifndef _ROUTE_INFO_CLASS
#define _ROUTE_INFO_CLASS

#include <windows.h>
#include <tchar.h>
#include <comdef.h>

#include <iphlpapi.h>
#pragma comment(lib, "iphlpapi.lib")
#define DIRECT_ROUTE        MIB_IPROUTE_TYPE_DIRECT
#define INDIRECT_ROUTE      MIB_IPROUTE_TYPE_INDIRECT

#include <vector>
#include <string>

#include "addrinfo.h"

typedef basic_string<_TCHAR>    String;

class RouteInfo;
typedef vector<RouteInfo>       route_list;

class RouteInfo {
public:
    RouteInfo(const RouteInfo& info);
    RouteInfo(String dest, String mask, String gateway, unsigned long index, unsigned long metric, bool defaultGw);
    RouteInfo();
    ~RouteInfo();

    String              getDest();
    String              getMask();
    String              getGateway();
    unsigned long       getIndex();
    unsigned long       getType();
    unsigned long       getMetric();
    bool                isDefaultGateway();
    bool                isPrimaryGateway();
    bool                isSameRouteInfo(String dest, String mask, String gateway);

    static route_list       getRouteList();

private:
    String          dest;
    String          mask;
    String          gateway;
    unsigned long   index;
    unsigned long   type;
    unsigned long   metric;
    bool            defaultGw;
    bool            primaryGw;
};

#endif  //  _ROUTE_INFO_CLASS

プログラム本体は、
RouteInfo.cpp

#include "routeinfo.h"

//  Copy Constructor
RouteInfo::RouteInfo(const RouteInfo& info) {
    dest = info.dest;
    mask = info.mask;
    gateway = info.gateway;
    index = info.index;
    type = info.type;
    metric = info.metric;
    defaultGw = info.defaultGw;
    primaryGw = info.primaryGw;
}

//  Constructor
RouteInfo::RouteInfo(String dest, String mask, String gateway, unsigned long index, unsigned long metric, bool defaultGw) {
    this->dest = dest;
    this->mask = mask;
    this->gateway = gateway;
    this->index = index;
    this->type = type;
    this->metric = metric;
    this->defaultGw = defaultGw;
    this->primaryGw = false;
}

//  Constructor
RouteInfo::RouteInfo() {
    index = 0;
    type = DIRECT_ROUTE;
    metric = 0;
    defaultGw = false;
    primaryGw = false;
}

//  Destructor
RouteInfo::~RouteInfo() {
}

/*
 *  あて先アドレスを取得する
 *  param   : 無し
 *
 *  return  : String
 */
String RouteInfo::getDest() {
    return dest;
}

/*
 *  ネットマスクを取得する
 *  param   : 無し
 *
 *  return  : String
 */
String RouteInfo::getMask() {
    return mask;
}

/*
 *  ゲートウエイアドレスを取得する
 *  param   : 無し
 *
 *  return  : String
 */
String RouteInfo::getGateway() {
    return gateway;
}

/*
 *  インデックスを取得する
 *  param   : 無し
 *
 *  return  : unsigned long
 */
unsigned long RouteInfo::getIndex() {
    return index;
}

/*
 *  タイプを取得する
 *  param   : 無し
 *
 *  return  : unsigned long
 */
unsigned long RouteInfo::getType() {
    return type;
}

/*
 *  メトリックを取得する
 *  param   : 無し
 *
 *  return  : unsigned long
 */
unsigned long RouteInfo::getMetric() {
    return metric;
}

/*
 *  デフォルトゲートウエイかどうかを取得する
 *  param   : 無し
 *
 *  return  : bool
 *            true  = デフォルトゲートウエイである
 *            false = デフォルトゲートウエイでない
 */
bool RouteInfo::isDefaultGateway() {
    return defaultGw;
}

/*
 *  プライマリゲートウエイかどうかを取得する
 *  param   : 無し
 *
 *  return  : bool
 *            true  = プライマリゲートウエイである
 *            false = プライマリゲートウエイでない
 */
bool RouteInfo::isPrimaryGateway() {
    return primaryGw;
}

/*
 *  同じルート情報か確認する
 *  param   : あて先アドレス(String)
 *            ネットマスク(String)
 *            ゲートウエイアドレス(String)
 *
 *  return  : bool
 *            true  = 同じルート情報である
 *            false = 同じルート情報でない
 */
bool RouteInfo::isSameRouteInfo(String dest, String mask, String gateway) {
    if (this->dest != dest)
        return false;
    if (this->mask != mask)
        return false;
    if (this->gateway != gateway)
        return false;
    return true;
}

/*
 *  ルート情報の一覧を取得する
 *  param   : 無し
 *
 *  return  : route_list
 */
route_list RouteInfo::getRouteList() {
    route_list rt_list;

    DWORD lowest_metric = ~0;
    int pgw = -1;

    DWORD dwSize = 0;
    GetIpForwardTable(NULL, &dwSize, false);
    PMIB_IPFORWARDTABLE pForwardInfo = (MIB_IPFORWARDTABLE *)calloc(dwSize, 1);
    if (GetIpForwardTable(pForwardInfo, &dwSize, false) == NO_ERROR) {
        for (unsigned long i = 0; i < pForwardInfo->dwNumEntries; i++) {
            MIB_IPFORWARDROW *row = &pForwardInfo->table[i];
            RouteInfo route_entry = RouteInfo();
            route_entry.dest = String((_TCHAR *)_bstr_t(inet_ntoa(*(struct in_addr *)&row->dwForwardDest)));
            route_entry.mask = String((_TCHAR *)_bstr_t(inet_ntoa(*(struct in_addr *)&row->dwForwardMask)));
            route_entry.gateway = String((_TCHAR *)_bstr_t(inet_ntoa(*(struct in_addr *)&row->dwForwardNextHop)));
            route_entry.index = row->dwForwardIfIndex;
            route_entry.type = row->dwForwardType;
            route_entry.metric = row->dwForwardMetric1;
            if (!row->dwForwardDest && !row->dwForwardMask) {
                route_entry.defaultGw = true;
            }
            rt_list.push_back(route_entry);
            if (route_entry.isDefaultGateway() && (lowest_metric > row->dwForwardMetric1)) {
                pgw = i;
                lowest_metric = row->dwForwardMetric1;
            }
        }
        if (pgw >= 0) {
            rt_list[pgw].primaryGw = true;
        }
        free(pForwardInfo);
    }

    return rt_list;
}

/*
 *  あて先ネットワークに最適のルート情報を取得する
 *  param   : あて先ネットワークアドレス(String)
 *            あて先ネットマスク(String)
 *            ルート情報の一覧(route_list)
 *
 *  return  : RouteInfo
 */
RouteInfo RouteInfo::getBestRoute(String dest, String mask, route_list rtList) {
    RouteInfo route_entry = RouteInfo();

    unsigned long rt_index = ~0;
    unsigned long rt_metric = ~0;
    unsigned long rt_mask = 0;

    unsigned long dest_net = inet_addr((const char *)_bstr_t(dest.c_str()));
    if (mask.empty())
        mask = HOST_MASK;
    unsigned long dest_mask = inet_addr((const char *)_bstr_t(mask.c_str()));

    if (rtList.empty()) {
        rtList = RouteInfo::getRouteList();
    }
    for (size_t i = 0; i < rtList.size(); i++) {
        RouteInfo rt = rtList.at(i);
        unsigned long route_dest = inet_addr((const char *)_bstr_t(rt.getDest().c_str()));
        unsigned long route_mask = inet_addr((const char *)_bstr_t(rt.getMask().c_str()));
        unsigned long route_base = route_dest & ((route_mask < dest_mask) ? route_mask : dest_mask);
        unsigned long cmp = dest_net & route_base;
        if (cmp == route_base) {
            if (route_mask >= rt_mask) {
                if ((route_mask > rt_mask) || (rt.getMetric() < rt_metric)) {
                    rt_index = (unsigned long)i;
                    rt_metric = rt.getMetric();
                }
                rt_mask = route_mask;
            }
        }
    }
    if (rt_index < rtList.size()) {
        route_entry = rtList.at(rt_index);
    }

    return route_entry;
}

テスト用のプログラムロジックとして今までにも使っていた、
netinfo.cppに

void getRoute() {
    route_list list = RouteInfo::getRouteList();
    for (size_t i = 0; i < list.size(); i++) {
        RouteInfo info = list.at(i);
        COUT << _T("RouteInfo ") << info.getIndex() << _T(" : ") << info.getDest() << _T("-") << info.getMask() << _T("-") << info.getGateway() << endl;
    }

    RouteInfo info = RouteInfo::getBestRoute(String(_T("202.239.113.18")));
    COUT << _T("Gateway ") << info.getIndex() << _T(" : ") << info.getDest() << _T("-") << info.getMask() << _T("-") << info.getGateway() << endl;
}

を追加して実行しました。


実はIPHLPAPIには最適なルート情報を取得するGetBestRoute()という関数が存在するんですが、システムが持っているルート情報から判断するのではなく、検証したいルーティングテーブルを自分で与えられるようにしたかったので、あえて作ってみました。

幾つかメソッドを追加してみた

Javaの標準にあるInetAddressクラスの中で利用できるメソッドの幾つかをAddrInfoクラスに実装してみました。
実装したのは、

getAllByName(String hostname)
パラメータで渡したホスト名(hostname)からIPアドレスリストを取得する。
getByName(String hostname)
パラメータで渡したホスト名(hostname)からIPアドレスを取得する。
getNetworkAddress(String hostaddr, String netmask)
パラメータで渡したホストアドレス(hostaddr)とネットマスク(netmask)からネットワークアドレスを計算する。
isAddrInNetwork(String base, String mask, String target)
パラメータで渡したホストアドレスorネットワークアドレス(base)とネットマスク(mask)と同じネットワークに指定したIPアドレス(target)が含まれるか計算する。

の、4つのメソッドです。


以前のAddrInfoクラスは、IfInfo用のJNIからデータを格納するためだけのクラスとして用意されていたため、時にAddrInfoクラスとしてのJNIの実装がかかれていなかったので、
AddrInfo.javaに初期化の処理を追加すると共に、次のようにスタティックメソッドを追加しました。

    static {
        System.loadLibrary("jninet");
        init();
    }

    private static native void init();
    public static native String[] getAllByName(String hostname);
    public static native String getByName(String hostname);
    public static native String getNetworkAddress(String hostaddr, String netmask);
    public static native boolean isAddrInNetwork(String base, String mask, String target);

何時もの様にコンパイル後、ヘッダファイルを作成。
それで前には必要なかったJNIの実装を追加します。
AddrInfo.hと

#ifndef _JNI_ADDRINFO_CALL
#define _JNI_ADDRINFO_CALL

#include <windows.h>

#include "addrinfo.h"
using namespace penguin;

#include "net_AddrInfo.h"

jclass      str_class;          /* String */

#endif  //  _JNI_ADDRINFO_CALL

AddrInfo.cppを

#include "jniaddrinfo.h"

String jStringToString(JNIEnv *env, jstring j_string) {
    const jchar *words = env->GetStringChars(j_string, NULL);
    _bstr_t orginal = _bstr_t((wchar_t *)words);
    env->ReleaseStringChars(j_string, words);
    String c_string = String((_TCHAR *)orginal);
    return c_string;
}

JNIEXPORT void JNICALL Java_net_AddrInfo_init(JNIEnv *env, jclass cls) {
    jclass c = env->FindClass("java/lang/String");
    str_class = (jclass)env->NewGlobalRef(c);
    env->DeleteLocalRef(c);
}

JNIEXPORT jobjectArray JNICALL Java_net_AddrInfo_getAllByName(JNIEnv *env, jclass cls, jstring hostname) {
    jobjectArray ai_list = NULL;

    String hostname_value = jStringToString(hostname);
    address_list list = AddrInfo::getAllByName(hostname_value);
    if (!list.empty()) {
        jsize ai_count = (jsize)list.size();
        if (ai_count > 0) {
            ai_list = env->NewObjectArray(ai_count, str_class, NULL);
            for (jsize i = 0; i < ai_count; i++) {
                AddrInfo addr = list.at(i);
                String address_value = addr.getIpAddress();
                _bstr_t address_str = _bstr_t((_TCHAR *)address_value.c_str());
                jobject address = env->NewString((const jchar *)((wchar_t *)address_str), (jsize)address_str.length());
                env->SetObjectArrayElement(ai_list, i, address);
            }
        }
        list.clear();
    }
    return ai_list;
}

JNIEXPORT jstring JNICALL Java_net_AddrInfo_getByName(JNIEnv *env, jclass cls, jstring hostname) {
    jstring one_address = NULL;

    String hostname_value = jStringToString(hostname);
    String address_value = AddrInfo::getByName(hostname_value);
    _bstr_t address_str = _bstr_t((_TCHAR *)address_value.c_str());
    one_address = env->NewString((const jchar *)((wchar_t *)address_str), (jsize)address_str.length());
    return one_address;
}

JNIEXPORT jstring JNICALL Java_net_AddrInfo_getNetworkAddress (JNIEnv *env, jclass cls, jstring hostaddr, jstring netmask) {
    jstring one_address = NULL;

    String hostaddr_value = jStringToString(hostaddr);
    String netmask_value = jStringToString(netmask);
    String address_value = AddrInfo::getNetworkAddress(hostaddr_value, netmask_value);
    _bstr_t address_str = _bstr_t((_TCHAR *)address_value.c_str());
    one_address = env->NewString((const jchar *)((wchar_t *)address_str), (jsize)address_str.length());
    return one_address;
}

JNIEXPORT jboolean JNICALL Java_net_AddrInfo_isAddrInNetwork(JNIEnv *env, jclass cls, jstring base, jstring mask, jstring target) {

    String base_value = jStringToString(base);
    String mask_value = jStringToString(mask);
    String target_value = jStringToString(target);
    return (jboolean)AddrInfo::isAddrInNetwork(base_value, mask_value, target_value);
}

実装しました。


後はJavaからのテストで

    String[] addrs = AddrInfo.getAllByName("www.google.co.jp");
    for (int i = 0; i < addrs.length; i++) {
        System.out.println("Google addrs "+i+" = "+addrs[i]);
    }
    
    String netAddr = AddrInfo.getNetworkAddress("172.16.255.1", "255.255.255.0");
    System.out.println("Network address = "+netAddr);
    
    if (AddrInfo.isAddrInNetwork("172.16.255.0", "255.255.255.0", "172.16.255.1"))
        System.out.println("Network address is local");
    else
        System.out.println("Network address is not local");

で確認してOkでした。

ちょっと余談で

ロジック中に

ipaddress = String((_TCHAR *)_bstr_t(inet_ntoa(*(struct in_addr *)&pIPAddrTable->table[num].dwAddr)));

のように、_bstr_tが多用されているのをみて、「何してるんだ」と思われている方も多いかもしれませんが、
これは使っているプログラムがVisualStudiの文字セットオプションで、「Unicode文字セットを使用する」を選択しても、「マルチバイト文字セットを使用する」を選択しても、文字列処理の動作を書き換えなくても良いようにと、怠慢をするために勝手につかっちゃっているものです。


本来であれば、

#ifdef  _UNICODE
    char *address = inet_ntoa(*(struct in_addr *)&pIPAddrTable->table[num].dwAddr);
    size_t count = mbstowcs(NULL, address, strlen(address));
    wchar_t *addr_str = (wchar_t*)calloc(count+1, sizeof(wchar_t));
    count = mbstowcs(addr_str, address, strlen(address));
    ipaddress = String(addr_str);
    free(addr_str);
#else
    ipaddress = String(inet_ntoa(*(struct in_addr *)&pIPAddrTable->table[num].dwAddr));
#endif  //  _UNICODE

こんな感じで文字セットを考慮したロジックにしたほうが良いんだと思うんだけど、Windowsでのロジックだからこれもありかなってつかっちゃってます。


ちなみに_bstr_tクラスですが、
http://msdn.microsoft.com/ja-jp/library/9k3ebasf.aspx
にあるように、"char *"も"wchar_t *"もどちらも入力できて、"char *"タイプの文字が入力されると内部で"wchar_t *"に変換してバッファに保存します。
また、
http://msdn.microsoft.com/ja-jp/library/btdzb8eb.aspx
にあるように、"char *"も"wchar_t *"もどちらも出力できて、"char *"タイプでの出力では、内部で保存している"wchar_t *"タイプの文字を"char *"タイプに変換した後に、そのバッファのポインターを返すように作られています。

ネットワークインターフェース情報の取得(5)

ロジックに一部間違いがあったので、Loopbackインターフェース(MS TCP Loopback interface)にアドレスが表示されていなかった。
これは、アドレスをloadInterfaceInfoメソッド中のループで取得するように組み込んでいたのが間違いで、GetAdaptersInfo()関数ではLoopbackにアダプタが割り当てられていないので、処理をすることが出来なかったことに起因していた。
アドレスを設定するロジックを、loadInterfaceInfo()メソッドではなくgetInterfaceList()メソッドの中で処理させることで、Loopbackインターフェースもアドレス情報を設定することが出来た。
変更点としては、
ifinfo.cppのgetInterfaceList()メソッドを

interface_list IfInfo::getInterfaceList() {
    interface_list if_list;

    DWORD dwSize = 0;
    GetIfTable(NULL, &dwSize, true);
    PMIB_IFTABLE pIfTable = (MIB_IFTABLE *)calloc(dwSize, 1);
    if (pIfTable != NULL) {
        if (GetIfTable(pIfTable, &dwSize, true) == NO_ERROR) {
            for (u_int i = 0; i < pIfTable->dwNumEntries; i++) {
                IfInfo if_table = IfInfo();
                MIB_IFROW pInterface = pIfTable->table[i];
                if_table.ifIndex = pInterface.dwIndex;
                if_table.ifName = if_table.description = String((_TCHAR *)_bstr_t((char *)pInterface.bDescr));
                if_table.ipAddressList = AddrInfo::setAddressList(if_table.ifIndex);
                if_table.ifType = pInterface.dwType;
                if_table.opStatus = pInterface.dwOperStatus;
                if_list.push_back(if_table);
            }
        }
        free(pIfTable);
    }

    return if_list;
}

に変更し、loadInterfaceInfo()メソッドを

void IfInfo::loadInterfaceInfo(IfInfo& if_table) {
    DWORD dwSize = 0;
    GetAdaptersInfo(NULL, &dwSize);
    PIP_ADAPTER_INFO pAdapterInfo = (IP_ADAPTER_INFO *)calloc(dwSize, 1);
    if (pAdapterInfo != NULL) {
        if (GetAdaptersInfo(pAdapterInfo, &dwSize) == ERROR_SUCCESS) {
            PIP_ADAPTER_INFO pAdapter = pAdapterInfo;
            while (pAdapter) {
                if (pAdapter->Index == if_table.ifIndex) {
                    if_table.adapterName = String((_TCHAR *)_bstr_t(pAdapter->AdapterName));
                    if_table.ifAddress = getMacAddress(pAdapter->Address, pAdapter->AddressLength);
                    if_table.dhcpEnabled = pAdapter->DhcpEnabled ? true : false;
                    HKEY hkey = NULL;
                    _bstr_t adapter = _bstr_t(_T("SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}\\"));
                    adapter += _bstr_t((char *)pAdapter->AdapterName);
                    adapter += _bstr_t(_T("\\Connection"));
                    int rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, (_TCHAR *)adapter, 0, KEY_READ, &hkey);
                    if (rc == ERROR_SUCCESS) {
                        DWORD valueType = 0;
                        DWORD nameLen = 0;
                        rc = RegQueryValueEx(hkey, _T("Name"), 0, &valueType, NULL, &nameLen);
                        _TCHAR *buff = (_TCHAR *)calloc(nameLen+1, sizeof(char));
                        rc = RegQueryValueEx(hkey, _T("Name"), 0, &valueType, (LPBYTE)buff, &nameLen);
                        if_table.ifName = String(buff);
                        RegCloseKey(hkey);
                        free(buff);
                    }
                }
                pAdapter = pAdapter->Next;
            }
            free(pAdapterInfo);
        }
    }
}

に変更した。


余談ですが、WMIの時にはAPIについて色々と説明がつけられていたのに、ネットワークインターフェース情報の取得にはあまり説明のコメントがないと思っておられる方もいるかと思いますが、ある程度形になってきた時点で、きちんとしたソースコードをダウンロードできるように整理したいと思っているので、その時にはコメント付きにしたいと思ってます。

ネットワークインターフェース情報の取得(4)

JavaのNetworkInterfaceにある、getByInetAddress()メソッドを実装するために、C++のIfInfoクラスと、JNIにメソッドを追加しました。


ifinfo.h

    static  IfInfo              findInterfaceByAddress(String address);

ifinfo.cpp

/*
 *  インターフェースの一覧から指定のIPアドレスがバインドされたインターフェース情報を取得する
 *  param   : IPアドレス(String)
 *
 *  return  : IfInfo
 */
IfInfo IfInfo::findInterfaceByAddress(String address) {
    IfInfo if_table;

    interface_list net_list = getIfList();
    for (if_iter if_ent = net_list.begin(); if_ent != net_list.end(); if_ent++) {
        address_list ad_list = if_ent->getIpAddressList();
        for (al_iter al_ent = ad_list.begin(); al_ent != ad_list.end(); al_ent++) {
            if (al_ent->getIpAddress().compare(address) == 0) {
                return *if_ent;
            }
        }
    }
    return if_table;
}

IfInfo.java

    public static native IfInfo getInterfaceByAddress(String address);

jninet.cpp

JNIEXPORT jobject JNICALL Java_net_IfInfo_getInterfaceByAddress(JNIEnv *env, jclass cls, jstring address) {
    jobject one_interface = NULL;

    const jchar *words = env->GetStringChars(address, NULL);
    _bstr_t address_str = _bstr_t((wchar_t *)words);
    env->ReleaseStringChars(address, words);
    String address_value = String((_TCHAR *)address_str);
    IfInfo info = IfInfo::findInterfaceByAddress(address_value);
    if (info.getIndex() > 0) {
        one_interface = load_class(env, info);
    }
    return one_interface;
}

それで、Javaのテストプログラムに

    ni = IfInfo.getInterfaceByAddress("192.168.xxx.xxx");
    System.out.println("Found interface:");
    print(ni);

を追加して、動作確認はOKでした。

ネットワークインターフェース情報の取得(3)

作業を本題のJavaでネットワークインターフェースの情報を取得するロジックに戻して・・・。


単純に一覧にして取得するところまでは昨日のロジックで完了していたので、インターフェース番号からとインターフェース名から情報を取得するメソッドを追加します。
IfInfo.javaに以下の2つのメソッドを追加して、

    public static native IfInfo getInterfaceByIndex(long index);
    public static native IfInfo getInterfaceByName(String name);

いつものようにコンパイルとヘッダファイルの生成。


次に、jniに必要なロジックを追加します。

JNIEXPORT jobject JNICALL Java_net_IfInfo_getInterfaceByIndex(JNIEnv *env, jclass cls, jlong index) {
    jobject one_interface = NULL;

    IfInfo info = IfInfo::findInterfaceByIndex((int)index);
    if (info.getIndex() > 0) {
        one_interface = load_class(env, info);
    }
    return one_interface;
}

JNIEXPORT jobject JNICALL Java_net_IfInfo_getInterfaceByName(JNIEnv *env, jclass cls, jstring name) {
    jobject one_interface = NULL;

    const jchar *words = env->GetStringChars(name, NULL);
    _bstr_t name_str = _bstr_t((wchar_t *)words);
    env->ReleaseStringChars(name, words);
    String name_value = String((_TCHAR *)name_str);
    IfInfo info = IfInfo::findInterfaceByName(name_value);
    if (info.getIndex() > 0) {
        one_interface = load_class(env, info);
    }
    return one_interface;
}

Javaのテストに

    IfInfo ni = IfInfo.getInterfaceByName("ローカル エリア接続");
    System.out.println("Found interface:");
    print(ni);

を追加すると、完了!