P2P: Maintain list of per-client PSKs for persistent groups
Record all generated per-client PSKs in the persistent group network block and configure these for the GO Authenticator whenever re-starting the persistent group. This completes per-client PSK support for persistent groups. Signed-hostap: Jouni Malinen <j@w1.fi>
This commit is contained in:
parent
759fd76b7f
commit
01a57fe420
11 changed files with 277 additions and 2 deletions
|
@ -204,6 +204,11 @@ struct hostapd_data {
|
|||
void (*setup_complete_cb)(void *ctx);
|
||||
void *setup_complete_cb_ctx;
|
||||
|
||||
void (*new_psk_cb)(void *ctx, const u8 *mac_addr,
|
||||
const u8 *p2p_dev_addr, const u8 *psk,
|
||||
size_t psk_len);
|
||||
void *new_psk_cb_ctx;
|
||||
|
||||
#ifdef CONFIG_P2P
|
||||
struct p2p_data *p2p;
|
||||
struct p2p_group *p2p_group;
|
||||
|
|
|
@ -125,6 +125,11 @@ static int hostapd_wps_new_psk_cb(void *ctx, const u8 *mac_addr,
|
|||
os_memcpy(p->p2p_dev_addr, p2p_dev_addr, ETH_ALEN);
|
||||
os_memcpy(p->psk, psk, PMK_LEN);
|
||||
|
||||
if (hapd->new_psk_cb) {
|
||||
hapd->new_psk_cb(hapd->new_psk_cb_ctx, mac_addr, p2p_dev_addr,
|
||||
psk, psk_len);
|
||||
}
|
||||
|
||||
p->next = ssid->wpa_psk;
|
||||
ssid->wpa_psk = p;
|
||||
|
||||
|
|
|
@ -367,6 +367,19 @@ static void ap_sta_authorized_cb(void *ctx, const u8 *mac_addr,
|
|||
}
|
||||
|
||||
|
||||
#ifdef CONFIG_P2P
|
||||
static void ap_new_psk_cb(void *ctx, const u8 *mac_addr, const u8 *p2p_dev_addr,
|
||||
const u8 *psk, size_t psk_len)
|
||||
{
|
||||
|
||||
struct wpa_supplicant *wpa_s = ctx;
|
||||
if (wpa_s->ap_iface == NULL || wpa_s->current_ssid == NULL)
|
||||
return;
|
||||
wpas_p2p_new_psk_cb(wpa_s, mac_addr, p2p_dev_addr, psk, psk_len);
|
||||
}
|
||||
#endif /* CONFIG_P2P */
|
||||
|
||||
|
||||
static int ap_vendor_action_rx(void *ctx, const u8 *buf, size_t len, int freq)
|
||||
{
|
||||
#ifdef CONFIG_P2P
|
||||
|
@ -571,6 +584,8 @@ int wpa_supplicant_create_ap(struct wpa_supplicant *wpa_s,
|
|||
hapd_iface->bss[i]->sta_authorized_cb = ap_sta_authorized_cb;
|
||||
hapd_iface->bss[i]->sta_authorized_cb_ctx = wpa_s;
|
||||
#ifdef CONFIG_P2P
|
||||
hapd_iface->bss[i]->new_psk_cb = ap_new_psk_cb;
|
||||
hapd_iface->bss[i]->new_psk_cb_ctx = wpa_s;
|
||||
hapd_iface->bss[i]->p2p = wpa_s->global->p2p;
|
||||
hapd_iface->bss[i]->p2p_group = wpas_p2p_group_init(wpa_s,
|
||||
ssid);
|
||||
|
|
|
@ -1372,6 +1372,60 @@ static char * wpa_config_write_p2p_client_list(const struct parse_data *data,
|
|||
}
|
||||
#endif /* NO_CONFIG_WRITE */
|
||||
|
||||
|
||||
static int wpa_config_parse_psk_list(const struct parse_data *data,
|
||||
struct wpa_ssid *ssid, int line,
|
||||
const char *value)
|
||||
{
|
||||
struct psk_list_entry *p;
|
||||
const char *pos;
|
||||
|
||||
p = os_zalloc(sizeof(*p));
|
||||
if (p == NULL)
|
||||
return -1;
|
||||
|
||||
pos = value;
|
||||
if (os_strncmp(pos, "P2P-", 4) == 0) {
|
||||
p->p2p = 1;
|
||||
pos += 4;
|
||||
}
|
||||
|
||||
if (hwaddr_aton(pos, p->addr)) {
|
||||
wpa_printf(MSG_ERROR, "Line %d: Invalid psk_list address '%s'",
|
||||
line, pos);
|
||||
os_free(p);
|
||||
return -1;
|
||||
}
|
||||
pos += 17;
|
||||
if (*pos != '-') {
|
||||
wpa_printf(MSG_ERROR, "Line %d: Invalid psk_list '%s'",
|
||||
line, pos);
|
||||
os_free(p);
|
||||
return -1;
|
||||
}
|
||||
pos++;
|
||||
|
||||
if (hexstr2bin(pos, p->psk, PMK_LEN) || pos[PMK_LEN * 2] != '\0') {
|
||||
wpa_printf(MSG_ERROR, "Line %d: Invalid psk_list PSK '%s'",
|
||||
line, pos);
|
||||
os_free(p);
|
||||
return -1;
|
||||
}
|
||||
|
||||
dl_list_add(&ssid->psk_list, &p->list);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#ifndef NO_CONFIG_WRITE
|
||||
static char * wpa_config_write_psk_list(const struct parse_data *data,
|
||||
struct wpa_ssid *ssid)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
#endif /* NO_CONFIG_WRITE */
|
||||
|
||||
#endif /* CONFIG_P2P */
|
||||
|
||||
/* Helper macros for network block parser */
|
||||
|
@ -1539,6 +1593,7 @@ static const struct parse_data ssid_fields[] = {
|
|||
{ INT_RANGE(ignore_broadcast_ssid, 0, 2) },
|
||||
#ifdef CONFIG_P2P
|
||||
{ FUNC(p2p_client_list) },
|
||||
{ FUNC(psk_list) },
|
||||
#endif /* CONFIG_P2P */
|
||||
#ifdef CONFIG_HT_OVERRIDES
|
||||
{ INT_RANGE(disable_ht, 0, 1) },
|
||||
|
@ -1731,6 +1786,8 @@ static void eap_peer_config_free(struct eap_peer_config *eap)
|
|||
*/
|
||||
void wpa_config_free_ssid(struct wpa_ssid *ssid)
|
||||
{
|
||||
struct psk_list_entry *psk;
|
||||
|
||||
os_free(ssid->ssid);
|
||||
os_free(ssid->passphrase);
|
||||
os_free(ssid->ext_psk);
|
||||
|
@ -1745,6 +1802,11 @@ void wpa_config_free_ssid(struct wpa_ssid *ssid)
|
|||
#ifdef CONFIG_HT_OVERRIDES
|
||||
os_free(ssid->ht_mcs);
|
||||
#endif /* CONFIG_HT_OVERRIDES */
|
||||
while ((psk = dl_list_first(&ssid->psk_list, struct psk_list_entry,
|
||||
list))) {
|
||||
dl_list_del(&psk->list);
|
||||
os_free(psk);
|
||||
}
|
||||
os_free(ssid);
|
||||
}
|
||||
|
||||
|
@ -1908,6 +1970,7 @@ struct wpa_ssid * wpa_config_add_network(struct wpa_config *config)
|
|||
if (ssid == NULL)
|
||||
return NULL;
|
||||
ssid->id = id;
|
||||
dl_list_init(&ssid->psk_list);
|
||||
if (last)
|
||||
last->next = ssid;
|
||||
else
|
||||
|
|
|
@ -158,6 +158,7 @@ static struct wpa_ssid * wpa_config_read_network(FILE *f, int *line, int id)
|
|||
ssid = os_zalloc(sizeof(*ssid));
|
||||
if (ssid == NULL)
|
||||
return NULL;
|
||||
dl_list_init(&ssid->psk_list);
|
||||
ssid->id = id;
|
||||
|
||||
wpa_config_set_network_defaults(ssid);
|
||||
|
@ -604,6 +605,7 @@ static void write_wep_key(FILE *f, int idx, struct wpa_ssid *ssid)
|
|||
|
||||
|
||||
#ifdef CONFIG_P2P
|
||||
|
||||
static void write_p2p_client_list(FILE *f, struct wpa_ssid *ssid)
|
||||
{
|
||||
char *value = wpa_config_get(ssid, "p2p_client_list");
|
||||
|
@ -612,6 +614,20 @@ static void write_p2p_client_list(FILE *f, struct wpa_ssid *ssid)
|
|||
fprintf(f, "\tp2p_client_list=%s\n", value);
|
||||
os_free(value);
|
||||
}
|
||||
|
||||
|
||||
static void write_psk_list(FILE *f, struct wpa_ssid *ssid)
|
||||
{
|
||||
struct psk_list_entry *psk;
|
||||
char hex[32 * 2 + 1];
|
||||
|
||||
dl_list_for_each(psk, &ssid->psk_list, struct psk_list_entry, list) {
|
||||
wpa_snprintf_hex(hex, sizeof(hex), psk->psk, sizeof(psk->psk));
|
||||
fprintf(f, "\tpsk_list=%s" MACSTR "-%s\n",
|
||||
psk->p2p ? "P2P-" : "", MAC2STR(psk->addr), hex);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* CONFIG_P2P */
|
||||
|
||||
|
||||
|
@ -696,6 +712,7 @@ static void wpa_config_write_network(FILE *f, struct wpa_ssid *ssid)
|
|||
STR(id_str);
|
||||
#ifdef CONFIG_P2P
|
||||
write_p2p_client_list(f, ssid);
|
||||
write_psk_list(f, ssid);
|
||||
#endif /* CONFIG_P2P */
|
||||
INT(dtim_period);
|
||||
INT(beacon_int);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* WPA Supplicant / Network configuration structures
|
||||
* Copyright (c) 2003-2008, Jouni Malinen <j@w1.fi>
|
||||
* Copyright (c) 2003-2013, Jouni Malinen <j@w1.fi>
|
||||
*
|
||||
* This software may be distributed under the terms of the BSD license.
|
||||
* See README for more details.
|
||||
|
@ -10,6 +10,7 @@
|
|||
#define CONFIG_SSID_H
|
||||
|
||||
#include "common/defs.h"
|
||||
#include "utils/list.h"
|
||||
#include "eap_peer/eap_config.h"
|
||||
|
||||
#define MAX_SSID_LEN 32
|
||||
|
@ -33,6 +34,13 @@
|
|||
#define DEFAULT_AMPDU_FACTOR -1 /* no change */
|
||||
#define DEFAULT_AMPDU_DENSITY -1 /* no change */
|
||||
|
||||
struct psk_list_entry {
|
||||
struct dl_list list;
|
||||
u8 addr[ETH_ALEN];
|
||||
u8 psk[32];
|
||||
u8 p2p;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct wpa_ssid - Network configuration data
|
||||
*
|
||||
|
@ -455,6 +463,11 @@ struct wpa_ssid {
|
|||
#define P2P_MAX_STORED_CLIENTS 100
|
||||
#endif /* P2P_MAX_STORED_CLIENTS */
|
||||
|
||||
/**
|
||||
* psk_list - Per-client PSKs (struct psk_list_entry)
|
||||
*/
|
||||
struct dl_list psk_list;
|
||||
|
||||
/**
|
||||
* p2p_group - Network generated as a P2P group (used internally)
|
||||
*/
|
||||
|
|
|
@ -302,6 +302,7 @@ static struct wpa_ssid * wpa_config_read_network(HKEY hk, const TCHAR *netw,
|
|||
RegCloseKey(nhk);
|
||||
return NULL;
|
||||
}
|
||||
dl_list_init(&ssid->psk_list);
|
||||
ssid->id = id;
|
||||
|
||||
wpa_config_set_network_defaults(ssid);
|
||||
|
|
|
@ -600,6 +600,11 @@ static int wpas_p2p_store_persistent_group(struct wpa_supplicant *wpa_s,
|
|||
s->ssid_len = ssid->ssid_len;
|
||||
os_memcpy(s->ssid, ssid->ssid, s->ssid_len);
|
||||
}
|
||||
if (ssid->mode == WPAS_MODE_P2P_GO && wpa_s->global->add_psk) {
|
||||
dl_list_add(&s->psk_list, &wpa_s->global->add_psk->list);
|
||||
wpa_s->global->add_psk = NULL;
|
||||
changed = 1;
|
||||
}
|
||||
|
||||
#ifndef CONFIG_NO_CONFIG_WRITE
|
||||
if (changed && wpa_s->conf->update_config &&
|
||||
|
@ -775,6 +780,10 @@ static void wpas_group_formation_completed(struct wpa_supplicant *wpa_s,
|
|||
if (persistent)
|
||||
network_id = wpas_p2p_store_persistent_group(wpa_s->parent,
|
||||
ssid, go_dev_addr);
|
||||
else {
|
||||
os_free(wpa_s->global->add_psk);
|
||||
wpa_s->global->add_psk = NULL;
|
||||
}
|
||||
if (network_id < 0 && ssid)
|
||||
network_id = ssid->id;
|
||||
if (!client) {
|
||||
|
@ -879,6 +888,44 @@ static void wpas_start_wps_enrollee(struct wpa_supplicant *wpa_s,
|
|||
}
|
||||
|
||||
|
||||
static void wpas_p2p_add_psk_list(struct wpa_supplicant *wpa_s,
|
||||
struct wpa_ssid *ssid)
|
||||
{
|
||||
struct wpa_ssid *persistent;
|
||||
struct psk_list_entry *psk;
|
||||
struct hostapd_data *hapd;
|
||||
|
||||
if (!wpa_s->ap_iface)
|
||||
return;
|
||||
|
||||
persistent = wpas_p2p_get_persistent(wpa_s->parent, NULL, ssid->ssid,
|
||||
ssid->ssid_len);
|
||||
if (persistent == NULL)
|
||||
return;
|
||||
|
||||
hapd = wpa_s->ap_iface->bss[0];
|
||||
|
||||
dl_list_for_each(psk, &persistent->psk_list, struct psk_list_entry,
|
||||
list) {
|
||||
struct hostapd_wpa_psk *hpsk;
|
||||
|
||||
wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Add persistent group PSK entry for "
|
||||
MACSTR " psk=%d",
|
||||
MAC2STR(psk->addr), psk->p2p);
|
||||
hpsk = os_zalloc(sizeof(*hpsk));
|
||||
if (hpsk == NULL)
|
||||
break;
|
||||
os_memcpy(hpsk->psk, psk->psk, PMK_LEN);
|
||||
if (psk->p2p)
|
||||
os_memcpy(hpsk->p2p_dev_addr, psk->addr, ETH_ALEN);
|
||||
else
|
||||
os_memcpy(hpsk->addr, psk->addr, ETH_ALEN);
|
||||
hpsk->next = hapd->conf->ssid.wpa_psk;
|
||||
hapd->conf->ssid.wpa_psk = hpsk;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void p2p_go_configured(void *ctx, void *data)
|
||||
{
|
||||
struct wpa_supplicant *wpa_s = ctx;
|
||||
|
@ -918,10 +965,12 @@ static void p2p_go_configured(void *ctx, void *data)
|
|||
" [PERSISTENT]" : "");
|
||||
}
|
||||
|
||||
if (params->persistent_group)
|
||||
if (params->persistent_group) {
|
||||
network_id = wpas_p2p_store_persistent_group(
|
||||
wpa_s->parent, ssid,
|
||||
wpa_s->global->p2p_dev_addr);
|
||||
wpas_p2p_add_psk_list(wpa_s, ssid);
|
||||
}
|
||||
if (network_id < 0)
|
||||
network_id = ssid->id;
|
||||
wpas_notify_p2p_group_started(wpa_s, ssid, network_id, 0);
|
||||
|
@ -3970,6 +4019,9 @@ int wpas_p2p_connect(struct wpa_supplicant *wpa_s, const u8 *peer_addr,
|
|||
return -1;
|
||||
}
|
||||
|
||||
os_free(wpa_s->global->add_psk);
|
||||
wpa_s->global->add_psk = NULL;
|
||||
|
||||
if (go_intent < 0)
|
||||
go_intent = wpa_s->conf->p2p_go_intent;
|
||||
|
||||
|
@ -4424,6 +4476,9 @@ int wpas_p2p_group_add(struct wpa_supplicant *wpa_s, int persistent_group,
|
|||
if (wpa_s->global->p2p_disabled || wpa_s->global->p2p == NULL)
|
||||
return -1;
|
||||
|
||||
os_free(wpa_s->global->add_psk);
|
||||
wpa_s->global->add_psk = NULL;
|
||||
|
||||
/* Make sure we are not running find during connection establishment */
|
||||
wpa_printf(MSG_DEBUG, "P2P: Stop any on-going P2P FIND");
|
||||
wpas_p2p_stop_find_oper(wpa_s);
|
||||
|
@ -4515,6 +4570,9 @@ int wpas_p2p_group_add_persistent(struct wpa_supplicant *wpa_s,
|
|||
return 0;
|
||||
}
|
||||
|
||||
os_free(wpa_s->global->add_psk);
|
||||
wpa_s->global->add_psk = NULL;
|
||||
|
||||
/* Make sure we are not running find during connection establishment */
|
||||
wpas_p2p_stop_find_oper(wpa_s);
|
||||
|
||||
|
@ -5843,6 +5901,11 @@ struct wpa_ssid * wpas_p2p_get_persistent(struct wpa_supplicant *wpa_s,
|
|||
(ssid_len != s->ssid_len ||
|
||||
os_memcmp(ssid, s->ssid, ssid_len) != 0))
|
||||
continue;
|
||||
if (addr == NULL) {
|
||||
if (s->mode == WPAS_MODE_P2P_GO)
|
||||
return s;
|
||||
continue;
|
||||
}
|
||||
if (os_memcmp(s->bssid, addr, ETH_ALEN) == 0)
|
||||
return s; /* peer is GO in the persistent group */
|
||||
if (s->mode != WPAS_MODE_P2P_GO || s->p2p_client_list == NULL)
|
||||
|
@ -5966,3 +6029,90 @@ void wpas_p2p_continue_after_scan(struct wpa_supplicant *wpa_s)
|
|||
wpas_p2p_search_delay(wpa_s));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void wpas_p2p_new_psk_cb(struct wpa_supplicant *wpa_s, const u8 *mac_addr,
|
||||
const u8 *p2p_dev_addr,
|
||||
const u8 *psk, size_t psk_len)
|
||||
{
|
||||
struct wpa_ssid *ssid = wpa_s->current_ssid;
|
||||
struct wpa_ssid *persistent;
|
||||
struct psk_list_entry *p;
|
||||
|
||||
if (psk_len != sizeof(p->psk))
|
||||
return;
|
||||
|
||||
if (p2p_dev_addr) {
|
||||
wpa_dbg(wpa_s, MSG_DEBUG, "P2P: New PSK for addr=" MACSTR
|
||||
" p2p_dev_addr=" MACSTR,
|
||||
MAC2STR(mac_addr), MAC2STR(p2p_dev_addr));
|
||||
if (is_zero_ether_addr(p2p_dev_addr))
|
||||
p2p_dev_addr = NULL;
|
||||
} else {
|
||||
wpa_dbg(wpa_s, MSG_DEBUG, "P2P: New PSK for addr=" MACSTR,
|
||||
MAC2STR(mac_addr));
|
||||
}
|
||||
|
||||
if (ssid->mode == WPAS_MODE_P2P_GROUP_FORMATION) {
|
||||
wpa_dbg(wpa_s, MSG_DEBUG, "P2P: new_psk_cb during group formation");
|
||||
/* To be added to persistent group once created */
|
||||
if (wpa_s->global->add_psk == NULL) {
|
||||
wpa_s->global->add_psk = os_zalloc(sizeof(*p));
|
||||
if (wpa_s->global->add_psk == NULL)
|
||||
return;
|
||||
}
|
||||
p = wpa_s->global->add_psk;
|
||||
if (p2p_dev_addr) {
|
||||
p->p2p = 1;
|
||||
os_memcpy(p->addr, p2p_dev_addr, ETH_ALEN);
|
||||
} else {
|
||||
p->p2p = 0;
|
||||
os_memcpy(p->addr, mac_addr, ETH_ALEN);
|
||||
}
|
||||
os_memcpy(p->psk, psk, psk_len);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ssid->mode != WPAS_MODE_P2P_GO || !ssid->p2p_persistent_group) {
|
||||
wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Ignore new_psk_cb on not-persistent GO");
|
||||
return;
|
||||
}
|
||||
|
||||
persistent = wpas_p2p_get_persistent(wpa_s->parent, NULL, ssid->ssid,
|
||||
ssid->ssid_len);
|
||||
if (!persistent) {
|
||||
wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Could not find persistent group information to store the new PSK");
|
||||
return;
|
||||
}
|
||||
|
||||
p = os_zalloc(sizeof(*p));
|
||||
if (p == NULL)
|
||||
return;
|
||||
if (p2p_dev_addr) {
|
||||
p->p2p = 1;
|
||||
os_memcpy(p->addr, p2p_dev_addr, ETH_ALEN);
|
||||
} else {
|
||||
p->p2p = 0;
|
||||
os_memcpy(p->addr, mac_addr, ETH_ALEN);
|
||||
}
|
||||
os_memcpy(p->psk, psk, psk_len);
|
||||
|
||||
if (dl_list_len(&persistent->psk_list) > P2P_MAX_STORED_CLIENTS) {
|
||||
struct psk_list_entry *last;
|
||||
last = dl_list_last(&persistent->psk_list,
|
||||
struct psk_list_entry, list);
|
||||
wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Remove oldest PSK entry for "
|
||||
MACSTR " (p2p=%u) to make room for a new one",
|
||||
MAC2STR(last->addr), last->p2p);
|
||||
dl_list_del(&last->list);
|
||||
os_free(last);
|
||||
}
|
||||
|
||||
dl_list_add(&persistent->psk_list, &p->list);
|
||||
|
||||
#ifndef CONFIG_NO_CONFIG_WRITE
|
||||
if (wpa_s->parent->conf->update_config &&
|
||||
wpa_config_write(wpa_s->parent->confname, wpa_s->parent->conf))
|
||||
wpa_printf(MSG_DEBUG, "P2P: Failed to update configuration");
|
||||
#endif /* CONFIG_NO_CONFIG_WRITE */
|
||||
}
|
||||
|
|
|
@ -152,6 +152,9 @@ int wpas_p2p_scan_no_go_seen(struct wpa_supplicant *wpa_s);
|
|||
int wpas_p2p_get_ht40_mode(struct wpa_supplicant *wpa_s,
|
||||
struct hostapd_hw_modes *mode, u8 channel);
|
||||
unsigned int wpas_p2p_search_delay(struct wpa_supplicant *wpa_s);
|
||||
void wpas_p2p_new_psk_cb(struct wpa_supplicant *wpa_s, const u8 *mac_addr,
|
||||
const u8 *p2p_dev_addr,
|
||||
const u8 *psk, size_t psk_len);
|
||||
|
||||
#ifdef CONFIG_P2P
|
||||
void wpas_p2p_continue_after_scan(struct wpa_supplicant *wpa_s);
|
||||
|
|
|
@ -3480,6 +3480,7 @@ void wpa_supplicant_deinit(struct wpa_global *global)
|
|||
os_free(global->params.override_ctrl_interface);
|
||||
|
||||
os_free(global->p2p_disallow_freq);
|
||||
os_free(global->add_psk);
|
||||
|
||||
os_free(global);
|
||||
wpa_debug_close_syslog();
|
||||
|
|
|
@ -272,6 +272,8 @@ struct wpa_global {
|
|||
#define MAX_WFD_SUBELEMS 10
|
||||
struct wpabuf *wfd_subelem[MAX_WFD_SUBELEMS];
|
||||
#endif /* CONFIG_WIFI_DISPLAY */
|
||||
|
||||
struct psk_list_entry *add_psk; /* From group formation */
|
||||
};
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue