[systemd-devel] [PATCH v4] Added support for Uplink Failure Detection using BindCarrier

Tom Gundersen teg at jklm.no
Fri Feb 20 02:26:57 PST 2015


On Fri, Feb 20, 2015 at 10:50 AM, Rauta, Alin <alin.rauta at intel.com> wrote:
> Hi Tom, Lennart, Zbyszek,
> Did you have any chance to look at this patch version ?

I hope to review it this weekend. I might go ahead and implement the
DOWN logic independently if that is still an issue (saw your question,
but didn't yet look at how you dealt with it in the patch). We need
that anyway for manually UP/DOWN of networks.

> -----Original Message-----
> From: Rauta, Alin
> Sent: Tuesday, February 17, 2015 12:07 PM
> To: teg at jklm.no; lennart at poettering.net; zbyszek at in.waw.pl
> Cc: systemd-devel at lists.freedesktop.org; Kinsella, Ray; Rauta, Alin
> Subject: [PATCH v4] Added support for Uplink Failure Detection using BindCarrier
>
> ---
>  man/systemd.network.xml                  |  11 +
>  src/libsystemd/sd-network/sd-network.c   |   8 +
>  src/network/networkctl.c                 |  43 ++--
>  src/network/networkd-link.c              | 394 +++++++++++++++++++++++++++++--
>  src/network/networkd-link.h              |   3 +
>  src/network/networkd-network-gperf.gperf |   1 +
>  src/network/networkd-network.c           |   1 +
>  src/network/networkd.h                   |   2 +-
>  src/systemd/sd-network.h                 |   6 +
>  9 files changed, 438 insertions(+), 31 deletions(-)
>
> diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 485876b..60252e5 100644
> --- a/man/systemd.network.xml
> +++ b/man/systemd.network.xml
> @@ -280,6 +280,17 @@
>            </listitem>
>          </varlistentry>
>          <varlistentry>
> +          <term><varname>BindCarrier=</varname></term>
> +          <listitem>
> +            <para>A port or a list of ports. When set, controls the
> +            behaviour of the current interface. When all ports in the list
> +            are in an operational down state, the current interface is brought
> +            down. When at least one port has carrier, the current interface
> +            is brought up.
> +            </para>
> +          </listitem>
> +        </varlistentry>
> +        <varlistentry>
>            <term><varname>Address=</varname></term>
>            <listitem>
>              <para>A static IPv4 or IPv6 address and its prefix length, diff --git a/src/libsystemd/sd-network/sd-network.c b/src/libsystemd/sd-network/sd-network.c
> index c4713fe..fb5152c 100644
> --- a/src/libsystemd/sd-network/sd-network.c
> +++ b/src/libsystemd/sd-network/sd-network.c
> @@ -264,6 +264,14 @@ _public_ int sd_network_link_get_domains(int ifindex, char ***ret) {
>          return network_get_link_strv("DOMAINS", ifindex, ret);  }
>
> +_public_ int sd_network_link_get_carrier_bound_to(int ifindex, char ***ret) {
> +        return network_get_link_strv("CARRIER_BOUND_TO", ifindex, ret);
> +}
> +
> +_public_ int sd_network_link_get_carrier_bound_by(int ifindex, char ***ret) {
> +        return network_get_link_strv("CARRIER_BOUND_BY", ifindex, ret);
> +}
> +
>  _public_ int sd_network_link_get_wildcard_domain(int ifindex) {
>          int r;
>          _cleanup_free_ char *p = NULL, *s = NULL; diff --git a/src/network/networkctl.c b/src/network/networkctl.c index aa83f32..0637513 100644
> --- a/src/network/networkctl.c
> +++ b/src/network/networkctl.c
> @@ -508,6 +508,8 @@ static int link_status_one(
>          const char *driver = NULL, *path = NULL, *vendor = NULL, *model = NULL, *link = NULL;
>          const char *on_color_operational, *off_color_operational,
>                     *on_color_setup, *off_color_setup;
> +        _cleanup_strv_free_ char **carrier_bound_to = NULL;
> +        _cleanup_strv_free_ char **carrier_bound_by = NULL;
>          struct ether_addr e;
>          unsigned iftype;
>          int r, ifindex;
> @@ -606,12 +608,15 @@ static int link_status_one(
>
>          sd_network_link_get_network_file(ifindex, &network);
>
> +        sd_network_link_get_carrier_bound_to(ifindex, &carrier_bound_to);
> +        sd_network_link_get_carrier_bound_by(ifindex,
> + &carrier_bound_by);
> +
>          printf("%s%s%s %i: %s\n", on_color_operational, draw_special_char(DRAW_BLACK_CIRCLE), off_color_operational, ifindex, name);
>
> -        printf("   Link File: %s\n"
> -               "Network File: %s\n"
> -               "        Type: %s\n"
> -               "       State: %s%s%s (%s%s%s)\n",
> +        printf("       Link File: %s\n"
> +               "    Network File: %s\n"
> +               "            Type: %s\n"
> +               "           State: %s%s%s (%s%s%s)\n",
>                 strna(link),
>                 strna(network),
>                 strna(t),
> @@ -619,13 +624,13 @@ static int link_status_one(
>                 on_color_setup, strna(setup_state), off_color_setup);
>
>          if (path)
> -                printf("        Path: %s\n", path);
> +                printf("            Path: %s\n", path);
>          if (driver)
> -                printf("      Driver: %s\n", driver);
> +                printf("          Driver: %s\n", driver);
>          if (vendor)
> -                printf("      Vendor: %s\n", vendor);
> +                printf("          Vendor: %s\n", vendor);
>          if (model)
> -                printf("       Model: %s\n", model);
> +                printf("           Model: %s\n", model);
>
>          if (have_mac) {
>                  _cleanup_free_ char *description = NULL; @@ -634,23 +639,29 @@ static int link_status_one(
>                  ieee_oui(hwdb, &e, &description);
>
>                  if (description)
> -                        printf("  HW Address: %s (%s)\n", ether_addr_to_string(&e, ea), description);
> +                        printf("      HW Address: %s (%s)\n", ether_addr_to_string(&e, ea), description);
>                  else
> -                        printf("  HW Address: %s\n", ether_addr_to_string(&e, ea));
> +                        printf("      HW Address: %s\n", ether_addr_to_string(&e, ea));
>          }
>
>          if (mtu > 0)
> -                printf("         MTU: %u\n", mtu);
> +                printf("             MTU: %u\n", mtu);
>
> -        dump_addresses(rtnl, "     Address: ", ifindex);
> -        dump_gateways(rtnl, hwdb, "     Gateway: ", ifindex);
> +        dump_addresses(rtnl, "         Address: ", ifindex);
> +        dump_gateways(rtnl, hwdb, "         Gateway: ", ifindex);
>
>          if (!strv_isempty(dns))
> -                dump_list("         DNS: ", dns);
> +                dump_list("             DNS: ", dns);
>          if (!strv_isempty(domains))
> -                dump_list("      Domain: ", domains);
> +                dump_list("          Domain: ", domains);
>          if (!strv_isempty(ntp))
> -                dump_list("         NTP: ", ntp);
> +                dump_list("             NTP: ", ntp);
> +
> +        if (!strv_isempty(carrier_bound_to))
> +                dump_list("Carrier Bound To: ", carrier_bound_to);
> +
> +        if (!strv_isempty(carrier_bound_by))
> +                dump_list("Carrier Bound By: ", carrier_bound_by);
>
>          return 0;
>  }
> diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index f716e82..d718037 100644
> --- a/src/network/networkd-link.c
> +++ b/src/network/networkd-link.c
> @@ -275,6 +275,8 @@ static int link_new(Manager *manager, sd_rtnl_message *message, Link **ret) {
>
>  static void link_free(Link *link) {
>          Address *address;
> +        Iterator i;
> +        Link *carrier;
>
>          if (!link)
>                  return;
> @@ -312,6 +314,14 @@ static void link_free(Link *link) {
>
>          udev_device_unref(link->udev_device);
>
> +        HASHMAP_FOREACH (carrier, link->bound_to_links, i)
> +                hashmap_remove(link->bound_to_links, INT_TO_PTR(carrier->ifindex));
> +        hashmap_free(link->bound_to_links);
> +
> +        HASHMAP_FOREACH (carrier, link->bound_by_links, i)
> +                hashmap_remove(link->bound_by_links, INT_TO_PTR(carrier->ifindex));
> +        hashmap_free(link->bound_by_links);
> +
>          free(link);
>  }
>
> @@ -358,19 +368,6 @@ static void link_set_state(Link *link, LinkState state) {
>          return;
>  }
>
> -void link_drop(Link *link) {
> -        if (!link || link->state == LINK_STATE_LINGER)
> -                return;
> -
> -        link_set_state(link, LINK_STATE_LINGER);
> -
> -        log_link_debug(link, "link removed");
> -
> -        link_unref(link);
> -
> -        return;
> -}
> -
>  static void link_enter_unmanaged(Link *link) {
>          assert(link);
>
> @@ -1151,13 +1148,319 @@ static int link_up(Link *link) {
>          return 0;
>  }
>
> +static int link_down_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
> +        _cleanup_link_unref_ Link *link = userdata;
> +        int r;
> +
> +        assert(link);
> +
> +        if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
> +                return 1;
> +
> +        r = sd_rtnl_message_get_errno(m);
> +        if (r < 0)
> +                log_link_warning_errno(link, -r, "%-*s: could not bring
> + down interface: %m", IFNAMSIZ, link->ifname);
> +
> +        return 1;
> +}
> +
> +static int link_down(Link *link) {
> +        _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL;
> +        int r;
> +
> +        assert(link);
> +        assert(link->manager);
> +        assert(link->manager->rtnl);
> +
> +        log_link_debug(link, "bringing link down");
> +
> +        r = sd_rtnl_message_new_link(link->manager->rtnl, &req,
> +                                     RTM_SETLINK, link->ifindex);
> +        if (r < 0) {
> +                log_link_error(link, "Could not allocate RTM_SETLINK message");
> +                return r;
> +        }
> +
> +        r = sd_rtnl_message_link_set_flags(req, 0, IFF_UP);
> +        if (r < 0) {
> +                log_link_error(link, "Could not set link flags: %s",
> +                               strerror(-r));
> +                return r;
> +        }
> +
> +        r = sd_rtnl_call_async(link->manager->rtnl, req, link_down_handler, link,
> +                               0, NULL);
> +        if (r < 0) {
> +                log_link_error(link,
> +                               "Could not send rtnetlink message: %s",
> +                               strerror(-r));
> +                return r;
> +        }
> +
> +        link_ref(link);
> +
> +        return 0;
> +}
> +
> +static int link_handle_bound_to_list(Link *link) {
> +        Link *l;
> +        Iterator i;
> +        int r;
> +        bool required_up = false;
> +        bool link_is_up = false;
> +
> +        assert(link);
> +
> +        if (hashmap_isempty(link->bound_to_links))
> +                return 0;
> +
> +        if (link->flags & IFF_UP)
> +                link_is_up = true;
> +
> +        HASHMAP_FOREACH (l, link->bound_to_links, i)
> +                if (link_has_carrier(l)) {
> +                        required_up = true;
> +                        break;
> +                }
> +
> +        if (!required_up && link_is_up) {
> +                r = link_down(link);
> +                if (r < 0)
> +                        return r;
> +        } else if (required_up && !link_is_up) {
> +                r = link_up(link);
> +                if (r < 0)
> +                        return r;
> +        }
> +
> +        return 0;
> +}
> +
> +static int link_handle_bound_by_list(Link *link) {
> +        Iterator i;
> +        Link *l;
> +        int r;
> +
> +        assert(link);
> +
> +        if (hashmap_isempty(link->bound_by_links))
> +                return 0;
> +
> +        HASHMAP_FOREACH (l, link->bound_by_links, i) {
> +                r = link_handle_bound_to_list(l);
> +                if (r < 0)
> +                        return r;
> +        }
> +
> +        return 0;
> +}
> +
> +static int link_put_carrier(Link *link, Link *carrier, Hashmap **h) {
> +        int r;
> +
> +        assert(link);
> +        assert(carrier);
> +
> +        if (link == carrier)
> +                return 0;
> +
> +        if (hashmap_get(*h, INT_TO_PTR(carrier->ifindex)))
> +                return 0;
> +
> +        r = hashmap_ensure_allocated(h, NULL);
> +        if (r < 0)
> +                return r;
> +
> +        r = hashmap_put(*h, INT_TO_PTR(carrier->ifindex), carrier);
> +        if (r < 0)
> +                return r;
> +
> +        return 0;
> +}
> +
> +static int link_new_bound_by_list(Link *link) {
> +        Manager *m;
> +        Link *carrier;
> +        Iterator i;
> +        int r;
> +        bool list_updated = false;
> +
> +        assert(link);
> +        assert(link->manager);
> +
> +        m = link->manager;
> +
> +        HASHMAP_FOREACH (carrier, m->links, i) {
> +                if (!carrier->network)
> +                        continue;
> +
> +                if (strv_isempty(carrier->network->bind_carrier))
> +                        continue;
> +
> +                if (strv_fnmatch(carrier->network->bind_carrier, link->ifname, 0)) {
> +                        r = link_put_carrier(link, carrier, &link->bound_by_links);
> +                        if (r < 0)
> +                                return r;
> +
> +                        list_updated = true;
> +                }
> +        }
> +
> +        if (list_updated)
> +                link_save(link);
> +
> +        HASHMAP_FOREACH (carrier, link->bound_by_links, i) {
> +                r = link_put_carrier(carrier, link, &carrier->bound_to_links);
> +                if (r < 0)
> +                        return r;
> +
> +                link_save(carrier);
> +        }
> +
> +        return 0;
> +}
> +
> +static int link_new_bound_to_list(Link *link) {
> +        Manager *m;
> +        Link *carrier;
> +        Iterator i;
> +        int r;
> +        bool list_updated = false;
> +
> +        assert(link);
> +        assert(link->manager);
> +
> +        if (!link->network)
> +                return 0;
> +
> +        if (strv_isempty(link->network->bind_carrier))
> +                return 0;
> +
> +        m = link->manager;
> +
> +        HASHMAP_FOREACH (carrier, m->links, i) {
> +                if (strv_fnmatch(link->network->bind_carrier, carrier->ifname, 0)) {
> +                        r = link_put_carrier(link, carrier, &link->bound_to_links);
> +                        if (r < 0)
> +                                return r;
> +
> +                        list_updated = true;
> +                }
> +        }
> +
> +        if (list_updated)
> +                link_save(link);
> +
> +        HASHMAP_FOREACH (carrier, link->bound_to_links, i) {
> +                r = link_put_carrier(carrier, link, &carrier->bound_by_links);
> +                if (r < 0)
> +                        return r;
> +
> +                link_save(carrier);
> +        }
> +
> +        return 0;
> +}
> +
> +static int link_new_carrier_maps(Link *link) {
> +        int r;
> +
> +        r = link_new_bound_by_list(link);
> +        if (r < 0)
> +                return r;
> +
> +        r = link_handle_bound_by_list(link);
> +        if (r < 0)
> +                return r;
> +
> +        r = link_new_bound_to_list(link);
> +        if (r < 0)
> +                return r;
> +
> +        r = link_handle_bound_to_list(link);
> +        if (r < 0)
> +                return r;
> +
> +        return 0;
> +}
> +
> +static void link_free_bound_to_list(Link *link) {
> +        Link *bound_to;
> +        Iterator i;
> +
> +        HASHMAP_FOREACH (bound_to, link->bound_to_links, i) {
> +                hashmap_remove(link->bound_to_links,
> + INT_TO_PTR(bound_to->ifindex));
> +
> +                if (hashmap_remove(bound_to->bound_by_links, INT_TO_PTR(link->ifindex)))
> +                        link_save(bound_to);
> +        }
> +
> +        return;
> +}
> +
> +static void link_free_bound_by_list(Link *link) {
> +        Link *bound_by;
> +        Iterator i;
> +
> +        HASHMAP_FOREACH (bound_by, link->bound_by_links, i) {
> +                hashmap_remove(link->bound_by_links,
> + INT_TO_PTR(bound_by->ifindex));
> +
> +                if (hashmap_remove(bound_by->bound_to_links, INT_TO_PTR(link->ifindex))) {
> +                        link_save(bound_by);
> +                        link_handle_bound_to_list(bound_by);
> +                }
> +        }
> +
> +        return;
> +}
> +
> +static void link_free_carrier_maps(Link *link) {
> +        bool list_updated = false;
> +
> +        assert(link);
> +
> +        if (!hashmap_isempty(link->bound_to_links)) {
> +                link_free_bound_to_list(link);
> +                list_updated = true;
> +        }
> +
> +        if (!hashmap_isempty(link->bound_by_links)) {
> +                link_free_bound_by_list(link);
> +                list_updated = true;
> +        }
> +
> +        if (list_updated)
> +                link_save(link);
> +
> +        return;
> +}
> +
> +void link_drop(Link *link) {
> +        if (!link || link->state == LINK_STATE_LINGER)
> +                return;
> +
> +        link_set_state(link, LINK_STATE_LINGER);
> +
> +        link_free_carrier_maps(link);
> +
> +        log_link_debug(link, "link removed");
> +
> +        link_unref(link);
> +
> +        return;
> +}
> +
>  static int link_joined(Link *link) {
>          int r;
>
>          assert(link);
>          assert(link->network);
>
> -        if (!(link->flags & IFF_UP)) {
> +        if (!hashmap_isempty(link->bound_to_links)) {
> +                r = link_handle_bound_to_list(link);
> +                if (r < 0)
> +                        return r;
> +        } else if (!(link->flags & IFF_UP)) {
>                  r = link_up(link);
>                  if (r < 0) {
>                          link_enter_failed(link); @@ -1432,6 +1735,14 @@ static int link_initialized_and_synced(sd_rtnl *rtnl, sd_rtnl_message *m,
>
>          log_link_debug(link, "link state is up-to-date");
>
> +        r = link_new_bound_by_list(link);
> +        if (r < 0)
> +                return r;
> +
> +        r = link_handle_bound_by_list(link);
> +        if (r < 0)
> +                return r;
> +
>          r = network_get(link->manager, link->udev_device, link->ifname,
>                          &link->mac, &network);
>          if (r == -ENOENT) {
> @@ -1455,6 +1766,10 @@ static int link_initialized_and_synced(sd_rtnl *rtnl, sd_rtnl_message *m,
>          if (r < 0)
>                  return r;
>
> +        r = link_new_bound_to_list(link);
> +        if (r < 0)
> +                return r;
> +
>          r = link_configure(link);
>          if (r < 0)
>                  return r;
> @@ -1732,6 +2047,10 @@ static int link_carrier_gained(Link *link) {
>                  }
>          }
>
> +        r = link_handle_bound_by_list(link);
> +        if (r < 0)
> +                return r;
> +
>          return 0;
>  }
>
> @@ -1746,6 +2065,10 @@ static int link_carrier_lost(Link *link) {
>                  return r;
>          }
>
> +        r = link_handle_bound_by_list(link);
> +        if (r < 0)
> +                return r;
> +
>          return 0;
>  }
>
> @@ -1785,16 +2108,26 @@ int link_update(Link *link, sd_rtnl_message *m) {
>                  link_ref(link);
>                  log_link_info(link, "link readded");
>                  link_set_state(link, LINK_STATE_ENSLAVING);
> +
> +                r = link_new_carrier_maps(link);
> +                if (r < 0)
> +                        return r;
>          }
>
>          r = sd_rtnl_message_read_string(m, IFLA_IFNAME, &ifname);
>          if (r >= 0 && !streq(ifname, link->ifname)) {
>                  log_link_info(link, "renamed to %s", ifname);
>
> +                link_free_carrier_maps(link);
> +
>                  free(link->ifname);
>                  link->ifname = strdup(ifname);
>                  if (!link->ifname)
>                          return -ENOMEM;
> +
> +                r = link_new_carrier_maps(link);
> +                if (r < 0)
> +                        return r;
>          }
>
>          r = sd_rtnl_message_read_u32(m, IFLA_MTU, &mtu); @@ -2063,6 +2396,39 @@ int link_save(Link *link) {
>                          llmnr_support_to_string(link->network->llmnr));
>          }
>
> +        if (!hashmap_isempty(link->bound_to_links)) {
> +                Link *carrier;
> +                Iterator i;
> +                bool space = false;
> +
> +                fputs("CARRIER_BOUND_TO=", f);
> +                HASHMAP_FOREACH(carrier, link->bound_to_links, i) {
> +                        if (space)
> +                                fputc(' ', f);
> +                        fputs(carrier->ifname, f);
> +                        space = true;
> +                }
> +
> +                fputs("\n", f);
> +        }
> +
> +        if (!hashmap_isempty(link->bound_by_links)) {
> +                Link *carrier;
> +                Iterator i;
> +                bool space = false;
> +
> +                fputs("CARRIER_BOUND_BY=", f);
> +                space = false;
> +                HASHMAP_FOREACH(carrier, link->bound_by_links, i) {
> +                        if (space)
> +                                fputc(' ', f);
> +                        fputs(carrier->ifname, f);
> +                        space = true;
> +                }
> +
> +                fputs("\n", f);
> +        }
> +
>          if (link->dhcp_lease) {
>                  assert(link->network);
>
> diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index cec158e..479098c 100644
> --- a/src/network/networkd-link.h
> +++ b/src/network/networkd-link.h
> @@ -85,6 +85,9 @@ struct Link {
>
>          sd_lldp *lldp;
>          char *lldp_file;
> +
> +        Hashmap *bound_by_links;
> +        Hashmap *bound_to_links;
>  };
>
>  Link *link_unref(Link *link);
> diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
> index fc277df..b0c23a7 100644
> --- a/src/network/networkd-network-gperf.gperf
> +++ b/src/network/networkd-network-gperf.gperf
> @@ -48,6 +48,7 @@ Network.LLMNR,               config_parse_llmnr,                 0,
>  Network.NTP,                 config_parse_strv,                  0,                             offsetof(Network, ntp)
>  Network.IPForward,           config_parse_address_family_boolean,0,                             offsetof(Network, ip_forward)
>  Network.IPMasquerade,        config_parse_bool,                  0,                             offsetof(Network, ip_masquerade)
> +Network.BindCarrier,         config_parse_strv,                  0,                             offsetof(Network, bind_carrier)
>  Address.Address,             config_parse_address,               0,                             0
>  Address.Peer,                config_parse_address,               0,                             0
>  Address.Broadcast,           config_parse_broadcast,             0,                             0
> diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 35ac064..3be993c 100644
> --- a/src/network/networkd-network.c
> +++ b/src/network/networkd-network.c
> @@ -209,6 +209,7 @@ void network_free(Network *network) {
>          strv_free(network->ntp);
>          strv_free(network->dns);
>          strv_free(network->domains);
> +        strv_free(network->bind_carrier);
>
>          netdev_unref(network->bridge);
>
> diff --git a/src/network/networkd.h b/src/network/networkd.h index bdb2f20..e75746f 100644
> --- a/src/network/networkd.h
> +++ b/src/network/networkd.h
> @@ -151,7 +151,7 @@ struct Network {
>          Hashmap *fdb_entries_by_section;
>
>          bool wildcard_domain;
> -        char **domains, **dns, **ntp;
> +        char **domains, **dns, **ntp, **bind_carrier;
>
>          LLMNRSupport llmnr;
>
> diff --git a/src/systemd/sd-network.h b/src/systemd/sd-network.h index 027730d..4d96c86 100644
> --- a/src/systemd/sd-network.h
> +++ b/src/systemd/sd-network.h
> @@ -116,6 +116,12 @@ int sd_network_link_get_lldp(int ifindex, char **lldp);
>  /* Get the DNS domain names for a given link. */  int sd_network_link_get_domains(int ifindex, char ***domains);
>
> +/* Get the CARRIERS to which current link is bound to. */ int
> +sd_network_link_get_carrier_bound_to(int ifindex, char ***carriers);
> +
> +/* Get the CARRIERS that are bound to current link. */ int
> +sd_network_link_get_carrier_bound_by(int ifindex, char ***carriers);
> +
>  /* Returns whether or not domains that don't match any link should be resolved
>   * on this link. 1 for yes, 0 for no and negative value for error */  int sd_network_link_get_wildcard_domain(int ifindex);
> --
> 1.9.3
>


More information about the systemd-devel mailing list