[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [PATCH] efinet: filter multicast traffic based on addresses
From: |
Andrei Borzenkov |
Subject: |
Re: [PATCH] efinet: filter multicast traffic based on addresses |
Date: |
Sun, 29 Nov 2015 19:18:24 +0300 |
User-agent: |
Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Thunderbird/38.3.0 |
29.11.2015 10:02, Andrei Borzenkov пишет:
> 17.11.2015 21:35, Josef Bacik пишет:
>> We have some hardware that claims to support PROMISCUOUS_MULTICAST but
>> doesn't
>> actually work. Instead utilize the multicast filters and specifically enable
>> the multicast traffic we care about. In reality we only care about ipv6
>> multicast traffic but enable ipv4 multicast as well just in case. Whenever
>> we
>> add a new address to the card we calculate the solicited node multicast
>> address
>> to the multicast filter. With this patch my broken hardware is still broken
>> but
>> functional. Thanks,
>>
>> Signed-off-by: Josef Bacik <address@hidden>
>> ---
>> grub-core/net/drivers/efi/efinet.c | 84
>> ++++++++++++++++++++++++++++++++++----
>> grub-core/net/net.c | 2 +
>> include/grub/net.h | 54 ++++++++++++------------
>> 3 files changed, 105 insertions(+), 35 deletions(-)
>>
>> diff --git a/grub-core/net/drivers/efi/efinet.c
>> b/grub-core/net/drivers/efi/efinet.c
>> index c8f80a1..bbbadd2 100644
>> --- a/grub-core/net/drivers/efi/efinet.c
>> +++ b/grub-core/net/drivers/efi/efinet.c
>> @@ -23,6 +23,7 @@
>> #include <grub/efi/api.h>
>> #include <grub/efi/efi.h>
>> #include <grub/i18n.h>
>> +#include <grub/net/ip.h>
>>
>> GRUB_MOD_LICENSE ("GPLv3+");
>>
>> @@ -183,8 +184,9 @@ open_card (struct grub_net_card *dev)
>> We need unicast and broadcast and additionaly all nodes and
>> solicited multicast for IPv6. Solicited multicast is per-IPv6
>> address and we currently do not have API to do it so simply
>
> This comment becomes wrong now after your patch
>
>> - try to enable receive of all multicast packets or evertyhing in
>> - the worst case (i386 PXE driver always enables promiscuous too).
>> + enable the all node addresses and the link local address. We do this
>> + because some firmware has been found to not do promiscuous multicast
>> + mode properly.
>>
>> This does trust firmware to do what it claims to do.
>> */
>> @@ -192,14 +194,25 @@ open_card (struct grub_net_card *dev)
>> {
>> grub_uint32_t filters = GRUB_EFI_SIMPLE_NETWORK_RECEIVE_UNICAST |
>> GRUB_EFI_SIMPLE_NETWORK_RECEIVE_BROADCAST |
>> -
>> GRUB_EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS_MULTICAST;
>> + GRUB_EFI_SIMPLE_NETWORK_RECEIVE_MULTICAST;
>> + grub_efi_status_t st;
>> + grub_efi_mac_address_t mac_filter[2] = {
>> + { 0x1, 0, 0x5e, 0, 0, 1, },
>
> Why this one? Where is it defined?
>
>> + { 0x33, 0x33, 0, 0, 0, 1, },};
>>
>> filters &= net->mode->receive_filter_mask;
>> - if (!(filters &
>> GRUB_EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS_MULTICAST))
>> - filters |= (net->mode->receive_filter_mask &
>> - GRUB_EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS);
>> -
>> - efi_call_6 (net->receive_filters, net, filters, 0, 0, 0, NULL);
>> + if (net->mode->max_mcast_filter_count < 2)
>> + filters &= ~GRUB_EFI_SIMPLE_NETWORK_RECEIVE_MULTICAST;
>> +
>> + if (filters & GRUB_EFI_SIMPLE_NETWORK_RECEIVE_MULTICAST)
>> + st = efi_call_6 (net->receive_filters, net, filters, 0, 0, 2,
>> + mac_filter);
>> + else
>> + st = efi_call_6 (net->receive_filters, net, filters, 0, 0, 0,
>> + NULL);
>
> I think we still should attempt to fallback to promiscuous in this case.
> It is better than giving up completely. Also grub_dprintf with current
> filter mask would be good.
>
>> + if (st != GRUB_EFI_SUCCESS)
>> + grub_dprintf("efinet", "failed to set receive filters %u, %u\n",
>> + (unsigned)filters, (unsigned)st);
>> }
>>
>> efi_call_4 (grub_efi_system_table->boot_services->close_protocol,
>> @@ -212,6 +225,58 @@ open_card (struct grub_net_card *dev)
>> return GRUB_ERR_NONE;
>> }
>>
>> +/* We only need the lower 24 bits of the address, so just take the bottom
>> part
>> + of the address and convert it over.
>> + */
>> +static void
>> +solicited_node_mcast_addr_to_mac (grub_uint64_t addr,
>> + grub_efi_mac_address_t mac)
>> +{
>> + grub_uint64_t cpu_addr = grub_be_to_cpu64(addr);
>
> Why cannot you simply copy last 3 bytes?
>
>> + int i, c = 0;
>> +
>> + /* The format is 33:33:xx:xx:xx:xx, where xx is the last 32 bits of the
>> + multicast address.
>> +
>> + The solicited node mcast addr is in the format
>> ff02:0:0:0:0:1:ffxx:xxxx,
>> + where xx is the last 24 bits of the ipv6 address.
>> + */
>> + mac[0] = 0x33;
>> + mac[1] = 0x33;
>> + mac[2] = 0xff;
>> + for (i = 3; i < 6; i++, c++)
>> + mac[i] = (grub_uint8_t)((cpu_addr >> (16 - 8 * c)) & 0xff);
>> +}
>> +
>> +static void
>> +add_addr (struct grub_net_card *dev,
>> + const grub_net_network_level_address_t *address)
>> +{
>> + grub_efi_simple_network_t *net = dev->efi_net;
>> + grub_efi_mac_address_t mac_filters[16];
>> + grub_efi_status_t st;
>> + unsigned slot = net->mode->mcast_filter_count;
>> +
>> + /* We don't need to add anything for ipv4 addresses. */
>> + if (address->type != GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6)
>> + return;
>> +
>> + if ((slot >= net->mode->max_mcast_filter_count)
Should not we fall back to promiscuous in this case as the last resort?
>> + || !(GRUB_EFI_SIMPLE_NETWORK_RECEIVE_MULTICAST &
>> + net->mode->receive_filter_mask))
>> + return;
>> +
>> + grub_memcpy(mac_filters, net->mode->mcast_filter,
>> + sizeof (grub_efi_mac_address_t) * slot);
>> + solicited_node_mcast_addr_to_mac (address->ipv6[1], mac_filters[slot++]);
>> + st = efi_call_6 (net->receive_filters, net,
>> + GRUB_EFI_SIMPLE_NETWORK_RECEIVE_MULTICAST, 0, 0, slot,
>> + mac_filters);
>
> What about overlapping addresses (see also below)?
>
>> + if (st != GRUB_EFI_SUCCESS)
>> + grub_dprintf("efinet", "failed to add new receive filter %u\n",
>> + (unsigned)st);
>> +}
>> +
>> static void
>> close_card (struct grub_net_card *dev)
>> {
>> @@ -228,7 +293,8 @@ static struct grub_net_card_driver efidriver =
>> .open = open_card,
>> .close = close_card,
>> .send = send_card_buffer,
>> - .recv = get_card_packet
>> + .recv = get_card_packet,
>> + .add_addr = add_addr,
>
> Well ... if you add function to add filter, you should also add function
> to remove filter when address is removed. And here it becomes
> complicated; mapping is not 1-to-1 so we need to reference count used
> multicast addresses.
>
Actually I think it should simply build filter completely every time
from addresses (interfaces) on this card. This automatically gives us
duplicates elimination as well as correct handling of removed addresses.
>> };
>>
>> grub_efi_handle_t
>> diff --git a/grub-core/net/net.c b/grub-core/net/net.c
>> index 65bea28..c4d2484 100644
>> --- a/grub-core/net/net.c
>> +++ b/grub-core/net/net.c
>> @@ -252,6 +252,8 @@ grub_net_add_addr_real (char *name,
>> inter->dhcp_ack = NULL;
>> inter->dhcp_acklen = 0;
>>
>> + if (card->driver->add_addr)
>> + card->driver->add_addr(card, addr);
>> grub_net_network_level_interface_register (inter);
>>
>> return inter;
>> diff --git a/include/grub/net.h b/include/grub/net.h
>> index a1ff519..885f0be 100644
>> --- a/include/grub/net.h
>> +++ b/include/grub/net.h
>> @@ -67,6 +67,32 @@ typedef enum grub_net_card_flags
>> GRUB_NET_CARD_NO_MANUAL_INTERFACES = 2
>> } grub_net_card_flags_t;
>>
>> +typedef enum grub_network_level_protocol_id
>> +{
>> + GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV,
>> + GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4,
>> + GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6
>> +} grub_network_level_protocol_id_t;
>> +
>> +typedef enum
>> +{
>> + DNS_OPTION_IPV4,
>> + DNS_OPTION_IPV6,
>> + DNS_OPTION_PREFER_IPV4,
>> + DNS_OPTION_PREFER_IPV6
>> +} grub_dns_option_t;
>> +
>> +typedef struct grub_net_network_level_address
>> +{
>> + grub_network_level_protocol_id_t type;
>> + union
>> + {
>> + grub_uint32_t ipv4;
>> + grub_uint64_t ipv6[2];
>> + };
>> + grub_dns_option_t option;
>> +} grub_net_network_level_address_t;
>> +
>> struct grub_net_card;
>>
>> struct grub_net_card_driver
>> @@ -79,6 +105,8 @@ struct grub_net_card_driver
>> grub_err_t (*send) (struct grub_net_card *dev,
>> struct grub_net_buff *buf);
>> struct grub_net_buff * (*recv) (struct grub_net_card *dev);
>> + void (*add_addr) (struct grub_net_card *dev,
>> + const grub_net_network_level_address_t *address);
>> };
>>
>> typedef struct grub_net_packet
>> @@ -150,32 +178,6 @@ struct grub_net_card
>>
>> struct grub_net_network_level_interface;
>>
>> -typedef enum grub_network_level_protocol_id
>> -{
>> - GRUB_NET_NETWORK_LEVEL_PROTOCOL_DHCP_RECV,
>> - GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV4,
>> - GRUB_NET_NETWORK_LEVEL_PROTOCOL_IPV6
>> -} grub_network_level_protocol_id_t;
>> -
>> -typedef enum
>> -{
>> - DNS_OPTION_IPV4,
>> - DNS_OPTION_IPV6,
>> - DNS_OPTION_PREFER_IPV4,
>> - DNS_OPTION_PREFER_IPV6
>> -} grub_dns_option_t;
>> -
>> -typedef struct grub_net_network_level_address
>> -{
>> - grub_network_level_protocol_id_t type;
>> - union
>> - {
>> - grub_uint32_t ipv4;
>> - grub_uint64_t ipv6[2];
>> - };
>> - grub_dns_option_t option;
>> -} grub_net_network_level_address_t;
>> -
>> typedef struct grub_net_network_level_netaddress
>> {
>> grub_network_level_protocol_id_t type;
>>
>