diff --git a/cores/esp8266/LwipIntfDev.h b/cores/esp8266/LwipIntfDev.h
index 00f29e9dfe..389376c976 100644
--- a/cores/esp8266/LwipIntfDev.h
+++ b/cores/esp8266/LwipIntfDev.h
@@ -46,6 +46,13 @@
 #define DEFAULT_MTU 1500
 #endif
 
+enum EthernetLinkStatus
+{
+    Unknown,
+    LinkON,
+    LinkOFF
+};
+
 template<class RawDev>
 class LwipIntfDev: public LwipIntf, public RawDev
 {
@@ -93,9 +100,11 @@ class LwipIntfDev: public LwipIntf, public RawDev
     void setDefault(bool deflt = true);
 
     // true if interface has a valid IPv4 address
+    // (and ethernet link status is not detectable or is up)
     bool connected()
     {
-        return !!ip4_addr_get_u32(ip_2_ip4(&_netif.ip_addr));
+        return !!ip4_addr_get_u32(ip_2_ip4(&_netif.ip_addr))
+               && (!RawDev::isLinkDetectable() || RawDev::isLinked());
     }
 
     bool routable()
@@ -106,6 +115,9 @@ class LwipIntfDev: public LwipIntf, public RawDev
     // ESP8266WiFi API compatibility
     wl_status_t status();
 
+    // Arduino Ethernet compatibility
+    EthernetLinkStatus linkStatus();
+
 protected:
     err_t netif_init();
     void  check_route();
@@ -282,6 +294,12 @@ wl_status_t LwipIntfDev<RawDev>::status()
     return _started ? (connected() ? WL_CONNECTED : WL_DISCONNECTED) : WL_NO_SHIELD;
 }
 
+template<class RawDev>
+EthernetLinkStatus LwipIntfDev<RawDev>::linkStatus()
+{
+    return RawDev::isLinkDetectable() ? _started && RawDev::isLinked() ? LinkON : LinkOFF : Unknown;
+}
+
 template<class RawDev>
 err_t LwipIntfDev<RawDev>::linkoutput_s(netif* netif, struct pbuf* pbuf)
 {
diff --git a/libraries/lwIP_Ethernet/examples/EthClient/EthClient.ino b/libraries/lwIP_Ethernet/examples/EthClient/EthClient.ino
index 8ca28fa112..169930f6ef 100644
--- a/libraries/lwIP_Ethernet/examples/EthClient/EthClient.ino
+++ b/libraries/lwIP_Ethernet/examples/EthClient/EthClient.ino
@@ -52,6 +52,8 @@ void loop() {
   Serial.print(':');
   Serial.println(port);
 
+  Serial.printf("Link sense: %d (detectable: %d)\n", eth.isLinked(), eth.isLinkDetectable());
+
   // Use WiFiClient class to create TCP connections
   // (this class could have been named TCPClient)
   WiFiClient client;
diff --git a/libraries/lwIP_Ethernet/src/EthernetCompat.h b/libraries/lwIP_Ethernet/src/EthernetCompat.h
index e398d5719d..99dca03e67 100644
--- a/libraries/lwIP_Ethernet/src/EthernetCompat.h
+++ b/libraries/lwIP_Ethernet/src/EthernetCompat.h
@@ -10,13 +10,6 @@ using EthernetUDP    = WiFiUDP;
 using EthernetClient = WiFiClient;
 using EthernetServer = ArduinoWiFiServer;
 
-enum EthernetLinkStatus
-{
-    Unknown,
-    LinkON,
-    LinkOFF
-};
-
 enum
 {
     DHCP_CHECK_NONE        = 0,
@@ -40,7 +33,6 @@ class ArduinoEthernet: public LwipIntfDev<RawDev>
         LwipIntfDev<RawDev>(cs, spi, intr)
     {
         _hardwareStatus = EthernetNoHardware;
-        _linkStatus     = Unknown;
     }
 
     // Arduino-Ethernet API compatibility, order can be either:
@@ -70,7 +62,6 @@ class ArduinoEthernet: public LwipIntfDev<RawDev>
         if (ret)
         {
             _hardwareStatus = EthernetHardwareFound;
-            _linkStatus     = LinkON;
         }
 
         return ret;
@@ -81,19 +72,13 @@ class ArduinoEthernet: public LwipIntfDev<RawDev>
         return _hardwareStatus;
     }
 
-    EthernetLinkStatus linkStatus() const
-    {
-        return _linkStatus;
-    }
-
     int maintain() const
     {
         return DHCP_CHECK_NONE;
     }
 
 protected:
-    HardwareStatus     _hardwareStatus;
-    EthernetLinkStatus _linkStatus;
+    HardwareStatus _hardwareStatus;
 };
 
 using ArduinoWiznet5500lwIP = ArduinoEthernet<Wiznet5500>;
diff --git a/libraries/lwIP_enc28j60/src/utility/enc28j60.cpp b/libraries/lwIP_enc28j60/src/utility/enc28j60.cpp
index 979d88e9ea..09f8ae0fe2 100644
--- a/libraries/lwIP_enc28j60/src/utility/enc28j60.cpp
+++ b/libraries/lwIP_enc28j60/src/utility/enc28j60.cpp
@@ -106,6 +106,7 @@ void serial_printf(const char* fmt, ...)
 #define MACONX_BANK 0x02
 
 #define MACON1 0x00
+#define MACSTAT1 0x01
 #define MACON3 0x02
 #define MACON4 0x03
 #define MABBIPG 0x04
@@ -113,6 +114,16 @@ void serial_printf(const char* fmt, ...)
 #define MAIPGH 0x07
 #define MAMXFLL 0x0a
 #define MAMXFLH 0x0b
+#define MACON2 0x10
+#define MACSTAT2 0x11
+#define MICMD 0x12
+#define MIREGADR 0x14
+#define MIRDL 0x18
+#define MIRDH 0x19
+
+/* MICMD Register Bit Definitions */
+#define MICMD_MIISCAN 0x02
+#define MICMD_MIIRD 0x01
 
 #define MACON1_TXPAUS 0x08
 #define MACON1_RXPAUS 0x04
@@ -135,6 +146,9 @@ void serial_printf(const char* fmt, ...)
 #define MISTAT 0x0a
 #define EREVID 0x12
 
+/* MISTAT Register Bit Definitions */
+#define MISTAT_BUSY 0x01
+
 #define EPKTCNT_BANK 0x01
 #define ERXFCON 0x18
 #define EPKTCNT 0x19
@@ -720,3 +734,26 @@ uint16_t ENC28J60::readFrameData(uint8_t* buffer, uint16_t framesize)
 
     return _len;
 }
+
+uint16_t ENC28J60::phyread(uint8_t reg)
+{
+    // ( https://github.com/JAndrassy/EthernetENC/tree/master/src/utility/enc28j60.h )
+
+    setregbank(MACONX_BANK);
+    writereg(MIREGADR, reg);
+    writereg(MICMD, MICMD_MIIRD);
+    // wait until the PHY read completes
+    while (readreg(MISTAT) & MISTAT_BUSY)
+    {
+        delayMicroseconds(15);
+    }
+    writereg(MICMD, 0);
+    return (readreg(MIRDL) | readreg(MIRDH) << 8);
+}
+
+bool ENC28J60::isLinked()
+{
+    // ( https://github.com/JAndrassy/EthernetENC/tree/master/src/utility/enc28j60.h )
+
+    return !!(phyread(MACSTAT2) & 0x400);
+}
diff --git a/libraries/lwIP_enc28j60/src/utility/enc28j60.h b/libraries/lwIP_enc28j60/src/utility/enc28j60.h
index 07f71f96ff..e6c65d7759 100644
--- a/libraries/lwIP_enc28j60/src/utility/enc28j60.h
+++ b/libraries/lwIP_enc28j60/src/utility/enc28j60.h
@@ -79,6 +79,21 @@ class ENC28J60
     */
     virtual uint16_t readFrame(uint8_t* buffer, uint16_t bufsize);
 
+    /**
+        Check physical link
+        @return true when physical link is up
+    */
+    bool isLinked();
+
+    /**
+        Report whether ::isLinked() API is implemented
+        @return true when ::isLinked() API is implemented
+    */
+    constexpr bool isLinkDetectable() const
+    {
+        return true;
+    }
+
 protected:
     static constexpr bool interruptIsPossible()
     {
@@ -133,6 +148,8 @@ class ENC28J60
     // Previously defined in contiki/core/sys/clock.h
     void clock_delay_usec(uint16_t dt);
 
+    uint16_t phyread(uint8_t reg);
+
     uint8_t   _bank;
     int8_t    _cs;
     SPIClass& _spi;
diff --git a/libraries/lwIP_w5100/src/utility/w5100.h b/libraries/lwIP_w5100/src/utility/w5100.h
index 6e1f2c35cd..9ed1e4cdab 100644
--- a/libraries/lwIP_w5100/src/utility/w5100.h
+++ b/libraries/lwIP_w5100/src/utility/w5100.h
@@ -79,6 +79,24 @@ class Wiznet5100
     */
     uint16_t readFrame(uint8_t* buffer, uint16_t bufsize);
 
+    /**
+        Check physical link
+        @return true when physical link is up
+    */
+    bool isLinked() const
+    {
+        return true;  //XXX TODO
+    }
+
+    /**
+        Report whether ::isLinked() API is implemented
+        @return true when ::isLinked() API is implemented
+    */
+    constexpr bool isLinkDetectable() const
+    {
+        return false;
+    }
+
 protected:
     static constexpr bool interruptIsPossible()
     {
diff --git a/libraries/lwIP_w5500/src/utility/w5500.h b/libraries/lwIP_w5500/src/utility/w5500.h
index f7babb51e9..7e8058fdb1 100644
--- a/libraries/lwIP_w5500/src/utility/w5500.h
+++ b/libraries/lwIP_w5500/src/utility/w5500.h
@@ -79,6 +79,24 @@ class Wiznet5500
     */
     uint16_t readFrame(uint8_t* buffer, uint16_t bufsize);
 
+    /**
+        Check physical link
+        @return true when physical link is up
+    */
+    bool isLinked()
+    {
+        return wizphy_getphylink() == PHY_LINK_ON;
+    }
+
+    /**
+        Report whether ::isLinked() API is implemented
+        @return true when ::isLinked() API is implemented
+    */
+    constexpr bool isLinkDetectable() const
+    {
+        return true;
+    }
+
 protected:
     static constexpr bool interruptIsPossible()
     {