我想以编程方式获取iPad的IP地址。如何查询网络子系统以找出我的IPv4(和IPv6)地址是什么?
PS:我可以以某种方式禁用IPv6吗?
以下代码查找iOS或OSX设备上的所有IPv4和IPv6地址。第一个getIPAddress
方法或多或少地作为这个答案中的旧代码:你可以更喜欢一个或另一个类型的地址,它总是喜欢WIFI而不是蜂窝(显然你可以改变它)。
更有趣的是,它可以返回找到的所有地址的字典,跳过not up
接口的地址或与loopback
相关的地址。以前的代码以及关于该主题的其他解决方案将无法正确解码IPv6(inet_ntoa无法处理它们)。这是Jens Alfke在Apple论坛上向我指出的 - 使用的正确函数是inet_ntop(查看手册页,或者参考Jens提供的这篇inet_ntop文章。
字典键的形式为“interface”“/”“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指出的bug,请参阅注释)。现在返回了环回地址,但您可以轻松取消注释测试以自行排除它们。
EDIT2 :(由一些不知名的人):进一步改进2015年3月13日:如果用户使用VPN(无论是WiFi还是Cellular),之前的代码都会失败。现在,它甚至可以与VPN连接一起使用。 VPN连接优先于WiFi和Cell,因为这是设备处理它的方式。这应该适用于Mac,因为Mac上的VPN连接也使用IF utun0但未经过测试。
EDIT3:(2016年9月8日)鉴于@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地址查找服务?我已将http://ipof.in设置为将设备IP地址作为JSON / XML或纯文本返回的服务。你可以在这里找到它们
如果您想要HTTPS,可以使用与https
前缀相同的URL。优点是,即使您使用Wifi,您也会获得公共地址。
许多现有解决方案仅考虑无线接口,这些接口不适用于通过以太网适配器的有线连接(即没有Wifi或3G);看看这个更新的解决方案,它也考虑通过有线接口获得的IP地址。
iPad: How to get IP address programmatically WIRED (not via wireless)
使用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
}
当前的解决方案不会返回OS X上的en0设备,以下代码使用系统配置框架来获取接口,然后使用标准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;
}
这个答案的灵感来自@ 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的bug吗?),
{
""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
}
所以它是utun1而不是utun0
没有搞清楚为什么我只需要放弃vpn检查:(
----更新----
我向苹果公司提出了一个错误(28131847)并回复说:“并非所有utun接口都用于VPN。还有其他操作系统功能使用utun接口。”
但当我问到如何获得有效的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;
}
} ];
This file的快速解决方案,提供所有细节。
在我的一个应用程序中,我需要获取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在我的iPhone中为en0
地址返回两个接口,如“fb00 ::”和地址如“101.10.1.1”