aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOskari Timperi <oskari.timperi@iki.fi>2020-02-19 22:14:08 +0200
committerOskari Timperi <oskari.timperi@iki.fi>2020-02-19 22:14:08 +0200
commitb5665310e5c1e5d60893248ff146b1a60032dd87 (patch)
tree57e7eb9aea927f08c57879b452e82c3e6da4af14
downloadifaddr-nim-b5665310e5c1e5d60893248ff146b1a60032dd87.tar.gz
ifaddr-nim-b5665310e5c1e5d60893248ff146b1a60032dd87.zip
initial commitHEADmaster
-rw-r--r--LICENSE21
-rw-r--r--README.md14
-rw-r--r--ifaddr.nimble13
-rw-r--r--src/ifaddr.nim26
-rw-r--r--src/ifaddr/ifaddrposix.nim77
-rw-r--r--src/ifaddr/private/utils.nim26
-rw-r--r--src/ifaddr/windows.nim81
-rw-r--r--tests/config.nims1
-rw-r--r--tests/test1.nim18
9 files changed, 277 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..2887a70
--- /dev/null
+++ b/LICENSE
@@ -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)