diff options
| author | Oskari Timperi <oskari.timperi@iki.fi> | 2020-02-19 22:14:08 +0200 |
|---|---|---|
| committer | Oskari Timperi <oskari.timperi@iki.fi> | 2020-02-19 22:14:08 +0200 |
| commit | b5665310e5c1e5d60893248ff146b1a60032dd87 (patch) | |
| tree | 57e7eb9aea927f08c57879b452e82c3e6da4af14 | |
| download | ifaddr-nim-master.tar.gz ifaddr-nim-master.zip | |
| -rw-r--r-- | LICENSE | 21 | ||||
| -rw-r--r-- | README.md | 14 | ||||
| -rw-r--r-- | ifaddr.nimble | 13 | ||||
| -rw-r--r-- | src/ifaddr.nim | 26 | ||||
| -rw-r--r-- | src/ifaddr/ifaddrposix.nim | 77 | ||||
| -rw-r--r-- | src/ifaddr/private/utils.nim | 26 | ||||
| -rw-r--r-- | src/ifaddr/windows.nim | 81 | ||||
| -rw-r--r-- | tests/config.nims | 1 | ||||
| -rw-r--r-- | tests/test1.nim | 18 |
9 files changed, 277 insertions, 0 deletions
@@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Oskari Timperi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5f0e32f --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# ifaddr - Enumerate IP addresses on the local network adapters + +This is a Nim port of a Python package of the same name. See the original [here](https://github.com/pydron/ifaddr). + +Check the example code in `tests/test1.nim`. It should output something like the following: + +``` +IPs of network adapter lo + 127.0.0.1/8 + 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:01/128 +IPs of network adapter wlp2s0 + 192.168.0.101/24 + FE:80:00:00:00:00:00:00:65:93:59:F2:33:F0:AB:33/64 +``` diff --git a/ifaddr.nimble b/ifaddr.nimble new file mode 100644 index 0000000..70cd21f --- /dev/null +++ b/ifaddr.nimble @@ -0,0 +1,13 @@ +# Package + +version = "0.1.0" +author = "Oskari Timperi" +description = "A new awesome nimble package" +license = "MIT" +srcDir = "src" + + + +# Dependencies + +requires "nim >= 1.0.6" diff --git a/src/ifaddr.nim b/src/ifaddr.nim new file mode 100644 index 0000000..b2b3cd9 --- /dev/null +++ b/src/ifaddr.nim @@ -0,0 +1,26 @@ +from net import IpAddress, IpAddressFamily + +type + Adapter* = object + ## Represents a network interface device controller, such as a network card. An adapter can + ## have multiple IP addresses. + ips*: seq[IP] ## List of ``IP`` instances in the order reported by the system. + name*: string ## Unique name that identifies the adapter in the system. + niceName*: string ## Human readable name of the adapter. + + IP* = object + ## Represents an IP address of an adapter. + case family*: IpAddressFamily + of IpAddressFamily.IPv6: + flowInfo*: uint32 + scopeId*: uint32 + of IpAddressFamily.IPv4: + discard + ip*: IpAddress ## IP address + networkPrefix*: int ## Number of bits of the IP that represent the network. + niceName*: string ## Human readable name for this IP. + +when defined(windows) or defined(nimdoc): + include ifaddr/windows +elif defined(linux) or defined(maxosx): + include ifaddr/ifaddrposix diff --git a/src/ifaddr/ifaddrposix.nim b/src/ifaddr/ifaddrposix.nim new file mode 100644 index 0000000..1fca199 --- /dev/null +++ b/src/ifaddr/ifaddrposix.nim @@ -0,0 +1,77 @@ +import bitops +from nativesockets import getAddrString, ntohl, Port +import os +from posix import SockAddr, AF_INET, AF_INET6, Sockaddr_in, Sockaddr_in6, SockLen +import tables + +import private/utils + +type + ifaddrs {.importc: "struct ifaddrs", header: "<ifaddrs.h>".} = object + ifa_next: ptr ifaddrs + ifa_name: cstring + ifa_flags: cuint + ifa_addr: ptr SockAddr + ifa_netmask: ptr SockAddr + # Skip fields we don't care about + +proc getifaddrs(ifap: ptr ptr ifaddrs): cint {.importc, header: "<ifaddrs.h>".} + +proc freeifaddrs(ifa: ptr ifaddrs) {.importc, header: "<ifaddrs.h>".} + +proc getPrefixLen(sa: ptr SockAddr): int = + if sa.sa_family.uint32 == AF_INET.uint32: + let sa4 = cast[ptr Sockaddr_in](sa) + result = countLeadingZeroBits(bitnot(ntohl(sa4.sin_addr.s_addr))) + elif sa.sa_family.uint32 == AF_INET6.uint32: + # TODO: not sure if this is exactly the right thing to do ... + let sa6 = cast[ptr Sockaddr_in6](sa) + for i in 0..15: + inc(result, countSetBits(sa6.sin6_addr.s6_addr[i].uint8)) + else: + result = 0 + +proc getAdapters*(): seq[Adapter] = + var addrs: ptr ifaddrs + + if getifaddrs(addr addrs) != 0: + raiseOSError(osLastError(), "getifaddrs") + + defer: freeifaddrs(addrs) + + var ips = initOrderedTable[string, Adapter]() + + var curr = addrs + while curr != nil: + let name = $curr.ifa_name + if curr.ifa_addr != nil: + var + ip: IpAddress + flowInfo: uint32 + scopeId: uint32 + try: + fromSockAddrPtr(curr.ifa_addr, ip, flowInfo, scopeId) + except: + curr = curr.ifa_next + continue + if name notin ips: + ips[name] = Adapter(name: name, niceName: name, ips: @[]) + let prefix = getPrefixLen(curr.ifa_netmask) + case ip.family + of IpAddressFamily.IPv6: + ips[name].ips.add(IP(family: IpAddressFamily.IPv6, + flowInfo: flowInfo, + scopeId: scopeId, + ip: ip, + networkPrefix: prefix, + niceName: name)) + of IpAddressFamily.IPv4: + ips[name].ips.add(IP(family: IpAddressFamily.IPv4, + ip: ip, + networkPrefix: prefix, + niceName: name)) + curr = curr.ifa_next + + result = @[] + for v in ips.values: + result.add(v) diff --git a/src/ifaddr/private/utils.nim b/src/ifaddr/private/utils.nim new file mode 100644 index 0000000..ee84b70 --- /dev/null +++ b/src/ifaddr/private/utils.nim @@ -0,0 +1,26 @@ +from net import IpAddress + +when defined(windows) or defined(nimdoc): + from winlean import SockAddr, Sockaddr_in, Sockaddr_in6, AF_INET, AF_INET6, SockLen +elif defined(linux) or defined(maxosx): + from posix import SockAddr, Sockaddr_in, Sockaddr_in6, AF_INET, AF_INET6, SockLen + +from nativesockets import Port +from net import fromSockAddr + + +proc fromSockAddrPtr*(sa: ptr SockAddr, address: var IpAddress, flowInfo: var uint32, + scopeId: var uint32) = + var port: Port + if sa.sa_family.uint32 == AF_INET.uint32: + let sa4 = cast[ptr Sockaddr_in](sa)[] + let size = sizeof(Sockaddr_in).SockLen + fromSockAddr(sa4, size, address, port) + elif sa.sa_family.uint32 == AF_INET6.uint32: + let sa6 = cast[ptr Sockaddr_in6](sa)[] + let size = sizeof(Sockaddr_in6).SockLen + fromSockAddr(sa6, size, address, port) + flowInfo = sa6.sin6_flowinfo.uint32 + scopeId = sa6.sin6_scope_id.uint32 + else: + raise newException(Exception, "unknown sa_family") diff --git a/src/ifaddr/windows.nim b/src/ifaddr/windows.nim new file mode 100644 index 0000000..0d0e10d --- /dev/null +++ b/src/ifaddr/windows.nim @@ -0,0 +1,81 @@ +# Required for IP_ADAPTER_ADDRESSES_LH +{.emit:"""/*INCLUDESECTION*/ +#include <winsock2.h> +""".} + +import winlean +import os + +import private/utils + + +type + IP_ADAPTER_ADDRESSES_LH {.importc, header: "<iphlpapi.h>".} = object + Next: ptr IP_ADAPTER_ADDRESSES_LH + AdapterName: cstring + FirstUnicastAddress: ptr IP_ADAPTER_UNICAST_ADDRESS_LH + Description: WideCString + FriendlyName: WideCString + + IP_ADAPTER_UNICAST_ADDRESS_LH {.importc, header: "<iphlpapi.h>".} = object + Next: ptr IP_ADAPTER_UNICAST_ADDRESS_LH + Address: SOCKET_ADDRESS + OnLinkPrefixLength: uint8 + + SOCKET_ADDRESS {.importc, header: "<winsock2.h>"} = object + lpSockaddr: ptr SockAddr + iSockaddrLength: cint + +const + ERROR_BUFFER_OVERFLOW = 0x6f + +proc GetAdaptersAddresses(family: ULONG, flags: ULONG, reserved: pointer, adapterAddresses: ptr IP_ADAPTER_ADDRESSES_LH, size: ptr ULONG): ULONG {.stdcall, dynlib: "iphlpapi.dll", importc.} + +proc getAdapters*(): seq[Adapter] = + result = @[] + + var + bufsz: ULONG = 15*1024 + rc: DWORD = ERROR_BUFFER_OVERFLOW + buffer: seq[byte] + + while rc == ERROR_BUFFER_OVERFLOW: + buffer.setLen(bufsz) + rc = GetAdaptersAddresses(AF_UNSPEC, 0, nil, + cast[ptr IP_ADAPTER_ADDRESSES_LH](addr buffer[0]), addr bufsz) + + if rc != 0: + raiseOSError(rc.OSErrorCode, "GetAdaptersAddresses") + + let first = cast[ptr IP_ADAPTER_ADDRESSES_LH](addr buffer[0]) + var curr = first + while curr != nil: + let name = $curr.AdapterName + let niceName = $curr.Description + var ips: seq[IP] = @[] + + var currAddr = curr.FirstUnicastAddress + while currAddr != nil: + var + ip: IpAddress + scopeId: uint32 + flowInfo: uint32 + fromSockAddrPtr(currAddr.Address.lpSockaddr, ip, flowInfo, scopeId) + case ip.family + of IpAddressFamily.IPv6: + ips.add(IP(family: IpAddressFamily.IPv6, + flowInfo: flowInfo, + scopeId: scopeId, + ip: ip, + networkPrefix: currAddr.OnLinkPrefixLength.int, + niceName: $curr.FriendlyName)) + of IpAddressFamily.IPv4: + ips.add(IP(family: IpAddressFamily.IPv4, + ip: ip, + networkPrefix: currAddr.OnLinkPrefixLength.int, + niceName: $curr.FriendlyName)) + currAddr = currAddr.Next + + result.add(Adapter(ips: ips, name: name, niceName: niceName)) + + curr = curr.Next diff --git a/tests/config.nims b/tests/config.nims new file mode 100644 index 0000000..3bb69f8 --- /dev/null +++ b/tests/config.nims @@ -0,0 +1 @@ +switch("path", "$projectDir/../src")
\ No newline at end of file diff --git a/tests/test1.nim b/tests/test1.nim new file mode 100644 index 0000000..6f4ff82 --- /dev/null +++ b/tests/test1.nim @@ -0,0 +1,18 @@ +from net import IpAddressFamily +from strutils import join, toHex +from sequtils import mapIt + +import ifaddr + +proc `$`(ip: IP): string = + case ip.ip.family + of IpAddressFamily.IPv6: + result = join(mapIt(ip.ip.address_v6, it.toHex()), ":") + of IpAddressFamily.IPv4: + result = join(ip.ip.address_v4, ".") + result &= "/" & $ip.networkPrefix + +for adapter in getAdapters(): + echo("IPs of network adapter ", adapter.niceName) + for ip in adapter.ips: + echo(" ", ip) |
