WIFI-4201: Add provision to set the AP LAN Ports to Bridge/NAT

1. Add provision to tag with vlan when set to Bridge mode (br-wan).
2. Add provision to set the local IP address in NAT (br-lan)
3. Display the Ethernet port status(up/down) and negotiated rate.
4. Add provision to set lan eth port to bridge/nat.

Signed-off-by: Chaitanya Godavarthi <chaitanya.kiran@netexperience.com>
This commit is contained in:
Chaitanya Godavarthi
2021-09-12 19:55:13 -04:00
committed by Arif Alam
parent 16149f6ba5
commit 98104a2e8d
8 changed files with 651 additions and 12 deletions

View File

@@ -1,6 +1,21 @@
#!/bin/sh
. /usr/share/libubox/jshn.sh
if="$(uci get network.wan.ifname)"
json_init
json_load "$(cat /etc/board.json)"
json_select network
json_select "wan"
json_get_vars ifname
json_select ..
json_select ..
[ -n "$ifname" ] || {
ifname=$(uci get network.wan.ifname)
ifname=${ifname%% *}
}
if="$ifname"
[ "$(cat /sys/class/net/"${if}"/carrier)" = 0 ] && {
return 0
}

View File

@@ -1,7 +1,21 @@
#!/bin/sh
. /usr/share/libubox/jshn.sh
[ "$ACTION" = "add" ] || exit 0
json_init
json_load "$(cat /etc/board.json)"
json_select network
json_select "wan"
json_get_vars ifname
json_select ..
json_select ..
[ -n "$ifname" ] || {
ifname=$(uci get network.wan.ifname)
ifname=${ifname%% *}
}
net=$(uci get wireless.${DEVICENAME}.network)
vid=$(uci get wireless.${DEVICENAME}.vid)
@@ -9,6 +23,6 @@ vid=$(uci get wireless.${DEVICENAME}.vid)
bridge vlan add vid $vid dev br-lan self
bridge vlan add vid $vid dev br-wan self
bridge vlan add vid $vid dev $(uci get network.wan.ifname)
bridge vlan add vid $vid dev $ifname
bridge vlan add pvid $vid vid $vid dev ${DEVICENAME} untagged
exit 0

View File

@@ -1,5 +1,20 @@
#!/bin/sh
. /usr/share/libubox/jshn.sh
[ "$ACTION" = "add" ] || exit 0
json_init
json_load "$(cat /etc/board.json)"
json_select network
json_select "wan"
json_get_vars ifname
json_select ..
json_select ..
[ -n "$ifname" ] || {
ifname=$(uci get network.wan.ifname)
ifname=${ifname%% *}
}
dvid=`echo $DEVICENAME | awk -F "." '{ printf $2 }'`
[ -z "$dvid" -o "$dvid" = 0 -o "$dvid" = 1 ] && exit 0
@@ -13,7 +28,7 @@ brctl delif br-wan.$dvid $DEVICENAME
brctl addif br-wan $DEVICENAME
bridge vlan add vid $dvid dev br-wan self
bridge vlan add vid $dvid dev $(uci get network.wan.ifname)
bridge vlan add vid $dvid dev $ifname
bridge vlan add pvid $dvid vid $dvid dev $DEVICENAME untagged
bridge vlan add vid $dvid dev br-lan self

View File

@@ -0,0 +1,45 @@
Index: opensync-2.0.5.0/interfaces/opensync.ovsschema
===================================================================
--- opensync-2.0.5.0.orig/interfaces/opensync.ovsschema
+++ opensync-2.0.5.0/interfaces/opensync.ovsschema
@@ -900,6 +900,17 @@
"min": 0,
"max": 64
}
+ },
+ "eth_ports": {
+ "type": {
+ "key": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 32
+ },
+ "min": 0,
+ "max": 1
+ }
}
},
"isRoot": true,
@@ -1170,6 +1181,22 @@
"min": 0,
"max": 64
}
+ },
+ "eth_ports": {
+ "type": {
+ "key": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 32
+ },
+ "value": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 64
+ },
+ "min": 0,
+ "max": 32
+ }
}
},
"isRoot": true,

View File

@@ -90,6 +90,24 @@ const char mesh_options_table[SCHEMA_MESH_OPTS_MAX][SCHEMA_MESH_OPT_SZ] =
SCHEMA_CONSTS_MESH_HOP_PENALTY,
};
char* get_eth_map_info(char* iface);
static void init_eth_ports_config(struct schema_Wifi_Inet_Config *config)
{
char *wan = NULL;
char *lan = NULL;
wan = get_eth_map_info("wan");
if (!strncmp(config->if_name, "wan", 3)) {
SCHEMA_SET_STR(config->eth_ports, wan);
}
lan = get_eth_map_info("lan");
if (!strncmp(config->if_name, "lan", 3)) {
SCHEMA_SET_STR(config->eth_ports, lan);
}
}
static void wifi_inet_conf_load(struct uci_section *s)
{
struct blob_attr *tb[__NET_ATTR_MAX] = { };
@@ -190,6 +208,7 @@ static void wifi_inet_conf_load(struct uci_section *s)
}
}
init_eth_ports_config(&conf);
firewall_get_config(&conf);
if (!ovsdb_table_upsert(&table_Wifi_Inet_Config, &conf, false))
@@ -261,8 +280,9 @@ static int wifi_inet_conf_add(struct schema_Wifi_Inet_Config *iconf)
snprintf(uci_ifname, sizeof(uci_ifname), "gre4t-%s.%d", iconf->parent_ifname, iconf->vlan_id);
blobmsg_add_string(&b, "ifname", uci_ifname);
blobmsg_add_string(&b, "type", "bridge");
}
else {
} else if(!strncmp(iconf->parent_ifname, "eth", strlen("eth"))) {
blobmsg_add_string(&b, "ifname", iconf->parent_ifname);
} else {
snprintf(uci_ifname, sizeof(uci_ifname), "br-%s.%d", iconf->parent_ifname, iconf->vlan_id);
blobmsg_add_string(&b, "ifname", uci_ifname);
}
@@ -345,6 +365,101 @@ static int wifi_inet_conf_add(struct schema_Wifi_Inet_Config *iconf)
return 0;
}
static int wifi_inet_dup_conf_single(struct blob_buf *dup, char *if_name, int exclude)
{
struct blob_attr *tb[__NET_ATTR_MAX] = { };
struct uci_package *network = NULL;
struct uci_section *s;
int i = 0;
/* Load the network uci and lookup the if_name section */
uci_load(uci, "network", &network);
s = uci_lookup_section(uci, network , if_name);
if(!s) {
LOG(ERR, " %s: Failed to load uci", __func__);
uci_unload(uci, network);
return -1;
}
/* Copy the uci section to blob and parse it */
blob_buf_init(&b, 0);
uci_to_blob(&b, s, &network_param);
uci_unload(uci, network);
blobmsg_parse(network_policy, __NET_ATTR_MAX,
tb, blob_data(b.head),
blob_len(b.head));
blob_buf_init(dup, 0);
blob_buf_init(&del, 0);
/* copy the parsed data to the passed blob buffer */
for (i = 0; i < __NET_ATTR_MAX; i++) {
if (i == exclude)
continue;
if (tb[i]) {
switch (network_policy[i].type) {
case BLOBMSG_TYPE_STRING:
blobmsg_add_string(dup, network_policy[i].name,
blobmsg_get_string(tb[i]));
break;
case BLOBMSG_TYPE_INT32:
blobmsg_add_u32(dup, network_policy[i].name,
blobmsg_get_u32(tb[i]));
break;
case BLOBMSG_TYPE_BOOL:
blobmsg_add_bool(dup, network_policy[i].name,
blobmsg_get_bool(tb[i]));
break;
default:
break;
}
}
}
return 0;
}
static int wifi_inet_conf_modify(struct schema_Wifi_Inet_Config *iconf)
{
struct blob_buf dup = { };
if (iconf->eth_ports_changed) {
LOGI("%s+%d: changed=%d if_name=%s, eth_ports=%s",
__func__, __LINE__, iconf->eth_ports_changed,
iconf->if_name, iconf->eth_ports);
iconf->eth_ports_changed = 0;
/* Copy the current uci interface section to the blob excluding
* the passed NET_ATTR (changed config attribute) */
if (wifi_inet_dup_conf_single(&dup, iconf->if_name,
NET_ATTR_IFNAME) != 0)
return -1;
/* Add the changed configuration to the blob */
blobmsg_add_string(&dup, "ifname", iconf->eth_ports);
/* Delete the current uci interface section */
if (uci_section_del(uci, "network", "network",
iconf->if_name, "interface") != 0)
return -1;
/* Add the resulting modified blob to uci */
if (blob_to_uci_section(uci, "network",
iconf->if_name,
"interface", dup.head,
&network_param,
del.head) != 0)
return -1;
uci_commit_all(uci);
blob_buf_free(&dup);
}
return 0;
}
static void wifi_inet_conf_del(struct schema_Wifi_Inet_Config *iconf)
{
if (!strcmp(iconf->if_name, "wan") || !strcmp(iconf->if_name, "lan")) {
@@ -386,6 +501,13 @@ static void callback_Wifi_Inet_Config(ovsdb_update_monitor_t *mon,
}
wifi_inet_conf_add(iconf);
system("cp /etc/config/network /tmp/bkp-network");
if (wifi_inet_conf_modify(iconf) != 0) {
LOG(ERR, "Failed to modify network Conf restoring old conf");
system("cp /tmp/bkp-network /etc/config/network");
}
system("rm /tmp/bkp-network");
netifd_modify_inet_conf(iconf);
break;
case OVSDB_UPDATE_DEL:
@@ -413,10 +535,12 @@ void wifi_inet_config_init(void)
uci_foreach_element(&network->sections, e) {
struct uci_section *s = uci_to_section(e);
if (!strcmp(s->type, "interface"))
if (!strcmp(s->type, "interface")) {
wifi_inet_conf_load(s);
}
}
uci_unload(uci, network);
OVSDB_TABLE_MONITOR(Wifi_Inet_Config, false);
return;

View File

@@ -5,6 +5,9 @@
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <dirent.h>
#include <libubox/blobmsg_json.h>
enum {
NET_ATTR_INTERFACE,
@@ -100,6 +103,259 @@ int l3_device_split(char *l3_device, struct iface_info *info)
return 0;
}
bool wifi_inet_state_del(const char *ifname);
bool wifi_inet_master_del(const char *ifname);
#define DOT_OR_DOTDOT(s) ((s)[0] == '.' && (!(s)[1] || ((s)[1] == '.' && !(s)[2])))
static struct blob_buf bb;
#define DEFAULT_BOARD_JSON "/etc/board.json"
#define MAX_ETH_PORTS 5
static struct blob_attr *board_info;
struct eth_port_state {
char ifname[8];
char state[8];
char speed[8];
char duplex[16];
char bridge[8];
};
static struct eth_port_state wanport;
static struct eth_port_state lanport[MAX_ETH_PORTS];
static struct blob_attr* config_find_blobmsg_attr(struct blob_attr *attr, const char *name, int type)
{
struct blobmsg_policy policy = { .name = name, .type = type };
struct blob_attr *cur;
blobmsg_parse(&policy, 1, &cur, blobmsg_data(attr), blobmsg_len(attr));
return cur;
}
char* get_eth_map_info(char* iface)
{
struct blob_attr *cur;
blob_buf_init(&bb, 0);
if (!blobmsg_add_json_from_file(&bb, DEFAULT_BOARD_JSON)) {
return NULL;
}
if (board_info != NULL) {
free(board_info);
board_info = NULL;
}
cur = config_find_blobmsg_attr(bb.head, "network", BLOBMSG_TYPE_TABLE);
if (!cur) {
LOGD("Failed to find network in board.json file");
return NULL;
}
board_info = blob_memdup(cur);
if (!board_info)
return NULL;
cur = config_find_blobmsg_attr(board_info, iface, BLOBMSG_TYPE_TABLE);
if (!cur) {
LOGD("Failed to find %s in board.json file", iface);
return NULL;
}
cur = config_find_blobmsg_attr(cur, "ifname", BLOBMSG_TYPE_STRING);
if (!cur) {
LOGD("Failed to find ifname in board.json file");
return NULL;
}
return blobmsg_get_string(cur);
}
static void update_eth_state (char *eth, struct eth_port_state *eth_state)
{
char sysfs_path[128];
int fd = 0;
struct dirent *ent;
DIR *iface;
int carrier = 1;
ssize_t len = 0;
memset(eth_state, 0, sizeof(struct eth_port_state));
/* eth interface name */
strncpy(eth_state->ifname, eth, sizeof(eth_state->ifname));
/* eth interface state */
snprintf(sysfs_path, sizeof(sysfs_path),
"/sys/class/net/%s/carrier", eth);
fd = open(sysfs_path, O_RDONLY);
if (fd < 0) {
carrier = 0;
close(fd);
} else {
len = read(fd, eth_state->state, 15);
if (len < 0) {
carrier = 0;
}
close(fd);
}
if(!strncmp(eth_state->state, "0", 1))
carrier = 0;
strncpy(eth_state->state, carrier? "up":"down", sizeof(eth_state->state));
/* eth interface wan bridge */
snprintf(sysfs_path, sizeof(sysfs_path),
"/sys/class/net/br-wan/brif");
iface = opendir(sysfs_path);
if (iface) {
while ((ent = readdir(iface)) != NULL) {
if (DOT_OR_DOTDOT(ent->d_name))
continue;
if (strncmp(ent->d_name, eth_state->ifname, sizeof(eth_state->ifname)) == 0)
strncpy(eth_state->bridge, "br-wan", sizeof(eth_state->ifname));
}
closedir(iface);
}
/* eth interface lan bridge */
snprintf(sysfs_path, sizeof(sysfs_path),
"/sys/class/net/br-lan/brif");
iface = opendir(sysfs_path);
if (iface) {
while ((ent = readdir(iface)) != NULL) {
if (DOT_OR_DOTDOT(ent->d_name))
continue;
if (strncmp(ent->d_name, eth_state->ifname, sizeof(eth_state->ifname)) == 0)
strncpy(eth_state->bridge, "br-lan", sizeof(eth_state->bridge));
}
closedir(iface);
}
/* eth interface speed Mbits/sec */
snprintf(sysfs_path, sizeof(sysfs_path),
"/sys/class/net/%s/speed", eth);
fd = open(sysfs_path, O_RDONLY);
if (fd < 0) {
close(fd);
} else {
len = read(fd, eth_state->speed, sizeof(eth_state->speed) -1);
if (len < 0)
snprintf(eth_state->speed, sizeof(eth_state->speed), "0");
else
eth_state->speed[len-1] = '\0';
close(fd);
}
/* eth interface duplex */
snprintf(sysfs_path, sizeof(sysfs_path),
"/sys/class/net/%s/duplex", eth);
fd = open(sysfs_path, O_RDONLY);
if (fd < 0) {
close(fd);
} else {
len = read(fd, eth_state->duplex, sizeof(eth_state->duplex) -1);
if (len < 0)
snprintf(eth_state->duplex, sizeof(eth_state->duplex), "none");
else
eth_state->duplex[len-1] = '\0';
close(fd);
}
}
static void update_eth_ports_states(struct schema_Wifi_Inet_State *state)
{
char *wan = NULL;
char *lan = NULL;
char *eth = NULL;
int cnt = 0;
int i = 0;
char port_status[128] = {'\0'};
char brname[IFNAMSIZ] = {'\0'};
wan = get_eth_map_info("wan");
update_eth_state(wan, &wanport);
lan = get_eth_map_info("lan");
eth = strtok (lan," ");
for (i = 0; i < MAX_ETH_PORTS && eth != NULL; i++)
{
update_eth_state(eth, &lanport[i]);
eth = strtok (NULL, " ");
}
if (!strncmp(state->if_name, "wan", 3))
strncpy(brname, "br-wan", 6);
else if (!strncmp(state->if_name, "lan", 3))
strncpy(brname, "br-lan", 6);
if (strcmp(wanport.bridge, brname) == 0) {
STRSCPY(state->eth_ports_keys[0], wanport.ifname);
snprintf(port_status, sizeof(port_status), "%s wan %sMbps %s",
wanport.state, wanport.speed, wanport.duplex);
STRSCPY(state->eth_ports[0], port_status);
cnt++;
state->eth_ports_len = cnt;
}
for (i = 0; i < MAX_ETH_PORTS && lanport[i].ifname != NULL; i++) {
if (strcmp(lanport[i].bridge, brname) == 0) {
STRSCPY(state->eth_ports_keys[cnt+i], lanport[i].ifname);
memset(port_status, '\0', sizeof(port_status));
snprintf(port_status, sizeof(port_status), "%s lan %sMbps %s",
lanport[i].state, lanport[i].speed, lanport[i].duplex);
STRSCPY(state->eth_ports[cnt+i], port_status);
cnt++;
state->eth_ports_len = cnt;
}
}
if (!strncmp(state->if_name, "eth", 3)) {
char *delim = NULL;
char name[IFNAMSIZ] = {'\0'};
delim = strstr(state->if_name, "_");
if (delim)
strncpy(name, state->if_name, delim - state->if_name);
if (name[0] == '\0')
return;
if (strcmp(wanport.ifname, name) == 0) {
STRSCPY(state->eth_ports_keys[0], wanport.ifname);
memset(port_status, '\0', sizeof(port_status));
snprintf(port_status, sizeof(port_status), "%s wan %sMbps %s",
wanport.state, wanport.speed, wanport.duplex);
STRSCPY(state->eth_ports[0], port_status);
state->eth_ports_len = 1;
} else {
for (i = 0; i < MAX_ETH_PORTS && lanport[i].ifname != NULL; i++) {
if (strcmp(lanport[i].ifname, name) == 0) {
STRSCPY(state->eth_ports_keys[0], lanport[i].ifname);
memset(port_status, '\0', sizeof(port_status));
snprintf(port_status, sizeof(port_status), "%s lan %sMbps %s",
lanport[i].state, lanport[i].speed, lanport[i].duplex);
STRSCPY(state->eth_ports[0], port_status);
state->eth_ports_len = 1;
}
}
}
}
}
void wifi_inet_state_set(struct blob_attr *msg)
{
struct blob_attr *tb[__NET_ATTR_MAX] = { };
@@ -124,6 +380,13 @@ void wifi_inet_state_set(struct blob_attr *msg)
state.enabled = true;
state.network = true;
} else {
/* Delete VLAN interface state column if disabled */
if (strstr(state.if_name, "_") != NULL) {
wifi_inet_state_del(state.if_name);
return;
}
state.enabled = false;
state.network = false;
}
@@ -143,6 +406,23 @@ void wifi_inet_state_set(struct blob_attr *msg)
else if (!strncmp(l3_device, "gre4", strlen("gre4")) ||
!strncmp(l3_device, "gre6", strlen("gre6")))
SCHEMA_SET_STR(state.if_type, "gre");
/* Fill if_type, vlan_id and parent_ifname using
* if_name (eg:eth0_100) */
else if (!strncmp(l3_device, "eth", strlen("eth"))) {
char *delim = NULL;
delim = strstr(state.if_name, "_");
if (delim) {
struct iface_info info;
memset (&info, 0, sizeof(info));
SCHEMA_SET_STR(state.if_type, "vlan");
strncpy(info.name, &l3_device[0],
delim - state.if_name);
SCHEMA_SET_STR(state.parent_ifname, info.name);
info.vid = atoi(&delim[1]);
SCHEMA_SET_INT(state.vlan_id, info.vid);
}
}
else
SCHEMA_SET_STR(state.if_type, "eth");
if (!l3_device_split(l3_device, &info) && strcmp(info.name, state.if_name)) {
@@ -262,6 +542,8 @@ void wifi_inet_state_set(struct blob_attr *msg)
}
}
update_eth_ports_states(&state);
if (!ovsdb_table_upsert(&table_Wifi_Inet_State, &state, false))
LOG(ERR, "inet_state: failed to insert");
}
@@ -289,6 +571,12 @@ void wifi_inet_master_set(struct blob_attr *msg)
SCHEMA_SET_STR(state.port_state, "active");
SCHEMA_SET_STR(state.network_state, "up");
} else {
/* Delete VLAN interface state column if disabled */
if (strstr(state.if_name, "_") != NULL) {
wifi_inet_master_del(state.if_name);
return;
}
SCHEMA_SET_STR(state.port_state, "inactive");
SCHEMA_SET_STR(state.network_state, "down");
}
@@ -329,7 +617,14 @@ void wifi_inet_master_set(struct blob_attr *msg)
if (!net_is_bridge(l3_device))
SCHEMA_SET_STR(state.if_type, "bridge");
else
/* Fill if_type, vlan_id and parent_ifname using
* if_name (eg:eth0_100) */
else if (!strncmp(l3_device, "eth", strlen("eth"))) {
char *delim = NULL;
delim = strstr(state.if_name, "_");
if (delim)
SCHEMA_SET_STR(state.if_type, "vlan");
} else
SCHEMA_SET_STR(state.if_type, "eth");
} else
SCHEMA_SET_STR(state.if_type, "eth");
@@ -349,6 +644,17 @@ void wifi_inet_master_set(struct blob_attr *msg)
LOG(ERR, "master_state: failed to insert");
}
bool wifi_inet_master_del(const char *ifname)
{
int ret;
ret = ovsdb_table_delete_simple(&table_Wifi_Master_State, SCHEMA_COLUMN(Wifi_Inet_State, if_name), ifname);
if (ret <= 0)
LOG(ERR, "inet_state: Error deleting Wifi_Master_State for interface %s.", ifname);
return ret;
}
bool wifi_inet_state_del(const char *ifname)
{
int ret;
@@ -356,11 +662,6 @@ bool wifi_inet_state_del(const char *ifname)
ret = ovsdb_table_delete_simple(&table_Wifi_Inet_State, SCHEMA_COLUMN(Wifi_Inet_State, if_name), ifname);
if (ret <= 0)
LOG(ERR, "inet_state: Error deleting Wifi_Inet_State for interface %s.", ifname);
ret = ovsdb_table_delete_simple(&table_Wifi_Master_State, SCHEMA_COLUMN(Wifi_Inet_State, if_name), ifname);
if (ret <= 0)
LOG(ERR, "inet_state: Error deleting Wifi_Master_State for interface %s.", ifname);
return ret;
}

View File

@@ -0,0 +1,40 @@
#!/bin/sh
. /usr/share/libubox/jshn.sh
[ "$ACTION" = ifup -o "$ACTION" = ifupdate -o "$ACTION" = ifdown ] || exit 0
json_init
json_load "$(cat /etc/board.json)"
json_select network
json_select "wan"
json_get_vars ifname
json_select ..
json_select ..
[ -n "$ifname" ] || {
ifname=$(uci get network.wan.ifname)
ifname=${ifname%% *}
}
if [ "$ACTION" = ifup -o "$ACTION" = ifupdate ]; then
vid=$(uci get network.${INTERFACE}.vid)
net=$(uci get network.${INTERFACE}.ifname)
[ -z "$net" -o -z "$vid" -o "$vid" = 0 ] && exit 0
bridge vlan add vid $vid dev br-lan self
bridge vlan add vid $vid dev br-wan self
bridge vlan add vid $vid dev $ifname
bridge vlan add pvid $vid vid $vid dev $net untagged
exit 0
else
if [ "$ACTION" = ifdown ]; then
vid=`echo $INTERFACE | awk -F "_" '{ print $2 }'`
net=`echo $INTERFACE | awk -F "_" '{ print $1 }'`
[ -z "$net" -o -z "$vid" -o "$vid" = 0 ] && exit 0
bridge vlan del vid $vid dev $ifname
bridge vlan del pvid $vid vid $vid dev $net untagged
bridge vlan add pvid 1 vid 1 dev $net untagged
exit 0
fi
fi

View File

@@ -0,0 +1,85 @@
From b14c0b96644cf86b36b0e60da800dd11733b26a9 Mon Sep 17 00:00:00 2001
From: Chaitanya Godavarthi <chaitanya.kiran@netexperience.com>
Date: Tue, 14 Sep 2021 10:23:49 -0400
Subject: [PATCH] ubus notify when ethernet ports change state
Signed-off-by: Chaitanya Godavarthi <chaitanya.kiran@netexperience.com>
---
.../0106-add-ubus-notify-eth-state.patch | 65 +++++++++++++++++++
1 file changed, 65 insertions(+)
create mode 100644 package/network/config/netifd/patches/0106-add-ubus-notify-eth-state.patch
diff --git a/package/network/config/netifd/patches/0106-add-ubus-notify-eth-state.patch b/package/network/config/netifd/patches/0106-add-ubus-notify-eth-state.patch
new file mode 100644
index 0000000000..3c8eaa530d
--- /dev/null
+++ b/package/network/config/netifd/patches/0106-add-ubus-notify-eth-state.patch
@@ -0,0 +1,65 @@
+Index: netifd-2019-08-05-5e02f944/device.c
+===================================================================
+--- netifd-2019-08-05-5e02f944.orig/device.c
++++ netifd-2019-08-05-5e02f944/device.c
+@@ -29,6 +29,7 @@
+ #include "netifd.h"
+ #include "system.h"
+ #include "config.h"
++#include "ubus.h"
+
+ static struct list_head devtypes = LIST_HEAD_INIT(devtypes);
+ static struct avl_tree devices;
+@@ -638,6 +639,7 @@ void device_set_present(struct device *d
+
+ void device_set_link(struct device *dev, bool state)
+ {
++ ubus_link_state_notify(dev, state);
+ if (dev->link_active == state)
+ return;
+
+Index: netifd-2019-08-05-5e02f944/ubus.c
+===================================================================
+--- netifd-2019-08-05-5e02f944.orig/ubus.c
++++ netifd-2019-08-05-5e02f944/ubus.c
+@@ -817,6 +817,29 @@ netifd_dump_status(struct interface *ifa
+ netifd_add_interface_errors(&b, iface);
+ }
+
++void ubus_link_state_notify(struct device *dev, bool state)
++{
++ struct interface *iface = NULL;
++ /* proceed to notify if eth link state change and bridge change */
++ if((strncmp(dev->ifname, "eth", 3) && (dev->link_active == state)) &&
++ strncmp(dev->ifname, "br-wan", 6) &&
++ strncmp(dev->ifname, "br-lan", 6))
++ return;
++ netifd_log_message(L_NOTICE, "%s:%s\n", __func__, dev->ifname);
++
++ vlist_for_each_element(&interfaces, iface, node) {
++ if( !strncmp(iface->name, "wan", 3) ||
++ !strncmp(iface->name, "lan", 3) ||
++ !strncmp(iface->name, "eth", 3)) {
++ blob_buf_init(&b, 0);
++ blobmsg_add_string(&b, "interface", iface->name);
++ netifd_dump_status(iface);
++ ubus_notify(ubus_ctx, &iface->ubus, "interface.update",
++ b.head, -1);
++ }
++ }
++}
++
+ static int
+ netifd_handle_status(struct ubus_context *ctx, struct ubus_object *obj,
+ struct ubus_request_data *req, const char *method,
+Index: netifd-2019-08-05-5e02f944/ubus.h
+===================================================================
+--- netifd-2019-08-05-5e02f944.orig/ubus.h
++++ netifd-2019-08-05-5e02f944/ubus.h
+@@ -22,5 +22,6 @@ void netifd_ubus_add_interface(struct in
+ void netifd_ubus_remove_interface(struct interface *iface);
+ void netifd_ubus_interface_event(struct interface *iface, bool up);
+ void netifd_ubus_interface_notify(struct interface *iface, bool up);
++void ubus_link_state_notify(struct device *dev, bool state);
+
+ #endif
--
2.25.1