티스토리 뷰

iOS

iOS ) 현재 IP 주소 가져오기.

Zedd0202 2020. 9. 22. 15:42
반응형

 

현재 IP 주소를 가져오는 방법. 기록용 글.

먼저, 터미널에

open /Library/Preferences/SystemConfiguration/NetworkInterfaces.plist 

를 친다.

Wi-Fi일때 en0(이런거는 물리 네트워크 인터페이스 이름이라고 보면됨) 인터페이스 사용.

en1~en4는 Thunderbolt를 의미.

info.plist에는 없지만, 셀룰러 일때는 pdp_ip0..x을 쓰는 것 같다. (내 디바이스는 pdp_ip0만 나온다.)

 

만약 WiFi 주소를 가져오고 싶다!고 하면

func getWiFiAddress() -> 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:
            let name = String(cString: interface.ifa_name)
            if name == "en0" {

                // 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
}

이렇게 하면 된다. 

let name = String(cString: interface.ifa_name)
if name == "en0" {...}

위가 핵심코드. 인터페이스 이름이 en0이면 WiFi니까 WiFi주소를 가져오는거다.

 

근데 만약 내가 셀룰러 환경이다? 

getAddress() // nil 

getAddress는 nil이 되게 된다. 

왜냐면 셀룰러 환경에서는 en0인터페이스가 아닌데, 저걸로 조건문을 걸고있으니 당연히 안된다.

 

그럼 WiFi환경이든 셀룰러환경이든 일단 IP주소를 가지고오고 싶다. 

그럼 일단 

1. name == "en0"조건 삭제.

2. 내가 WiFi환경인지, 셀룰러 환경인지 판단. (optional)

셀룰러를 판단하는 인터페이스 이름도

["pdp_ip0", "pdp_ip1", "pdp_ip2", "pdp_ip3"]

이런것들이 있는 것 같은데....내 디바이스에서는 pdp_ip0밖에 나오질 않는다.

 

# 내가 WiFi환경/셀룰러 환경인지 판단하는 방법

struct ReachabilityStatus {
    
    enum status {
        
        case notReachable
        case reachableViaWWAN
        case reachableViaWiFi
    }
    
    var currentReachabilityStatus: status {
        
        var zeroAddress = sockaddr_in()
        zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
        zeroAddress.sin_family = sa_family_t(AF_INET)
        guard let defaultRouteReachability = withUnsafePointer(to: &zeroAddress, {
            $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
                SCNetworkReachabilityCreateWithAddress(nil, $0)
            }
        }) else {
            return .notReachable
        }
        
        var flags: SCNetworkReachabilityFlags = []
        if !SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags) {
            return .notReachable
        }
        
        if flags.contains(.reachable) == false {
            // The target host is not reachable.
            return .notReachable
        }
        else if flags.contains(.isWWAN) == true {
            // WWAN connections are OK if the calling application is using the CFNetwork APIs.
            return .reachableViaWWAN
        }
        else if flags.contains(.connectionRequired) == false {
            // If the target host is reachable and no connection is required then we'll assume that you're on Wi-Fi...
            return .reachableViaWiFi
        }
        else if (flags.contains(.connectionOnDemand) == true || flags.contains(.connectionOnTraffic) == true) && flags.contains(.interventionRequired) == false {
            // The connection is on-demand (or on-traffic) if the calling application is using the CFSocketStream or higher APIs and no [user] intervention is needed
            return .reachableViaWiFi
        }
        else {
            return .notReachable
        }
    }
}

 

# 해당 환경에 따라 주소 가져오기

enum Network: String {
    case wifi = "en0"
    case cellular = "pdp_ip0"
}

func getAddress(for network: Network) -> 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:
            let name = String(cString: interface.ifa_name)
            if name == network.rawValue {

                // 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) + " \(name)"
            }
        }
    }
    freeifaddrs(ifaddr)

    return address
}

 

# 사용방법

let status = ReachabilityStatus()

switch status.currentReachabilityStatus {
case .reachableViaWiFi:
    self.address1.text = status.getAddress(for: .wifi)
case .reachableViaWWAN:
    self.address1.text = status.getAddress(for: .cellular)
default:
    break
}

 

사실 위 코드는 

enum Network: String {
    case wifi = "en0"
    case cellular = "pdp_ip0"
}

이렇게 되어있고,  넘긴 network의 rawValue로 검사하고 있기 때문에...

if name == "en0" || name == "pdp_ip0"

이거랑 똑같다. 

 

물론 인터페이스가 en0과 pdp_ip0만 있는건 아니다.

private struct InterfaceNames {
    static let wifi = ["en0"]
    static let wired = ["en2", "en3", "en4"]
    static let cellular = ["pdp_ip0", "pdp_ip1", "pdp_ip2", "pdp_ip3"]
    static let supported = wifi + wired + cellular
}

이런것들이 있을 수 있는데 이렇게 해서,

InterfaceNames.supported.contains(name)

이런식으로도 검사할 수도 있다.

 

하지만 이렇게 하면 어떤 인터페이스가 먼저 나오냐에 따라 결과가 달라진다.

현재. 

for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {	
    ...
    let name = String(cString: interface.ifa_name)
    
    if InterfaceNames.supported.contains(name) { .. }
}

이런식으로 되어있는데 for문에서

WiFi환경 + en0이 먼저나옴 -> en4가 먼저나왔다고 해보자 

en0을 가져올 수 있음에도 불구하고 en4가 address로 나오게 된다. 

그게 이상한(?) IP라고는 말할수는 없지만...이게 맞나 싶다.

 

참고로,  

모든 인터페이스에 대해 address를 출력해보면,

"125.0.0.0"이런식으로 나오는 주소가 있는 반면

"ee80::111:11a1:11aa:1111%en2" (지어낸거임)

이런식으로 나오는 ip를 볼 수 있다.

이는 IPv6이며, 뒤에 인터페이스 이름이 붙는것은 "Scoped literal IPv6 addresses"라고 한다. 

 

관련 프로젝트를 github에 올려놨다. 

 

Zedd0202/IP_Address

Contribute to Zedd0202/IP_Address development by creating an account on GitHub.

github.com

 

참고:

stackoverflow.com/a/30754194

 

Swift - Get device's WIFI IP Address

I need to get IP Address of iOS device in Swift. This is not a duplicate of other questions about this! I need to get only WiFi IP address, if there is no wifi ip address - I need to handle it. The...

stackoverflow.com

 

반응형