linux-nvme/libnvme

IP address comparison may fail even when two addresses are equivalent

martin-belanger opened this issue · 10 comments

This is somehow related to PR: #647

IPv6 has a special syntax where consecutive sections of zeros can be replaced with two colons (::). In other words, the following two IPv6 addresses are equivalent:

2001:0db8:0000:0000:0000:ff00:0042:8329 == 2001:0db8::ff00:0042:8329

Also, IPv6 supports "IPv4-mapped IPv6 addresses" where an IPv4 address can be expressed as an IPv6 address. Again, this can lead to comparison failures when in fact two addresses match. Here's an example:

::ffff:192.0.2.128  == 192.0.2.128

Although unlikely, IPv4 addresses can include leading 0s.

192.0.2.128 == 192.000.002.128

Currently, libnvme uses case-insensitive string comparison (strcasecmp) to determine if two IP addresses are equal. This would fail for the above cases.

I don't have a solution for this. Python provides a module (ipaddress) that makes comparing IP addresses easy and covers all cases above. I don't know if there is an equivalent C library that we could use. Otherwise, we may have to write an IP address comparison function.

Just to give you an idea of what this is trying to solve.

##########################################################################
Step 1) Connect using compressed IPv6 (fe80::800:27ff:fe00:0)
and specify interface using scope syntax (%enp0s8).
##########################################################################
$ sudo nvme discover -p -t tcp -s 8009 -a fe80::800:27ff:fe00:0%enp0s8
$ nvme list -v
Subsystem        Subsystem-NQN                                                                                    Controllers
---------------- ------------------------------------------------------------------------------------------------ ----------------
nvme-subsys0     nqn.2014-08.org.nvmexpress.discovery                                                             nvme0

Device   SN                   MN                                       FR       TxPort Address        Subsystem    Namespaces
-------- -------------------- ---------------------------------------- -------- ------ -------------- ------------ ----------------
nvme0    139315012e3c3b70fe9c Linux                                    5.19.0-4 tcp    traddr=fe80::800:27ff:fe00:0%enp0s8,trsvcid=8009,src_addr=fe80::c71b:9e1:c456:f855 nvme-subsys0

Device       Generic      NSID       Usage                      Format           Controllers
------------ ------------ ---------- -------------------------- ---------------- ----------------


##########################################################################
Step 2) Same as before, but specify interface using -f option.

We see that a new connection is created even though it's the same IPv6
address and interface.
##########################################################################
$ sudo nvme discover -p -t tcp -s 8009 -a fe80::800:27ff:fe00:0 -f enp0s8
$ nvme list -v
Subsystem        Subsystem-NQN                                                                                    Controllers
---------------- ------------------------------------------------------------------------------------------------ ----------------
nvme-subsys1     nqn.2014-08.org.nvmexpress.discovery                                                             nvme1
nvme-subsys0     nqn.2014-08.org.nvmexpress.discovery                                                             nvme0

Device   SN                   MN                                       FR       TxPort Address        Subsystem    Namespaces
-------- -------------------- ---------------------------------------- -------- ------ -------------- ------------ ----------------
nvme1    139315012e3c3b70fe9c Linux                                    5.19.0-4 tcp    traddr=fe80::800:27ff:fe00:0,trsvcid=8009,host_iface=enp0s8,src_addr=fe80::c71b:9e1:c456:f855 nvme-subsys1
nvme0    139315012e3c3b70fe9c Linux                                    5.19.0-4 tcp    traddr=fe80::800:27ff:fe00:0%enp0s8,trsvcid=8009,src_addr=fe80::c71b:9e1:c456:f855 nvme-subsys0

Device       Generic      NSID       Usage                      Format           Controllers
------------ ------------ ---------- -------------------------- ---------------- ----------------


##########################################################################
Step 3) Connect using expanded IPv6 address (fe80:0000:0000:0000:800:27ff:fe00:0)
and specify interface using -f option.

We see that a new connection is created even though it's the same IPv6
address and interface as previous 2 connections.
##########################################################################
$ sudo nvme discover -p -t tcp -s 8009 -a fe80:0000:0000:0000:800:27ff:fe00:0 -f enp0s8
$ nvme list -v
Subsystem        Subsystem-NQN                                                                                    Controllers
---------------- ------------------------------------------------------------------------------------------------ ----------------
nvme-subsys2     nqn.2014-08.org.nvmexpress.discovery                                                             nvme2
nvme-subsys1     nqn.2014-08.org.nvmexpress.discovery                                                             nvme1
nvme-subsys0     nqn.2014-08.org.nvmexpress.discovery                                                             nvme0

Device   SN                   MN                                       FR       TxPort Address        Subsystem    Namespaces
-------- -------------------- ---------------------------------------- -------- ------ -------------- ------------ ----------------
nvme2    139315012e3c3b70fe9c Linux                                    5.19.0-4 tcp    traddr=fe80:0000:0000:0000:800:27ff:fe00:0,trsvcid=8009,host_iface=enp0s8,src_addr=fe80::c71b:9e1:c456:f855 nvme-subsys2
nvme1    139315012e3c3b70fe9c Linux                                    5.19.0-4 tcp    traddr=fe80::800:27ff:fe00:0,trsvcid=8009,host_iface=enp0s8,src_addr=fe80::c71b:9e1:c456:f855 nvme-subsys1
nvme0    139315012e3c3b70fe9c Linux                                    5.19.0-4 tcp    traddr=fe80::800:27ff:fe00:0%enp0s8,trsvcid=8009,src_addr=fe80::c71b:9e1:c456:f855 nvme-subsys0

Device       Generic      NSID       Usage                      Format           Controllers
------------ ------------ ---------- -------------------------- ---------------- ----------------


##########################################################################
Step 4) Repeat with more or less 0s in the IPv6 address:

fe80:0:0:0:800:27ff:fe00:0
fe80:00:0:0:800:27ff:fe00:0
fe80:00:00:0:800:27ff:fe00:0
fe80:00:00:00:800:27ff:fe00:0
etc.

Every time, a new connection is created because we do a string comparison
instead of checking whether the addresses are equivalent.
##########################################################################
igaw commented

Thanks for the detailed report. We should definitely fix this.

We may need to sanitize the IP address as soon as it is passed in by the user.

I noticed that even if libnvme is able to recognize that two IP addresses are identical with this new function (see #652), the kernel still thinks they are different IP addresses and will make new connections.

All that to say that I need to submit more changes in #652.

igaw commented

As mentioned in #652, the new compare function should probably prefixed with nvme_ and needs to be exported via libnvme.map if the aim is to be useful outside of the library. If this is not the intent that we should move the declaration to private.h

I will prefix the function with nvme_ and add it to libnvme.map. I think this is a good idea because nvme-cli may also need to check IP addresses, which will require this new function to be public.

igaw commented

BTW, don't know if you care, but there seems at least one combination of IP comparison which is not tested yet: https://app.codecov.io/gh/linux-nvme/libnvme/blob/master/src%2Fnvme%2Futil.c

(not important, just having fun looking at the report :))

Thanks for letting me know. I shall add one more test to cover that part of the code.

@igaw - This is just FYI.

I found that the function traddr_is_hostname() is duplicated (see links below). I plan to address this as part of the IP address sanitization effort.

https://github.com/linux-nvme/libnvme/blob/master/src/nvme/tree.c#L1000
https://github.com/linux-nvme/libnvme/blob/master/src/nvme/fabrics.c#L534

igaw commented

@martin-belanger are we good with this bug report? Is there anything still missing?

Yes we're good now. Closing.