IPadのIPアドレスをプログラムで取得したいと思います。ネットワークサブシステムに照会して、IPv4(およびIPv6)アドレスを調べるにはどうすればよいですか?
PS:どういうわけかIPv6を無効にできますか?
次のコードは、iOSまたはOSXデバイス上のすべてのIPv4およびIPv6アドレスを検索します。最初のgetIPAddress
メソッドは、この回答の古いコードとほぼ同じように機能します。どちらかのタイプのアドレスを優先でき、常にセルラーよりもWIFIを優先します(これは明らかに変更できます)。
さらに興味深いことに、not up
インターフェースのアドレス、またはloopback
に関連付けられたアドレスをスキップして、見つかったすべてのアドレスの辞書を返すことができます。このトピックに関する以前のコードおよび他のソリューションは、IPv6を適切にデコードしません(inet_ntoaはそれらを処理できません)。これはAppleフォーラムで Jens Alfke によって指摘されました-使用する適切な関数はinet_ntopです(manページを参照、またはこれを参照してください inet_ntop Jensが提供する記事。
辞書キーの形式は、「インターフェース」「/」「ipv4またはipv6」です。
#include <ifaddrs.h>
#include <arpa/inet.h>
#include <net/if.h>
#define IOS_CELLULAR @"pdp_ip0"
#define IOS_WIFI @"en0"
//#define IOS_VPN @"utun0"
#define IP_ADDR_IPv4 @"ipv4"
#define IP_ADDR_IPv6 @"ipv6"
- (NSString *)getIPAddress:(BOOL)preferIPv4
{
NSArray *searchArray = preferIPv4 ?
@[ /*IOS_VPN @"/" IP_ADDR_IPv4, IOS_VPN @"/" IP_ADDR_IPv6,*/ IOS_WIFI @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6 ] :
@[ /*IOS_VPN @"/" IP_ADDR_IPv6, IOS_VPN @"/" IP_ADDR_IPv4,*/ IOS_WIFI @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4 ] ;
NSDictionary *addresses = [self getIPAddresses];
NSLog(@"addresses: %@", addresses);
__block NSString *address;
[searchArray enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop)
{
address = addresses[key];
if(address) *stop = YES;
} ];
return address ? address : @"0.0.0.0";
}
- (NSDictionary *)getIPAddresses
{
NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8];
// retrieve the current interfaces - returns 0 on success
struct ifaddrs *interfaces;
if(!getifaddrs(&interfaces)) {
// Loop through linked list of interfaces
struct ifaddrs *interface;
for(interface=interfaces; interface; interface=interface->ifa_next) {
if(!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */ ) {
continue; // deeply nested code harder to read
}
const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr;
char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ];
if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) {
NSString *name = [NSString stringWithUTF8String:interface->ifa_name];
NSString *type;
if(addr->sin_family == AF_INET) {
if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) {
type = IP_ADDR_IPv4;
}
} else {
const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr;
if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) {
type = IP_ADDR_IPv6;
}
}
if(type) {
NSString *key = [NSString stringWithFormat:@"%@/%@", name, type];
addresses[key] = [NSString stringWithUTF8String:addrBuf];
}
}
}
// Free memory
freeifaddrs(interfaces);
}
return [addresses count] ? addresses : nil;
}
EDIT1:コードは2014年5月16日に更新されました(lhunathが指摘したバグ、コメントを参照)。ループバックアドレスが返されるようになりましたが、テストのコメントを外して自分で除外するのは簡単です。
EDIT2:(不明な人による):2015年3月13日のさらなる改善:ユーザーがVPN(WiFiまたはCellularを介して)を使用する場合、以前のコードは失敗します。現在、VPN接続でも機能します。 VPN接続は、デバイスがそれを処理する方法であるため、WiFiおよびCellよりも優先されます。 MacのVPN接続もIF utun0を使用していますが、テストされていないため、これはMacでも機能するはずです。
EDIT3:(9/8/2016)@Qiulang(コメントを参照)がVPNコード(他の誰かが追加した)で経験した問題を考えると、コメントアウトしました。ユーザーVPNの指定方法が明確にわかっている場合は、コメントでお知らせください。
実装ファイル.mで、
#import <ifaddrs.h>
#import <arpa/inet.h>
// Get IP Address
- (NSString *)getIPAddress {
NSString *address = @"error";
struct ifaddrs *interfaces = NULL;
struct ifaddrs *temp_addr = NULL;
int success = 0;
// retrieve the current interfaces - returns 0 on success
success = getifaddrs(&interfaces);
if (success == 0) {
// Loop through linked list of interfaces
temp_addr = interfaces;
while(temp_addr != NULL) {
if(temp_addr->ifa_addr->sa_family == AF_INET) {
// Check if interface is en0 which is the wifi connection on the iPhone
if([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) {
// Get NSString from C String
address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];
}
}
temp_addr = temp_addr->ifa_next;
}
}
// Free memory
freeifaddrs(interfaces);
return address;
}
IPアドレス検索サービスの1つを呼び出すのは簡単でしょうか?デバイスIPアドレスをJSON/XMLまたはプレーンテキストとして返すサービスとして http://ipof.in を設定しました。ここで見つけることができます
HTTPSが必要な場合は、https
プレフィックス付きの同じURLを使用できます。利点は、Wifiを使用している場合でも、パブリックアドレスを取得できることです。
既存のソリューションの多くは、ワイヤレスインターフェースのみを考慮しています。これは、イーサネットアダプターを介した有線接続では機能しません(つまり、Wifiまたは3Gなし)。有線インターフェースを介して取得されたIPアドレスも考慮する最新のソリューションを参照してください。
Swift 3を使用してIPアドレスを取得します。
func getIPAddress() -> String {
var address: String = "error"
var interfaces: ifaddrs? = nil
var temp_addr: ifaddrs? = nil
var success: Int = 0
// retrieve the current interfaces - returns 0 on success
success = getifaddrs(interfaces)
if success == 0 {
// Loop through linked list of interfaces
temp_addr = interfaces
while temp_addr != nil {
if temp_addr?.ifa_addr?.sa_family == AF_INET {
// Check if interface is en0 which is the wifi connection on the iPhone
if (String(utf8String: temp_addr?.ifa_name) == "en0") {
// Get NSString from C String
address = String(utf8String: inet_ntoa((temp_addr?.ifa_addr as? sockaddr_in)?.sin_addr))
}
}
temp_addr = temp_addr?.ifa_next
}
}
// Free memory
freeifaddrs(interfaces)
return address
}
この回答は、@ DavidHの回答に触発されました。いくつかの問題を修正し、inet_ntop
をgetnameinfo
に置き換えました。これにより、よりクリーンなアプローチが可能になりました。これにより、インターフェイス名をIPアドレスの配列にマップする辞書が生成されることに注意してください(技術的には、インターフェイスに複数のIPv4およびIPv6を関連付けることができます)。 IPv4とIPv6を区別しません:
// Get all our interface addresses.
struct ifaddrs *ifAddresses;
if (getifaddrs( &ifAddresses ) != 0) {
NSLog( @"Couldn't get interface addresses: %d", errno );
return nil;
}
int error;
char Host[MAX( INET_ADDRSTRLEN, INET6_ADDRSTRLEN )];
_ipAddressesByInterface = [NSMutableDictionary dictionaryWithCapacity:8];
for (struct ifaddrs *ifAddress = ifAddresses; ifAddress; ifAddress = ifAddress->ifa_next) {
if (!(ifAddress->ifa_flags & IFF_UP) || (ifAddress->ifa_flags & IFF_LOOPBACK))
// Ignore interfaces that aren't up and loopback interfaces.
continue;
if (ifAddress->ifa_addr->sa_family != AF_INET && ifAddress->ifa_addr->sa_family != AF_INET6)
// Ignore non-internet addresses.
continue;
if ((error = getnameinfo( ifAddress->ifa_addr, ifAddress->ifa_addr->sa_len, Host, sizeof( Host ), NULL, 0, NI_NUMERICHOST )) != noErr) {
// Couldn't to format Host name for this address.
NSLog( @"Couldn't resolve Host name for address: %s", gai_strerror( error ) );
continue;
}
NSString *ifName = [NSString stringWithCString:ifAddress->ifa_name encoding: NSUTF8StringEncoding];
NSMutableArray *ifIpAddresses = _ipAddressesByInterface[ifName];
if (!ifIpAddresses)
ifIpAddresses = _ipAddressesByInterface[ifName] = [NSMutableArray arrayWithCapacity:2];
[ifIpAddresses addObject:[NSString stringWithCString:Host encoding: NSUTF8StringEncoding]];
}
freeifaddrs( ifAddresses );
return _ipAddressesByInterface;
@DavidHの答えは、4Gセルラーネットワークからこの結果が得られるまではうまくいきます。
{
"lo0/ipv4" = "127.0.0.1";
"lo0/ipv6" = "fe80::1";
"pdp_ip0/ipv4" = "10.132.76.168";
"utun0/ipv6" = "fe80::72c3:e25e:da85:b730";
}
私はvpnを使用していないので、なぜutun0/ipv6があったのかわかりません。
- - 更新しました - -
さらにこの問題をデバッグすると、他の4Gネットワークでも偽のVPNアドレスを取得できることがわかりました(このiOSのバグですか??)。
{
""awdl0/ipv6"" = ""fe80::c018:9fff:feb2:988"";
""en0/ipv6"" = ""fe80::181a:2e43:f91b:db2b"";
""lo0/ipv4"" = ""127.0.0.1"";
""lo0/ipv6"" = ""fe80::1"";
""pdp_ip0/ipv4"" = ""10.48.10.210"";
""utun0/ipv4"" = ""192.168.99.2"";
}
私がVPNを使用した場合、私はこれを取得します:
{
"lo0/ipv4" = "127.0.0.1";
"lo0/ipv6" = "fe80::1";
"pdp_ip0/ipv4" = "10.49.187.23";
"utun0/ipv6" = "fe80::5748:5b5d:2bf0:658d";
"utun1/ipv4" = "192.168.99.2"; //the real one
}
tun1 NOT tun
なぜ私はVPNチェックをドロップする必要があるのかを理解することなく:(
----更新----
Appleにバグ(28131847)を上げて、「すべてのutunインターフェイスがVPN用ではありません。utunインターフェイスを使用する他のOS機能があります」と返信しました。
しかし、有効なvpn IPアドレスを取得する方法を尋ねると、「設定-> VPNに進み、VPN構成を見て、VPNがアクティブかどうかを確認できます。場合によっては、 IPアドレスもそこに割り当てられました。現在、このバグレポートを閉じています。」 :(
---- 2016/11/04更新----
私は再び問題にぶつかったので、@ DavidHの答えをさらに修正して修正する必要があります。
私は4Gネットワークにいて、このアドレスを取得しました:
addresses: {
"awdl0/ipv6" = "fe80::98fd:e6ff:fea9:3afd";
"en0/ipv6" = "fe80::8dd:7d92:4159:170e";
"lo0/ipv4" = "127.0.0.1";
"lo0/ipv6" = "fe80::1";
"pdp_ip0/ipv4" = "10.37.212.102";
"utun0/ipv6" = "fe80::279c:ea56:a2ef:d128";
}
彼の元の答えで、wifi IP fe80 :: 8dd:7d92:4159:170eを取得します。これは偽物で、接続に失敗しました。
そこで、コードを次のように変更しました。
[searchArray enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop)
{
if ((internetReach.isReachableViaWiFi && [key hasPrefix:IOS_WIFI]) ||
(internetReach.isReachableViaWWAN && [key hasPrefix:IOS_CELLULAR])) {
address = addresses[key];
if(address) *stop = YES;
}
} ];
現在のソリューションはOS Xでen0デバイスを返しません。次のコードは、System Configuration Frameworkを使用してインターフェイスを取得し、次に標準C関数を使用してIPアドレスを取得します。
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <net/if.h>
#define IFT_ETHER 0x6
#include <SystemConfiguration/SCDynamicStore.h>
+(void)getInterfaces
{
SCDynamicStoreRef storeRef = SCDynamicStoreCreate(NULL, (CFStringRef)@"FindCurrentInterfaceIpMac", NULL, NULL);
CFPropertyListRef global = SCDynamicStoreCopyValue (storeRef,CFSTR("State:/Network/Interface"));
id primaryInterface = [(__bridge NSDictionary *)global valueForKey:@"Interfaces"];
for (NSString* item in primaryInterface)
{
if(get_iface_address([item UTF8String]))
{
NSString *ip = [NSString stringWithUTF8String:get_iface_address([item UTF8String])];
NSLog(@"interface: %@ - %@",item,ip);
} else
NSLog(@"interface: %@",item);
}
}
static char * get_iface_address (char *interface)
{
int sock;
uint32_t ip;
struct ifreq ifr;
char *val;
if (!interface)
return NULL;
/* determine UDN according to MAC address */
sock = socket (AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
perror ("socket");
return NULL;
}
strcpy (ifr.ifr_name, interface);
ifr.ifr_addr.sa_family = AF_INET;
if (ioctl (sock, SIOCGIFADDR, &ifr) < 0)
{
perror ("ioctl");
close (sock);
return NULL;
}
val = (char *) malloc (16 * sizeof (char));
ip = ((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr.s_addr;
ip = ntohl (ip);
sprintf (val, "%d.%d.%d.%d",
(ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF);
close (sock);
return val;
}
このファイル のSwiftの優れたソリューションは、すべての詳細を提供します。
私のアプリの1つで、wifi IPアドレスを取得する必要があります。上記の回答を使用しました。Swift 3は次のようになります。
let WIFI_IF = "en0"
let UNKNOWN_IP_ADDRESS = ""
var addresses: [AnyHashable: Any] = ["wireless": UNKNOWN_IP_ADDRESS, "wired": UNKNOWN_IP_ADDRESS, "cell": UNKNOWN_IP_ADDRESS]
var interfaces: UnsafeMutablePointer<ifaddrs>? = nil
var temp_addr: UnsafeMutablePointer<ifaddrs>? = nil
var success: Int = 0
success = Int(getifaddrs(&interfaces))
if success == 0 {
temp_addr = interfaces
while temp_addr != nil {
if temp_addr?.pointee.ifa_addr == nil {
continue
}
if temp_addr?.pointee.ifa_addr.pointee.sa_family == UInt8(AF_INET) {
if (String(utf8String: (temp_addr?.pointee.ifa_name)!) == WIFI_IF) {
addresses["wireless"] = String(utf8String: inet_ntoa(((temp_addr?.pointee.ifa_addr as? sockaddr_in)?.sin_addr)!))
}
}
temp_addr = temp_addr?.pointee.ifa_next
}
}
このコードでは、?
でオプションとして使用した各ステートメントでnil
を確認する必要があるため、クラッシュします。したがって、クラスで特定のリンクされたファイルを使用することをお勧めします。次のように確認するのが簡単になりました。
class func getWifiIPAddress() -> String {
var wifiIp = ""
let WIFI_IF = "en0"
let allInterface = Interface.allInterfaces()
for interf in allInterface {
if interf.name == WIFI_IF {
if let address = interf.address {
if address.contains(".") {
wifiIp = address
break
}
}
}
}
return wifiIp
}
Interface Classは、「fb00 ::」のような"."
アドレスと「101.10.1.1」のようなアドレスに対して、iPhoneで2つのインターフェイスを返すため、en0
の文字列を解析しました。