Skip to content

Adding IPv6 Support for Arduino 3.0.0 #8907

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
225 changes: 178 additions & 47 deletions cores/esp32/IPAddress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,68 +20,86 @@
#include <Arduino.h>
#include <IPAddress.h>
#include <Print.h>
#include <StreamString.h>

IPAddress::IPAddress()
{
_address.dword = 0;
IPAddress::IPAddress() {
#if LWIP_IPV6
_ip = *IP6_ADDR_ANY;
#else
_ip = *IP_ADDR_ANY;
#endif
Copy link
Contributor

@TD-er TD-er Nov 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default constructor does not set all bytes to 0.

}

IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet)
IPAddress::IPAddress(const IPAddress& from)
{
_address.bytes[0] = first_octet;
_address.bytes[1] = second_octet;
_address.bytes[2] = third_octet;
_address.bytes[3] = fourth_octet;
ip_addr_copy(_ip, from._ip);
}

IPAddress::IPAddress(uint32_t address)
{
_address.dword = address;
IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet) {
uint8_t addr[] {
first_octet,
second_octet,
third_octet,
fourth_octet,
};
*this = &addr[0];
}

IPAddress::IPAddress(const uint8_t *address)
{
memcpy(_address.bytes, address, sizeof(_address.bytes));
IPAddress::IPAddress(uint8_t o1, uint8_t o2, uint8_t o3, uint8_t o4, uint8_t o5, uint8_t o6, uint8_t o7, uint8_t o8, uint8_t o9, uint8_t o10, uint8_t o11, uint8_t o12, uint8_t o13, uint8_t o14, uint8_t o15, uint8_t o16) {
setV6();
(*this)[0] = o1;
(*this)[1] = o2;
(*this)[2] = o3;
(*this)[3] = o4;
(*this)[4] = o5;
(*this)[5] = o6;
(*this)[6] = o7;
(*this)[7] = o8;
(*this)[8] = o9;
(*this)[9] = o10;
(*this)[10] = o11;
(*this)[11] = o12;
(*this)[12] = o13;
(*this)[13] = o14;
(*this)[14] = o15;
(*this)[15] = o16;
}

IPAddress& IPAddress::operator=(const uint8_t *address)
{
memcpy(_address.bytes, address, sizeof(_address.bytes));
return *this;
IPAddress::IPAddress(IPType type, const uint8_t *address) {
IPAddress(type, address, 0);
}

IPAddress& IPAddress::operator=(uint32_t address)
{
_address.dword = address;
return *this;
}
IPAddress::IPAddress(IPType type, const uint8_t *address, uint8_t zone) {
if (type == IPv4) {
setV4();
memcpy(&this->_ip.u_addr.ip4, address, 4);
} else if (type == IPv6) {
setV6();
memcpy(&this->_ip.u_addr.ip6.addr[0], address, 16);
setZone(zone);
} else {
#if LWIP_IPV6
_ip = *IP6_ADDR_ANY;
#else
_ip = *IP_ADDR_ANY;
#endif
}

bool IPAddress::operator==(const uint8_t* addr) const
{
return memcmp(addr, _address.bytes, sizeof(_address.bytes)) == 0;
}

size_t IPAddress::printTo(Print& p) const
{
size_t n = 0;
for(int i = 0; i < 3; i++) {
n += p.print(_address.bytes[i], DEC);
n += p.print('.');
bool IPAddress::fromString(const char *address) {
if (!fromString4(address)) {
#if LWIP_IPV6
return fromString6(address);
#else
return false;
#endif
}
n += p.print(_address.bytes[3], DEC);
return n;
}

String IPAddress::toString() const
{
char szRet[16];
sprintf(szRet,"%u.%u.%u.%u", _address.bytes[0], _address.bytes[1], _address.bytes[2], _address.bytes[3]);
return String(szRet);
return true;
}

bool IPAddress::fromString(const char *address)
{
// TODO: add support for "a", "a.b", "a.b.c" formats
bool IPAddress::fromString4(const char *address) {
// TODO: (IPv4) add support for "a", "a.b", "a.b.c" formats

uint16_t acc = 0; // Accumulator
uint8_t dots = 0;
Expand All @@ -103,7 +121,7 @@ bool IPAddress::fromString(const char *address)
// Too much dots (there must be 3 dots)
return false;
}
_address.bytes[dots++] = acc;
(*this)[dots++] = acc;
acc = 0;
}
else
Expand All @@ -117,9 +135,122 @@ bool IPAddress::fromString(const char *address)
// Too few dots (there must be 3 dots)
return false;
}
_address.bytes[3] = acc;
(*this)[3] = acc;

setV4();
return true;
}

IPAddress& IPAddress::operator=(const uint8_t *address) {
uint32_t value;
memcpy_P(&value, address, sizeof(value));
*this = value;
return *this;
}

IPAddress& IPAddress::operator=(uint32_t address) {
setV4();
v4() = address;
return *this;
}

bool IPAddress::operator==(const uint8_t* addr) const {
if (!isV4()) {
return false;
}

uint32_t value;
memcpy_P(&value, addr, sizeof(value));

return v4() == value;
}

size_t IPAddress::printTo(Print& p) const {
size_t n = 0;

#if LWIP_IPV6
if (isV6()) {
int count0 = 0;
for (int i = 0; i < 8; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this supposed to output the canonical format, i.e. with consecutive zeros of the longest run (not necessarily the first) combined?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a ip6addr_ntoa() that will do the convert for us (and we then copy to the print buffer).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also an IPAddress:printTo() function in https://github.com/arduino/ArduinoCore-API/blob/master/api/IPAddress.cpp which has the correct IPv6 IETF canonical format to compress the left-most longest run of two or more zero fields.

It does two passes... first start from the left to find a run of two or more zero fields, only replacing if a later run is longer (not the same). This gives a single location of the left-most longest run. The second pass then outputs, adding the compression at the detected location.

... but if we have access to ip6addr_ntoa() it might be better to just use that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also suggest adding an automated test project. The ArduinoCore-API has automated tests for a whole bunch of corner cases of canonical to string, at https://github.com/arduino/ArduinoCore-API/blob/master/test/src/IPAddress/test_printTo6.cpp

e.g. where there is a run of 2 zeros followed by a later run of 3 zeros.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a cut and paste from ESP8266. I have not experienced any issue in IPv6 formatting. Do you have a breaking example?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A tricky example, where the last run of zeros is longest is: ip(0,0, 0,1, 0,0, 0,0, 0,2, 0,0, 0,0, 0,0). This should output: "0:1:0:0:2::"

Executing the current algorithm by hand gives ":1:0:0:2:0:0:0" (I think)

Some other tricky ones to check

ip(0,0, 0,1, 0,0, 0,0, 0,0, 0,2, 0,0, 0,0); => "0:1::2:0:0"
ip(0,0, 0,2, 0,3, 0,4, 0,5, 0,6, 0,7, 0,8); => "0:2:3:4:5:6:7:8"
ip(0,1, 0,2, 0,3, 0,4, 0,5, 0,6, 0,7, 0,0); => "1:2:3:4:5:6:7:0"
ip(0,0, 0,0, 0,3, 0,4, 0,5, 0,6, 0,7, 0,0); => "::3:4:5:6:7:0"
ip(0,0, 0,2, 0,3, 0,4, 0,5, 0,6, 0,0, 0,0); => "0:2:3:4:5:6::"
ip(0,1, 0,0, 0,0, 0,4, 0,5, 0,0, 0,0, 0,8); => "1::4:5:0:0:8" (both the same length, so compress the left-most)

If you have automated tests, just copy the cases from ArduinoCore

uint16_t bit = PP_NTOHS(raw6()[i]);
if (bit || count0 < 0) {
n += p.printf("%x", bit);
if (count0 > 0)
// no more hiding 0
count0 = -8;
} else
count0++;
if ((i != 7 && count0 < 2) || count0 == 7)
n += p.print(':');
}
// add a zone if zone-id si non-zero
if (_ip.u_addr.ip6.zone) {
n += p.print('%');
char if_name[NETIF_NAMESIZE];
netif_index_to_name(_ip.u_addr.ip6.zone, if_name);
n += p.print(if_name);
}
return n;
}
#endif

for(int i = 0; i < 4; i++) {
n += p.print((*this)[i], DEC);
if (i != 3)
n += p.print('.');
}
return n;
}

String IPAddress::toString() const
{
StreamString sstr;
#if LWIP_IPV6
if (isV6())
sstr.reserve(44); // 8 shorts x 4 chars each + 7 colons + nullterm + '%' + zone-id
else
#endif
sstr.reserve(16); // 4 bytes with 3 chars max + 3 dots + nullterm, or '(IP unset)'
printTo(sstr);
return sstr;
}

bool IPAddress::isValid(const String& arg) {
return IPAddress().fromString(arg);
}

bool IPAddress::isValid(const char* arg) {
return IPAddress().fromString(arg);
}

const IPAddress INADDR46_ANY; // generic "0.0.0.0" for IPv4 & IPv6
const IPAddress INADDR46_NONE(255,255,255,255);

void IPAddress::clear() {
(*this) = INADDR46_ANY;
}

/**************************************/

#if LWIP_IPV6

bool IPAddress::fromString6(const char *address) {
ip6_addr_t ip6;
if (ip6addr_aton(address, &ip6)) {
setV6();
memcpy(&this->_ip.u_addr.ip6.addr[0], ip6.addr, 16);
// look for '%' in string
const char *s = address;
while (*s && *s != '%') { s++; }
if (*s == '%') {
// we have a zone id
setZone(netif_name_to_index(s + 1));
}
return true;
}
return false;
}
#endif // LWIP_IPV6

// declared one time - as external in IPAddress.h
IPAddress INADDR_NONE(0, 0, 0, 0);
IPAddress INADDR_NONE(0, 0, 0, 0); // TODO
Loading