Как получить IP-адрес в Swift
Как я могу получить свой местный IpAddress?
Я пытался использовать это Obj C
пример: как программно получить ip адрес iphone
Когда я доберусь до функции getifaddrs()
Я не могу получить дальше. я не могу использовать функцию.
Есть ли альтернативный способ сделать это, или я подхожу к этому неправильно?
6 ответов
Как выяснилось в обсуждении, OP нужен адрес интерфейса на Mac, а не на устройстве iOS, как я думал изначально. Код, указанный в вопросе, проверяет имя интерфейса "en0", которое является интерфейсом WiFi на iPhone. На Mac имеет больше смысла проверять любой "работающий" интерфейс. Поэтому я переписал ответ. Теперь это быстрый перевод кода в Обнаружение любой подключенной сети.
getifaddrs()
определяется в <ifaddrs.h>
, который не включен по умолчанию. Поэтому вы должны создать соединительный заголовок и добавить
#include <ifaddrs.h>
Следующая функция возвращает массив с именами всех локальных "запущенных" сетевых интерфейсов.
func getIFAddresses() -> [String] {
var addresses = [String]()
// Get list of all interfaces on the local machine:
var ifaddr : UnsafeMutablePointer<ifaddrs> = nil
if getifaddrs(&ifaddr) == 0 {
// For each interface ...
var ptr = ifaddr
while ptr != nil {
defer { ptr = ptr.memory.ifa_next }
let flags = Int32(ptr.memory.ifa_flags)
let addr = ptr.memory.ifa_addr.memory
// Check for running IPv4, IPv6 interfaces. Skip the loopback interface.
if (flags & (IFF_UP|IFF_RUNNING|IFF_LOOPBACK)) == (IFF_UP|IFF_RUNNING) {
if addr.sa_family == UInt8(AF_INET) || addr.sa_family == UInt8(AF_INET6) {
// Convert interface address to a human readable string:
var hostname = [CChar](count: Int(NI_MAXHOST), repeatedValue: 0)
if (getnameinfo(ptr.memory.ifa_addr, socklen_t(addr.sa_len), &hostname, socklen_t(hostname.count),
nil, socklen_t(0), NI_NUMERICHOST) == 0) {
if let address = String.fromCString(hostname) {
addresses.append(address)
}
}
}
}
}
freeifaddrs(ifaddr)
}
return addresses
}
Обновление для Swift 3. В дополнение к принятию кода для множества изменений в Swift 3, итерирование по всем интерфейсам теперь может использовать новый обобщенный sequence()
функция:
func getIFAddresses() -> [String] {
var addresses = [String]()
// Get list of all interfaces on the local machine:
var ifaddr : UnsafeMutablePointer<ifaddrs>?
guard getifaddrs(&ifaddr) == 0 else { return [] }
guard let firstAddr = ifaddr else { return [] }
// For each interface ...
for ptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
let flags = Int32(ptr.pointee.ifa_flags)
let addr = ptr.pointee.ifa_addr.pointee
// Check for running IPv4, IPv6 interfaces. Skip the loopback interface.
if (flags & (IFF_UP|IFF_RUNNING|IFF_LOOPBACK)) == (IFF_UP|IFF_RUNNING) {
if addr.sa_family == UInt8(AF_INET) || addr.sa_family == UInt8(AF_INET6) {
// Convert interface address to a human readable string:
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
if (getnameinfo(ptr.pointee.ifa_addr, socklen_t(addr.sa_len), &hostname, socklen_t(hostname.count),
nil, socklen_t(0), NI_NUMERICHOST) == 0) {
let address = String(cString: hostname)
addresses.append(address)
}
}
}
}
freeifaddrs(ifaddr)
return addresses
}
Отвечая на Is there an alternative way to getifaddrs() for getting the ip-address?
Да, есть альтернативный способ. Вы также можете получить IP-адрес, используя ioctl()
, Самым чистым способом было бы сделать это в C, а затем обернуть его в Swift. Итак, рассмотрим это:
Создать C Source File
(Xcode должен создать его вместе с.h) и не забудьте добавить заголовок в заголовок моста вашего проекта или в заголовок зонтика, если у вас есть каркас сенсорного ввода какао.
Добавьте следующее в ваш источник.c и объявление метода в.h:
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>
int _interfaceAddressForName(char* interfaceName, struct sockaddr* interfaceAddress) {
struct ifreq ifr;
int fd = socket(AF_INET, SOCK_DGRAM, 0);
ifr.ifr_addr.sa_family = AF_INET;
strncpy(ifr.ifr_name, interfaceName, IFNAMSIZ-1);
int ioctl_res;
if ( (ioctl_res = ioctl(fd, SIOCGIFADDR, &ifr)) < 0){
return ioctl_res;
}
close(fd);
memcpy(interfaceAddress, &ifr.ifr_addr, sizeof(struct sockaddr));
return 0;
}
Ваша оболочка Swift может выглядеть примерно так:
public enum Error:ErrorType {
case IOCTLFailed(Int32)
case StringIsNotAnASCIIString
}
public func interfaceAddress(forInterfaceWithName interfaceName: String) throws -> sockaddr_in {
guard let cString = interfaceName.cStringUsingEncoding(NSASCIIStringEncoding) else {
throw Error.StringIsNotAnASCIIString
}
let addressPtr = UnsafeMutablePointer<sockaddr>.alloc(1)
let ioctl_res = _interfaceAddressForName(strdup(cString), addressPtr)
let address = addressPtr.move()
addressPtr.dealloc(1)
if ioctl_res < 0 {
throw Error.IOCTLFailed(errno)
} else {
return unsafeBitCast(address, sockaddr_in.self)
}
}
Затем вы можете использовать его в своем коде, как:
let interfaceName = "en0"
do {
let wlanInterfaceAddress = try interfaceAddress(forInterfaceWithName: interfaceName)
print(String.fromCString(inet_ntoa(wlanInterfaceAddress.sin_addr))!)
} catch {
if case Error.IOCTLFailed(let errno) = error where errno == ENXIO {
print("interface(\(interfaceName)) is not available")
} else {
print(error)
}
}
en0
как правило, это интерфейс, который вам нужен, что означает WLAN
Если вам также нужно знать доступные имена интерфейсов, вы можете использовать if_indextoname()
:
public func interfaceNames() -> [String] {
let MAX_INTERFACES = 128;
var interfaceNames = [String]()
let interfaceNamePtr = UnsafeMutablePointer<Int8>.alloc(Int(IF_NAMESIZE))
for interfaceIndex in 1...MAX_INTERFACES {
if (if_indextoname(UInt32(interfaceIndex), interfaceNamePtr) != nil){
if let interfaceName = String.fromCString(interfaceNamePtr) {
interfaceNames.append(interfaceName)
}
} else {
break
}
}
interfaceNamePtr.dealloc(Int(IF_NAMESIZE))
return interfaceNames
}
func getIfConfigOutput() -> [String:String] {
let cmd = "for i in $(ifconfig -lu); do if ifconfig $i | grep -q \"status: active\" ; then echo $i; fi; done"
let interfaceString = shell(cmd)
let interfaceArray = interfaceString.components(separatedBy: "\n")
var finalDictionary:[String:String] = [String:String]()
for (i,_) in interfaceArray.enumerated() {
if (interfaceArray[i].hasPrefix("en")){
let sp = shell("ifconfig \(interfaceArray[i]) | grep \"inet \" | grep -v 127.0.0.1 | cut -d\\ -f2")
finalDictionary[interfaceArray[i]] = sp.replacingOccurrences(of: "\n", with: "")
}
}
print(finalDictionary)
return finalDictionary
}
func shell(_ args: String) -> String {
var outstr = ""
let task = Process()
task.launchPath = "/bin/sh"
task.arguments = ["-c", args]
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
if let output = NSString(data: data, encoding: String.Encoding.utf8.rawValue) {
outstr = output as String
}
task.waitUntilExit()
return outstr
}
Это поможет вам. Код возвращает словарь с интерфейсами и IP-адресами, связанными с ним.
Код работает для Swift 5+ и iOS 13+
Чтобы получить IP-адрес устройства при включении Wi-Fi , проводных или мобильных данных , используйте следующий метод:
func getIPAddress() -> String? {
var address : String?
// Get list of all interfaces on the local machine:
var ifaddr : UnsafeMutablePointer<ifaddrs>?
guard getifaddrs(&ifaddr) == 0 else { return nil }
guard let firstAddr = ifaddr else { return nil }
// For each interface ...
for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
let interface = ifptr.pointee
// Check for IPv4 or IPv6 interface:
let addrFamily = interface.ifa_addr.pointee.sa_family
if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) {
// Check interface name:
// wifi = ["en0"]
// wired = ["en2", "en3", "en4"]
// cellular = ["pdp_ip0","pdp_ip1","pdp_ip2","pdp_ip3"]
let name = String(cString: interface.ifa_name)
if name == "en0" || name == "en2" || name == "en3" || name == "en4" || name == "pdp_ip0" || name == "pdp_ip1" || name == "pdp_ip2" || name == "pdp_ip3" {
// Convert interface address to a human readable string:
var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
getnameinfo(interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len),
&hostname, socklen_t(hostname.count),
nil, socklen_t(0), NI_NUMERICHOST)
address = String(cString: hostname)
}
}
}
freeifaddrs(ifaddr)
return address
}
Здесь код/проверки работают для Wi-Fi , проводных и мобильных данных :
если имя == "en0" || имя == "en2" || имя == "en3" || имя == "en4" || имя == "pdp_ip0" || имя == "pdp_ip1" || имя == "pdp_ip2" || имя == "pdp_ip3"
Надежда поможет! :)
В Swift 5 вы можете использовать ioctl напрямую со Swift, вместо того, чтобы прибегать к «C» для получения деталей IP-адреса для данного интерфейса.
Нравится:
private enum AddressRequestType {
case ipAddress
case netmask
}
// From BDS ioccom.h
// Macro to create ioctl request
private static func _IOC (_ io: UInt32, _ group: UInt32, _ num: UInt32, _ len: UInt32) -> UInt32 {
let rv = io | (( len & UInt32(IOCPARM_MASK)) << 16) | ((group << 8) | num)
return rv
}
// Macro to create read/write IOrequest
private static func _IOWR (_ group: Character , _ num : UInt32, _ size: UInt32) -> UInt32 {
return _IOC(IOC_INOUT, UInt32 (group.asciiValue!), num, size)
}
private static func _interfaceAddressForName (_ name: String, _ requestType: AddressRequestType) throws -> String {
var ifr = ifreq ()
ifr.ifr_ifru.ifru_addr.sa_family = sa_family_t(AF_INET)
// Copy the name into a zero padded 16 CChar buffer
let ifNameSize = Int (IFNAMSIZ)
var b = [CChar] (repeating: 0, count: ifNameSize)
strncpy (&b, name, ifNameSize)
// Convert the buffer to a 16 CChar tuple - that's what ifreq needs
ifr.ifr_name = (b [0], b [1], b [2], b [3], b [4], b [5], b [6], b [7], b [8], b [9], b [10], b [11], b [12], b [13], b [14], b [15])
let ioRequest: UInt32 = {
switch requestType {
case .ipAddress: return _IOWR("i", 33, UInt32(MemoryLayout<ifreq>.size)) // Magic number SIOCGIFADDR - see sockio.h
case .netmask: return _IOWR("i", 37, UInt32(MemoryLayout<ifreq>.size)) // Magic number SIOCGIFNETMASK
}
} ()
if ioctl(socket(AF_INET, SOCK_DGRAM, 0), UInt(ioRequest), &ifr) < 0 {
throw POSIXError (POSIXErrorCode (rawValue: errno) ?? POSIXErrorCode.EINVAL)
}
let sin = unsafeBitCast(ifr.ifr_ifru.ifru_addr, to: sockaddr_in.self)
let rv = String (cString: inet_ntoa (sin.sin_addr))
return rv
}
public static func getInterfaceIPAddress (interfaceName: String) throws -> String {
return try _interfaceAddressForName(interfaceName, .ipAddress)
}
public static func getInterfaceNetMask (interfaceName: String) throws -> String {
return try _interfaceAddressForName(interfaceName, .netmask)
}
К сожалению, ни один из методов не будет работать в iOS 11, так как Apple заблокировала доступ сторонних разработчиков к MAC-адресам. Разработчики сетевых приложений даже подали петицию в Apple, чтобы отменить эту политику https://www.change.org/p/apple-save-networking-tools-on-ios-11