There are many kinds of networks in the world. These range from the very old such as serial links, through to wide area networks made from copper and fibre, to wireless networks of various kinds, both for computers and for telecommunications devices such as phones. These networks obviously differ at the physical link layer, but in many cases they also differed at higher layers of the OSI stack.
Over the years there has been a convergence to the "internet stack" of IP and TCP/UDP. For example, Bluetooth defines physical layers and protocol layers, but on top of that is an IP stack so that the same internet programming techniques can be employed on many Bluetooth devices. Similarly, developing 4G wireless phone technologies such as LTE (Long Term Evolution) will also use an IP stack.
While IP provides the networking layer 3 of the OSI stack, TCP and UDP deal with layer 4. These are not the final word, even in the interenet world: SCTP has come from the telecommunications to challenge both TCP and UDP, while to provide internet services in interplanetary space requires new, under development protocols such as DTN. Nevertheless, IP, TCP and UDP hold sway as principal networking technologies now and at least for a considerable time into the future. Each langauge has some measure of support for this style of programming.
This chapter discusses the IP layer as this is fundamental to all IP networking programs.
The OSI model was devised using a committee process wherein the standard was set up and then implemented. Some parts of the OSI standard are obscure, some parts cannot easily be implemented, some parts have not been implemented.
The TCP/IP protocol was devised through a long-running DARPA project. This worked by implementation followed by RFCs (Request For Comment). TCP/IP is the principal Unix networking protocol. TCP/IP = Transmission Control Protocol/Internet Protocol.
The TCP/IP stack is shorter than the OSI one:
TCP is a connection-oriented protocol,
UDP (User Datagram Protocol) is a connectionless
protocol.
The IP layer provides a connectionless and unreliable delivery system. It considers each datagram independently of the others. Any association between datagrams must be supplied by the higher layers.
The IP layer supplies a checksum that includes its own header. The header includes the source and destination addresses.
The IP layer handles routing through an Internet. It is also responsible for breaking up large datagrams into smaller ones for transmission and reassembling them at the other end.
In order to use a service you must be able to find it. The Internet uses an address scheme for devices such as computers so that they can be located. This addressing scheme was originally devised when there were only a handful of connected computers, and very generously allowed upto 2^32 addresses, using a 32 bit unsigned integer. These are the so-called IPv4 addresses. In recent years, the number of connected (or at least directly addressable) devices has threatened to exceed this number, and so "any day now" we will switch to IPv6 addressing which will allow upto 2^128 addresses, using an unsigned 128 bit integer. The changeover is most likely to be forced by emerging countries, as the developed world has already taken nearly all of the pool of IPv4 addresses.
The address is a 32 bit integer which gives the IP address. This addresses down to a network interface card on a single device. The address is usually written as four bytes in decimal with a dot '.' between them, as in "127.0.0.1" or "66.102.11.104".
The IP address of any device is generally composed of two parts: the address of the network in which the device resides, and the address of the device within that network. Once upon a time, the split between network address and internal address was simple and was based upon the bytes used in the IP address.
This scheme doesn't work well if you want, say, 400 computers on a network. 254 is too small, while 65,536 (-2) is too large. In binary arithmetic terms, you want about 512 (-2). This can be achieved by using a 23 bit network address and 9 bits for the device addresses. Similarly, if you want upto 1024 (-2) devices, you use a 22 bit network address and a 10 bit device address.
Given an IP address of a device, and knowing how many bits N are used for the network address gives a relatively straightforward process for extracting the network address and the device address within that network. Form a "network mask" which is a 32-bit binary number with all ones in the first N places and all zeroes in the remaining ones. For example, if 16 bits are used for the network address, the mask is 11111111111111110000000000000000. It's a little inconvenient using binary, so decimal bytes are usually used. The netmask for 16 bit network addresses is 255.255.0.0, for 24 bit network addresses it is 255.255.255.0, while for 23 bit addresses it would be 255.255.254.0 and for 22 bit addresses it would be 255.255.252.0.
Then to find the network of a device, bit-wise AND it's IP address with the network mask, while the device address within the subnet is found with bit-wise AND of the 1's complement of the mask with the IP address.
The internet has grown vastly beyond original expectations. The initially generous 32-bit addressing scheme is on the verge of running out. There are unpleasant workarounds such as NAT addressing, but eventually we will have to switch to a wider address space. IPv6 uses 128-bit addresses. Even bytes becomes cumbersome to express such addresses, so hexadecimal digits are used, grouped into 4 digits and separated by a colon ':'. A typical address might be 2002:c0e8:82e7:0:0:0:c0e8:82e7.
These addresses are not easy to remember! DNS will become even more important. There are tricks to reducing some addresses, such as eliding zeroes and repeated digits. For example, "localhost" is 0:0:0:0:0:0:0:1, which can be shortened to ::1
Each address is divided into three components: the first is the network address used for internet routing. My ISP for example gives me a 56 bit network address for my home network. Within that, I have 16 bits in which to create subnets. Most homes for example will only have a single subnet. The last part is the device component, of 64 bits, often based on a hosts MAC address, but not necessarily.
IPv6 can be unicast or multicast. Unicast addresses are primarily of three types
fe80::c474:4605:44af:462c%eth0
They are in the range fe80::10
For users, working with IP addresses is too difficult.
Consequently, most hosts are given a host name
such as www.google.com
.
These names are much easier for users to work with.
However, the names must be
resolved to IP addresses for most
network functions. The resolver may be a list
of hard-coded name-address pairs, but much more
common is to use the Domain Name Service (DNS).
This is a highly distributed service that maps names to IP
addresses, and sometimes IP addresses back to names.
We won't go into any of the details of DNS, but most of the rest of this chapter is concerned with how each language uses DNS services to get IP addresses from host names.
The world no longer accepts ASCII as the 'only' text encoding. An increasing number of organisations prefer to work in their own language such as Greek, Arabic, Thai, etc. IDN (Internationalized Domain Names) allows host names to be in any language. But DNS won't accept most of them and they have to be encoded into ASCII for lookup services to find them.
The actual domain name registered is the ASCII name, and s/w has to convert the language-specific name to the ASCII version. This will be discussed in the chapter on Text as it is a complex issue.
The Javadoc for the appropriate class is Class InetAddress
The class java.net.InetAddress
has two subclasses
java.net.Inet4Address
java.net.Inet6Address
InetAddress
can be contructed from the bytes
of the address: 4 bytes for an IPv4 address, and 16 bytes for
an IPv6 address. The method getByName()
can take
a string representation ("127.0.0.1" or "::1" for example)
and create an InetAddress
.
You can tell which type of InetAddress
you have
using instanceof
.
There are methods in each class to tell what type of address you have.
For IPv4, these are isLinkLocalAddress()
,
isMulticastAddress()
and others.
For IPv6 these are isLinkLocalAddress()
,
isMulticastAddress()
and others. There is no
isUniqueLocal()
but there is
IsSiteLocalAddress()
which shouldn't really be there.
Java has the class InetAddress
in package
java.net
. This class has the static method
getByName()
which will create an
InetAddress
object from a host name.
From there, other methods will return the host name and host
address. A sample program is
GetInetInfo.java:
import java.net.InetAddress;
import java.net.UnknownHostException;
public class GetInetInfo{
public static void main(String[] args){
if (args.length != 1) {
System.err.println("Usage: GetInetInfo address");
// System.exit(1);
return;
}
InetAddress address = null;
try {
address = InetAddress.getByName(args[0]);
} catch(UnknownHostException e) {
e.printStackTrace();
// System.exit(2);
return;
}
System.out.println("Host name: " + address.getHostName());
System.out.println("Host address: " + address.getHostAddress());
// System.exit(0);
return;
}
} // GetInetInfo
This can be run by e.g. java jan.newmarch.name
to give
Host name: jan.newmarch.name
Host address: 103.79.105.27
Note that it is only giving one address, with my setup (and probably yours)
an IPv4 address. if you want to get all addresses registered with DNS,
you need to use the method getAllByName()
as in
GetAllInetInfo.java:
import java.net.InetAddress;
import java.net.Inet4Address;
import java.net.UnknownHostException;
public class GetAllInetInfo{
public static void main(String[] args){
if (args.length != 1) {
System.err.println("Usage: GetInetInfo address");
// System.exit(1);
return;
}
InetAddress[] addresses = null;
try {
addresses = InetAddress.getAllByName(args[0]);
} catch(UnknownHostException e) {
e.printStackTrace();
// System.exit(2);
return;
}
if (addresses.length > 0)
System.out.println("Host name: " + addresses[0].getHostName());
for (int n = 0; n < addresses.length; n++) {
InetAddress address = addresses[n];
if (address instanceof Inet4Address)
System.out.print("IPv4 address is ");
else
System.out.print("Ipv6 address is ");
System.out.println(address.getHostAddress());
}
// System.exit(0);
return;
}
} // GetAllInetInfo
Then a typical run gives
$java GetAllInetInfo jan.newmarch.name
Host name: jan.newmarch.name
IPv4 address is 103.79.105.27
Ipv6 address is 2400:3740:200:d900:0:0:0:260
An interface (such as eth0) may have multiple IPv6 addresses. There will usually be a link local address, probably a site local address and there could be many global addresses. A program to list them all is ListNets.java:
// From https://docs.oracle.com/javase/tutorial/networking/nifs/listing.html
import java.io.*;
import java.net.*;
import java.util.*;
import static java.lang.System.out;
public class ListNets {
public static void main(String args[]) throws SocketException {
Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
for (NetworkInterface netint : Collections.list(nets))
displayInterfaceInformation(netint);
}
static void displayInterfaceInformation(NetworkInterface netint) throws SocketException {
out.printf("Display name: %s\n", netint.getDisplayName());
out.printf("Name: %s\n", netint.getName());
Enumeration<InetAddress> inetAddresses = netint.getInetAddresses();
for (InetAddress inetAddress : Collections.list(inetAddresses)) {
out.printf("InetAddress: %s\n", inetAddress);
}
out.printf("\n");
}
}
The package "net" defines many types, functions and methods of use in Go network programming. It is described at Go Package net
The type IP
is defined as an array of bytes
type IP []byte
There are several functions to manipulate a variable of type
IP
, but you are likely to use only some of them in
practice. The function ParseIP(String)
will take a dotted IPv4 address or a colon IPv6 address,
while the IP
method String
will
return a string. Note that you may not get back what you
started with: the string form of 0:0:0:0:0:0:0:1 is ::1.
A program to illustrate this is IP.go:
/* IP
*/
package main
import (
"fmt"
"net"
"os"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s ip-addr\n", os.Args[0])
os.Exit(1)
}
name := os.Args[1]
addr := net.ParseIP(name)
if addr == nil {
fmt.Println("Invalid address")
} else {
fmt.Println("The address is ", addr.String())
}
os.Exit(0)
}
If this is compiled to the executable IP
then it
can run for example as
with response
IP 127.0.0.1
or as
The address is 127.0.0.1
with response
IP 0:0:0:0:0:0:0:1
The address is ::1
There are functions isLinkLocalUnicast()
,
isMulticast()
and isGlobalUnicast()
,
but no function to check IPv6 addresses as unique local.
Many of the other functions and methods in the net package return
a pointer to an IPAddr
. This is simply a structure
containing an IP
.
type IPAddr {
IP IP
}
A primary use of this type is to perform DNS lookups on IP host names.
func ResolveIPAddr(net, addr string) (*IPAddr, os.Error)
where net
is one of "ip", "ip4" or "ip6".
This is shown in the program
ResolveIP.go
/* ResolveIP
*/
package main
import (
"fmt"
"net"
"os"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s hostname\n", os.Args[0])
fmt.Println("Usage: ", os.Args[0], "hostname")
os.Exit(1)
}
name := os.Args[1]
addr, err := net.ResolveIPAddr("ip6", name)
if err != nil {
fmt.Println("Resolution error", err.Error())
os.Exit(1)
}
fmt.Println("Resolved address is ", addr.String())
os.Exit(0)
}
Running ResolveIP www.google.com
returns
If the net parameter is given as "ip6" instead of "ip",
I get
Resolved address is 172.217.25.164
Resolved address is 2404:6800:4006:801::2004
Youo may get different results, depending on where Google appears
to live to you.
The function ResolveIPAddr
will perform a DNS lookup
on a hostname, and return a single IP address. However, hosts may
have multiple IP addresses, usually from multiple network interface
cards. They may also have multiple host names, acting as aliases.
func LookupHost(name string) (cname string, addrs []string, err os.Error)
One of these addresses will be labelled as the "canonical" host name.
If you wish to find the canonical name, use
func LookupCNAME(name string) (cname string, err os.Error)
.
This is shown in the following program
/* LookupHost
*/
package main
import (
"fmt"
"net"
"os"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s hostname\n", os.Args[0])
os.Exit(1)
}
name := os.Args[1]
addrs, err := net.LookupHost(name)
if err != nil {
fmt.Println("Error: ", err.Error())
os.Exit(2)
}
for _, s := range addrs {
fmt.Println(s)
}
os.Exit(0)
}
Note that this function returns strings, not IPAddress
values.
For www.google.com
it prints both the IPv4 and IPv6 addresses
172.217.25.164
2404:6800:4006:806::2004
Similar to the Java program, a Go program to get all addresses for all interfaces is Interfaces.go
// From https://www.socketloop.com/tutorials/golang-get-the-ipv4-and-ipv6-addresses-for-a-specific-network-interface
package main
import (
"fmt"
"net"
)
func main() {
// get available network interfaces for
// this machine
interfaces, err := net.Interfaces()
if err != nil {
fmt.Print(err)
return
}
for _, i := range interfaces {
fmt.Printf("Name : %v \n", i.Name)
byNameInterface, err := net.InterfaceByName(i.Name)
if err != nil {
fmt.Println(err)
}
//fmt.Println("Interface by Name : ", byNameInterface)
addresses, err := byNameInterface.Addrs()
for k, v := range addresses {
fmt.Printf("Interface Address #%v : %v\n", k, v.String())
}
fmt.Println("------------------------------------")
}
}
When run on my server, it gives
Name : lo
Interface Address #0 : 127.0.0.1/8
Interface Address #1 : ::1/128
------------------------------------
Name : eth0
Interface Address #0 : 192.168.2.216/24
Interface Address #1 : fd5a:1870:766c::260/128
Interface Address #2 : 2400:3740:200:d900::260/128
Interface Address #3 : fd5a:1870:766c:0:cc2f:3de0:9947:6895/64
Interface Address #4 : fd5a:1870:766c:0:461e:a1ff:fe3b:7531/64
Interface Address #5 : 2400:3740:200:d900:cc2f:3de0:9947:6895/64
Interface Address #6 : 2400:3740:200:d900:461e:a1ff:fe3b:7531/64
Interface Address #7 : fe80::461e:a1ff:fe3b:7531/64
------------------------------------
Name : docker0
Interface Address #0 : 172.17.42.1/16
An IP address is typically divided into the components of a network address, a subnet and a device portion. The network address and subnet form a prefix to the device portion. The mask is an IP address of all binary ones to match the prefix length, followed by all zeroes.
In order to handle masking operations, there is the type
type IPMask []byte
The simplest function to create a netmask uses the CIDR
notation of ones followed by zeroes upto the number of bits:
func CIDRMask(ones, bits int) IPMask
A mask can then be used by a method of an IP address to find the network for that IP address
func (ip IP) Mask(mask IPMask) IP
An example of the use of this is the following program Mask.go:
/* Mask
*/
package main
import (
"fmt"
"net"
"os"
"strconv"
)
func main() {
if len(os.Args) != 4 {
fmt.Fprintf(os.Stderr, "Usage: %s dotted-ip-addr ones bits\n", os.Args[0])
os.Exit(1)
}
dotAddr := os.Args[1]
ones, _ := strconv.Atoi(os.Args[2])
bits, _ := strconv.Atoi(os.Args[3])
addr := net.ParseIP(dotAddr)
if addr == nil {
fmt.Println("Invalid address")
os.Exit(1)
}
mask := net.CIDRMask(ones, bits)
network := addr.Mask(mask)
fmt.Println("Address is ", addr.String(),
"\nMask length is ", bits,
"\nLeading ones count is ", ones,
"\nMask is (hex) ", mask.String(),
"\nNetwork is ", network.String())
os.Exit(0)
}
This can be compiled to Mask
and run by
For an IPv4 address of
Mask <ip-address> <ones> <zeroes>
103.232.159.187
on a /24 network
we get
For an IPv6 address
go run Mask.go 103.232.159.187 24 32
Address is 103.232.159.187
Mask length is 32
Leading ones count is 24
Mask is (hex) ffffff00
Network is 103.232.159.0
fda3:97c:1eb:fff0:5444:903a:33f0:3a6b
where the network component is fda3:97c:1eb
,
the subnet is fff0
and the device part
is 5444:903a:33f0:3a6b
it gives
go run Mask.go fda3:97c:1eb:fff0:5444:903a:33f0:3a6b 52 128
Address is fda3:97c:1eb:fff0:5444:903a:33f0:3a6b
Mask length is 128
Leading ones count is 52
Mask is (hex) fffffffffffff0000000000000000000
Network is fda3:97c:1eb:f000:
IPv4 netmasks are often given in the 4-byte dotted notation
such as 255.255.255.0
for a /24 network.
There is a function to create a mask from such a 4-byte
IPv4 address
func IPv4Mask(a, b, c, d byte) IPMask
Also, there is a method of IP
which returns
the default mask for IPv4
func (ip IP) DefaultMask() IPMask
Note that the string form of a mask is a hex number such as ffffff00
for a /24 mask.
The following program IPv4Mask.go illustrates these:
/* IPv4Mask
*/
package main
import (
"fmt"
"net"
"os"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "Usage: %s dotted-ip-addr\n", os.Args[0])
os.Exit(1)
}
dotAddr := os.Args[1]
addr := net.ParseIP(dotAddr)
if addr == nil {
fmt.Println("Invalid address")
os.Exit(1)
}
mask := addr.DefaultMask()
network := addr.Mask(mask)
ones, bits := mask.Size()
fmt.Println("Address is ", addr.String(),
"\nDefault mask length is ", bits,
"\nLeading ones count is ", ones,
"\nMask is (hex) ", mask.String(),
"\nNetwork is ", network.String())
os.Exit(0)
}
Python uses the package socket
described at
socket — Low-level networking interface
There is the type ipaddress.ip_address
with
subclasses
IPv4Address
and IPv6Address
.
Example construtors are
ipaddress.IPAddress('192.168.0.1')
ipaddress.IPAddress(3232235521)
ipaddress.IPAddress(b'\xC0\xA8\x00\x01')
all of which are the same IPv4 address.
The method version
will return 4 or 6 depending on the type,
and there are methods such as is_global
The following program gets a single IPv4 only, not IPv6 IP.py illustrates these:
import socket
import sys
if len(sys.argv) < 2:
print('Usage: comm hostname')
exit(1)
try:
# only gets IPv4 address
addr = socket.gethostbyname(sys.argv[1])
except:
print('No address found')
exit(2)
print(addr)
exit(0)
The following program gets all addresses IPs.py illustrates these:
import socket
import sys
if len(sys.argv) < 2:
print('Usage: comm hostname')
exit(1)
try:
allAddr = socket.getaddrinfo(sys.argv[1], None)
except:
print('No address found')
exit(2)
# format is array of (family, type, proto, canonname, sockaddr)
# IPv4 sockaddr is (address, port)
# IPv6 sockaddr is (address, port, flow info, scope id)
ips = set()
for tuple in allAddr:
addr = tuple[4][0]
ips.add(addr)
print(ips)
exit(0)
There doesn't seem to be a generic solution but many O/S specific ones.
See e.g. https://stackoverflow.com/questions/24196932/how-can-i-get-the-ip-address-from-nic-in-python.
The method socket.if_nameindex()
returns all iface names though, getting
partially there.
Node.js uses the package dns
for DNS lookups.
The documentation is at
Node.js Documentation
There does not appear to be a specific type for Ip addresses.
The following program gets one address, here an IPv6 address IP.js illustrates this:
if (process.argv.length < 3) {
concole.log('Usage: command hostname')
process.exit(1)
}
hostname = process.argv[2]
const dns = require('dns');
const options = {
family: 6,
};
dns.lookup(hostname, options, (err, address, family) =>
console.log('address: %j family: IPv%s', address, family));
The following program gets all addresses. IP.js illustrates this:
if (process.argv.length < 3) {
concole.log('Usage: command hostname')
process.exit(1)
}
hostname = process.argv[2]
const dns = require('dns');
// When options.all is true, the result will be an Array.
options = {
all: true
}
dns.lookup(hostname, options, (err, addresses) =>
console.log('addresses: %j', addresses));
// addresses: [{"address":"2606:2800:220:1:248:1893:25c8:1946","family":6}]
This is given using the os.networkInterfaces()
function
interfaces.js illustrates this:
const os = require('os')
ifaces = os.networkInterfaces()
console.dir(ifaces)
Rust has a very small standard library documented at
Crate std
.
The std.net
library has types for IP addresses and
for some TCP and UDP calls. There are no DNS functions, unlike the
other languages considered here.
The small standard library appears to be a deliberate choice by Rust
designers. There is a huge library of user-contributed 'crates' at
The Rust community’s crate registry
and if you search for DNS there it will return over 200 matches.
The philosophy appears to be that crates.io
is
the defacto standard library and that crates should be pulled from there
rather than cluttering up the "real" standard library.
The philosophy is contentious: see Expansion of standard library for lengthy discussions either way. I'm in favour of a large standard library, with consistency of approach, guarantee of non-breaking updates, security, etc, but that isn't going to happen with Rust. So I'll pick what seem to be the most popular crates here and in the sequel. Of course, that may change over time, or even next month.
Rust has the types IpAddr
, Ipv4Addr
and Ipv6Addr
.
The type Ipv4Addr
has a new
constructor that takes
four 8 bit octets (of type u8
) as in
let addr = Ipv4Addr::new(127, 0, 0, 1);
The type Ipv4Addr
also implements fromStr
.
There may be parsing errors in this, so a Result
type is
created
as in
let addr: Result<Ipv4Addr, AddrParseError> = "103.79.105.27".parse();
match addr {
Ok(a) => println!("address {}", a),
Err(e) => println!("error {}", e)
}
Functions include is_loopback()
,
is_private()
as in
use std::net::Ipv4Addr;
assert_eq!(Ipv4Addr::new(127, 0, 0, 1).is_loopback(), true);
assert_eq!(Ipv4Addr::new(45, 22, 13, 197).is_loopback(), false);
assert_eq!(Ipv4Addr::new(10, 0, 0, 1).is_private(), true);
assert_eq!(Ipv4Addr::new(10, 10, 10, 10).is_private(), true);
assert_eq!(Ipv4Addr::new(172, 16, 10, 10).is_private(), true);
assert_eq!(Ipv4Addr::new(172, 29, 45, 14).is_private(), true);
assert_eq!(Ipv4Addr::new(172, 32, 0, 2).is_private(), false);
assert_eq!(Ipv4Addr::new(192, 168, 0, 2).is_private(), true);
assert_eq!(Ipv4Addr::new(192, 169, 0, 2).is_private(), false);
The type Ipv6Addr
has a new
constructor that takes eight 16-bit segments as in
use std::net::Ipv6Addr;
let localhost = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1);
let newmarch = Ipv6Addr::new(0x2400, 0x3740, 0x200, 0xd900, 0, 0, 0, 0x260);
The type Ipv6Addr
also implements fromStr
.
There may be parsing errors in this, so a Result
type is
created
as in
let addr: Result<Ipv6Addr, AddrParseError> = "2400:3740:200:d900::260".parse();
match addr {
Ok(a) => println!("address {}", a),
Err(e) => println!("error {}", e)
}
Functions include is_global()
, is_unicast_link_local_strict()
.
There is no support for DNS in the standard library. The most popular crate for DNS (as at May, 2020) seems to be trust-dns-resolver .
Creating and managing projects using crates is more complex than simply interpreting or compiling a source file. First you need to create a project
cargo new Resolv
which creates the following directory structure
Resolv
Resolv/.git
Resolv/.git/...
Resolv/.gitignore
Resolv/src
Resolv/src/main.rs
Resolv/Cargo.toml
To install the trust-dns-resolver
crate, keep it ip to date, build it,etc
the following line needs to be added to Resolv/Cargo.toml
in the
Dependencies
section:
trust-dns-resolver = "0.19.5"
Then in the Resolv
direstory, after every change to main.rs
, run
cargo build
I copied the example code into src/main.js
and built it - with two main results:
cargo
point this out when I created the project?
The example program will return a single IP address, and IPv4 address if it exists, otherwise an IPv6 address. Adapted to get a host name from standard input, it is IP.rs illustrates these:
use trust_dns_resolver::Resolver;
use trust_dns_resolver::config::*;
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
println!("Usage {} hostname", args[0]);
std::process::exit(1);
}
let hostname = &args[1];
// Construct a new Resolver with default configuration options
// Default tries IPv4 and then IPv6 (if IPv4 fails)
let opts = ResolverOpts::default();
match opts.ip_strategy {
LookupIpStrategy::Ipv4Only => println!("IPv4 only"),
LookupIpStrategy::Ipv6Only => println!("IPv6 only"),
LookupIpStrategy::Ipv4AndIpv6 => println!("IPv4 and v6"),
LookupIpStrategy::Ipv6thenIpv4 => println!("IPv6 then 4"),
LookupIpStrategy::Ipv4thenIpv6 => println!("IPv4 then 6")
}
let resolver = Resolver::new(ResolverConfig::default(), opts).unwrap();
let response = resolver.lookup_ip(hostname);
match response {
Ok(resp) =>
{
// only one value to return
for a in resp.iter() {
println!("addr {}", a);
}
},
Err(err) =>
{
println!("err {}", err);
std::process::exit(2)
}
}
std::process::exit(0);
}
The changes to get both IPv4 and IPv6 addresses are basically just to the options: IPs.rs illustrates these:
use trust_dns_resolver::Resolver;
use trust_dns_resolver::config::*;
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
println!("Usage {} hostname", args[0]);
std::process::exit(1);
}
let hostname = &args[1];
// Construct a new Resolver with default configuration options
// Default gets IPv4 and then IPv6
let mut opts = ResolverOpts::default();
match opts.ip_strategy {
LookupIpStrategy::Ipv4Only => println!("IPv4 only"),
LookupIpStrategy::Ipv6Only => println!("IPv6 only"),
LookupIpStrategy::Ipv4AndIpv6 => println!("IPv4 and v6"),
LookupIpStrategy::Ipv6thenIpv4 => println!("IPv6 then 4"),
LookupIpStrategy::Ipv4thenIpv6 => println!("IPv4 then 6")
}
// then change to find both
opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
let resolver = Resolver::new(ResolverConfig::default(), opts).unwrap();
let response = resolver.lookup_ip(hostname);
match response {
Ok(resp) =>
{
for a in resp.iter() {
println!("addr {}", a);
}
},
Err(err) =>
{
println!("err {}", err);
std::process::exit(2)
}
}
std::process::exit(0);
}
Again, there isn't anything in the standard library.
The most popular crate that runs under Posix and Windows seems to be
get_if_addrs
.
The dependency entry in Cargo.toml
is
get_if_addrs = "0.5.3"
The program to list all IP addresses for each NIC is simplicity itself: iface.rs illustrates these:
use get_if_addrs;
fn main() {
for iface in get_if_addrs::get_if_addrs().unwrap() {
println!("{:#?}", iface);
}
}
Partial output is
Interface {
name: "wlp2s0",
addr: V4(
Ifv4Addr {
ip: 192.168.2.190,
netmask: 255.255.255.0,
broadcast: Some(
192.168.2.255,
),
},
),
}
Interface {
name: "lo",
addr: V6(
Ifv6Addr {
ip: ::1,
netmask: ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff,
broadcast: None,
},
),
}
Interface {
name: "wlp2s0",
addr: V6(
Ifv6Addr {
ip: fd5a:1870:766c::2aa,
netmask: ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff,
broadcast: None,
},
),
}
The Julia network functions are in package Socket
and are formally documented at
Sockets
Julia has the abstract type IPAddr
with subtypes
IPv4
and IPv6
.
There are constructors for each, using an apprpopriate
integer for each e.g.
julia> IPv4(3223256218)
ip"192.30.252.154"
julia> IPv6(3223256218)
ip"::c01e:fc9a"
and also string constructors
julia> ip"127.0.0.1"
ip"127.0.0.1"
julia> @ip_str "2001:db8:0:0:0:0:2:1"
ip"2001:db8::2:1"
Julia uses the C function getaddrinfo()
to get an IPv4 or IPv6 address
using a DNS lookup. A sample program is
IP.jl illustrates these:
using Sockets
if size(ARGS)[1] < 1
println("Usage: IP hostname")
exit(1)
end
try
global ip
ip = Sockets.getaddrinfo(ARGS[1], IPv6)
catch exc
println("No address")
exit(2)
end
println(ip)
To get all IP addresses for a host, use getalladdrinfo()
as in
IPs.jl illustrates these:
# See https://docs.julialang.org/en/v1/stdlib/Sockets/#Sockets.getaddrinfo
using Sockets
if size(ARGS)[1] < 1
println("Usage: IP hostname")
exit(1)
end
try
global ips
ips = Sockets.getalladdrinfo(ARGS[1])
catch exc
println("No address")
exit(2)
end
println(ips)
There doesn't seem to be anything yet.
Copyright © Jan Newmarch, jan@newmarch.name
" Network Programming using Java, Go, Python, Rust, JavaScript and Julia"
by
Jan Newmarch
is licensed under a
Creative Commons Attribution-ShareAlike 4.0 International License
.
Based on a work at
https://jan.newmarch.name/NetworkProgramming/
.