From 5ecb131d94797c53574c13b4ea6b89cfa1748403 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Sat, 30 Nov 2019 15:54:07 +0900 Subject: [PATCH] network: include NLMSGERR_ATTR_MSG attribute in error message --- src/network/networkd-address-label.c | 2 +- src/network/networkd-address.c | 2 +- src/network/networkd-brvlan.c | 2 +- src/network/networkd-can.c | 6 +++--- src/network/networkd-dhcp4.c | 8 +++---- src/network/networkd-dhcp6.c | 10 ++++----- src/network/networkd-fdb.c | 2 +- src/network/networkd-ipv4ll.c | 2 +- src/network/networkd-ipv6-proxy-ndp.c | 2 +- src/network/networkd-link.c | 25 ++++++++++++++-------- src/network/networkd-link.h | 7 ++++++ src/network/networkd-manager.c | 19 ++++++++++------ src/network/networkd-ndisc.c | 4 ++-- src/network/networkd-neighbor.c | 4 ++-- src/network/networkd-nexthop.c | 2 +- src/network/networkd-route.c | 2 +- src/network/networkd-routing-policy-rule.c | 4 ++-- src/network/tc/qdisc.c | 2 +- 18 files changed, 63 insertions(+), 42 deletions(-) diff --git a/src/network/networkd-address-label.c b/src/network/networkd-address-label.c index 108f4e87348..254e8f0bcc8 100644 --- a/src/network/networkd-address-label.c +++ b/src/network/networkd-address-label.c @@ -95,7 +95,7 @@ static int address_label_handler(sd_netlink *rtnl, sd_netlink_message *m, Link * r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) { - log_link_warning_errno(link, r, "could not set address label: %m"); + log_link_message_warning_errno(link, m, r, "Could not set address label"); link_enter_failed(link); return 1; } else if (r >= 0) diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 23d40ccc411..2810a6420e3 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -431,7 +431,7 @@ static int address_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EADDRNOTAVAIL) - log_link_warning_errno(link, r, "Could not drop address: %m"); + log_link_message_warning_errno(link, m, r, "Could not drop address"); return 1; } diff --git a/src/network/networkd-brvlan.c b/src/network/networkd-brvlan.c index c3c5d535ac6..970065d32ad 100644 --- a/src/network/networkd-brvlan.c +++ b/src/network/networkd-brvlan.c @@ -141,7 +141,7 @@ static int set_brvlan_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *lin r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) - log_link_error_errno(link, r, "Could not add VLAN to bridge port: %m"); + log_link_message_warning_errno(link, m, r, "Could not add VLAN to bridge port"); return 1; } diff --git a/src/network/networkd-can.c b/src/network/networkd-can.c index 5755df57bd7..bdd88659c54 100644 --- a/src/network/networkd-can.c +++ b/src/network/networkd-can.c @@ -20,7 +20,7 @@ static int link_up_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) r = sd_netlink_message_get_errno(m); if (r < 0) /* we warn but don't fail the link, as it may be brought up later */ - log_link_warning_errno(link, r, "Could not bring up interface: %m"); + log_link_message_warning_errno(link, m, r, "Could not bring up interface"); return 1; } @@ -60,7 +60,7 @@ static int link_set_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) { - log_link_error_errno(link, r, "Failed to configure CAN link: %m"); + log_link_message_warning_errno(link, m, r, "Failed to configure CAN link"); link_enter_failed(link); } @@ -183,7 +183,7 @@ static int link_down_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link r = sd_netlink_message_get_errno(m); if (r < 0) { - log_link_warning_errno(link, r, "Could not bring down interface: %m"); + log_link_message_warning_errno(link, m, r, "Could not bring down interface"); link_enter_failed(link); return 1; } diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 8ca87d99d4d..d430115f561 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -65,11 +65,11 @@ static int dhcp4_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *li /* It seems kernel does not support that the prefix route cannot be configured with * route table. Let's once drop the config and reconfigure them later. */ - log_link_debug_errno(link, r, "Could not set DHCPv4 route, retrying later: %m"); + log_link_message_debug_errno(link, m, r, "Could not set DHCPv4 route, retrying later: %m"); link->dhcp4_route_failed = true; link->manager->dhcp4_prefix_root_cannot_set_table = true; } else if (r < 0 && r != -EEXIST) { - log_link_error_errno(link, r, "Could not set DHCPv4 route: %m"); + log_link_message_warning_errno(link, m, r, "Could not set DHCPv4 route: %m"); link_enter_failed(link); return 1; } @@ -543,7 +543,7 @@ static int dhcp_remove_address_handler(sd_netlink *rtnl, sd_netlink_message *m, r = sd_netlink_message_get_errno(m); if (r < 0) - log_link_debug_errno(link, r, "Failed to remove DHCPv4 address, ignoring: %m"); + log_link_message_warning_errno(link, m, r, "Failed to remove DHCPv4 address, ignoring"); else (void) manager_rtnl_process_address(rtnl, m, link->manager); @@ -665,7 +665,7 @@ static int dhcp4_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link * r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) { - log_link_error_errno(link, r, "Could not set DHCPv4 address: %m"); + log_link_message_warning_errno(link, m, r, "Could not set DHCPv4 address"); link_enter_failed(link); return 1; } else if (r >= 0) diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 647623ac377..7304270c60b 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -112,7 +112,7 @@ static int dhcp6_route_remove_handler(sd_netlink *nl, sd_netlink_message *m, Lin r = sd_netlink_message_get_errno(m); if (r < 0) - log_link_debug_errno(link, r, "Received error on unreachable route removal for DHCPv6 delegated subnet: %m"); + log_link_message_warning_errno(link, m, r, "Received error on unreachable route removal for DHCPv6 delegated subnet"); return 1; } @@ -251,7 +251,7 @@ static int dhcp6_route_handler(sd_netlink *nl, sd_netlink_message *m, Link *link r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) - log_link_debug_errno(link, r, "Received error when adding unreachable route for DHCPv6 delegated subnet: %m"); + log_link_message_warning_errno(link, m, r, "Received error when adding unreachable route for DHCPv6 delegated subnet"); return 1; } @@ -402,7 +402,7 @@ static int dhcp6_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link * r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) { - log_link_error_errno(link, r, "Could not set DHCPv6 address: %m"); + log_link_message_warning_errno(link, m, r, "Could not set DHCPv6 address"); link_enter_failed(link); return 1; } else if (r >= 0) @@ -714,7 +714,7 @@ static int dhcp6_route_add_handler(sd_netlink *nl, sd_netlink_message *m, Link * r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) { - log_link_debug_errno(link, r, "Received error adding DHCPv6 Prefix Delegation route: %m"); + log_link_message_warning_errno(link, m, r, "Received error adding DHCPv6 Prefix Delegation route"); link_enter_failed(link); return 1; } @@ -780,7 +780,7 @@ static int dhcp6_prefix_remove_handler(sd_netlink *nl, sd_netlink_message *m, Li r = sd_netlink_message_get_errno(m); if (r < 0) { - log_link_debug_errno(link, r, "Received error on DHCPv6 Prefix Delegation route removal: %m"); + log_link_message_warning_errno(link, m, r, "Received error on DHCPv6 Prefix Delegation route removal"); link_enter_failed(link); return 1; } diff --git a/src/network/networkd-fdb.c b/src/network/networkd-fdb.c index 07c77a3fa3e..839efe73a72 100644 --- a/src/network/networkd-fdb.c +++ b/src/network/networkd-fdb.c @@ -105,7 +105,7 @@ static int set_fdb_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) { - log_link_error_errno(link, r, "Could not add FDB entry: %m"); + log_link_message_warning_errno(link, m, r, "Could not add FDB entry"); link_enter_failed(link); return 1; } diff --git a/src/network/networkd-ipv4ll.c b/src/network/networkd-ipv4ll.c index 6216a06d20c..5750ea091ef 100644 --- a/src/network/networkd-ipv4ll.c +++ b/src/network/networkd-ipv4ll.c @@ -51,7 +51,7 @@ static int ipv4ll_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) { - log_link_error_errno(link, r, "could not set ipv4ll address: %m"); + log_link_message_warning_errno(link, m, r, "could not set ipv4ll address"); link_enter_failed(link); return 1; } else if (r >= 0) diff --git a/src/network/networkd-ipv6-proxy-ndp.c b/src/network/networkd-ipv6-proxy-ndp.c index 863171b0712..14a8ceaddea 100644 --- a/src/network/networkd-ipv6-proxy-ndp.c +++ b/src/network/networkd-ipv6-proxy-ndp.c @@ -141,7 +141,7 @@ static int set_ipv6_proxy_ndp_address_handler(sd_netlink *rtnl, sd_netlink_messa r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) - log_link_error_errno(link, r, "Could not add IPv6 proxy ndp address entry: %m"); + log_link_message_warning_errno(link, m, r, "Could not add IPv6 proxy ndp address entry, ignoring"); return 1; } diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 279f9025ede..36d24fd0f3d 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -929,7 +929,7 @@ static int nexthop_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) { - log_link_warning_errno(link, r, "Could not set nexthop: %m"); + log_link_message_warning_errno(link, m, r, "Could not set nexthop"); link_enter_failed(link); return 1; } @@ -981,7 +981,7 @@ static int route_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) { - log_link_warning_errno(link, r, "Could not set route: %m"); + log_link_message_warning_errno(link, m, r, "Could not set route"); link_enter_failed(link); return 1; } @@ -1170,7 +1170,7 @@ static int address_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) { - log_link_warning_errno(link, r, "could not set address: %m"); + log_link_message_warning_errno(link, m, r, "Could not set address"); link_enter_failed(link); return 1; } else if (r >= 0) @@ -1303,7 +1303,7 @@ static int set_mtu_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) r = sd_netlink_message_get_errno(m); if (r < 0) - log_link_warning_errno(link, r, "Could not set MTU, ignoring: %m"); + log_link_message_warning_errno(link, m, r, "Could not set MTU, ignoring"); else log_link_debug(link, "Setting MTU done."); @@ -1415,7 +1415,7 @@ static int set_flags_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link r = sd_netlink_message_get_errno(m); if (r < 0) - log_link_warning_errno(link, r, "Could not set link flags, ignoring: %m"); + log_link_message_warning_errno(link, m, r, "Could not set link flags, ignoring"); return 1; } @@ -1582,7 +1582,7 @@ static int link_address_genmode_handler(sd_netlink *rtnl, sd_netlink_message *m, r = sd_netlink_message_get_errno(m); if (r < 0) - log_link_warning_errno(link, r, "Could not set address genmode for interface, ignoring: %m"); + log_link_message_warning_errno(link, m, r, "Could not set address genmode for interface, ignoring"); return 1; } @@ -1656,7 +1656,7 @@ static int link_up_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) r = sd_netlink_message_get_errno(m); if (r < 0) /* we warn but don't fail the link, as it may be brought up later */ - log_link_warning_errno(link, r, "Could not bring up interface: %m"); + log_link_message_warning_errno(link, m, r, "Could not bring up interface"); return 1; } @@ -1713,7 +1713,7 @@ static int link_down_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link r = sd_netlink_message_get_errno(m); if (r < 0) - log_link_warning_errno(link, r, "Could not bring down interface: %m"); + log_link_message_warning_errno(link, m, r, "Could not bring down interface"); return 1; } @@ -2118,7 +2118,7 @@ static int netdev_join_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *li r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) { - log_link_error_errno(link, r, "Could not join netdev: %m"); + log_link_message_warning_errno(link, m, r, "Could not join netdev"); link_enter_failed(link); return 1; } @@ -4053,3 +4053,10 @@ static const char* const link_state_table[_LINK_STATE_MAX] = { }; DEFINE_STRING_TABLE_LOOKUP(link_state, LinkState); + +int log_link_message_full_errno(Link *link, sd_netlink_message *m, int level, int err, const char *msg) { + const char *err_msg = NULL; + + (void) sd_netlink_message_read_string(m, NLMSGERR_ATTR_MSG, &err_msg); + return log_link_full(link, level, err, "%s: %s%s%m", msg, strempty(err_msg), err_msg ? " " : ""); +} diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index c8b8241ff19..e944f2b7155 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -213,6 +213,13 @@ int link_request_set_nexthop(Link *link); int link_reconfigure(Link *link, bool force); +int log_link_message_full_errno(Link *link, sd_netlink_message *m, int level, int err, const char *msg); +#define log_link_message_error_errno(link, m, err, msg) log_link_message_full_errno(link, m, LOG_ERR, err, msg) +#define log_link_message_warning_errno(link, m, err, msg) log_link_message_full_errno(link, m, LOG_WARNING, err, msg) +#define log_link_message_notice_errno(link, m, err, msg) log_link_message_full_errno(link, m, LOG_NOTICE, err, msg) +#define log_link_message_info_errno(link, m, err, msg) log_link_message_full_errno(link, m, LOG_INFO, err, msg) +#define log_link_message_debug_errno(link, m, err, msg) log_link_message_full_errno(link, m, LOG_DEBUG, err, msg) + #define ADDRESS_FMT_VAL(address) \ be32toh((address).s_addr) >> 24, \ (be32toh((address).s_addr) >> 16) & 0xFFu, \ diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index 40fbf14743e..3526c85b065 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -41,6 +41,13 @@ /* use 8 MB for receive socket kernel queue. */ #define RCVBUF_SIZE (8*1024*1024) +static int log_message_warning_errno(sd_netlink_message *m, int err, const char *msg) { + const char *err_msg = NULL; + + (void) sd_netlink_message_read_string(m, NLMSGERR_ATTR_MSG, &err_msg); + return log_warning_errno(err, "%s: %s%s%m", msg, strempty(err_msg), err_msg ? " " : ""); +} + static int setup_default_address_pool(Manager *m) { AddressPool *p; int r; @@ -283,7 +290,7 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, vo if (sd_netlink_message_is_error(message)) { r = sd_netlink_message_get_errno(message); if (r < 0) - log_warning_errno(r, "rtnl: failed to receive route message, ignoring: %m"); + log_message_warning_errno(message, r, "rtnl: failed to receive route message, ignoring"); return 0; } @@ -576,7 +583,7 @@ int manager_rtnl_process_neighbor(sd_netlink *rtnl, sd_netlink_message *message, if (sd_netlink_message_is_error(message)) { r = sd_netlink_message_get_errno(message); if (r < 0) - log_warning_errno(r, "rtnl: failed to receive neighbor message, ignoring: %m"); + log_message_warning_errno(message, r, "rtnl: failed to receive neighbor message, ignoring"); return 0; } @@ -714,7 +721,7 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, if (sd_netlink_message_is_error(message)) { r = sd_netlink_message_get_errno(message); if (r < 0) - log_warning_errno(r, "rtnl: failed to receive address message, ignoring: %m"); + log_message_warning_errno(message, r, "rtnl: failed to receive address message, ignoring"); return 0; } @@ -867,7 +874,7 @@ static int manager_rtnl_process_link(sd_netlink *rtnl, sd_netlink_message *messa if (sd_netlink_message_is_error(message)) { r = sd_netlink_message_get_errno(message); if (r < 0) - log_warning_errno(r, "rtnl: Could not receive link message, ignoring: %m"); + log_message_warning_errno(message, r, "rtnl: Could not receive link message, ignoring"); return 0; } @@ -957,7 +964,7 @@ int manager_rtnl_process_rule(sd_netlink *rtnl, sd_netlink_message *message, voi if (sd_netlink_message_is_error(message)) { r = sd_netlink_message_get_errno(message); if (r < 0) - log_warning_errno(r, "rtnl: failed to receive rule message, ignoring: %m"); + log_message_warning_errno(message, r, "rtnl: failed to receive rule message, ignoring"); return 0; } @@ -1170,7 +1177,7 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, if (sd_netlink_message_is_error(message)) { r = sd_netlink_message_get_errno(message); if (r < 0) - log_warning_errno(r, "rtnl: failed to receive rule message, ignoring: %m"); + log_message_warning_errno(message, r, "rtnl: failed to receive rule message, ignoring"); return 0; } diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 402d1acd4b2..d1db9e4931c 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -32,7 +32,7 @@ static int ndisc_netlink_route_message_handler(sd_netlink *rtnl, sd_netlink_mess r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) { - log_link_error_errno(link, r, "Could not set NDisc route or address: %m"); + log_link_message_error_errno(link, m, r, "Could not set NDisc route or address"); link_enter_failed(link); return 1; } @@ -63,7 +63,7 @@ static int ndisc_netlink_address_message_handler(sd_netlink *rtnl, sd_netlink_me r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) { - log_link_error_errno(link, r, "Could not set NDisc route or address: %m"); + log_link_message_error_errno(link, m, r, "Could not set NDisc route or address"); link_enter_failed(link); return 1; } else if (r >= 0) diff --git a/src/network/networkd-neighbor.c b/src/network/networkd-neighbor.c index fd61ebd5d37..f7a4bdbe65b 100644 --- a/src/network/networkd-neighbor.c +++ b/src/network/networkd-neighbor.c @@ -102,7 +102,7 @@ static int neighbor_configure_handler(sd_netlink *rtnl, sd_netlink_message *m, L r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) /* Neighbor may not exist yet. So, do not enter failed state here. */ - log_link_warning_errno(link, r, "Could not set neighbor, ignoring: %m"); + log_link_message_warning_errno(link, m, r, "Could not set neighbor, ignoring"); if (link->neighbor_messages == 0) { log_link_debug(link, "Neighbors set"); @@ -171,7 +171,7 @@ static int neighbor_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link r = sd_netlink_message_get_errno(m); if (r < 0 && r != -ESRCH) /* Neighbor may not exist because it already got deleted, ignore that. */ - log_link_warning_errno(link, r, "Could not remove neighbor: %m"); + log_link_message_warning_errno(link, m, r, "Could not remove neighbor"); return 1; } diff --git a/src/network/networkd-nexthop.c b/src/network/networkd-nexthop.c index 9658fe30c0e..45c13ca88f3 100644 --- a/src/network/networkd-nexthop.c +++ b/src/network/networkd-nexthop.c @@ -278,7 +278,7 @@ static int nexthop_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link r = sd_netlink_message_get_errno(m); if (r < 0 && r != -ESRCH) - log_link_warning_errno(link, r, "Could not drop nexthop: %m"); + log_link_message_warning_errno(link, m, r, "Could not drop nexthop, ignoring"); return 1; } diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index 592de4e430a..2a42628e5b9 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -398,7 +398,7 @@ static int route_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *l r = sd_netlink_message_get_errno(m); if (r < 0 && r != -ESRCH) - log_link_warning_errno(link, r, "Could not drop route: %m"); + log_link_message_warning_errno(link, m, r, "Could not drop route, ignoring"); return 1; } diff --git a/src/network/networkd-routing-policy-rule.c b/src/network/networkd-routing-policy-rule.c index 8203f87c9fa..cc6ab74a8a9 100644 --- a/src/network/networkd-routing-policy-rule.c +++ b/src/network/networkd-routing-policy-rule.c @@ -317,7 +317,7 @@ static int routing_policy_rule_remove_handler(sd_netlink *rtnl, sd_netlink_messa r = sd_netlink_message_get_errno(m); if (r < 0) - log_link_warning_errno(link, r, "Could not drop routing policy rule: %m"); + log_link_message_warning_errno(link, m, r, "Could not drop routing policy rule"); return 1; } @@ -431,7 +431,7 @@ static int routing_policy_rule_handler(sd_netlink *rtnl, sd_netlink_message *m, r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) { - log_link_warning_errno(link, r, "Could not add routing policy rule: %m"); + log_link_message_warning_errno(link, m, r, "Could not add routing policy rule"); link_enter_failed(link); return 1; } diff --git a/src/network/tc/qdisc.c b/src/network/tc/qdisc.c index 7f4b2b53cb9..ed9bd9167ae 100644 --- a/src/network/tc/qdisc.c +++ b/src/network/tc/qdisc.c @@ -100,7 +100,7 @@ static int qdisc_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { r = sd_netlink_message_get_errno(m); if (r < 0 && r != -EEXIST) { - log_link_error_errno(link, r, "Could not set QDisc: %m"); + log_link_message_error_errno(link, m, r, "Could not set QDisc: %m"); link_enter_failed(link); return 1; } -- 2.47.3