| # SPDX-License-Identifier: (GPL-2.0 OR Linux-OpenIB) | |
| # Copyright (c) 2020 Nvidia. All rights reserved. | |
| from pyverbs.pyverbs_error import PyverbsError | |
| from libc.string cimport memcpy | |
| import socket, struct | |
| U32_MASK = 0xffffffff | |
| cdef class Spec(PyverbsObject): | |
| """ | |
| Abstract class for all the specs to derive from. | |
| """ | |
| def __init__(self): | |
| raise NotImplementedError('This class is abstract.') | |
| @property | |
| def size(self): | |
| return self.size | |
| cpdef _copy_data(self, unsigned long ptr): | |
| """ | |
| memcpy the spec to the provided address in proper order. | |
| This function must be implemented in each subclass. | |
| :param addr: address to copy spec to | |
| """ | |
| raise NotImplementedError('Must be implemented in subclass.') | |
| def __str__(self): | |
| return f"{'Spec type':<16}: {self.type_to_str(self.spec_type):<20}\n" \ | |
| f"{'Size':<16}: {self.size:<20}\n" | |
| @staticmethod | |
| def type_to_str(spec_type): | |
| types = {v.IBV_FLOW_SPEC_ETH : 'IBV_FLOW_SPEC_ETH', | |
| v.IBV_FLOW_SPEC_IPV4_EXT : "IBV_FLOW_SPEC_IPV4_EXT", | |
| v.IBV_FLOW_SPEC_IPV6 : "IBV_FLOW_SPEC_IPV6", | |
| v.IBV_FLOW_SPEC_TCP : "IBV_FLOW_SPEC_TCP", | |
| v.IBV_FLOW_SPEC_UDP : "IBV_FLOW_SPEC_UDP"} | |
| res_str = "" | |
| if spec_type & v.IBV_FLOW_SPEC_INNER: | |
| res_str += 'IBV_FLOW_SPEC_INNER ' | |
| try: | |
| s_type = spec_type & ~v.IBV_FLOW_SPEC_INNER | |
| res_str += types[s_type] | |
| except IndexError: | |
| raise PyverbsError(f'This type {s_type} is not implemented yet') | |
| return res_str | |
| @staticmethod | |
| def _set_val_mask(default_mask, val=None, val_mask=None): | |
| """ | |
| If value is given without val_mask, default_mask will be returned. | |
| :param default_mask: default mask to set if not provided | |
| :param val: user provided value | |
| :param val_mask: user provided mask | |
| :return: resulting value and mask | |
| """ | |
| res_val = 0 | |
| res_mask = 0 | |
| if val is not None: | |
| res_val = val | |
| res_mask = default_mask if val_mask is None else val_mask | |
| return res_val, res_mask | |
| cdef class EthSpec(Spec): | |
| MAC_LEN = 6 | |
| MAC_MASK = ('ff:' * MAC_LEN)[:-1] | |
| ZERO_MAC = [0] * MAC_LEN | |
| def __init__(self, dst_mac=None, dst_mac_mask=None, src_mac=None, | |
| src_mac_mask=None, ether_type=None, ether_type_mask=None, | |
| vlan_tag=None, vlan_tag_mask=None, is_inner=0): | |
| """ | |
| Initialize a EthSpec object over an underlying ibv_flow_spec_eth C | |
| object that defines Ethernet header specifications for steering flow to | |
| match on. | |
| :param dst_mac: destination mac to match on (e.g. 'aa:bb:12:13:14:fe') | |
| :param dst_mac_mask: destination mac mask (e.g. 'ff:ff:ff:ff:ff:ff') | |
| :param src_mac: source mac to match on | |
| :param src_mac_mask: source mac mask | |
| :param ether_type: ethertype to match on | |
| :param ether_type_mask: ethertype mask | |
| :param vlan_tag: VLAN tag to match on | |
| :param vlan_tag_mask: VLAN tag mask | |
| :param is_inner: is inner spec | |
| """ | |
| self.spec_type = v.IBV_FLOW_SPEC_ETH | |
| if is_inner: | |
| self.spec_type |= v.IBV_FLOW_SPEC_INNER | |
| self.size = sizeof(v.ibv_flow_spec_eth) | |
| self.dst_mac, self.dst_mac_mask = self._set_val_mask(self.MAC_MASK, | |
| dst_mac, | |
| dst_mac_mask) | |
| self.src_mac, self.src_mac_mask = self._set_val_mask(self.MAC_MASK, | |
| src_mac, | |
| src_mac_mask) | |
| self.val.ether_type, self.mask.ether_type = \ | |
| map(socket.htons, self._set_val_mask(0xffff, ether_type, | |
| ether_type_mask)) | |
| self.val.vlan_tag, self.mask.vlan_tag = \ | |
| map(socket.htons, self._set_val_mask(0xffff, vlan_tag, | |
| vlan_tag_mask)) | |
| cdef _mac_to_str(self, unsigned char mac[6]): | |
| s = '' | |
| if len(mac) == 0: | |
| return s | |
| # Building string from array | |
| # [0xa, 0x1b, 0x2c, 0x3c, 0x4d, 0x5e] -> "0a:1b:2c:3c:4d:5e" | |
| for i in range(self.MAC_LEN): | |
| s += hex(mac[i])[2:].zfill(2) + ':' | |
| return s[:-1] | |
| def _set_mac(self, val): | |
| mac = EthSpec.ZERO_MAC[:] | |
| if val: | |
| s = val.split(':') | |
| for i in range(self.MAC_LEN): | |
| mac[i] = int(s[i], 16) | |
| return mac | |
| @property | |
| def dst_mac(self): | |
| return self._mac_to_str(self.val.dst_mac) | |
| @dst_mac.setter | |
| def dst_mac(self, val): | |
| self.val.dst_mac = self._set_mac(val) | |
| @property | |
| def dst_mac_mask(self): | |
| return self._mac_to_str(self.mask.dst_mac) | |
| @dst_mac_mask.setter | |
| def dst_mac_mask(self, val): | |
| self.mask.dst_mac = self._set_mac(val) | |
| @property | |
| def src_mac(self): | |
| return self._mac_to_str(self.val.src_mac) | |
| @src_mac.setter | |
| def src_mac(self, val): | |
| self.val.src_mac = self._set_mac(val) | |
| @property | |
| def src_mac_mask(self): | |
| return self._mac_to_str(self.mask.src_mac) | |
| @src_mac_mask.setter | |
| def src_mac_mask(self, val): | |
| self.mask.src_mac = self._set_mac(val) | |
| @property | |
| def ether_type(self): | |
| return socket.ntohs(self.val.ether_type) | |
| @ether_type.setter | |
| def ether_type(self, val): | |
| self.val.ether_type = socket.htons(val) | |
| @property | |
| def ether_type_mask(self): | |
| return socket.ntohs(self.mask.ether_type) | |
| @ether_type_mask.setter | |
| def ether_type_mask(self, val): | |
| self.mask.ether_type = socket.htons(val) | |
| @property | |
| def vlan_tag(self): | |
| return socket.ntohs(self.val.vlan_tag) | |
| @vlan_tag.setter | |
| def vlan_tag(self, val): | |
| self.val.vlan_tag = socket.htons(val) | |
| @property | |
| def vlan_tag_mask(self): | |
| return socket.ntohs(self.mask.vlan_tag) | |
| @vlan_tag_mask.setter | |
| def vlan_tag_mask(self, val): | |
| self.mask.vlan_tag = socket.htons(val) | |
| def __str__(self): | |
| return super().__str__() + \ | |
| f"{'Src mac':<16}: {self.src_mac:<20} {self.src_mac_mask:<20}\n" \ | |
| f"{'Dst mac':<16}: {self.dst_mac:<20} {self.dst_mac_mask:<20}\n" \ | |
| f"{'Ether type':<16}: {self.val.ether_type:<20} " \ | |
| f"{self.mask.ether_type:<20}\n" \ | |
| f"{'Vlan tag':<16}: {self.val.vlan_tag:<20} " \ | |
| f"{self.mask.vlan_tag:<20}\n" | |
| cpdef _copy_data(self, unsigned long ptr): | |
| cdef v.ibv_flow_spec_eth eth | |
| eth.size = self.size | |
| eth.type = self.spec_type | |
| eth.val = self.val | |
| eth.mask = self.mask | |
| memcpy(<void*>ptr, ð, self.size) | |
| cdef class Ipv4ExtSpec(Spec): | |
| def __init__(self, dst_ip=None, dst_ip_mask=None, src_ip=None, | |
| src_ip_mask=None, proto=None, proto_mask=None, tos=None, | |
| tos_mask=None, ttl=None, ttl_mask=None, flags=None, | |
| flags_mask=None, is_inner=False): | |
| """ | |
| Initialize an Ipv4ExtSpec object over an underlying ibv_flow_ipv4_ext C | |
| object that defines IPv4 header specifications for steering flow to | |
| match on. | |
| :param dst_ip: Destination IP to match on (e.g. '1.2.3.4') | |
| :param dst_ip_mask: Destination IP mask (e.g. '255.255.255.255') | |
| :param src_ip: source IP to match on | |
| :param src_ip_mask: Source IP mask | |
| :param proto: Protocol to match on | |
| :param proto_mask: Protocol mask | |
| :param tos: Type of service to match on | |
| :param tos_mask: Type of service mask | |
| :param ttl: Time to live to match on | |
| :param ttl_mask: Time to live mask | |
| :param flags: Flags to match on | |
| :param flags_mask: Flags mask | |
| :param is_inner: Is inner spec | |
| """ | |
| self.spec_type = v.IBV_FLOW_SPEC_IPV4_EXT | |
| if is_inner: | |
| self.spec_type |= v.IBV_FLOW_SPEC_INNER | |
| self.size = sizeof(v.ibv_flow_spec_ipv4_ext) | |
| self.val.dst_ip, self.mask.dst_ip = \ | |
| map(socket.htonl, self._set_val_mask(U32_MASK, | |
| self._str_to_ip(dst_ip), | |
| self._str_to_ip(dst_ip_mask))) | |
| self.val.src_ip, self.mask.src_ip = \ | |
| map(socket.htonl, self._set_val_mask(U32_MASK, | |
| self._str_to_ip(src_ip), | |
| self._str_to_ip(src_ip_mask))) | |
| self.val.proto, self.mask.proto = self._set_val_mask(0xff, proto, | |
| proto_mask) | |
| self.val.tos, self.mask.tos = self._set_val_mask(0xff, tos, tos_mask) | |
| self.val.ttl, self.mask.ttl = self._set_val_mask(0xff, ttl, ttl_mask) | |
| self.val.flags, self.mask.flags = self._set_val_mask(0xff, flags, | |
| flags_mask) | |
| @staticmethod | |
| def _str_to_ip(ip_str): | |
| return None if ip_str is None else \ | |
| struct.unpack('!L', socket.inet_aton(ip_str))[0] | |
| @staticmethod | |
| def _ip_to_str(ip): | |
| return socket.inet_ntoa(struct.pack('!L', ip)) | |
| @property | |
| def dst_ip(self): | |
| return self._ip_to_str(socket.ntohl(self.val.dst_ip)) | |
| @dst_ip.setter | |
| def dst_ip(self, val): | |
| self.val.dst_ip = socket.htonl(self._str_to_ip(val)) | |
| @property | |
| def dst_ip_mask(self): | |
| return self._ip_to_str(socket.ntohl(self.mask.dst_ip)) | |
| @dst_ip_mask.setter | |
| def dst_ip_mask(self, val): | |
| self.mask.dst_ip = socket.htonl(self._str_to_ip(val)) | |
| @property | |
| def src_ip(self): | |
| return self._ip_to_str(socket.ntohl(self.val.src_ip)) | |
| @src_ip.setter | |
| def src_ip(self, val): | |
| self.val.src_ip = socket.htonl(self._str_to_ip(val)) | |
| @property | |
| def src_ip_mask(self): | |
| return self._ip_to_str(socket.ntohl(self.mask.src_ip)) | |
| @src_ip_mask.setter | |
| def src_ip_mask(self, val): | |
| self.mask.src_ip = socket.htonl(self._str_to_ip(val)) | |
| @property | |
| def proto(self): | |
| return self.val.proto | |
| @proto.setter | |
| def proto(self, val): | |
| self.val.proto = val | |
| @property | |
| def proto_mask(self): | |
| return self.mask.proto | |
| @proto_mask.setter | |
| def proto_mask(self, val): | |
| self.mask.proto = val | |
| @property | |
| def tos(self): | |
| return self.val.tos | |
| @tos.setter | |
| def tos(self, val): | |
| self.val.tos = val | |
| @property | |
| def tos_mask(self): | |
| return self.mask.tos | |
| @tos_mask.setter | |
| def tos_mask(self, val): | |
| self.mask.tos = val | |
| @property | |
| def ttl(self): | |
| return self.val.ttl | |
| @ttl.setter | |
| def ttl(self, val): | |
| self.val.ttl = val | |
| @property | |
| def ttl_mask(self): | |
| return self.mask.ttl | |
| @ttl_mask.setter | |
| def ttl_mask(self, val): | |
| self.mask.ttl = val | |
| @property | |
| def flags(self): | |
| return self.val.flags | |
| @flags.setter | |
| def flags(self, val): | |
| self.val.flags = val | |
| @property | |
| def flags_mask(self): | |
| return self.mask.flags | |
| @flags_mask.setter | |
| def flags_mask(self, val): | |
| self.mask.flags = val | |
| def __str__(self): | |
| return super().__str__() + \ | |
| f"{'Src IP':<16}: {self.src_ip:<20} {self.src_ip_mask:<20}\n" \ | |
| f"{'Dst IP':<16}: {self.dst_ip:<20} {self.dst_ip_mask:<20}\n" \ | |
| f"{'Proto':<16}: {self.val.proto:<20} {self.mask.proto:<20}\n" \ | |
| f"{'ToS':<16}: {self.val.tos:<20} {self.mask.tos:<20}\n" \ | |
| f"{'TTL':<16}: {self.val.ttl:<20} {self.mask.ttl:<20}\n" \ | |
| f"{'Flags':<16}: {self.val.flags:<20} {self.mask.flags:<20}\n" | |
| cpdef _copy_data(self, unsigned long ptr): | |
| cdef v.ibv_flow_spec_ipv4_ext ipv4 | |
| ipv4.size = self.size | |
| ipv4.type = self.spec_type | |
| ipv4.val = self.val | |
| ipv4.mask = self.mask | |
| memcpy(<void*>ptr, &ipv4, self.size) | |
| cdef class TcpUdpSpec(Spec): | |
| def __init__(self, v.ibv_flow_spec_type spec_type, dst_port=None, | |
| dst_port_mask=None, src_port=None, src_port_mask=None, | |
| is_inner=False): | |
| """ | |
| Initialize a TcpUdpSpec object over an underlying ibv_flow_tcp_udp C | |
| object that defines TCP or UDP header specifications for steering flow | |
| to match on. | |
| :param spec_type: IBV_FLOW_SPEC_TCP or IBV_FLOW_SPEC_UDP | |
| :param dst_port: Destination port to match on | |
| :param dst_port_mask: Destination port mask | |
| :param src_port: Source port to match on | |
| :param src_port_mask: Source port mask | |
| :param is_inner: Is inner spec | |
| """ | |
| if spec_type is not v.IBV_FLOW_SPEC_TCP and spec_type is not\ | |
| v.IBV_FLOW_SPEC_UDP: | |
| raise PyverbsError('Spec type must be IBV_FLOW_SPEC_TCP or' | |
| ' IBV_FLOW_SPEC_UDP') | |
| self.spec_type = spec_type | |
| if is_inner: | |
| self.spec_type |= v.IBV_FLOW_SPEC_INNER | |
| self.size = sizeof(v.ibv_flow_spec_tcp_udp) | |
| self.val.dst_port, self.mask.dst_port = \ | |
| map(socket.htons, self._set_val_mask(0xffff, dst_port, | |
| dst_port_mask)) | |
| self.val.src_port, self.mask.src_port = \ | |
| map(socket.htons, self._set_val_mask(0xffff, src_port, | |
| src_port_mask)) | |
| @property | |
| def dst_port(self): | |
| return socket.ntohs(self.val.dst_port) | |
| @dst_port.setter | |
| def dst_port(self, val): | |
| self.val.dst_port = socket.htons(val) | |
| @property | |
| def dst_port_mask(self): | |
| return socket.ntohs(self.mask.dst_port) | |
| @dst_port_mask.setter | |
| def dst_port_mask(self, val): | |
| self.mask.dst_port = socket.htons(val) | |
| @property | |
| def src_port(self): | |
| return socket.ntohs(self.val.src_port) | |
| @src_port.setter | |
| def src_port(self, val): | |
| self.val.src_port = socket.htons(val) | |
| @property | |
| def src_port_mask(self): | |
| return socket.ntohs(self.mask.src_port) | |
| @src_port_mask.setter | |
| def src_port_mask(self, val): | |
| self.mask.src_port = socket.htons(val) | |
| def __str__(self): | |
| return super().__str__() + \ | |
| f"{'Src port':<16}: {self.src_port:<20} {self.src_port_mask:<20}\n" \ | |
| f"{'Dst port':<16}: {self.dst_port:<20} {self.dst_port_mask:<20}\n" | |
| cpdef _copy_data(self, unsigned long ptr): | |
| cdef v.ibv_flow_spec_tcp_udp tcp_udp | |
| tcp_udp.size = self.size | |
| tcp_udp.type = self.spec_type | |
| tcp_udp.val = self.val | |
| tcp_udp.mask = self.mask | |
| memcpy(<void*>ptr, &tcp_udp, self.size) | |
| cdef class Ipv6Spec(Spec): | |
| EMPTY_IPV6 = '::' | |
| IPV6_MASK = ("ffff:" * 8)[:-1] | |
| FLOW_LABEL_MASK = 0xfffff | |
| def __init__(self, dst_ip=None, dst_ip_mask=None, src_ip=None, | |
| src_ip_mask=None, flow_label=None, flow_label_mask=None, | |
| next_hdr=None, next_hdr_mask=None, traffic_class=None, | |
| traffic_class_mask=None, hop_limit=None, hop_limit_mask=None, | |
| is_inner=False): | |
| """ | |
| Initialize an Ipv6Spec object over an underlying ibv_flow_ipv6 C | |
| object that defines IPv6 header specifications for steering flow to | |
| match on. | |
| :param dst_ip: Destination IPv6 to match on (e.g. 'a0a1::a2a3:a4a5:a6a7:a8a9') | |
| :param dst_ip_mask: Destination IPv6 mask (e.g. 'ffff::ffff:ffff:ffff:ffff') | |
| :param src_ip: Source IPv6 to match on | |
| :param src_ip_mask: Source IPv6 mask | |
| :param flow_label: Flow label to match on | |
| :param flow_label_mask: Flow label mask | |
| :param next_hdr: Next header to match on | |
| :param next_hdr_mask: Next header mask | |
| :param traffic_class: Traffic class to match on | |
| :param traffic_class_mask: Traffic class mask | |
| :param hop_limit: Hop limit to match on | |
| :param hop_limit_mask: Hop limit mask | |
| :param is_inner: Is inner spec | |
| """ | |
| self.spec_type = v.IBV_FLOW_SPEC_IPV6 | |
| if is_inner: | |
| self.spec_type |= v.IBV_FLOW_SPEC_INNER | |
| self.size = sizeof(v.ibv_flow_spec_ipv6) | |
| if dst_ip is None: | |
| def_dst_ip_mask = self.EMPTY_IPV6 | |
| dst_ip = '::' | |
| else: | |
| def_dst_ip_mask = self.IPV6_MASK | |
| if src_ip is None: | |
| def_src_ip_mask = self.EMPTY_IPV6 | |
| src_ip = '::' | |
| else: | |
| def_src_ip_mask = self.IPV6_MASK | |
| self.dst_ip, self.dst_ip_mask = self._set_val_mask(def_dst_ip_mask, | |
| dst_ip, dst_ip_mask) | |
| self.src_ip, self.src_ip_mask = self._set_val_mask(def_src_ip_mask, | |
| src_ip, src_ip_mask) | |
| self.val.flow_label, self.mask.flow_label = \ | |
| map(socket.htonl, self._set_val_mask(self.FLOW_LABEL_MASK, | |
| flow_label, flow_label_mask)) | |
| self.val.next_hdr, self.mask.next_hdr = \ | |
| self._set_val_mask(0xff, next_hdr, next_hdr_mask) | |
| self.val.traffic_class, self.mask.traffic_class = \ | |
| self._set_val_mask(0xff, traffic_class, traffic_class_mask) | |
| self.val.hop_limit, self.mask.hop_limit = \ | |
| self._set_val_mask(0xff, hop_limit, hop_limit_mask) | |
| @property | |
| def dst_ip(self): | |
| return socket.inet_ntop(socket.AF_INET6, self.val.dst_ip[:16]) | |
| @dst_ip.setter | |
| def dst_ip(self, val): | |
| self.val.dst_ip = socket.inet_pton(socket.AF_INET6, val) | |
| @property | |
| def dst_ip_mask(self): | |
| return socket.inet_ntop(socket.AF_INET6, self.mask.dst_ip[:16]) | |
| @dst_ip_mask.setter | |
| def dst_ip_mask(self, val): | |
| self.mask.dst_ip = socket.inet_pton(socket.AF_INET6, val) | |
| @property | |
| def src_ip(self): | |
| return socket.inet_ntop(socket.AF_INET6, self.val.src_ip[:16]) | |
| @src_ip.setter | |
| def src_ip(self, val): | |
| self.val.src_ip = socket.inet_pton(socket.AF_INET6, val) | |
| @property | |
| def src_ip_mask(self): | |
| return socket.inet_ntop(socket.AF_INET6, self.mask.src_ip[:16]) | |
| @src_ip_mask.setter | |
| def src_ip_mask(self, val): | |
| self.mask.src_ip = socket.inet_pton(socket.AF_INET6, val) | |
| @property | |
| def flow_label(self): | |
| return socket.ntohl(self.val.flow_label) | |
| @flow_label.setter | |
| def flow_label(self, val): | |
| self.val.flow_label = socket.htonl(val) | |
| @property | |
| def flow_label_mask(self): | |
| return socket.ntohl(self.mask.flow_label) | |
| @flow_label_mask.setter | |
| def flow_label_mask(self, val): | |
| self.mask.flow_label = socket.htonl(val) | |
| @property | |
| def next_hdr(self): | |
| return self.val.next_hdr | |
| @next_hdr.setter | |
| def next_hdr(self, val): | |
| self.val.next_hdr = val | |
| @property | |
| def next_hdr_mask(self): | |
| return self.mask.next_hdr | |
| @next_hdr_mask.setter | |
| def next_hdr_mask(self, val): | |
| self.mask.next_hdr = val | |
| @property | |
| def traffic_class(self): | |
| return self.val.traffic_class | |
| @traffic_class.setter | |
| def traffic_class(self, val): | |
| self.val.traffic_class = val | |
| @property | |
| def traffic_class_mask(self): | |
| return self.mask.traffic_class | |
| @traffic_class_mask.setter | |
| def traffic_class_mask(self, val): | |
| self.mask.traffic_class = val | |
| @property | |
| def hop_limit(self): | |
| return self.val.hop_limit | |
| @hop_limit.setter | |
| def hop_limit(self, val): | |
| self.val.hop_limit = val | |
| @property | |
| def hop_limit_mask(self): | |
| return self.mask.hop_limit | |
| @hop_limit_mask.setter | |
| def hop_limit_mask(self, val): | |
| self.mask.hop_limit = val | |
| def __str__(self): | |
| return super().__str__() + \ | |
| f"{'Src IP':<16}: {self.src_ip:<20} {self.src_ip_mask:<20}\n" \ | |
| f"{'Dst IP':<16}: {self.dst_ip:<20} {self.dst_ip_mask:<20}\n" \ | |
| f"{'Flow label':<16}: {self.flow_label:<20} {self.flow_label_mask:<20}\n" \ | |
| f"{'Next header':<16}: {self.next_hdr:<20} {self.next_hdr_mask:<20}\n" \ | |
| f"{'Traffic class':<16}: {self.traffic_class:<20} {self.traffic_class_mask:<20}\n" \ | |
| f"{'Hop limit':<16}: {self.hop_limit:<20} {self.hop_limit_mask:<20}\n" | |
| cpdef _copy_data(self, unsigned long ptr): | |
| cdef v.ibv_flow_spec_ipv6 ipv6 | |
| ipv6.size = self.size | |
| ipv6.type = self.spec_type | |
| ipv6.val = self.val | |
| ipv6.mask = self.mask | |
| memcpy(<void*>ptr, &ipv6, self.size) |