// SPDX-License-Identifier: GPL-2.0
/*
* Shared Memory Communications over RDMA (SMC-R) and RoCE
*
* CLC (connection layer control) handshake over initial TCP socket to
* prepare for RDMA traffic
*
* Copyright IBM Corp. 2016, 2018
*
* Author(s): Ursula Braun <ubraun@linux.vnet.ibm.com>
*/
#include <linux/in.h>
#include <linux/inetdevice.h>
#include <linux/if_ether.h>
#include <linux/sched/signal.h>
#include <linux/utsname.h>
#include <linux/ctype.h>
#include <net/addrconf.h>
#include <net/sock.h>
#include <net/tcp.h>
#include "smc.h"
#include "smc_core.h"
#include "smc_clc.h"
#include "smc_ib.h"
#include "smc_ism.h"
#include "smc_netlink.h"
#define SMCR_CLC_ACCEPT_CONFIRM_LEN 68
#define SMCD_CLC_ACCEPT_CONFIRM_LEN 48
#define SMCD_CLC_ACCEPT_CONFIRM_LEN_V2 78
#define SMCR_CLC_ACCEPT_CONFIRM_LEN_V2 108
#define SMC_CLC_RECV_BUF_LEN 100
/* eye catcher "SMCR" EBCDIC for CLC messages */
static const char SMC_EYECATCHER[4] = {'\xe2', '\xd4', '\xc3', '\xd9'};
/* eye catcher "SMCD" EBCDIC for CLC messages */
static const char SMCD_EYECATCHER[4] = {'\xe2', '\xd4', '\xc3', '\xc4'};
static u8 smc_hostname[SMC_MAX_HOSTNAME_LEN];
struct smc_clc_eid_table {
rwlock_t lock;
struct list_head list;
u8 ueid_cnt;
u8 seid_enabled;
};
static struct smc_clc_eid_table smc_clc_eid_table;
struct smc_clc_eid_entry {
struct list_head list;
u8 eid[SMC_MAX_EID_LEN];
};
/* The size of a user EID is 32 characters.
* Valid characters should be (single-byte character set) A-Z, 0-9, '.' and '-'.
* Blanks should only be used to pad to the expected size.
* First character must be alphanumeric.
*/
static bool smc_clc_ueid_valid(char *ueid)
{
char *end = ueid + SMC_MAX_EID_LEN;
while (--end >= ueid && isspace(*end))
;
if (end < ueid)
return false;
if (!isalnum(*ueid) || islower(*ueid))
return false;
while (ueid <= end) {
if ((!isalnum(*ueid) || islower(*ueid)) && *ueid != '.' &&
*ueid != '-')
return false;
ueid++;
}
return true;
}
static int smc_clc_ueid_add(char *ueid)
{
struct smc_clc_eid_entry *new_ueid, *tmp_ueid;
int rc;
if (!smc_clc_ueid_valid(ueid))
return -EINVAL;
/* add a new ueid entry to the ueid table if there isn't one */
new_ueid = kzalloc(sizeof(*new_ueid), GFP_KERNEL);
if (!new_ueid)
return -ENOMEM;
memcpy(new_ueid->eid, ueid, SMC_MAX_EID_LEN);
write_lock(&smc_clc_eid_table.lock);
if (smc_clc_eid_table.ueid_cnt >= SMC_MAX_UEID) {
rc = -ERANGE;
goto err_out;
}
list_for_each_entry(tmp_ueid, &smc_clc_eid_table.list, list) {
if (!memcmp(tmp_ueid->eid, ueid, SMC_MAX_EID_LEN)) {
rc = -EEXIST;
goto err_out;
}
}
list_add_tail(&new_ueid->list, &smc_clc_eid_table.list);
smc_clc_eid_table.ueid_cnt++;
write_unlock(&smc_clc_eid_table.lock);
return 0;
err_out:
write_unlock(&smc_clc_eid_table.lock);
kfree(new_ueid);
return rc;
}
int smc_clc_ueid_count(void)
{
int count;
read_lock(&smc_clc_eid_table.lock);
count = smc_clc_eid_table.ueid_cnt;
read_unlock(&smc_clc_eid_table.lock);
return count;
}
int smc_nl_add_ueid(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *nla_ueid = info->attrs[SMC_NLA_EID_TABLE_ENTRY];
char *ueid;
if (!nla_ueid || nla_len(nla_ueid) != SMC_MAX_EID_LEN + 1)
return -EINVAL;
ueid = (char *)nla_data(nla_ueid);
return smc_clc_ueid_add(ueid);
}
/* remove one or all ueid entries from the table */
static int smc_clc_ueid_remove(char *ueid)
{
struct smc_clc_eid_entry *lst_ueid, *tmp_ueid;
int rc = -ENOENT;
/* remove table entry */
write_lock(&smc_clc_eid_table.lock);
list_for_each_entry_safe(