mirror of
				https://github.com/Telecominfraproject/ols-ucentral-client.git
				synced 2025-10-31 01:48:03 +00:00 
			
		
		
		
	Merge pull request #5 from Telecominfraproject/plv_next_270224
Plv next 270224
This commit is contained in:
		| @@ -41,6 +41,7 @@ extern "C" { | |||||||
|  */ |  */ | ||||||
| #define PID_TO_NAME(p, name) sprintf(name, "Ethernet%hu", p) | #define PID_TO_NAME(p, name) sprintf(name, "Ethernet%hu", p) | ||||||
| #define NAME_TO_PID(p, name) sscanf((name), "Ethernet%hu", (p)) | #define NAME_TO_PID(p, name) sscanf((name), "Ethernet%hu", (p)) | ||||||
|  | #define VLAN_TO_NAME(v, name) sprintf((name), "Vlan%hu", (v)) | ||||||
|  |  | ||||||
| struct plat_vlan_memberlist; | struct plat_vlan_memberlist; | ||||||
| struct plat_port_vlan; | struct plat_port_vlan; | ||||||
| @@ -64,6 +65,18 @@ enum plat_ieee8021x_port_host_mode { | |||||||
| 	PLAT_802_1X_PORT_HOST_MODE_SINGLE_HOST, | 	PLAT_802_1X_PORT_HOST_MODE_SINGLE_HOST, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | enum plat_ieee8021x_das_auth_type { | ||||||
|  | 	PLAT_802_1X_DAS_AUTH_TYPE_ANY, | ||||||
|  | 	PLAT_802_1X_DAS_AUTH_TYPE_ALL, | ||||||
|  | 	PLAT_802_1X_DAS_AUTH_TYPE_SESSION_KEY, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum plat_igmp_version { | ||||||
|  | 	PLAT_IGMP_VERSION_1, | ||||||
|  | 	PLAT_IGMP_VERSION_2, | ||||||
|  | 	PLAT_IGMP_VERSION_3 | ||||||
|  | }; | ||||||
|  |  | ||||||
| #define UCENTRAL_PORT_LLDP_PEER_INFO_MAX_MGMT_IPS (2) | #define UCENTRAL_PORT_LLDP_PEER_INFO_MAX_MGMT_IPS (2) | ||||||
| /* Interface LLDP peer's data, as defined in interface.lldp.yml*/ | /* Interface LLDP peer's data, as defined in interface.lldp.yml*/ | ||||||
| struct plat_port_lldp_peer_info { | struct plat_port_lldp_peer_info { | ||||||
| @@ -252,10 +265,27 @@ struct plat_port_l2 { | |||||||
| 	struct plat_ipv4 ipv4; | 	struct plat_ipv4 ipv4; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | struct plat_igmp { | ||||||
|  | 	bool exist; | ||||||
|  | 	bool snooping_enabled; | ||||||
|  | 	bool querier_enabled; | ||||||
|  | 	bool fast_leave_enabled; | ||||||
|  | 	uint32_t query_interval; | ||||||
|  | 	uint32_t last_member_query_interval; | ||||||
|  | 	uint32_t max_response_time; | ||||||
|  | 	enum plat_igmp_version version; | ||||||
|  | 	size_t num_groups; | ||||||
|  | 	struct { | ||||||
|  | 		struct in_addr addr; | ||||||
|  | 		struct plat_ports_list *egress_ports_list; | ||||||
|  | 	} *groups; | ||||||
|  | }; | ||||||
|  |  | ||||||
| struct plat_port_vlan { | struct plat_port_vlan { | ||||||
| 	struct plat_vlan_memberlist *members_list_head; | 	struct plat_vlan_memberlist *members_list_head; | ||||||
| 	struct plat_ipv4 ipv4; | 	struct plat_ipv4 ipv4; | ||||||
| 	struct plat_dhcp dhcp; | 	struct plat_dhcp dhcp; | ||||||
|  | 	struct plat_igmp igmp; | ||||||
| 	uint16_t id; | 	uint16_t id; | ||||||
| 	uint16_t mstp_instance; | 	uint16_t mstp_instance; | ||||||
| }; | }; | ||||||
| @@ -282,6 +312,18 @@ struct plat_syslog_cfg { | |||||||
| 	char host[SYSLOG_CFG_FIELD_STR_MAX_LEN]; | 	char host[SYSLOG_CFG_FIELD_STR_MAX_LEN]; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | struct plat_enabled_service_cfg { | ||||||
|  | 	struct { | ||||||
|  | 		bool enabled; | ||||||
|  | 	} ssh; | ||||||
|  | 	struct telnet { | ||||||
|  | 		bool enabled; | ||||||
|  | 	} telnet; | ||||||
|  | 	struct { | ||||||
|  | 		bool enabled; | ||||||
|  | 	} http; | ||||||
|  | }; | ||||||
|  |  | ||||||
| struct plat_rtty_cfg { | struct plat_rtty_cfg { | ||||||
| 	char id[RTTY_CFG_FIELD_STR_MAX_LEN]; | 	char id[RTTY_CFG_FIELD_STR_MAX_LEN]; | ||||||
| 	char passwd[RTTY_CFG_FIELD_STR_MAX_LEN]; | 	char passwd[RTTY_CFG_FIELD_STR_MAX_LEN]; | ||||||
| @@ -376,6 +418,31 @@ struct plat_radius_hosts_list { | |||||||
| 	struct plat_radius_host host; | 	struct plat_radius_host host; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | struct plat_ieee8021x_dac_host { | ||||||
|  | 	char hostname[RADIUS_CFG_HOSTNAME_STR_MAX_LEN]; | ||||||
|  | 	char passkey[RADIUS_CFG_PASSKEY_STR_MAX_LEN]; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct plat_ieee8021x_dac_list { | ||||||
|  | 	struct plat_ieee8021x_dac_list *next; | ||||||
|  | 	struct plat_ieee8021x_dac_host host; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct plat_port_isolation_session_ports { | ||||||
|  | 	struct plat_ports_list *ports_list; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct plat_port_isolation_session { | ||||||
|  | 	uint64_t id; | ||||||
|  | 	struct plat_port_isolation_session_ports uplink; | ||||||
|  | 	struct plat_port_isolation_session_ports downlink; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct plat_port_isolation_cfg { | ||||||
|  | 	struct plat_port_isolation_session *sessions; | ||||||
|  | 	size_t sessions_num; | ||||||
|  | }; | ||||||
|  |  | ||||||
| struct plat_cfg { | struct plat_cfg { | ||||||
| 	struct plat_unit unit; | 	struct plat_unit unit; | ||||||
| 	/* Alloc all ports, but access them only if bit is set. */ | 	/* Alloc all ports, but access them only if bit is set. */ | ||||||
| @@ -385,6 +452,7 @@ struct plat_cfg { | |||||||
| 	BITMAP_DECLARE(vlans_to_cfg, MAX_VLANS); | 	BITMAP_DECLARE(vlans_to_cfg, MAX_VLANS); | ||||||
| 	struct plat_metrics_cfg metrics; | 	struct plat_metrics_cfg metrics; | ||||||
| 	struct plat_syslog_cfg *log_cfg; | 	struct plat_syslog_cfg *log_cfg; | ||||||
|  | 	struct plat_enabled_service_cfg enabled_services_cfg; | ||||||
| 	/* Port's interfaces (provide l2 iface w/o bridge caps) */ | 	/* Port's interfaces (provide l2 iface w/o bridge caps) */ | ||||||
| 	struct plat_port_l2 portsl2[MAX_NUM_OF_PORTS]; | 	struct plat_port_l2 portsl2[MAX_NUM_OF_PORTS]; | ||||||
| 	struct ucentral_router router; | 	struct ucentral_router router; | ||||||
| @@ -393,7 +461,17 @@ struct plat_cfg { | |||||||
| 	/* Instance zero is for global instance (like common values in rstp) */ | 	/* Instance zero is for global instance (like common values in rstp) */ | ||||||
| 	struct plat_stp_instance_cfg stp_instances[MAX_VLANS]; | 	struct plat_stp_instance_cfg stp_instances[MAX_VLANS]; | ||||||
| 	struct plat_radius_hosts_list *radius_hosts_list; | 	struct plat_radius_hosts_list *radius_hosts_list; | ||||||
| 	bool ieee8021x_is_auth_ctrl_enabled; | 	struct { | ||||||
|  | 		bool is_auth_ctrl_enabled; | ||||||
|  | 		bool bounce_port_ignore; | ||||||
|  | 		bool disable_port_ignore; | ||||||
|  | 		bool ignore_server_key; | ||||||
|  | 		bool ignore_session_key; | ||||||
|  | 		char server_key[RADIUS_CFG_PASSKEY_STR_MAX_LEN]; | ||||||
|  | 		enum plat_ieee8021x_das_auth_type das_auth_type; | ||||||
|  | 		struct plat_ieee8021x_dac_list *das_dac_list; | ||||||
|  | 	} ieee8021x; | ||||||
|  | 	struct plat_port_isolation_cfg port_isolation_cfg; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| struct plat_learned_mac_addr { | struct plat_learned_mac_addr { | ||||||
| @@ -503,15 +581,57 @@ enum { | |||||||
| 	PLAT_REBOOT_CAUSE_UNAVAILABLE, | 	PLAT_REBOOT_CAUSE_UNAVAILABLE, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | enum sfp_form_factor { | ||||||
|  | 	UCENTRAL_SFP_FORM_FACTOR_NA = 0, | ||||||
|  |  | ||||||
|  | 	UCENTRAL_SFP_FORM_FACTOR_SFP, | ||||||
|  | 	UCENTRAL_SFP_FORM_FACTOR_SFP_PLUS, | ||||||
|  | 	UCENTRAL_SFP_FORM_FACTOR_SFP_28, | ||||||
|  | 	UCENTRAL_SFP_FORM_FACTOR_SFP_DD, | ||||||
|  | 	UCENTRAL_SFP_FORM_FACTOR_QSFP, | ||||||
|  | 	UCENTRAL_SFP_FORM_FACTOR_QSFP_PLUS, | ||||||
|  | 	UCENTRAL_SFP_FORM_FACTOR_QSFP_28, | ||||||
|  | 	UCENTRAL_SFP_FORM_FACTOR_QSFP_DD | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum sfp_link_mode { | ||||||
|  | 	UCENTRAL_SFP_LINK_MODE_NA = 0, | ||||||
|  |  | ||||||
|  | 	UCENTRAL_SFP_LINK_MODE_1000_X, | ||||||
|  | 	UCENTRAL_SFP_LINK_MODE_2500_X, | ||||||
|  | 	UCENTRAL_SFP_LINK_MODE_4000_SR, | ||||||
|  | 	UCENTRAL_SFP_LINK_MODE_10G_SR, | ||||||
|  | 	UCENTRAL_SFP_LINK_MODE_25G_SR, | ||||||
|  | 	UCENTRAL_SFP_LINK_MODE_40G_SR, | ||||||
|  | 	UCENTRAL_SFP_LINK_MODE_50G_SR, | ||||||
|  | 	UCENTRAL_SFP_LINK_MODE_100G_SR, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct plat_port_transceiver_info { | ||||||
|  | 	char vendor_name[64]; | ||||||
|  | 	char part_number[64]; | ||||||
|  | 	char serial_number[64]; | ||||||
|  | 	char revision[64]; | ||||||
|  | 	enum sfp_form_factor form_factor; | ||||||
|  | 	enum sfp_link_mode *supported_link_modes; | ||||||
|  | 	size_t num_supported_link_modes; | ||||||
|  | 	float temperature; | ||||||
|  | 	float tx_optical_power; | ||||||
|  | 	float rx_optical_power; | ||||||
|  | 	float max_module_power; | ||||||
|  | }; | ||||||
|  |  | ||||||
| struct plat_port_info { | struct plat_port_info { | ||||||
| 	struct plat_port_counters stats; | 	struct plat_port_counters stats; | ||||||
| 	struct plat_port_lldp_peer_info lldp_peer_info; | 	struct plat_port_lldp_peer_info lldp_peer_info; | ||||||
| 	struct plat_ieee8021x_port_info ieee8021x_info; | 	struct plat_ieee8021x_port_info ieee8021x_info; | ||||||
|  | 	struct plat_port_transceiver_info transceiver_info; | ||||||
| 	uint32_t uptime; | 	uint32_t uptime; | ||||||
| 	uint32_t speed; | 	uint32_t speed; | ||||||
| 	uint8_t carrier_up; | 	uint8_t carrier_up; | ||||||
| 	uint8_t duplex; | 	uint8_t duplex; | ||||||
| 	uint8_t has_lldp_peer_info; | 	uint8_t has_lldp_peer_info; | ||||||
|  | 	uint8_t has_transceiver_info; | ||||||
| 	char name[PORT_MAX_NAME_LEN]; | 	char name[PORT_MAX_NAME_LEN]; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -525,6 +645,17 @@ struct plat_system_info { | |||||||
| 	double load_average[3]; /* 1, 5, 15 minutes load average */ | 	double load_average[3]; /* 1, 5, 15 minutes load average */ | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | struct plat_iee8021x_coa_counters { | ||||||
|  | 	uint64_t coa_req_received; | ||||||
|  | 	uint64_t coa_ack_sent; | ||||||
|  | 	uint64_t coa_nak_sent; | ||||||
|  | 	uint64_t coa_ignored; | ||||||
|  | 	uint64_t coa_wrong_attr; | ||||||
|  | 	uint64_t coa_wrong_attr_value; | ||||||
|  | 	uint64_t coa_wrong_session_context; | ||||||
|  | 	uint64_t coa_administratively_prohibited_req; | ||||||
|  | }; | ||||||
|  |  | ||||||
| struct plat_state_info { | struct plat_state_info { | ||||||
| 	struct plat_poe_state poe_state; | 	struct plat_poe_state poe_state; | ||||||
| 	struct plat_poe_port_state poe_ports_state[MAX_NUM_OF_PORTS]; | 	struct plat_poe_port_state poe_ports_state[MAX_NUM_OF_PORTS]; | ||||||
| @@ -532,10 +663,13 @@ struct plat_state_info { | |||||||
|  |  | ||||||
| 	struct plat_port_info *port_info; | 	struct plat_port_info *port_info; | ||||||
| 	int port_info_count; | 	int port_info_count; | ||||||
|  | 	struct plat_port_vlan *vlan_info; | ||||||
|  | 	size_t vlan_info_count; | ||||||
| 	struct plat_learned_mac_addr *learned_mac_list; | 	struct plat_learned_mac_addr *learned_mac_list; | ||||||
| 	size_t learned_mac_list_size; | 	size_t learned_mac_list_size; | ||||||
|  |  | ||||||
| 	struct plat_system_info system_info; | 	struct plat_system_info system_info; | ||||||
|  | 	struct plat_iee8021x_coa_counters ieee8021x_global_coa_counters; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| struct plat_upgrade_info { | struct plat_upgrade_info { | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -27,6 +27,16 @@ struct gnma_radius_host_key { | |||||||
| 	char hostname[GNMA_RADIUS_CFG_HOSTNAME_STR_MAX_LEN]; | 	char hostname[GNMA_RADIUS_CFG_HOSTNAME_STR_MAX_LEN]; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | struct gnma_das_dac_host_key { | ||||||
|  | 	char hostname[GNMA_RADIUS_CFG_HOSTNAME_STR_MAX_LEN]; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | typedef enum _gnma_das_auth_type_t { | ||||||
|  | 	GNMA_802_1X_DAS_AUTH_TYPE_ANY, | ||||||
|  | 	GNMA_802_1X_DAS_AUTH_TYPE_ALL, | ||||||
|  | 	GNMA_802_1X_DAS_AUTH_TYPE_SESSION_KEY, | ||||||
|  | } gnma_das_auth_type_t; | ||||||
|  |  | ||||||
| struct gnma_metadata { | struct gnma_metadata { | ||||||
| 	char platform[GNMA_METADATA_STR_MAX_LEN]; | 	char platform[GNMA_METADATA_STR_MAX_LEN]; | ||||||
| 	char hwsku[GNMA_METADATA_STR_MAX_LEN]; | 	char hwsku[GNMA_METADATA_STR_MAX_LEN]; | ||||||
| @@ -59,6 +69,17 @@ typedef enum _gnma_port_stat_type_t { | |||||||
|  |  | ||||||
| } gnma_port_stat_type_t; | } gnma_port_stat_type_t; | ||||||
|  |  | ||||||
|  | typedef enum _gnma_ieee8021x_das_dac_stat_type_t { | ||||||
|  | 	GNMA_IEEE8021X_DAS_DAC_STAT_IN_COA_PKTS, | ||||||
|  | 	GNMA_IEEE8021X_DAS_DAC_STAT_OUT_COA_ACK_PKTS, | ||||||
|  | 	GNMA_IEEE8021X_DAS_DAC_STAT_OUT_COA_NAK_PKTS, | ||||||
|  | 	GNMA_IEEE8021X_DAS_DAC_STAT_IN_COA_IGNORED_PKTS, | ||||||
|  | 	GNMA_IEEE8021X_DAS_DAC_STAT_IN_COA_WRONG_ATTR_PKTS, | ||||||
|  | 	GNMA_IEEE8021X_DAS_DAC_STAT_IN_COA_WRONG_ATTR_VALUE_PKTS, | ||||||
|  | 	GNMA_IEEE8021X_DAS_DAC_STAT_IN_COA_WRONG_SESSION_CONTEXT_PKTS, | ||||||
|  | 	GNMA_IEEE8021X_DAS_DAC_STAT_IN_COA_ADMINISTRATIVELY_PROHIBITED_REQ_PKTS, | ||||||
|  | } gnma_ieee8021x_das_dac_stat_type_t; | ||||||
|  |  | ||||||
| struct gnma_alarm { | struct gnma_alarm { | ||||||
| 	const char *id; | 	const char *id; | ||||||
| 	const char *resource; | 	const char *resource; | ||||||
| @@ -269,6 +290,29 @@ struct gnma_fdb_entry { | |||||||
| 	char mac[18]; | 	char mac[18]; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | typedef enum _gnma_igmp_version_t { | ||||||
|  | 	GNMA_IGMP_VERSION_NA = 0, | ||||||
|  | 	GNMA_IGMP_VERSION_1 = 1, | ||||||
|  | 	GNMA_IGMP_VERSION_2 = 2, | ||||||
|  | 	GNMA_IGMP_VERSION_3 = 3 | ||||||
|  | } gnma_igmp_version_t; | ||||||
|  |  | ||||||
|  | struct gnma_igmp_snoop_attr { | ||||||
|  | 	bool enabled; | ||||||
|  | 	bool querier_enabled; | ||||||
|  | 	bool fast_leave_enabled; | ||||||
|  | 	uint32_t query_interval; | ||||||
|  | 	uint32_t last_member_query_interval; | ||||||
|  | 	uint32_t max_response_time; | ||||||
|  | 	gnma_igmp_version_t version; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct gnma_igmp_static_group_attr { | ||||||
|  | 	struct in_addr address; | ||||||
|  | 	size_t num_ports; | ||||||
|  | 	struct gnma_port_key *egress_ports; | ||||||
|  | }; | ||||||
|  |  | ||||||
| int gnma_switch_create(/* TODO id */ /* TODO: attr (adr, login, psw) */); | int gnma_switch_create(/* TODO id */ /* TODO: attr (adr, login, psw) */); | ||||||
| int gnma_port_admin_state_set(struct gnma_port_key *port_key, bool up); | int gnma_port_admin_state_set(struct gnma_port_key *port_key, bool up); | ||||||
| int gnma_port_speed_set(struct gnma_port_key *port_key, const char *speed); | int gnma_port_speed_set(struct gnma_port_key *port_key, const char *speed); | ||||||
| @@ -403,14 +447,33 @@ int gnma_stp_ports_enable(uint32_t list_size, struct gnma_port_key *ports_list); | |||||||
| int gnma_stp_instance_set(uint16_t instance, uint16_t prio, | int gnma_stp_instance_set(uint16_t instance, uint16_t prio, | ||||||
| 			  uint32_t list_size, uint16_t *vid_list); | 			  uint32_t list_size, uint16_t *vid_list); | ||||||
|  |  | ||||||
| int gnma_stp_vids_enable(uint32_t list_size, uint16_t *vid_list); | int gnma_stp_vids_set(uint32_t list_size, uint16_t *vid_list, bool enable); | ||||||
| int gnma_stp_vids_enable_all(void); | int gnma_stp_vids_set_all(bool enable); | ||||||
| int gnma_stp_vid_set(uint16_t vid, struct gnma_stp_attr *attr); | int gnma_stp_vid_set(uint16_t vid, struct gnma_stp_attr *attr); | ||||||
| int gnma_stp_vid_bulk_get(struct gnma_stp_attr *list, ssize_t size); | int gnma_stp_vid_bulk_get(struct gnma_stp_attr *list, ssize_t size); | ||||||
|  |  | ||||||
| int gnma_ieee8021x_system_auth_control_set(bool is_enabled); | int gnma_ieee8021x_system_auth_control_set(bool is_enabled); | ||||||
| int gnma_ieee8021x_system_auth_control_get(bool *is_enabled); | int gnma_ieee8021x_system_auth_control_get(bool *is_enabled); | ||||||
| int gnma_ieee8021x_system_auth_clients_get(char *buf, size_t buf_size); | int gnma_ieee8021x_system_auth_clients_get(char *buf, size_t buf_size); | ||||||
|  | int gnma_ieee8021x_das_bounce_port_ignore_set(bool bounce_port_ignore); | ||||||
|  | int gnma_ieee8021x_das_bounce_port_ignore_get(bool *bounce_port_ignore); | ||||||
|  | int gnma_ieee8021x_das_disable_port_ignore_set(bool disable_port_ignore); | ||||||
|  | int gnma_ieee8021x_das_disable_port_ignore_get(bool *disable_port_ignore); | ||||||
|  | int gnma_ieee8021x_das_ignore_server_key_set(bool ignore_server_key); | ||||||
|  | int gnma_ieee8021x_das_ignore_server_key_get(bool *ignore_server_key); | ||||||
|  | int gnma_ieee8021x_das_ignore_session_key_set(bool ignore_session_key); | ||||||
|  | int gnma_ieee8021x_das_ignore_session_key_get(bool *ignore_session_key); | ||||||
|  | int gnma_ieee8021x_das_auth_type_key_set(gnma_das_auth_type_t auth_type); | ||||||
|  | int gnma_ieee8021x_das_auth_type_key_get(gnma_das_auth_type_t *auth_type); | ||||||
|  | int gnma_ieee8021x_das_dac_hosts_list_get(size_t *list_size, | ||||||
|  | 					  struct gnma_das_dac_host_key *das_dac_keys_arr); | ||||||
|  | int gnma_ieee8021x_das_dac_host_add(struct gnma_das_dac_host_key *key, | ||||||
|  | 				    const char *passkey); | ||||||
|  | int gnma_ieee8021x_das_dac_host_remove(struct gnma_das_dac_host_key *key); | ||||||
|  | int | ||||||
|  | gnma_iee8021x_das_dac_global_stats_get(uint32_t num_of_counters, | ||||||
|  | 				       gnma_ieee8021x_das_dac_stat_type_t *counter_ids, | ||||||
|  | 				       uint64_t *counters); | ||||||
|  |  | ||||||
| int gnma_radius_hosts_list_get(size_t *list_size, | int gnma_radius_hosts_list_get(size_t *list_size, | ||||||
| 			       struct gnma_radius_host_key *hosts_list); | 			       struct gnma_radius_host_key *hosts_list); | ||||||
| @@ -419,6 +482,12 @@ int gnma_radius_host_add(struct gnma_radius_host_key *key, const char *passkey, | |||||||
| int gnma_radius_host_remove(struct gnma_radius_host_key *key); | int gnma_radius_host_remove(struct gnma_radius_host_key *key); | ||||||
| int gnma_mac_address_list_get(size_t *list_size, struct gnma_fdb_entry *list); | int gnma_mac_address_list_get(size_t *list_size, struct gnma_fdb_entry *list); | ||||||
| int gnma_system_password_set(char *password); | int gnma_system_password_set(char *password); | ||||||
|  | int gnma_igmp_snooping_set(uint16_t vid, struct gnma_igmp_snoop_attr *attr); | ||||||
|  | int gnma_igmp_static_groups_set(uint16_t vid, size_t num_groups, | ||||||
|  | 				struct gnma_igmp_static_group_attr *groups); | ||||||
|  |  | ||||||
|  | int gnma_igmp_iface_groups_get(struct gnma_port_key *iface, | ||||||
|  | 			       char *buf, size_t *buf_size); | ||||||
|  |  | ||||||
| struct gnma_change *gnma_change_create(void); | struct gnma_change *gnma_change_create(void); | ||||||
| void gnma_change_destory(struct gnma_change *); | void gnma_change_destory(struct gnma_change *); | ||||||
|   | |||||||
| @@ -44,6 +44,16 @@ | |||||||
|  |  | ||||||
| #define RTTY_SESS_MAX (10) | #define RTTY_SESS_MAX (10) | ||||||
|  |  | ||||||
|  | #define ARR_FIND_VALUE_IDX(A, len, value)				\ | ||||||
|  | 	({								\ | ||||||
|  | 		size_t it = 0;						\ | ||||||
|  | 		for ((it) = 0; (it) < (len); (++it)) {			\ | ||||||
|  | 			if ((A)[it] == (value))				\ | ||||||
|  | 				break;					\ | ||||||
|  | 		}							\ | ||||||
|  | 		(it);							\ | ||||||
|  | 	}) | ||||||
|  |  | ||||||
| static int plat_state_get(struct plat_state_info *state); | static int plat_state_get(struct plat_state_info *state); | ||||||
| static void plat_state_deinit(struct plat_state_info *state); | static void plat_state_deinit(struct plat_state_info *state); | ||||||
| static int plat_port_speed_get(uint16_t fp_p_id, uint32_t *speed); | static int plat_port_speed_get(uint16_t fp_p_id, uint32_t *speed); | ||||||
| @@ -137,6 +147,17 @@ plat_ieee8021x_system_auth_clients_get(uint16_t port_id, | |||||||
| 	} \ | 	} \ | ||||||
| 	(res);}) | 	(res);}) | ||||||
|  |  | ||||||
|  | #define PLAT_DAC_HOST_EXISTS_IN_CFG(_host, head) \ | ||||||
|  | 	({bool res = false; \ | ||||||
|  | 	struct plat_ieee8021x_dac_list *_pos; \ | ||||||
|  | 	UCENTRAL_LIST_FOR_EACH_MEMBER((_pos), (head)) { \ | ||||||
|  | 		if (strcmp((_host), ((_pos)->host.hostname)) == 0) { \ | ||||||
|  | 			res = true; \ | ||||||
|  | 			break; \ | ||||||
|  | 		} \ | ||||||
|  | 	} \ | ||||||
|  | 	(res);}) | ||||||
|  |  | ||||||
| /* For now, let's define abs max buf size as: | /* For now, let's define abs max buf size as: | ||||||
|  * 1024 (bytes) per client, 10 clients total at max for 100 ports; |  * 1024 (bytes) per client, 10 clients total at max for 100 ports; | ||||||
|  * Bare minimum client info has ~600B size (raw json). |  * Bare minimum client info has ~600B size (raw json). | ||||||
| @@ -195,15 +216,6 @@ struct poe_port { | |||||||
| 	gnma_poe_port_priority_t priority; | 	gnma_poe_port_priority_t priority; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /* Password is obfuscated and key changes all the time. |  | ||||||
|  * So cache only actual hosts (ip / hostname), and do a single |  | ||||||
|  * GNMI request to add host (with all parameters - passkey, port etc) upon |  | ||||||
|  * every cfg reqest. |  | ||||||
|  */ |  | ||||||
| struct radius_host { |  | ||||||
| 	struct gnma_radius_host_key key; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| struct port { | struct port { | ||||||
| 	struct gnma_port_key key; | 	struct gnma_port_key key; | ||||||
| 	struct { | 	struct { | ||||||
| @@ -244,6 +256,13 @@ struct plat_state { | |||||||
| 	} poe; | 	} poe; | ||||||
| 	struct { | 	struct { | ||||||
| 		bool is_auth_control_enabled; | 		bool is_auth_control_enabled; | ||||||
|  | 		bool bounce_port_ignore; | ||||||
|  | 		bool disable_port_ignore; | ||||||
|  | 		bool ignore_server_key; | ||||||
|  | 		bool ignore_session_key; | ||||||
|  | 		gnma_das_auth_type_t das_auth_type; | ||||||
|  | 		struct gnma_das_dac_host_key *das_dac_keys_arr; | ||||||
|  | 		size_t das_dac_keys_arr_size; | ||||||
| 	} ieee8021x; | 	} ieee8021x; | ||||||
| 	struct { | 	struct { | ||||||
| 		struct gnma_radius_host_key *hosts_keys_arr; | 		struct gnma_radius_host_key *hosts_keys_arr; | ||||||
| @@ -875,6 +894,98 @@ err: | |||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static int plat_state_ieee8021x_dac_list_init(void) | ||||||
|  | { | ||||||
|  | 	int ret; | ||||||
|  |  | ||||||
|  | 	free(plat_state.ieee8021x.das_dac_keys_arr); | ||||||
|  | 	plat_state.ieee8021x.das_dac_keys_arr = NULL; | ||||||
|  | 	plat_state.ieee8021x.das_dac_keys_arr_size = 0; | ||||||
|  |  | ||||||
|  | 	ret = gnma_ieee8021x_das_dac_hosts_list_get(&plat_state.ieee8021x.das_dac_keys_arr_size, | ||||||
|  | 						    NULL); | ||||||
|  | 	if (ret && ret != GNMA_ERR_OVERFLOW) { | ||||||
|  | 		UC_LOG_CRIT("gnma_ieee8021x_das_dac_hosts_list_get failed"); | ||||||
|  | 		plat_state.ieee8021x.das_dac_keys_arr_size = 0; | ||||||
|  | 		return ret; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/* No DAC hosts configured, no need to update cache. */ | ||||||
|  | 	if (0 == plat_state.ieee8021x.das_dac_keys_arr_size) | ||||||
|  | 		return 0; | ||||||
|  |  | ||||||
|  | 	plat_state.ieee8021x.das_dac_keys_arr = | ||||||
|  | 		calloc(plat_state.ieee8021x.das_dac_keys_arr_size, | ||||||
|  | 		       sizeof(*plat_state.ieee8021x.das_dac_keys_arr)); | ||||||
|  | 	if (!plat_state.ieee8021x.das_dac_keys_arr) { | ||||||
|  | 		ret = -ENOMEM; | ||||||
|  | 		goto err; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ret = gnma_ieee8021x_das_dac_hosts_list_get(&plat_state.ieee8021x.das_dac_keys_arr_size, | ||||||
|  | 						    plat_state.ieee8021x.das_dac_keys_arr); | ||||||
|  | 	if (ret) { | ||||||
|  | 		UC_LOG_CRIT("gnma_ieee8021x_das_dac_hosts_list_get failed"); | ||||||
|  | 		goto err; | ||||||
|  | 	} | ||||||
|  | 	return 0; | ||||||
|  |  | ||||||
|  | err: | ||||||
|  | 	free(plat_state.radius.hosts_keys_arr); | ||||||
|  | 	plat_state.radius.hosts_keys_arr = NULL; | ||||||
|  | 	plat_state.radius.hosts_keys_arr_size = 0; | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static int plat_state_ieee8021x_init(void) | ||||||
|  | { | ||||||
|  | 	int ret; | ||||||
|  |  | ||||||
|  | 	ret = gnma_ieee8021x_system_auth_control_get(&plat_state.ieee8021x.is_auth_control_enabled); | ||||||
|  | 	if (ret) { | ||||||
|  | 		UC_LOG_CRIT("gnma_ieee8021x_system_auth_control_get failed"); | ||||||
|  | 		return ret; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ret = gnma_ieee8021x_das_bounce_port_ignore_get(&plat_state.ieee8021x.bounce_port_ignore); | ||||||
|  | 	if (ret) { | ||||||
|  | 		UC_LOG_CRIT("gnma_ieee8021x_das_bounce_port_ignore_get failed"); | ||||||
|  | 		return ret; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ret = gnma_ieee8021x_das_disable_port_ignore_get(&plat_state.ieee8021x.disable_port_ignore); | ||||||
|  | 	if (ret) { | ||||||
|  | 		UC_LOG_CRIT("gnma_ieee8021x_das_disable_port_ignore_get failed"); | ||||||
|  | 		return ret; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ret = gnma_ieee8021x_das_ignore_server_key_get(&plat_state.ieee8021x.ignore_server_key); | ||||||
|  | 	if (ret) { | ||||||
|  | 		UC_LOG_CRIT("gnma_ieee8021x_das_ignore_server_key_get failed"); | ||||||
|  | 		return ret; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ret = gnma_ieee8021x_das_ignore_session_key_get(&plat_state.ieee8021x.ignore_session_key); | ||||||
|  | 	if (ret) { | ||||||
|  | 		UC_LOG_CRIT("gnma_ieee8021x_das_ignore_session_key_get failed"); | ||||||
|  | 		return ret; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ret = gnma_ieee8021x_das_auth_type_key_get(&plat_state.ieee8021x.das_auth_type); | ||||||
|  | 	if (ret) { | ||||||
|  | 		UC_LOG_CRIT("gnma_ieee8021x_das_auth_type_key_get failed"); | ||||||
|  | 		return ret; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ret = plat_state_ieee8021x_dac_list_init(); | ||||||
|  | 	if (ret) { | ||||||
|  | 		UC_LOG_CRIT("plat_state_ieee8021x_dac_list_init failed"); | ||||||
|  | 		return ret; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
| static int plat_state_init() | static int plat_state_init() | ||||||
| { | { | ||||||
| 	BITMAP_DECLARE(vlans, GNMA_MAX_VLANS); | 	BITMAP_DECLARE(vlans, GNMA_MAX_VLANS); | ||||||
| @@ -892,9 +1003,15 @@ static int plat_state_init() | |||||||
|  |  | ||||||
| 	featsts[FEAT_CORE] = FEATSTS_FAIL; | 	featsts[FEAT_CORE] = FEATSTS_FAIL; | ||||||
|  |  | ||||||
| 	ret = gnma_ieee8021x_system_auth_control_get(&plat_state.ieee8021x.is_auth_control_enabled); | 	ret = plat_state_ieee8021x_init(); | ||||||
| 	if (ret) { | 	if (ret) { | ||||||
| 		UC_LOG_CRIT("gnma_ieee8021x_system_auth_control_get failed"); | 		UC_LOG_CRIT("plat_state_ieee8021x_init failed"); | ||||||
|  | 		featsts[FEAT_AAA] = FEATSTS_FAIL; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ret = plat_state_radius_init(); | ||||||
|  | 	if (ret) { | ||||||
|  | 		UC_LOG_CRIT("plat_state_radius_init failed"); | ||||||
| 		featsts[FEAT_AAA] = FEATSTS_FAIL; | 		featsts[FEAT_AAA] = FEATSTS_FAIL; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -1012,12 +1129,6 @@ static int plat_state_init() | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ret = plat_state_radius_init(); |  | ||||||
| 	if (ret) { |  | ||||||
| 		UC_LOG_CRIT("plat_state_radius_init failed"); |  | ||||||
| 		featsts[FEAT_AAA] = FEATSTS_FAIL; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if (featsts[FEAT_AAA] == FEATSTS_FAIL) { | 	if (featsts[FEAT_AAA] == FEATSTS_FAIL) { | ||||||
| 		UC_LOG_CRIT("AAA feature failed to initialize"); | 		UC_LOG_CRIT("AAA feature failed to initialize"); | ||||||
| 	} else { | 	} else { | ||||||
| @@ -1458,16 +1569,6 @@ int plat_port_stats_get(uint16_t fp_p_id, struct plat_port_counters *stats) | |||||||
| 	uint64_t counters[ARRAY_LENGTH(stat_types)]; | 	uint64_t counters[ARRAY_LENGTH(stat_types)]; | ||||||
| 	struct gnma_port_key gnma_port; | 	struct gnma_port_key gnma_port; | ||||||
|  |  | ||||||
| #define ARR_FIND_VALUE_IDX(A, len, value)				\ |  | ||||||
| 	({								\ |  | ||||||
| 		size_t it = 0;						\ |  | ||||||
| 		for ((it) = 0; (it) < (len); (++it)) {			\ |  | ||||||
| 			if ((A)[it] == (value))				\ |  | ||||||
| 				break;					\ |  | ||||||
| 		}							\ |  | ||||||
| 		(it);							\ |  | ||||||
| 	}) |  | ||||||
|  |  | ||||||
| 	PID_TO_NAME(fp_p_id, gnma_port.name); | 	PID_TO_NAME(fp_p_id, gnma_port.name); | ||||||
|  |  | ||||||
| 	if (gnma_port_stats_get(&gnma_port, ARRAY_LENGTH(stat_types), | 	if (gnma_port_stats_get(&gnma_port, ARRAY_LENGTH(stat_types), | ||||||
| @@ -1522,7 +1623,6 @@ int plat_port_stats_get(uint16_t fp_p_id, struct plat_port_counters *stats) | |||||||
| 	stats->tx_packets += | 	stats->tx_packets += | ||||||
| 		counters[ARR_FIND_VALUE_IDX(stat_types, ARRAY_LENGTH(stat_types), | 		counters[ARR_FIND_VALUE_IDX(stat_types, ARRAY_LENGTH(stat_types), | ||||||
| 					    GNMA_PORT_STAT_OUT_BCAST_PKTS)]; | 					    GNMA_PORT_STAT_OUT_BCAST_PKTS)]; | ||||||
| #undef ARR_FIND_VALUE_IDX |  | ||||||
|  |  | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| @@ -1794,6 +1894,123 @@ err: | |||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | int plat_port_transceiver_info_get(uint16_t port_id, | ||||||
|  | 				   struct plat_port_transceiver_info *info) | ||||||
|  | { | ||||||
|  | 	/* TODO */ | ||||||
|  | 	(void)((port_id)); | ||||||
|  | 	(void)((info)); | ||||||
|  | 	return -1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static int plat_vlan_igmp_info_get(uint16_t vid, struct plat_igmp *info) | ||||||
|  | { | ||||||
|  | 	size_t list_size = 0, group_idx = 0; | ||||||
|  | 	struct plat_ports_list *port_node; | ||||||
|  | 	struct gnma_port_key iface = {0}; | ||||||
|  | 	cJSON *groups = NULL, *group; | ||||||
|  | 	size_t buf_size = 0; | ||||||
|  | 	char *buf = NULL; | ||||||
|  | 	int ret = 0; | ||||||
|  |  | ||||||
|  | 	VLAN_TO_NAME(vid, iface.name); | ||||||
|  | 	ret = gnma_igmp_iface_groups_get(&iface, NULL, &buf_size); | ||||||
|  | 	if (ret == GNMA_OK && !buf_size) | ||||||
|  | 		return 0; | ||||||
|  | 	if (ret != GNMA_ERR_OVERFLOW) | ||||||
|  | 		return -1; | ||||||
|  |  | ||||||
|  | 	buf = calloc(buf_size, sizeof(*buf)); | ||||||
|  | 	if (!buf) | ||||||
|  | 		return -1; | ||||||
|  |  | ||||||
|  | 	ret = gnma_igmp_iface_groups_get(&iface, buf, &buf_size); | ||||||
|  | 	if (ret != GNMA_OK) { | ||||||
|  | 		ret = -1; | ||||||
|  | 		goto err; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	groups = cJSON_Parse(buf); | ||||||
|  | 	if (!groups) { | ||||||
|  | 		ret = -1; | ||||||
|  | 		goto err; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	list_size = cJSON_GetArraySize(groups); | ||||||
|  | 	info->groups = calloc(list_size, sizeof(*info->groups)); | ||||||
|  | 	if (!info->groups) { | ||||||
|  | 		ret = -1; | ||||||
|  | 		goto err; | ||||||
|  | 	} | ||||||
|  | 	info->num_groups = list_size; | ||||||
|  |  | ||||||
|  | 	cJSON_ArrayForEach(group, groups) { | ||||||
|  | 		cJSON *state, *gaddr, *e_ifaces, *e_iface; | ||||||
|  |  | ||||||
|  | 		state = cJSON_GetObjectItemCaseSensitive(group, "state"); | ||||||
|  | 		if (!state || !cJSON_IsObject(state)) { | ||||||
|  | 			ret = -1; | ||||||
|  | 			goto err; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		gaddr = cJSON_GetObjectItemCaseSensitive(state, "group"); | ||||||
|  | 		if (!gaddr || !cJSON_GetStringValue(gaddr)) { | ||||||
|  | 			ret = -1; | ||||||
|  | 			goto err; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		e_ifaces = cJSON_GetObjectItemCaseSensitive(state, "outgoing-interface"); | ||||||
|  | 		if (!e_ifaces || !cJSON_IsArray(e_ifaces)) { | ||||||
|  | 			ret = -1; | ||||||
|  | 			goto err; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (inet_pton(AF_INET, cJSON_GetStringValue(gaddr), | ||||||
|  | 			      &info->groups[group_idx].addr) != 1) { | ||||||
|  | 			ret = -1; | ||||||
|  | 			goto err; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		cJSON_ArrayForEach(e_iface, e_ifaces) { | ||||||
|  | 			if (!cJSON_GetStringValue(e_iface)) { | ||||||
|  | 				ret = -1; | ||||||
|  | 				goto err; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			port_node = calloc(1, sizeof(*port_node)); | ||||||
|  | 			if (!port_node) { | ||||||
|  | 				ret = -1; | ||||||
|  | 				goto err; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			strncpy(port_node->name, | ||||||
|  | 				cJSON_GetStringValue(e_iface), | ||||||
|  | 				sizeof(port_node->name)); | ||||||
|  | 			UCENTRAL_LIST_PUSH_MEMBER( | ||||||
|  | 					&info->groups[group_idx].egress_ports_list, | ||||||
|  | 					port_node); | ||||||
|  | 		} | ||||||
|  | 		group_idx++; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	info->exist = true; | ||||||
|  | 	goto exit; | ||||||
|  |  | ||||||
|  | err: | ||||||
|  | 	if (info->groups) { | ||||||
|  | 		for (size_t i = 0; i < info->num_groups; ++i) { | ||||||
|  | 			UCENTRAL_LIST_DESTROY_SAFE( | ||||||
|  | 					&info->groups[i].egress_ports_list, | ||||||
|  | 					port_node); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	free(info->groups); | ||||||
|  | exit: | ||||||
|  | 	cJSON_Delete(groups); | ||||||
|  | 	free(buf); | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  |  | ||||||
| static int | static int | ||||||
| __poe_port_state_buf_parse(char *buf, size_t buf_size, | __poe_port_state_buf_parse(char *buf, size_t buf_size, | ||||||
| 			   struct plat_poe_port_state *port_state) | 			   struct plat_poe_port_state *port_state) | ||||||
| @@ -1920,6 +2137,7 @@ __poe_state_buf_parse(char *buf, size_t buf_size, | |||||||
| { | { | ||||||
| 	cJSON *status; | 	cJSON *status; | ||||||
| 	cJSON *state; | 	cJSON *state; | ||||||
|  | 	cJSON *tmp; | ||||||
|  |  | ||||||
| 	state = cJSON_ParseWithLength(buf, buf_size); | 	state = cJSON_ParseWithLength(buf, buf_size); | ||||||
| 	if (!state) | 	if (!state) | ||||||
| @@ -1927,10 +2145,19 @@ __poe_state_buf_parse(char *buf, size_t buf_size, | |||||||
|  |  | ||||||
| 	poe_state->max_power_budget = | 	poe_state->max_power_budget = | ||||||
| 		cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(state, "max-power-budget")); | 		cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(state, "max-power-budget")); | ||||||
|  |  | ||||||
|  | 	/* For some reason, new BRCM images report this value as string, not value... */ | ||||||
|  | 	tmp = cJSON_GetObjectItemCaseSensitive(state, "power-threshold"); | ||||||
|  | 	if (!tmp || !cJSON_GetStringValue(tmp)) | ||||||
|  | 		goto err; | ||||||
| 	poe_state->power_threshold = | 	poe_state->power_threshold = | ||||||
| 		cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(state, "power-threshold")); | 		(typeof(poe_state->power_threshold)) strtod(cJSON_GetStringValue(tmp), NULL); | ||||||
|  |  | ||||||
|  | 	tmp = cJSON_GetObjectItemCaseSensitive(state, "power-consumption"); | ||||||
|  | 	if (!tmp || !cJSON_GetStringValue(tmp)) | ||||||
|  | 		goto err; | ||||||
| 	poe_state->power_consumed = | 	poe_state->power_consumed = | ||||||
| 		cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(state, "power-consumption")); | 		(typeof(poe_state->power_consumed)) strtod(cJSON_GetStringValue(tmp), NULL); | ||||||
|  |  | ||||||
| 	status = cJSON_GetObjectItemCaseSensitive(state, "pse-oper-status"); | 	status = cJSON_GetObjectItemCaseSensitive(state, "pse-oper-status"); | ||||||
| 	if (!cJSON_GetStringValue(status)) | 	if (!cJSON_GetStringValue(status)) | ||||||
| @@ -2792,6 +3019,11 @@ err: | |||||||
| /* NOTE: In case of error this function left partial config */ | /* NOTE: In case of error this function left partial config */ | ||||||
| int plat_vlan_rif_set(uint16_t vid, struct plat_ipv4 *ipv4) | int plat_vlan_rif_set(uint16_t vid, struct plat_ipv4 *ipv4) | ||||||
| { | { | ||||||
|  | 	struct gnma_igmp_snoop_attr igmp_snoop_attr = { | ||||||
|  | 		.enabled=false, | ||||||
|  | 		.querier_enabled=false, | ||||||
|  | 		.version=GNMA_IGMP_VERSION_NA | ||||||
|  | 	}; | ||||||
| 	struct gnma_ip_prefix pref, pref_old; | 	struct gnma_ip_prefix pref, pref_old; | ||||||
| 	uint16_t list_size; | 	uint16_t list_size; | ||||||
| 	int i; | 	int i; | ||||||
| @@ -2818,6 +3050,11 @@ int plat_vlan_rif_set(uint16_t vid, struct plat_ipv4 *ipv4) | |||||||
| 			return -1; | 			return -1; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if (gnma_igmp_snooping_set(vid, &igmp_snoop_attr)) { | ||||||
|  | 		UC_LOG_DBG("Failed to set VLAN igmp.\n"); | ||||||
|  | 		return -1; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if (list_size > 0 && !ipv4->exist) { | 	if (list_size > 0 && !ipv4->exist) { | ||||||
| 		/* Force DHCP cache/state flush (delete), as at this point no | 		/* Force DHCP cache/state flush (delete), as at this point no | ||||||
| 		 * dhcp cfg for this vlan exists, and it won't be 'restored' | 		 * dhcp cfg for this vlan exists, and it won't be 'restored' | ||||||
| @@ -2852,6 +3089,83 @@ int plat_vlan_rif_set(uint16_t vid, struct plat_ipv4 *ipv4) | |||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static int plat_vlan_igmp_set(uint16_t vid, struct plat_igmp *igmp) | ||||||
|  | { | ||||||
|  | 	struct gnma_igmp_static_group_attr *group_list; | ||||||
|  | 	struct plat_ports_list *e_port; | ||||||
|  | 	size_t group_idx, port_idx; | ||||||
|  | 	int ret; | ||||||
|  | 	struct gnma_igmp_snoop_attr attr = { | ||||||
|  | 		.last_member_query_interval = igmp->last_member_query_interval, | ||||||
|  | 		.fast_leave_enabled = igmp->fast_leave_enabled, | ||||||
|  | 		.max_response_time = igmp->max_response_time, | ||||||
|  | 		.querier_enabled = igmp->querier_enabled, | ||||||
|  | 		.query_interval = igmp->query_interval, | ||||||
|  | 		.enabled = igmp->snooping_enabled, | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	if (!igmp->exist) | ||||||
|  | 		attr.version = GNMA_IGMP_VERSION_NA; | ||||||
|  | 	else if (igmp->version == PLAT_IGMP_VERSION_1) | ||||||
|  | 		attr.version = GNMA_IGMP_VERSION_1; | ||||||
|  | 	else if (igmp->version == PLAT_IGMP_VERSION_2) | ||||||
|  | 		attr.version = GNMA_IGMP_VERSION_2; | ||||||
|  | 	else if (igmp->version == PLAT_IGMP_VERSION_3) | ||||||
|  | 		attr.version = GNMA_IGMP_VERSION_3; | ||||||
|  | 	else | ||||||
|  | 		return -1; | ||||||
|  |  | ||||||
|  | 	group_list = calloc(igmp->num_groups, sizeof(*group_list)); | ||||||
|  | 	if (!group_list) { | ||||||
|  | 		UC_LOG_ERR("ENOMEM"); | ||||||
|  | 		return -1; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for (group_idx = 0; group_idx < igmp->num_groups; group_idx++) { | ||||||
|  | 		group_list[group_idx].address = igmp->groups[group_idx].addr; | ||||||
|  |  | ||||||
|  | 		group_list[group_idx].num_ports = 0; | ||||||
|  | 		UCENTRAL_LIST_FOR_EACH_MEMBER(e_port, &igmp->groups[group_idx].egress_ports_list) { | ||||||
|  | 			group_list[group_idx].num_ports++; | ||||||
|  | 		} | ||||||
|  | 		group_list[group_idx].egress_ports = calloc(group_list[group_idx].num_ports, | ||||||
|  | 							    sizeof(*group_list[group_idx].egress_ports)); | ||||||
|  | 		if (!group_list[group_idx].egress_ports) { | ||||||
|  | 			UC_LOG_ERR("ENOMEM"); | ||||||
|  | 			ret = -1; | ||||||
|  | 			goto err; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		port_idx = 0; | ||||||
|  | 		UCENTRAL_LIST_FOR_EACH_MEMBER(e_port, &igmp->groups[group_idx].egress_ports_list) { | ||||||
|  | 			strncpy(group_list[group_idx].egress_ports[port_idx].name, | ||||||
|  | 				e_port->name, | ||||||
|  | 				sizeof(group_list[group_idx].egress_ports[port_idx].name)); | ||||||
|  | 			port_idx++; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ret = gnma_igmp_snooping_set(vid, &attr); | ||||||
|  | 	if (ret) { | ||||||
|  | 		UC_LOG_ERR("gnma_igmp_snooping_set"); | ||||||
|  | 		ret = -1; | ||||||
|  | 		goto err; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ret = gnma_igmp_static_groups_set(vid, igmp->num_groups, group_list); | ||||||
|  | 	if (ret) { | ||||||
|  | 		UC_LOG_ERR("gnma_igmp_static_groups_set"); | ||||||
|  | 		ret = -1; | ||||||
|  | 		goto err; | ||||||
|  | 	} | ||||||
|  | err: | ||||||
|  | 	if (group_list) | ||||||
|  | 		for (group_idx = 0; group_idx < igmp->num_groups; group_idx++) | ||||||
|  | 			free(group_list[group_idx].egress_ports); | ||||||
|  | 	free(group_list); | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  |  | ||||||
| /* NOTE: In case of error this function left partial config */ | /* NOTE: In case of error this function left partial config */ | ||||||
| int plat_portl2_rif_set(uint16_t fp_p_id, struct plat_ipv4 *ipv4) | int plat_portl2_rif_set(uint16_t fp_p_id, struct plat_ipv4 *ipv4) | ||||||
| { | { | ||||||
| @@ -2887,8 +3201,31 @@ int plat_portl2_rif_set(uint16_t fp_p_id, struct plat_ipv4 *ipv4) | |||||||
|  |  | ||||||
| static void plat_state_deinit(struct plat_state_info *state) | static void plat_state_deinit(struct plat_state_info *state) | ||||||
| { | { | ||||||
|  | 	struct plat_ports_list *port_node; | ||||||
|  |  | ||||||
|  | 	for (int i = 0; i < state->port_info_count; i++) { | ||||||
|  | 		if (state->port_info[i].has_transceiver_info && | ||||||
|  | 		    state->port_info[i].transceiver_info.num_supported_link_modes) { | ||||||
|  | 			free(state->port_info[i].transceiver_info.supported_link_modes); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for (size_t i = 0; i < state->vlan_info_count; i++) { | ||||||
|  | 		struct plat_igmp *info = &state->vlan_info[i].igmp; | ||||||
|  |  | ||||||
|  | 		if (info->num_groups) { | ||||||
|  | 			for (size_t i = 0; i < info->num_groups; ++i) { | ||||||
|  | 				UCENTRAL_LIST_DESTROY_SAFE( | ||||||
|  | 						&info->groups[i].egress_ports_list, | ||||||
|  | 						port_node); | ||||||
|  | 			} | ||||||
|  | 			free(info->groups); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	free(state->learned_mac_list); | 	free(state->learned_mac_list); | ||||||
| 	free(state->port_info); | 	free(state->port_info); | ||||||
|  | 	free(state->vlan_info); | ||||||
| 	*state = (struct plat_state_info){ 0 }; | 	*state = (struct plat_state_info){ 0 }; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -2954,6 +3291,11 @@ static int plat_port_info_get(struct plat_port_info **port_info, int *count) | |||||||
| 			pinfo[i].has_lldp_peer_info = 1; | 			pinfo[i].has_lldp_peer_info = 1; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		if (!plat_port_transceiver_info_get(pid, | ||||||
|  | 						    &pinfo[i].transceiver_info)) { | ||||||
|  | 			pinfo[i].has_transceiver_info = 1; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		plat_ieee8021x_system_auth_clients_get(pid, | 		plat_ieee8021x_system_auth_clients_get(pid, | ||||||
| 						       &ieee8021x_buf, | 						       &ieee8021x_buf, | ||||||
| 						       &ieee8021x_buf_size, | 						       &ieee8021x_buf_size, | ||||||
| @@ -2973,6 +3315,54 @@ err: | |||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static int plat_vlan_info_get(struct plat_port_vlan **vlan_info, size_t *count) | ||||||
|  | { | ||||||
|  | 	BITMAP_DECLARE(vlans_bmp, GNMA_MAX_VLANS); | ||||||
|  | 	struct plat_port_vlan *vinfo = 0; | ||||||
|  | 	size_t num_vlans = 0; | ||||||
|  | 	size_t idx = 0; | ||||||
|  | 	size_t vid; | ||||||
|  | 	int ret; | ||||||
|  |  | ||||||
|  | 	BITMAP_CLEAR(vlans_bmp, GNMA_MAX_VLANS); | ||||||
|  | 	ret = gnma_vlan_list_get(vlans_bmp); | ||||||
|  | 	if (ret) | ||||||
|  | 		return -1; | ||||||
|  |  | ||||||
|  | 	BITMAP_FOR_EACH_BIT_SET(vid, vlans_bmp, GNMA_MAX_VLANS) { | ||||||
|  | 		num_vlans++; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (!num_vlans) { | ||||||
|  | 		*count = 0; | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	vinfo = calloc(num_vlans, sizeof(*vinfo)); | ||||||
|  | 	if (!vinfo) { | ||||||
|  | 		UC_LOG_ERR("ENOMEM"); | ||||||
|  | 		return -1; | ||||||
|  | 	} | ||||||
|  | 	memset(vinfo, 0, num_vlans * sizeof(*vinfo)); | ||||||
|  |  | ||||||
|  | 	BITMAP_FOR_EACH_BIT_SET(vid, vlans_bmp, GNMA_MAX_VLANS) { | ||||||
|  | 		vinfo[idx].id = vid; | ||||||
|  |  | ||||||
|  | 		if (plat_vlan_igmp_info_get(vid, &vinfo[idx].igmp)) { | ||||||
|  | 			UC_LOG_DBG("plat_vlan_igmp_info_get failed"); | ||||||
|  | 			return -1; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		idx++; | ||||||
|  | 		if (idx >= num_vlans) | ||||||
|  | 			break; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	*count = idx; | ||||||
|  | 	*vlan_info = vinfo; | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
| static int get_meminfo_cached_kib(uint64_t *cached) | static int get_meminfo_cached_kib(uint64_t *cached) | ||||||
| { | { | ||||||
| 	size_t n; | 	size_t n; | ||||||
| @@ -3074,6 +3464,54 @@ err: | |||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static int | ||||||
|  | plat_state_ieee8021x_coa_global_counters_get(struct plat_iee8021x_coa_counters *stats) | ||||||
|  | { | ||||||
|  | 	gnma_ieee8021x_das_dac_stat_type_t stat_types[] = { | ||||||
|  | 		GNMA_IEEE8021X_DAS_DAC_STAT_IN_COA_PKTS, | ||||||
|  | 		GNMA_IEEE8021X_DAS_DAC_STAT_OUT_COA_ACK_PKTS, | ||||||
|  | 		GNMA_IEEE8021X_DAS_DAC_STAT_OUT_COA_NAK_PKTS, | ||||||
|  | 		GNMA_IEEE8021X_DAS_DAC_STAT_IN_COA_IGNORED_PKTS, | ||||||
|  | 		GNMA_IEEE8021X_DAS_DAC_STAT_IN_COA_WRONG_ATTR_PKTS, | ||||||
|  | 		GNMA_IEEE8021X_DAS_DAC_STAT_IN_COA_WRONG_ATTR_VALUE_PKTS, | ||||||
|  | 		GNMA_IEEE8021X_DAS_DAC_STAT_IN_COA_WRONG_SESSION_CONTEXT_PKTS, | ||||||
|  | 		GNMA_IEEE8021X_DAS_DAC_STAT_IN_COA_ADMINISTRATIVELY_PROHIBITED_REQ_PKTS, | ||||||
|  | 	}; | ||||||
|  | 	uint64_t counters[ARRAY_LENGTH(stat_types)]; | ||||||
|  |  | ||||||
|  | 	if (gnma_iee8021x_das_dac_global_stats_get(ARRAY_LENGTH(stat_types), | ||||||
|  | 						   &stat_types[0], | ||||||
|  | 						   &counters[0])) | ||||||
|  | 		return -EINVAL; | ||||||
|  |  | ||||||
|  | 	stats->coa_req_received = | ||||||
|  | 		counters[ARR_FIND_VALUE_IDX(stat_types, ARRAY_LENGTH(stat_types), | ||||||
|  | 					    GNMA_IEEE8021X_DAS_DAC_STAT_IN_COA_PKTS)]; | ||||||
|  | 	stats->coa_ack_sent = | ||||||
|  | 		counters[ARR_FIND_VALUE_IDX(stat_types, ARRAY_LENGTH(stat_types), | ||||||
|  | 					    GNMA_IEEE8021X_DAS_DAC_STAT_OUT_COA_ACK_PKTS)]; | ||||||
|  | 	stats->coa_nak_sent = | ||||||
|  | 		counters[ARR_FIND_VALUE_IDX(stat_types, ARRAY_LENGTH(stat_types), | ||||||
|  | 					    GNMA_IEEE8021X_DAS_DAC_STAT_OUT_COA_NAK_PKTS)]; | ||||||
|  | 	stats->coa_ignored = | ||||||
|  | 		counters[ARR_FIND_VALUE_IDX(stat_types, ARRAY_LENGTH(stat_types), | ||||||
|  | 					    GNMA_IEEE8021X_DAS_DAC_STAT_IN_COA_IGNORED_PKTS)]; | ||||||
|  | 	stats->coa_wrong_attr = | ||||||
|  | 		counters[ARR_FIND_VALUE_IDX(stat_types, ARRAY_LENGTH(stat_types), | ||||||
|  | 					    GNMA_IEEE8021X_DAS_DAC_STAT_IN_COA_WRONG_ATTR_PKTS)]; | ||||||
|  | 	stats->coa_wrong_attr_value = | ||||||
|  | 		counters[ARR_FIND_VALUE_IDX(stat_types, ARRAY_LENGTH(stat_types), | ||||||
|  | 					   GNMA_IEEE8021X_DAS_DAC_STAT_IN_COA_WRONG_ATTR_VALUE_PKTS)]; | ||||||
|  | 	stats->coa_wrong_session_context = | ||||||
|  | 		counters[ARR_FIND_VALUE_IDX(stat_types, ARRAY_LENGTH(stat_types), | ||||||
|  | 					    GNMA_IEEE8021X_DAS_DAC_STAT_IN_COA_WRONG_SESSION_CONTEXT_PKTS)]; | ||||||
|  | 	stats->coa_administratively_prohibited_req = | ||||||
|  | 		counters[ARR_FIND_VALUE_IDX(stat_types, ARRAY_LENGTH(stat_types), | ||||||
|  | 					    GNMA_IEEE8021X_DAS_DAC_STAT_IN_COA_ADMINISTRATIVELY_PROHIBITED_REQ_PKTS)]; | ||||||
|  |  | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
| static int plat_state_get(struct plat_state_info *state) | static int plat_state_get(struct plat_state_info *state) | ||||||
| { | { | ||||||
| 	size_t i; | 	size_t i; | ||||||
| @@ -3092,10 +3530,16 @@ static int plat_state_get(struct plat_state_info *state) | |||||||
| 	if (plat_port_info_get(&state->port_info, &state->port_info_count)) | 	if (plat_port_info_get(&state->port_info, &state->port_info_count)) | ||||||
| 		return -1; | 		return -1; | ||||||
|  |  | ||||||
|  | 	if (plat_vlan_info_get(&state->vlan_info, &state->vlan_info_count)) | ||||||
|  | 		return -1; | ||||||
|  |  | ||||||
| 	if (plat_learned_mac_addrs_get(&state->learned_mac_list, | 	if (plat_learned_mac_addrs_get(&state->learned_mac_list, | ||||||
| 				       &state->learned_mac_list_size)) | 				       &state->learned_mac_list_size)) | ||||||
| 		return -1; | 		return -1; | ||||||
|  |  | ||||||
|  | 	if (plat_state_ieee8021x_coa_global_counters_get(&state->ieee8021x_global_coa_counters)) | ||||||
|  | 		return -1; | ||||||
|  |  | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -3111,6 +3555,12 @@ static int config_vlan_ipv4_apply(struct plat_cfg *cfg) | |||||||
| 			UC_LOG_DBG("Failed to set VLAN rif.\n"); | 			UC_LOG_DBG("Failed to set VLAN rif.\n"); | ||||||
| 			return ret; | 			return ret; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		ret = plat_vlan_igmp_set(cfg->vlans[i].id, &cfg->vlans[i].igmp); | ||||||
|  | 		if (ret) { | ||||||
|  | 			UC_LOG_DBG("Failed to set VLAN igmp.\n"); | ||||||
|  | 			return ret; | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return 0; | 	return 0; | ||||||
| @@ -3896,20 +4346,90 @@ static int plat_port_config_apply(struct plat_cfg *cfg) | |||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static int plat_dac_list_set(struct plat_ieee8021x_dac_list *hosts) | ||||||
|  | { | ||||||
|  | 	struct plat_ieee8021x_dac_list *iter; | ||||||
|  | 	bool cache_changed = false; | ||||||
|  | 	int ret = 0; | ||||||
|  | 	size_t i; | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 	 * Check cache and remove any host that is not present in | ||||||
|  | 	 * requested CFG (same as for VLAN: if not present in cfg = to | ||||||
|  | 	 * be removed). | ||||||
|  | 	 */ | ||||||
|  | 	for (i = 0; i < plat_state.ieee8021x.das_dac_keys_arr_size; ++i) { | ||||||
|  | 		if (!PLAT_DAC_HOST_EXISTS_IN_CFG(plat_state.ieee8021x.das_dac_keys_arr[i].hostname, &hosts)) { | ||||||
|  | 			UC_LOG_DBG("Removing DAC server <%s> (not in cfg, present on system)\n", | ||||||
|  | 				   plat_state.ieee8021x.das_dac_keys_arr[i].hostname); | ||||||
|  | 			ret = gnma_ieee8021x_das_dac_host_remove(&plat_state.ieee8021x.das_dac_keys_arr[i]); | ||||||
|  | 			if (ret) | ||||||
|  | 				return ret; | ||||||
|  | 			cache_changed = true; | ||||||
|  | 		} else { | ||||||
|  | 			/* Special case, when host exists in cache and new CFG omitted password: | ||||||
|  | 			 * - remove previous entry | ||||||
|  | 			 * - recreate it without specifying password. | ||||||
|  | 			 * Either way SONIC treats this host as if password is set | ||||||
|  | 			 * explicitly, and might result in false-obfuscations of | ||||||
|  | 			 * DaS / CoA exchange between DAC and switch. | ||||||
|  | 			 */ | ||||||
|  | 			UCENTRAL_LIST_FOR_EACH_MEMBER(iter, &hosts) { | ||||||
|  | 				if (!strcmp(plat_state.ieee8021x.das_dac_keys_arr[i].hostname, | ||||||
|  | 					   iter->host.hostname) && | ||||||
|  | 				    iter->host.passkey[0] == '\0') { | ||||||
|  | 					ret = gnma_ieee8021x_das_dac_host_remove(&plat_state.ieee8021x.das_dac_keys_arr[i]); | ||||||
|  | 					if (ret) { | ||||||
|  | 						UC_LOG_DBG("Failed to remove DAC host <%s> (new CFG pass is empty, tried to delete))\n", | ||||||
|  | 							   plat_state.ieee8021x.das_dac_keys_arr[i].hostname); | ||||||
|  | 						return ret; | ||||||
|  | 					} | ||||||
|  | 					cache_changed = true; | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/* Add any new hosts that are present in requested CFG. */ | ||||||
|  | 	UCENTRAL_LIST_FOR_EACH_MEMBER(iter, &hosts) { | ||||||
|  | 		struct gnma_das_dac_host_key key; | ||||||
|  |  | ||||||
|  | 		strcpy(key.hostname, iter->host.hostname); | ||||||
|  |  | ||||||
|  | 		ret = gnma_ieee8021x_das_dac_host_add(&key, iter->host.passkey); | ||||||
|  | 		if (ret) | ||||||
|  | 			return ret; | ||||||
|  | 		cache_changed = true; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/* Reinit DAC hosts cache. */ | ||||||
|  | 	if (cache_changed) | ||||||
|  | 		plat_state_ieee8021x_dac_list_init(); | ||||||
|  |  | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
| static int config_ieee8021x_apply(struct plat_cfg *cfg) | static int config_ieee8021x_apply(struct plat_cfg *cfg) | ||||||
| { | { | ||||||
| 	int ret; | 	int ret; | ||||||
|  |  | ||||||
| 	if (cfg->ieee8021x_is_auth_ctrl_enabled != plat_state.ieee8021x.is_auth_control_enabled) { | 	if (cfg->ieee8021x.is_auth_ctrl_enabled != plat_state.ieee8021x.is_auth_control_enabled) { | ||||||
| 		UC_LOG_DBG("802.1x: changing global auth ctrl state from %d to %d", | 		UC_LOG_DBG("802.1x: changing global auth ctrl state from %d to %d", | ||||||
| 			   plat_state.ieee8021x.is_auth_control_enabled, | 			   plat_state.ieee8021x.is_auth_control_enabled, | ||||||
| 			   cfg->ieee8021x_is_auth_ctrl_enabled); | 			   cfg->ieee8021x.is_auth_ctrl_enabled); | ||||||
| 		ret = gnma_ieee8021x_system_auth_control_set(cfg->ieee8021x_is_auth_ctrl_enabled); | 		ret = gnma_ieee8021x_system_auth_control_set(cfg->ieee8021x.is_auth_ctrl_enabled); | ||||||
| 		if (ret) { | 		if (ret) { | ||||||
| 			UC_LOG_DBG("802.1x: Failed to set global auth ctrl state."); | 			UC_LOG_DBG("802.1x: Failed to set global auth ctrl state."); | ||||||
| 			return ret; | 			return ret; | ||||||
| 		} | 		} | ||||||
| 		plat_state.ieee8021x.is_auth_control_enabled = cfg->ieee8021x_is_auth_ctrl_enabled; | 		plat_state.ieee8021x.is_auth_control_enabled = cfg->ieee8021x.is_auth_ctrl_enabled; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ret = plat_dac_list_set(cfg->ieee8021x.das_dac_list); | ||||||
|  | 	if (ret) { | ||||||
|  | 		UC_LOG_DBG("802.1x: DAS: Failed to configure DAC hosts list."); | ||||||
|  | 		return ret; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ret = plat_radius_hosts_list_set(cfg->radius_hosts_list); | 	ret = plat_radius_hosts_list_set(cfg->radius_hosts_list); | ||||||
| @@ -4003,6 +4523,7 @@ void plat_config_destroy(struct plat_cfg *cfg) | |||||||
| { | { | ||||||
| 	struct plat_vlan_memberlist *member_node = NULL; | 	struct plat_vlan_memberlist *member_node = NULL; | ||||||
| 	struct plat_radius_hosts_list *hosts_node = NULL; | 	struct plat_radius_hosts_list *hosts_node = NULL; | ||||||
|  | 	struct plat_ports_list *port_node; | ||||||
| 	size_t i; | 	size_t i; | ||||||
|  |  | ||||||
| 	if (!cfg) | 	if (!cfg) | ||||||
| @@ -4019,6 +4540,14 @@ void plat_config_destroy(struct plat_cfg *cfg) | |||||||
| 		cfg->vlans[i].members_list_head = NULL; | 		cfg->vlans[i].members_list_head = NULL; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	for (int i = 0; i < 0; ++i) { | ||||||
|  | 		UCENTRAL_LIST_DESTROY_SAFE(&cfg->port_isolation_cfg.sessions[i].uplink.ports_list, | ||||||
|  | 					   port_node); | ||||||
|  | 		UCENTRAL_LIST_DESTROY_SAFE(&cfg->port_isolation_cfg.sessions[i].uplink.ports_list, | ||||||
|  | 					   port_node); | ||||||
|  | 	} | ||||||
|  | 	free(cfg->port_isolation_cfg.sessions); | ||||||
|  |  | ||||||
| 	UCENTRAL_LIST_DESTROY_SAFE(&cfg->radius_hosts_list, | 	UCENTRAL_LIST_DESTROY_SAFE(&cfg->radius_hosts_list, | ||||||
| 			hosts_node); | 			hosts_node); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ | |||||||
| #define STR(x) #x | #define STR(x) #x | ||||||
|  |  | ||||||
| #define PLATFORM_REL_NUM 1.6 | #define PLATFORM_REL_NUM 1.6 | ||||||
| #define PLATFORM_BUILD_NUM 4 | #define PLATFORM_BUILD_NUM 5 | ||||||
|  |  | ||||||
| #ifndef PLATFORM_REVISION | #ifndef PLATFORM_REVISION | ||||||
| #define PLATFORM_REVISION "Rel " XSTR(PLATFORM_REL_NUM) " build " XSTR(PLATFORM_BUILD_NUM) | #define PLATFORM_REVISION "Rel " XSTR(PLATFORM_REL_NUM) " build " XSTR(PLATFORM_BUILD_NUM) | ||||||
|   | |||||||
| @@ -906,6 +906,171 @@ cfg_ethernet_ieee8021x_parse(cJSON *ieee8021x, struct plat_port *port) | |||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static int | ||||||
|  | cfg_ethernet_port_isolation_interface_parse(cJSON *iface, | ||||||
|  | 					    struct plat_port_isolation_session_ports *ports) { | ||||||
|  | 	struct plat_ports_list *port_node = NULL; | ||||||
|  | 	cJSON *iface_list; | ||||||
|  | 	int i; | ||||||
|  |  | ||||||
|  | 	iface_list = cJSON_GetObjectItemCaseSensitive(iface, "interface-list"); | ||||||
|  | 	if (!iface_list || !cJSON_IsArray(iface_list) || | ||||||
|  | 	    cJSON_GetArraySize(iface_list) == 0) { | ||||||
|  | 		UC_LOG_ERR("Ethernet obj 'port_isolation:interface-list' is invalid, parse failed\n"); | ||||||
|  | 		return -1; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for (i = 0; i < cJSON_GetArraySize(iface_list); ++i) { | ||||||
|  | 		if (!cJSON_GetStringValue(cJSON_GetArrayItem(iface_list, i))) { | ||||||
|  | 			UC_LOG_ERR("Ethernet obj 'port_isolation:interface-list:%d' has invalid port name, parse failed\n", | ||||||
|  | 				   i); | ||||||
|  | 			return -1; | ||||||
|  | 		} | ||||||
|  | 		port_node = calloc(1, sizeof(*port_node)); | ||||||
|  | 		if (!port_node) { | ||||||
|  | 			UC_LOG_ERR("Failed alloc port list list\n"); | ||||||
|  | 			return -1; | ||||||
|  | 		} | ||||||
|  | 		strcpy(port_node->name, | ||||||
|  | 		       cJSON_GetStringValue(cJSON_GetArrayItem(iface_list, i))); | ||||||
|  | 		UCENTRAL_LIST_PUSH_MEMBER(&ports->ports_list, port_node); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static int | ||||||
|  | cfg_ethernet_port_isolation_parse(cJSON *ethernet, struct plat_cfg *cfg) { | ||||||
|  | 	cJSON *eth = NULL, *port_isolation, *sessions, *session; | ||||||
|  | 	struct plat_port_isolation_session *session_arr; | ||||||
|  | 	struct plat_ports_list *port_node = NULL; | ||||||
|  | 	int i = 0, j = 0; | ||||||
|  |  | ||||||
|  | 	cJSON_ArrayForEach(eth, ethernet) { | ||||||
|  | 		port_isolation = cJSON_GetObjectItemCaseSensitive(eth, "port-isolation"); | ||||||
|  | 		if (!port_isolation) | ||||||
|  | 			continue; | ||||||
|  |  | ||||||
|  | 		if (!cJSON_IsObject(port_isolation)) { | ||||||
|  | 			UC_LOG_ERR("Ethernet obj holds 'port_isolation' object of wrongful type, parse failed\n"); | ||||||
|  | 			return -1; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		sessions = cJSON_GetObjectItemCaseSensitive(port_isolation, | ||||||
|  | 							    "sessions"); | ||||||
|  | 		if (!sessions || !cJSON_IsArray(sessions)) { | ||||||
|  | 			UC_LOG_ERR("Ethernet obj holds 'port_isolation:sessions' array of wrongful type (or empty), parse failed\n"); | ||||||
|  | 			return -1; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		cJSON_ArrayForEach(session, sessions) { | ||||||
|  | 			cfg->port_isolation_cfg.sessions_num++; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if (cfg->port_isolation_cfg.sessions_num == 0) { | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	session_arr = calloc(cfg->port_isolation_cfg.sessions_num, | ||||||
|  | 			     sizeof(struct plat_port_isolation_session)); | ||||||
|  | 	cfg->port_isolation_cfg.sessions = session_arr; | ||||||
|  |  | ||||||
|  | 	if (!session_arr) { | ||||||
|  | 		UC_LOG_ERR("Failed to alloc memory for port-isolation-cfg, parse failed\n"); | ||||||
|  | 		return -1; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	cJSON_ArrayForEach(eth, ethernet) { | ||||||
|  | 		port_isolation = cJSON_GetObjectItemCaseSensitive(eth, "port-isolation"); | ||||||
|  | 		if (!port_isolation) | ||||||
|  | 			continue; | ||||||
|  |  | ||||||
|  | 		/* | ||||||
|  | 		 * Highly unlikeable that the object is missing / invalid, | ||||||
|  | 		 * as it was okay prior (parsing above). | ||||||
|  | 		 * But this is still a sanity-check, in case if JSON | ||||||
|  | 		 * got corrupted for some reason. | ||||||
|  | 		 */ | ||||||
|  | 		if (!cJSON_IsObject(port_isolation)) { | ||||||
|  | 			UC_LOG_ERR("Ethernet obj holds 'port_isolation' object of wrongful type, parse failed\n"); | ||||||
|  | 			return -1; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		sessions = cJSON_GetObjectItemCaseSensitive(port_isolation, | ||||||
|  | 							    "sessions"); | ||||||
|  | 		if (!sessions || !cJSON_IsArray(sessions)) { | ||||||
|  | 			UC_LOG_ERR("Ethernet obj holds 'port_isolation:sessions' array of wrongful type (or empty), parse failed\n"); | ||||||
|  | 			return -1; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		cJSON_ArrayForEach(session, sessions) { | ||||||
|  | 			cJSON *id, *uplink, *downlink; | ||||||
|  | 			double session_arrid; | ||||||
|  |  | ||||||
|  | 			id = cJSON_GetObjectItemCaseSensitive(session, "id"); | ||||||
|  | 			if (!id || !cJSON_IsNumber(id)) { | ||||||
|  | 				UC_LOG_ERR("Ethernet obj 'port_isolation:id' is invalid, parse failed\n"); | ||||||
|  | 				goto err; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			session_arrid = cJSON_GetNumberValue(id); | ||||||
|  |  | ||||||
|  | 			if (i > 0) { | ||||||
|  | 				for (int j = i - 1; j >= 0; --j) { | ||||||
|  | 					if ((double) session_arr[j].id == session_arrid) { | ||||||
|  | 						UC_LOG_ERR("Expected unique 'port_isolation:id', duplicate (%lu) detected, parse failed\n", | ||||||
|  | 							   (uint64_t) session_arrid); | ||||||
|  | 						goto err; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			session_arr[j].id = (uint64_t) session_arrid; | ||||||
|  |  | ||||||
|  | 			uplink = cJSON_GetObjectItemCaseSensitive(session, | ||||||
|  | 								  "uplink"); | ||||||
|  | 			if (!uplink || !cJSON_IsObject(uplink)) { | ||||||
|  | 				UC_LOG_ERR("Ethernet obj 'port_isolation:uplink' is invalid, parse failed\n"); | ||||||
|  | 				goto err; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			downlink = cJSON_GetObjectItemCaseSensitive(session, | ||||||
|  | 								    "downlink"); | ||||||
|  | 			if (!downlink || !cJSON_IsObject(downlink)) { | ||||||
|  | 				UC_LOG_ERR("Ethernet obj 'port_isolation:downlink' is invalid, parse failed\n"); | ||||||
|  | 				goto err; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (cfg_ethernet_port_isolation_interface_parse(uplink, | ||||||
|  | 									&session_arr[j].uplink)) { | ||||||
|  | 				UC_LOG_ERR("Ethernet obj 'port_isolation:uplink' parse failed\n"); | ||||||
|  | 				goto err; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (cfg_ethernet_port_isolation_interface_parse(downlink, | ||||||
|  | 									&session_arr[j].downlink)) { | ||||||
|  | 				UC_LOG_ERR("Ethernet obj 'port_isolation:downlink' parse failed\n"); | ||||||
|  | 				goto err; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			++i; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return 0; | ||||||
|  | err: | ||||||
|  | 	for (int j = i; j >= 0; --j) { | ||||||
|  | 		UCENTRAL_LIST_DESTROY_SAFE(&session_arr[j].uplink.ports_list, | ||||||
|  | 					   port_node); | ||||||
|  | 		UCENTRAL_LIST_DESTROY_SAFE(&session_arr[j].downlink.ports_list, | ||||||
|  | 					   port_node); | ||||||
|  | 	} | ||||||
|  | 	cfg->port_isolation_cfg.sessions = 0; | ||||||
|  | 	free(cfg->port_isolation_cfg.sessions); | ||||||
|  | 	return -1; | ||||||
|  | } | ||||||
|  |  | ||||||
| static int cfg_ethernet_parse(cJSON *ethernet, struct plat_cfg *cfg) | static int cfg_ethernet_parse(cJSON *ethernet, struct plat_cfg *cfg) | ||||||
| { | { | ||||||
| 	cJSON *eth = NULL; | 	cJSON *eth = NULL; | ||||||
| @@ -986,6 +1151,11 @@ static int cfg_ethernet_parse(cJSON *ethernet, struct plat_cfg *cfg) | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if (cfg_ethernet_port_isolation_parse(ethernet, cfg)) { | ||||||
|  | 		UC_LOG_ERR("port-isolation config parse faile\n"); | ||||||
|  | 		return -1; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -1063,6 +1233,121 @@ static int cfg_port_interface_parse(cJSON *interface, struct plat_cfg *cfg) | |||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static int __cfg_vlan_interface_parse_multicast(cJSON *multicast, | ||||||
|  | 						struct plat_cfg *cfg, | ||||||
|  | 						uint16_t vid) | ||||||
|  | { | ||||||
|  | 	cJSON *igmp, *field, *group, *addr, *ports, *port; | ||||||
|  | 	struct plat_ports_list *e_port; | ||||||
|  | 	struct plat_igmp info = {  /* default values */ | ||||||
|  | 		.snooping_enabled = true, | ||||||
|  | 		.querier_enabled = false, | ||||||
|  | 		.fast_leave_enabled = false, | ||||||
|  | 		.query_interval = 60, | ||||||
|  | 		.last_member_query_interval = 60, | ||||||
|  | 		.max_response_time = 10, | ||||||
|  | 		.version = PLAT_IGMP_VERSION_3, | ||||||
|  | 		.num_groups = 0, | ||||||
|  | 		.groups = NULL | ||||||
|  | 	}; | ||||||
|  | 	size_t group_idx; | ||||||
|  |  | ||||||
|  | 	if (!(igmp = cJSON_GetObjectItemCaseSensitive(multicast, "igmp"))) | ||||||
|  | 		return 0; | ||||||
|  |  | ||||||
|  | 	/* handle igmp snooping parameters */ | ||||||
|  | 	if ((field = cJSON_GetObjectItemCaseSensitive(igmp, "version")) && cJSON_IsNumber(field)) { | ||||||
|  | 		switch ((uint32_t)cJSON_GetNumberValue(field)) { | ||||||
|  | 		case 1: | ||||||
|  | 			info.version = PLAT_IGMP_VERSION_1; | ||||||
|  | 			break; | ||||||
|  | 		case 2: | ||||||
|  | 			info.version = PLAT_IGMP_VERSION_2; | ||||||
|  | 			break; | ||||||
|  | 		case 3: | ||||||
|  | 			info.version = PLAT_IGMP_VERSION_3; | ||||||
|  | 			break; | ||||||
|  | 		default: | ||||||
|  | 			UC_LOG_ERR("Invalid IGMP version %f", cJSON_GetNumberValue(field)); | ||||||
|  | 			return -1; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if ((field = cJSON_GetObjectItemCaseSensitive(igmp, "snooping-enable")) && cJSON_IsBool(field)) | ||||||
|  | 		info.snooping_enabled = cJSON_IsTrue(field); | ||||||
|  | 	if ((field = cJSON_GetObjectItemCaseSensitive(igmp, "querier-enable")) && cJSON_IsBool(field)) | ||||||
|  | 		info.querier_enabled = cJSON_IsTrue(field); | ||||||
|  | 	if ((field = cJSON_GetObjectItemCaseSensitive(igmp, "fast-leave-enable")) && cJSON_IsBool(field)) | ||||||
|  | 		info.fast_leave_enabled = cJSON_IsTrue(field); | ||||||
|  | 	if ((field = cJSON_GetObjectItemCaseSensitive(igmp, "query-interval")) && cJSON_IsNumber(field)) | ||||||
|  | 		info.query_interval = cJSON_GetNumberValue(field); | ||||||
|  | 	if ((field = cJSON_GetObjectItemCaseSensitive(igmp, "last-member-query-interval")) && cJSON_IsNumber(field)) | ||||||
|  | 		info.last_member_query_interval = cJSON_GetNumberValue(field); | ||||||
|  | 	if ((field = cJSON_GetObjectItemCaseSensitive(igmp, "max-response-time")) && cJSON_IsNumber(field)) | ||||||
|  | 		info.max_response_time = cJSON_GetNumberValue(field); | ||||||
|  |  | ||||||
|  | 	field = cJSON_GetObjectItemCaseSensitive(igmp, "static-mcast-groups"); | ||||||
|  | 	if (!field || !cJSON_IsArray(field)) | ||||||
|  | 		goto skip_groups; | ||||||
|  |  | ||||||
|  | 	info.num_groups = cJSON_GetArraySize(field); | ||||||
|  | 	info.groups = calloc(info.num_groups, sizeof(*info.groups)); | ||||||
|  | 	if (!info.groups) | ||||||
|  | 		goto err; | ||||||
|  |  | ||||||
|  | 	/* handle static groups */ | ||||||
|  | 	group_idx = 0; | ||||||
|  | 	cJSON_ArrayForEach(group, field) { | ||||||
|  | 		addr = cJSON_GetObjectItemCaseSensitive(group, "address"); | ||||||
|  | 		ports = cJSON_GetObjectItemCaseSensitive(group, "egress-ports"); | ||||||
|  | 		if (!addr || !cJSON_IsString(addr) || !ports || !cJSON_IsArray(ports)) { | ||||||
|  | 			/* FIXME: workaround for parser issue */ | ||||||
|  | 			addr = cJSON_GetObjectItemCaseSensitive(group, "static-mcast-groups[].address"); | ||||||
|  | 			ports = cJSON_GetObjectItemCaseSensitive(group, "static-mcast-groups[].egress-ports"); | ||||||
|  | 			if (!addr || !cJSON_IsString(addr) || !ports || !cJSON_IsArray(ports)) { | ||||||
|  | 				UC_LOG_ERR("Missing static group info\n"); | ||||||
|  | 				goto err; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (inet_pton(AF_INET, addr->valuestring, &info.groups[group_idx].addr.s_addr) != 1) { | ||||||
|  | 			UC_LOG_ERR("Failed to parse ip addr %s\n", addr->valuestring); | ||||||
|  | 			goto err; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/* handle egress ports */ | ||||||
|  | 		cJSON_ArrayForEach(port, ports) { | ||||||
|  | 			e_port = calloc(1, sizeof(*e_port)); | ||||||
|  | 			if (!e_port) { | ||||||
|  | 				UC_LOG_ERR("Can't alloc port node\n"); | ||||||
|  | 				goto err; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (!cJSON_IsString(port)){ | ||||||
|  | 				UC_LOG_ERR("Invalid port name\n"); | ||||||
|  | 				goto err; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			strncpy(e_port->name, port->valuestring, sizeof(e_port->name)); | ||||||
|  | 			UCENTRAL_LIST_PUSH_MEMBER(&info.groups[group_idx].egress_ports_list, e_port); | ||||||
|  | 		} | ||||||
|  | 		group_idx++; | ||||||
|  | 	} | ||||||
|  | skip_groups: | ||||||
|  | 	info.exist = info.snooping_enabled || info.querier_enabled; | ||||||
|  | 	cfg->vlans[vid].igmp = info; | ||||||
|  | 	return 0; | ||||||
|  | err: | ||||||
|  | 	if (info.groups) { | ||||||
|  | 		for (group_idx = 0; group_idx < info.num_groups; group_idx++) { | ||||||
|  | 			UCENTRAL_LIST_DESTROY_SAFE( | ||||||
|  | 				&info.groups[group_idx].egress_ports_list, | ||||||
|  | 				e_port); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	free(info.groups); | ||||||
|  | 	return -1; | ||||||
|  | } | ||||||
|  |  | ||||||
| static int cfg_vlan_interface_parse(cJSON *interface, struct plat_cfg *cfg) | static int cfg_vlan_interface_parse(cJSON *interface, struct plat_cfg *cfg) | ||||||
| { | { | ||||||
| 	size_t i; | 	size_t i; | ||||||
| @@ -1076,6 +1361,7 @@ static int cfg_vlan_interface_parse(cJSON *interface, struct plat_cfg *cfg) | |||||||
| 	char *ipv4_subnet_str; | 	char *ipv4_subnet_str; | ||||||
| 	cJSON *select_ports; | 	cJSON *select_ports; | ||||||
| 	cJSON *ipv4_subnet; | 	cJSON *ipv4_subnet; | ||||||
|  | 	cJSON *multicast; | ||||||
| 	cJSON *vlan_tag; | 	cJSON *vlan_tag; | ||||||
| 	cJSON *ethernet; | 	cJSON *ethernet; | ||||||
| 	uint8_t tagged; | 	uint8_t tagged; | ||||||
| @@ -1141,6 +1427,7 @@ static int cfg_vlan_interface_parse(cJSON *interface, struct plat_cfg *cfg) | |||||||
| 	cfg->vlans[vid].dhcp.relay.enabled = false; | 	cfg->vlans[vid].dhcp.relay.enabled = false; | ||||||
| 	ipv4 = cJSON_GetObjectItemCaseSensitive(interface, "ipv4"); | 	ipv4 = cJSON_GetObjectItemCaseSensitive(interface, "ipv4"); | ||||||
| 	dhcp = cJSON_GetObjectItemCaseSensitive(ipv4, "dhcp"); | 	dhcp = cJSON_GetObjectItemCaseSensitive(ipv4, "dhcp"); | ||||||
|  | 	multicast = cJSON_GetObjectItemCaseSensitive(ipv4, "multicast"); | ||||||
| 	if (ipv4) { | 	if (ipv4) { | ||||||
| 		/*  TODO addressing */ | 		/*  TODO addressing */ | ||||||
| 		ipv4_subnet_str = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(ipv4, "subnet")); | 		ipv4_subnet_str = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(ipv4, "subnet")); | ||||||
| @@ -1181,6 +1468,13 @@ skip_subnet_old: | |||||||
| 		} | 		} | ||||||
| 		cfg->vlans[vid].ipv4.exist = true; | 		cfg->vlans[vid].ipv4.exist = true; | ||||||
| skip_subnet: | skip_subnet: | ||||||
|  | 		if (multicast) { | ||||||
|  | 			ret = __cfg_vlan_interface_parse_multicast(multicast, cfg, vid); | ||||||
|  | 			if (ret) { | ||||||
|  | 				UC_LOG_ERR("Failed parsing multicast config"); | ||||||
|  | 				return ret; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		if (!dhcp) | 		if (!dhcp) | ||||||
| 			return 0; | 			return 0; | ||||||
| @@ -1333,6 +1627,59 @@ static int cfg_services_parse(cJSON *services, struct plat_cfg *cfg) | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	/* Set default values in case if no cfg supplied */ | ||||||
|  | 	cfg->enabled_services_cfg.ssh.enabled = false; | ||||||
|  | 	cfg->enabled_services_cfg.telnet.enabled = false; | ||||||
|  | 	cfg->enabled_services_cfg.http.enabled = false; | ||||||
|  |  | ||||||
|  | 	s = cJSON_GetObjectItemCaseSensitive(services, "ssh"); | ||||||
|  | 	if (s) { | ||||||
|  | 		if (!cJSON_IsObject(s)) { | ||||||
|  | 			UC_LOG_ERR("Unexpected type of services:ssh: Object expected"); | ||||||
|  | 			return -1; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		cJSON *enable = cJSON_GetObjectItemCaseSensitive(s, "enable"); | ||||||
|  | 		if (enable && !cJSON_IsBool(enable)) { | ||||||
|  | 			UC_LOG_ERR("Unexpected type of services:ssh:enable: Boolean expected"); | ||||||
|  | 			return -1; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		cfg->enabled_services_cfg.ssh.enabled = cJSON_IsTrue(enable); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s = cJSON_GetObjectItemCaseSensitive(services, "telnet"); | ||||||
|  | 	if (s) { | ||||||
|  | 		if (!cJSON_IsObject(s)) { | ||||||
|  | 			UC_LOG_ERR("Unexpected type of services:telnet: Object expected"); | ||||||
|  | 			return -1; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		cJSON *enable = cJSON_GetObjectItemCaseSensitive(s, "enable"); | ||||||
|  | 		if (enable && !cJSON_IsBool(enable)) { | ||||||
|  | 			UC_LOG_ERR("Unexpected type of services:telnet:enable: Boolean expected"); | ||||||
|  | 			return -1; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		cfg->enabled_services_cfg.telnet.enabled = cJSON_IsTrue(enable); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	s = cJSON_GetObjectItemCaseSensitive(services, "http"); | ||||||
|  | 	if (s) { | ||||||
|  | 		if (!cJSON_IsObject(s)) { | ||||||
|  | 			UC_LOG_ERR("Unexpected type of services:http: Object expected"); | ||||||
|  | 			return -1; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		cJSON *enable = cJSON_GetObjectItemCaseSensitive(s, "enable"); | ||||||
|  | 		if (enable && !cJSON_IsBool(enable)) { | ||||||
|  | 			UC_LOG_ERR("Unexpected type of services:http:enable: Boolean expected"); | ||||||
|  | 			return -1; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		cfg->enabled_services_cfg.http.enabled = cJSON_IsTrue(enable); | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -1353,7 +1700,7 @@ static int cfg_switch_ieee8021x_parse(cJSON *sw, struct plat_cfg *cfg) | |||||||
| 	/* It's safe to check against NULL cJSON obj. | 	/* It's safe to check against NULL cJSON obj. | ||||||
| 	 * In case if option is missing - defaulting to 'false' is OK for us. | 	 * In case if option is missing - defaulting to 'false' is OK for us. | ||||||
| 	 */ | 	 */ | ||||||
| 	cfg->ieee8021x_is_auth_ctrl_enabled = cJSON_IsTrue(auth_ctrl_enabled); | 	cfg->ieee8021x.is_auth_ctrl_enabled = cJSON_IsTrue(auth_ctrl_enabled); | ||||||
| 	radius = cJSON_GetObjectItemCaseSensitive(ieee8021x, "radius"); | 	radius = cJSON_GetObjectItemCaseSensitive(ieee8021x, "radius"); | ||||||
|  |  | ||||||
| 	if (radius && !cJSON_IsArray(radius)) { | 	if (radius && !cJSON_IsArray(radius)) { | ||||||
| @@ -2511,6 +2858,10 @@ static void script_handle(cJSON **rpc) | |||||||
| 			UC_LOG_ERR("script message missing 'uri' parameter"); | 			UC_LOG_ERR("script message missing 'uri' parameter"); | ||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		script_reply(0, "pending", id); | ||||||
|  | 		UC_LOG_DBG("Script requested OK (pending. Waiting for plat to execute)\n"); | ||||||
|  |  | ||||||
| 		memset(&file_path[0], 0, sizeof(file_path)); | 		memset(&file_path[0], 0, sizeof(file_path)); | ||||||
| 		if (plat_diagnostic(&file_path[0])) { | 		if (plat_diagnostic(&file_path[0])) { | ||||||
| 			UC_LOG_ERR("Script failed\n"); | 			UC_LOG_ERR("Script failed\n"); | ||||||
| @@ -2518,9 +2869,6 @@ static void script_handle(cJSON **rpc) | |||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		script_reply(0, "pending", id); |  | ||||||
| 		UC_LOG_DBG("Script requested OK\n"); |  | ||||||
|  |  | ||||||
| 		/* Poll upgrade state - start periodical. */ | 		/* Poll upgrade state - start periodical. */ | ||||||
| 		while (access(file_path, F_OK)) | 		while (access(file_path, F_OK)) | ||||||
| 			sleep(1); | 			sleep(1); | ||||||
| @@ -2766,14 +3114,135 @@ err: | |||||||
| 	return -1; | 	return -1; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static int state_fill_transceiver_info(cJSON *root, | ||||||
|  | 				       struct plat_port_transceiver_info *info) | ||||||
|  | { | ||||||
|  | 	char *form_factor[] = { | ||||||
|  | 		[UCENTRAL_SFP_FORM_FACTOR_NA] = "", | ||||||
|  | 		[UCENTRAL_SFP_FORM_FACTOR_SFP] = "SFP", | ||||||
|  | 		[UCENTRAL_SFP_FORM_FACTOR_SFP_PLUS] = "SFP+", | ||||||
|  | 		[UCENTRAL_SFP_FORM_FACTOR_SFP_28] = "SFP28", | ||||||
|  | 		[UCENTRAL_SFP_FORM_FACTOR_SFP_DD] = "SFP-DD", | ||||||
|  | 		[UCENTRAL_SFP_FORM_FACTOR_QSFP] = "QSFP", | ||||||
|  | 		[UCENTRAL_SFP_FORM_FACTOR_QSFP_PLUS] = "QSFP+", | ||||||
|  | 		[UCENTRAL_SFP_FORM_FACTOR_QSFP_28] = "QSFP28", | ||||||
|  | 		[UCENTRAL_SFP_FORM_FACTOR_QSFP_DD] = "QSFP-DD" | ||||||
|  | 	}; | ||||||
|  | 	char *link_mode[] = { | ||||||
|  | 		[UCENTRAL_SFP_LINK_MODE_NA] = "", | ||||||
|  | 		[UCENTRAL_SFP_LINK_MODE_1000_X] = "1G", | ||||||
|  | 		[UCENTRAL_SFP_LINK_MODE_2500_X] = "2.5G", | ||||||
|  | 		[UCENTRAL_SFP_LINK_MODE_4000_SR] = "4G SR", | ||||||
|  | 		[UCENTRAL_SFP_LINK_MODE_10G_SR] = "10G SR", | ||||||
|  | 		[UCENTRAL_SFP_LINK_MODE_25G_SR] = "25G SR", | ||||||
|  | 		[UCENTRAL_SFP_LINK_MODE_40G_SR] = "40G SR", | ||||||
|  | 		[UCENTRAL_SFP_LINK_MODE_50G_SR] = "50G SR", | ||||||
|  | 		[UCENTRAL_SFP_LINK_MODE_100G_SR] = "100G SR" | ||||||
|  | 	}; | ||||||
|  | 	cJSON *supp_modes, *mode; | ||||||
|  | 	size_t i; | ||||||
|  |  | ||||||
|  | 	if (!cJSON_AddStringToObject(root, "vendor-name", | ||||||
|  | 				     info->vendor_name)) | ||||||
|  | 		goto err; | ||||||
|  | 	if (!cJSON_AddStringToObject(root, "part-number", | ||||||
|  | 				     info->part_number)) | ||||||
|  | 		goto err; | ||||||
|  | 	if (!cJSON_AddStringToObject(root, "serial-number", | ||||||
|  | 				     info->serial_number)) | ||||||
|  | 		goto err; | ||||||
|  | 	if (!cJSON_AddStringToObject(root, "revision", | ||||||
|  | 				     info->revision)) | ||||||
|  | 		goto err; | ||||||
|  | 	if (!cJSON_AddNumberToObject(root, "temperature", | ||||||
|  | 				     info->temperature)) | ||||||
|  | 		goto err; | ||||||
|  | 	if (!cJSON_AddNumberToObject(root, "tx-optical-power", | ||||||
|  | 				     info->tx_optical_power)) | ||||||
|  | 		goto err; | ||||||
|  | 	if (!cJSON_AddNumberToObject(root, "rx-optical-power", | ||||||
|  | 				     info->rx_optical_power)) | ||||||
|  | 		goto err; | ||||||
|  | 	if (!cJSON_AddNumberToObject(root, "max-module-power", | ||||||
|  | 				     info->max_module_power)) | ||||||
|  | 		goto err; | ||||||
|  | 	if (!cJSON_AddStringToObject(root, "form-factor", | ||||||
|  | 				     form_factor[info->form_factor])) | ||||||
|  | 		goto err; | ||||||
|  | 	if (!(supp_modes = cJSON_AddArrayToObject(root, "supported-link-modes"))) | ||||||
|  | 		goto err; | ||||||
|  | 	for (i = 0; i < info->num_supported_link_modes; i++) { | ||||||
|  | 		if (!(mode = cJSON_CreateString(link_mode[info->supported_link_modes[i]]))) | ||||||
|  | 			goto err; | ||||||
|  | 		if (!cJSON_AddItemToArray(supp_modes, mode)) { | ||||||
|  | 			cJSON_Delete(mode); | ||||||
|  | 			goto err; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return 0; | ||||||
|  | err: | ||||||
|  | 	return -1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static int state_fill_interface_multicast(cJSON *root, struct plat_port_vlan *vlan) | ||||||
|  | { | ||||||
|  | 	cJSON *igmp, *enabled_groups, *group, *outgoing_ports; | ||||||
|  | 	struct plat_ports_list *port_node = NULL; | ||||||
|  | 	char ip_addr[] = {"255.255.255.255"}; | ||||||
|  | 	int ret = -1; | ||||||
|  | 	size_t idx; | ||||||
|  |  | ||||||
|  | 	if (!vlan->igmp.exist) | ||||||
|  | 		return 0; | ||||||
|  |  | ||||||
|  | 	igmp = cJSON_AddObjectToObject(root, "igmp"); | ||||||
|  | 	enabled_groups = cJSON_AddArrayToObject(igmp, "enabled-groups"); | ||||||
|  | 	if (!igmp || !enabled_groups) | ||||||
|  | 		goto err; | ||||||
|  |  | ||||||
|  | 	for (idx = 0; idx < vlan->igmp.num_groups; idx++) { | ||||||
|  | 		if (!(group = cJSON_CreateObject())) | ||||||
|  | 			goto err; | ||||||
|  |  | ||||||
|  | 		if (!inet_ntop(AF_INET, &vlan->igmp.groups[idx].addr, | ||||||
|  | 			       ip_addr, sizeof(ip_addr))) | ||||||
|  | 			goto err; | ||||||
|  |  | ||||||
|  | 		if (!(cJSON_AddStringToObject(group, "address", ip_addr))) | ||||||
|  | 			goto err; | ||||||
|  |  | ||||||
|  | 		if (!(outgoing_ports = cJSON_AddArrayToObject(group, "egress-ports"))) | ||||||
|  | 			goto err; | ||||||
|  |  | ||||||
|  | 		UCENTRAL_LIST_FOR_EACH_MEMBER( | ||||||
|  | 				port_node, | ||||||
|  | 				&vlan->igmp.groups[idx].egress_ports_list) { | ||||||
|  | 			if (!cJSON_AddItemToArray(outgoing_ports, cJSON_CreateString(port_node->name))) | ||||||
|  | 				goto err; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (!cJSON_AddItemToArray(enabled_groups, group)) | ||||||
|  | 			goto err; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	ret = 0; | ||||||
|  | err: | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  |  | ||||||
| static int state_fill_interfaces_data(cJSON *interfaces, | static int state_fill_interfaces_data(cJSON *interfaces, | ||||||
| 				      struct plat_state_info *state) | 				      struct plat_state_info *state) | ||||||
| { | { | ||||||
|  | 	char location[] = { "/interfaces/XXXX" }; | ||||||
|  | 	char vlan_name[] = { "VlanXXXX" }; | ||||||
| 	cJSON *dns_servers; | 	cJSON *dns_servers; | ||||||
|  | 	cJSON *transceiver; | ||||||
| 	cJSON *interface; | 	cJSON *interface; | ||||||
|  | 	cJSON *multicast; | ||||||
| 	cJSON *counters; | 	cJSON *counters; | ||||||
| 	cJSON *clients; | 	cJSON *clients; | ||||||
| 	cJSON *ipv4; | 	cJSON *ipv4; | ||||||
|  | 	uint16_t id; | ||||||
| 	int ret; | 	int ret; | ||||||
| 	int i; | 	int i; | ||||||
|  |  | ||||||
| @@ -2807,6 +3276,16 @@ static int state_fill_interfaces_data(cJSON *interfaces, | |||||||
| 		if (!ipv4) | 		if (!ipv4) | ||||||
| 			goto err; | 			goto err; | ||||||
|  |  | ||||||
|  | 		if (state->port_info[i].has_transceiver_info) { | ||||||
|  | 			transceiver = cJSON_AddObjectToObject(interface, "transceiver-info"); | ||||||
|  | 			if (!transceiver) | ||||||
|  | 				goto err; | ||||||
|  | 			ret = state_fill_transceiver_info(transceiver, | ||||||
|  | 							  &state->port_info[i].transceiver_info); | ||||||
|  | 			if (ret) | ||||||
|  | 				goto err; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		ret = state_fill_interface_clients(clients, | 		ret = state_fill_interface_clients(clients, | ||||||
| 						   &state->port_info[i]); | 						   &state->port_info[i]); | ||||||
| 		if (ret) | 		if (ret) | ||||||
| @@ -2832,23 +3311,46 @@ static int state_fill_interfaces_data(cJSON *interfaces, | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		/* TBD: find out (?) proper <location> */ | 		/* TBD: find out (?) proper <location> */ | ||||||
| 		{ | 		NAME_TO_PID(&id, state->port_info[i].name); | ||||||
| 			char location[] = { "/interfaces/XXXX" }; | 		sprintf(location, "/interfaces/%hu", id); | ||||||
| 			uint16_t pid; |  | ||||||
|  |  | ||||||
| 			NAME_TO_PID(&pid, state->port_info[i].name); |  | ||||||
| 			sprintf(location, "/interfaces/%hu", pid); |  | ||||||
|  |  | ||||||
| 		if (!cJSON_AddStringToObject(interface, "location", | 		if (!cJSON_AddStringToObject(interface, "location", | ||||||
| 					     location)) | 					     location)) | ||||||
| 			goto err; | 			goto err; | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if (!jobj_u64_set(interface, "uptime", | 		if (!jobj_u64_set(interface, "uptime", | ||||||
| 				  state->system_info.uptime)) | 				  state->system_info.uptime)) | ||||||
| 			goto err; | 			goto err; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	for (i = 0; i < (int)state->vlan_info_count; i++) { | ||||||
|  | 		interface = cJSON_CreateObject(); | ||||||
|  | 		if (!interface || !cJSON_AddItemToArray(interfaces, interface)) | ||||||
|  | 			goto err; | ||||||
|  |  | ||||||
|  | 		clients = cJSON_AddArrayToObject(interface, "clients"); | ||||||
|  | 		counters = cJSON_AddObjectToObject(interface, "counters"); | ||||||
|  | 		dns_servers = cJSON_AddArrayToObject(interface, "dns_servers"); | ||||||
|  | 		ipv4 = cJSON_AddObjectToObject(interface, "ipv4"); | ||||||
|  | 		multicast = cJSON_AddObjectToObject(interface, "multicast"); | ||||||
|  | 		if (!clients || !counters || !dns_servers || !ipv4 || !multicast) | ||||||
|  | 			goto err; | ||||||
|  |  | ||||||
|  | 		ret = state_fill_interface_multicast(multicast, &state->vlan_info[i]); | ||||||
|  | 		if (ret) | ||||||
|  | 			goto err; | ||||||
|  |  | ||||||
|  | 		sprintf(location, "/SVI/%u", state->vlan_info[i].id); | ||||||
|  | 		sprintf(vlan_name, "Vlan%u", state->vlan_info[i].id); | ||||||
|  |  | ||||||
|  | 		if (!cJSON_AddStringToObject(interface, "name", vlan_name)) | ||||||
|  | 			goto err; | ||||||
|  | 		if (!cJSON_AddStringToObject(interface, "location", location)) | ||||||
|  | 			goto err; | ||||||
|  | 		if (!jobj_u64_set(interface, "uptime", state->system_info.uptime)) | ||||||
|  | 			goto err; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return 0; | 	return 0; | ||||||
| err: | err: | ||||||
| 	return -1; | 	return -1; | ||||||
| @@ -3157,15 +3659,15 @@ static int | |||||||
| state_fill_unit_poe_data(cJSON *poe, struct plat_poe_state *poe_state_info) | state_fill_unit_poe_data(cJSON *poe, struct plat_poe_state *poe_state_info) | ||||||
| { | { | ||||||
|  |  | ||||||
| 	if (!cJSON_AddNumberToObject(poe, "max-power-budget", | 	if (!jobj_u64_set(poe, "max-power-budget", | ||||||
| 			  poe_state_info->max_power_budget)) | 			  poe_state_info->max_power_budget)) | ||||||
| 		goto err; | 		goto err; | ||||||
|  |  | ||||||
| 	if (!cJSON_AddNumberToObject(poe, "power-consumed", | 	if (!jobj_u64_set(poe, "power-consumed", | ||||||
| 			  poe_state_info->power_consumed)) | 			  poe_state_info->power_consumed)) | ||||||
| 		goto err; | 		goto err; | ||||||
|  |  | ||||||
| 	if (!cJSON_AddNumberToObject(poe, "power-threshold", | 	if (!jobj_u64_set(poe, "power-threshold", | ||||||
| 			  poe_state_info->power_threshold)) | 			  poe_state_info->power_threshold)) | ||||||
| 		goto err; | 		goto err; | ||||||
|  |  | ||||||
| @@ -3178,8 +3680,51 @@ err: | |||||||
| 	return -1; | 	return -1; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | static int | ||||||
|  | state_fill_unit_ieee8021x_global_counters(cJSON *ieee8021x, | ||||||
|  | 					  struct plat_iee8021x_coa_counters *c) | ||||||
|  | { | ||||||
|  | 	cJSON *das, *stats; | ||||||
|  |  | ||||||
|  | 	das = cJSON_AddObjectToObject(ieee8021x, "dynamic-authorization"); | ||||||
|  | 	if (!das) | ||||||
|  | 		return -1; | ||||||
|  |  | ||||||
|  | 	stats = cJSON_AddObjectToObject(das, "stats"); | ||||||
|  | 	if (!stats) | ||||||
|  | 		return -1; | ||||||
|  |  | ||||||
|  | 	if (!jobj_u64_set(stats, "coa_req_received", | ||||||
|  | 			  c->coa_req_received)) | ||||||
|  | 		return -1; | ||||||
|  | 	if (!jobj_u64_set(stats, "coa_ack_sent", | ||||||
|  | 			  c->coa_ack_sent)) | ||||||
|  | 		return -1; | ||||||
|  | 	if (!jobj_u64_set(stats, "coa_nak_sent", | ||||||
|  | 			  c->coa_nak_sent)) | ||||||
|  | 		return -1; | ||||||
|  | 	if (!jobj_u64_set(stats, "coa_ignored", | ||||||
|  | 			  c->coa_ignored)) | ||||||
|  | 		return -1; | ||||||
|  | 	if (!jobj_u64_set(stats, "coa_wrong_attr", | ||||||
|  | 			  c->coa_wrong_attr)) | ||||||
|  | 		return -1; | ||||||
|  | 	if (!jobj_u64_set(stats, "coa_wrong_attr_value", | ||||||
|  | 			  c->coa_wrong_attr_value)) | ||||||
|  | 		return -1; | ||||||
|  | 	if (!jobj_u64_set(stats, "coa_wrong_session_context", | ||||||
|  | 			  c->coa_wrong_session_context)) | ||||||
|  | 		return -1; | ||||||
|  | 	if (!jobj_u64_set(stats, "administratively_prohibited_req", | ||||||
|  | 			  c->coa_administratively_prohibited_req)) | ||||||
|  | 		return -1; | ||||||
|  |  | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
| static int state_fill_unit_data(cJSON *unit, struct plat_state_info *state) | static int state_fill_unit_data(cJSON *unit, struct plat_state_info *state) | ||||||
| { | { | ||||||
|  | 	cJSON *ieee8021x; | ||||||
| 	cJSON *loadArr; | 	cJSON *loadArr; | ||||||
| 	cJSON *memory; | 	cJSON *memory; | ||||||
| 	cJSON *poe; | 	cJSON *poe; | ||||||
| @@ -3215,6 +3760,12 @@ static int state_fill_unit_data(cJSON *unit, struct plat_state_info *state) | |||||||
| 	if (!poe || state_fill_unit_poe_data(poe, &state->poe_state)) | 	if (!poe || state_fill_unit_poe_data(poe, &state->poe_state)) | ||||||
| 		goto err; | 		goto err; | ||||||
|  |  | ||||||
|  | 	ieee8021x = cJSON_AddObjectToObject(unit, "ieee8021x"); | ||||||
|  | 	if (!ieee8021x || | ||||||
|  | 	    state_fill_unit_ieee8021x_global_counters(ieee8021x, | ||||||
|  | 						      &state->ieee8021x_global_coa_counters)) | ||||||
|  | 		goto err; | ||||||
|  |  | ||||||
| 	return 0; | 	return 0; | ||||||
|  |  | ||||||
| err: | err: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Olexandr, Mazur
					Olexandr, Mazur