Move TIME_WAIT related functions and timer handling from files
other than repo copied tcp_subr.c into tcp_timewait.c#1.284: tcp_input.c#1.350 tcp_timewait() -> tcp_twcheck() tcp_timer.c#1.92 tcp_timer_2msl_reset() -> tcp_tw_2msl_reset() tcp_timer.c#1.92 tcp_timer_2msl_stop() -> tcp_tw_2msl_stop() tcp_timer.c#1.92 tcp_timer_2msl_tw() -> tcp_tw_2msl_scan() This is a mechanical move with appropriate renames and making them static if used only locally. The tcp_tw_2msl_scan() cleanup function is still run from the tcp_slowtimo() in tcp_timer.c.
This commit is contained in:
parent
daddc0011f
commit
2104448fe7
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=169608
@ -160,8 +160,6 @@ static void tcp_pulloutofband(struct socket *,
|
||||
struct tcphdr *, struct mbuf *, int);
|
||||
static void tcp_xmit_timer(struct tcpcb *, int);
|
||||
static void tcp_newreno_partial_ack(struct tcpcb *, struct tcphdr *);
|
||||
static int tcp_timewait(struct inpcb *, struct tcpopt *,
|
||||
struct tcphdr *, struct mbuf *, int);
|
||||
|
||||
/* Neighbor Discovery, Neighbor Unreachability Detection Upper layer hint. */
|
||||
#ifdef INET6
|
||||
@ -541,8 +539,8 @@ tcp_input(struct mbuf *m, int off0)
|
||||
if (inp->inp_vflag & INP_TIMEWAIT) {
|
||||
if (thflags & TH_SYN)
|
||||
tcp_dooptions(&to, optp, optlen, TO_SYN);
|
||||
/* NB: tcp_timewait unlocks the INP and frees the mbuf. */
|
||||
if (tcp_timewait(inp, &to, th, m, tlen))
|
||||
/* NB: tcp_twcheck unlocks the INP and frees the mbuf. */
|
||||
if (tcp_twcheck(inp, &to, th, m, tlen))
|
||||
goto findpcb;
|
||||
INP_INFO_WUNLOCK(&tcbinfo);
|
||||
return;
|
||||
@ -2940,142 +2938,3 @@ tcp_newreno_partial_ack(struct tcpcb *tp, struct tcphdr *th)
|
||||
tp->snd_cwnd = 0;
|
||||
tp->snd_cwnd += tp->t_maxseg;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns 1 if the TIME_WAIT state was killed and we should start over,
|
||||
* looking for a pcb in the listen state. Returns 0 otherwise.
|
||||
*/
|
||||
static int
|
||||
tcp_timewait(struct inpcb *inp, struct tcpopt *to, struct tcphdr *th,
|
||||
struct mbuf *m, int tlen)
|
||||
{
|
||||
struct tcptw *tw;
|
||||
int thflags;
|
||||
tcp_seq seq;
|
||||
#ifdef INET6
|
||||
int isipv6 = (mtod(m, struct ip *)->ip_v == 6) ? 1 : 0;
|
||||
#else
|
||||
const int isipv6 = 0;
|
||||
#endif
|
||||
|
||||
/* tcbinfo lock required for tcp_twclose(), tcp_timer_2msl_reset(). */
|
||||
INP_INFO_WLOCK_ASSERT(&tcbinfo);
|
||||
INP_LOCK_ASSERT(inp);
|
||||
|
||||
/*
|
||||
* XXXRW: Time wait state for inpcb has been recycled, but inpcb is
|
||||
* still present. This is undesirable, but temporarily necessary
|
||||
* until we work out how to handle inpcb's who's timewait state has
|
||||
* been removed.
|
||||
*/
|
||||
tw = intotw(inp);
|
||||
if (tw == NULL)
|
||||
goto drop;
|
||||
|
||||
thflags = th->th_flags;
|
||||
|
||||
/*
|
||||
* NOTE: for FIN_WAIT_2 (to be added later),
|
||||
* must validate sequence number before accepting RST
|
||||
*/
|
||||
|
||||
/*
|
||||
* If the segment contains RST:
|
||||
* Drop the segment - see Stevens, vol. 2, p. 964 and
|
||||
* RFC 1337.
|
||||
*/
|
||||
if (thflags & TH_RST)
|
||||
goto drop;
|
||||
|
||||
#if 0
|
||||
/* PAWS not needed at the moment */
|
||||
/*
|
||||
* RFC 1323 PAWS: If we have a timestamp reply on this segment
|
||||
* and it's less than ts_recent, drop it.
|
||||
*/
|
||||
if ((to.to_flags & TOF_TS) != 0 && tp->ts_recent &&
|
||||
TSTMP_LT(to.to_tsval, tp->ts_recent)) {
|
||||
if ((thflags & TH_ACK) == 0)
|
||||
goto drop;
|
||||
goto ack;
|
||||
}
|
||||
/*
|
||||
* ts_recent is never updated because we never accept new segments.
|
||||
*/
|
||||
#endif
|
||||
|
||||
/*
|
||||
* If a new connection request is received
|
||||
* while in TIME_WAIT, drop the old connection
|
||||
* and start over if the sequence numbers
|
||||
* are above the previous ones.
|
||||
*/
|
||||
if ((thflags & TH_SYN) && SEQ_GT(th->th_seq, tw->rcv_nxt)) {
|
||||
tcp_twclose(tw, 0);
|
||||
return (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Drop the the segment if it does not contain an ACK.
|
||||
*/
|
||||
if ((thflags & TH_ACK) == 0)
|
||||
goto drop;
|
||||
|
||||
/*
|
||||
* Reset the 2MSL timer if this is a duplicate FIN.
|
||||
*/
|
||||
if (thflags & TH_FIN) {
|
||||
seq = th->th_seq + tlen + (thflags & TH_SYN ? 1 : 0);
|
||||
if (seq + 1 == tw->rcv_nxt)
|
||||
tcp_timer_2msl_reset(tw, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Acknowledge the segment if it has data or is not a duplicate ACK.
|
||||
*/
|
||||
if (thflags != TH_ACK || tlen != 0 ||
|
||||
th->th_seq != tw->rcv_nxt || th->th_ack != tw->snd_nxt)
|
||||
tcp_twrespond(tw, TH_ACK);
|
||||
goto drop;
|
||||
|
||||
/*
|
||||
* Generate a RST, dropping incoming segment.
|
||||
* Make ACK acceptable to originator of segment.
|
||||
* Don't bother to respond if destination was broadcast/multicast.
|
||||
*/
|
||||
if (m->m_flags & (M_BCAST|M_MCAST))
|
||||
goto drop;
|
||||
if (isipv6) {
|
||||
struct ip6_hdr *ip6;
|
||||
|
||||
/* IPv6 anycast check is done at tcp6_input() */
|
||||
ip6 = mtod(m, struct ip6_hdr *);
|
||||
if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst) ||
|
||||
IN6_IS_ADDR_MULTICAST(&ip6->ip6_src))
|
||||
goto drop;
|
||||
} else {
|
||||
struct ip *ip;
|
||||
|
||||
ip = mtod(m, struct ip *);
|
||||
if (IN_MULTICAST(ntohl(ip->ip_dst.s_addr)) ||
|
||||
IN_MULTICAST(ntohl(ip->ip_src.s_addr)) ||
|
||||
ip->ip_src.s_addr == htonl(INADDR_BROADCAST) ||
|
||||
in_broadcast(ip->ip_dst, m->m_pkthdr.rcvif))
|
||||
goto drop;
|
||||
}
|
||||
if (thflags & TH_ACK) {
|
||||
tcp_respond(NULL,
|
||||
mtod(m, void *), th, m, 0, th->th_ack, TH_RST);
|
||||
} else {
|
||||
seq = th->th_seq + (thflags & TH_SYN ? 1 : 0);
|
||||
tcp_respond(NULL,
|
||||
mtod(m, void *), th, m, seq, 0, TH_RST|TH_ACK);
|
||||
}
|
||||
INP_UNLOCK(inp);
|
||||
return (0);
|
||||
|
||||
drop:
|
||||
INP_UNLOCK(inp);
|
||||
m_freem(m);
|
||||
return (0);
|
||||
}
|
||||
|
@ -298,7 +298,6 @@ tcp_init(void)
|
||||
tcpcb_zone = uma_zcreate("tcpcb", sizeof(struct tcpcb_mem),
|
||||
NULL, NULL, NULL, NULL, UMA_ALIGN_PTR, UMA_ZONE_NOFREE);
|
||||
uma_zone_set_max(tcpcb_zone, maxsockets);
|
||||
tcp_timer_init();
|
||||
tcp_tw_init();
|
||||
syncache_init();
|
||||
tcp_hc_init();
|
||||
|
@ -133,7 +133,7 @@ tcp_slowtimo(void)
|
||||
|
||||
tcp_maxidle = tcp_keepcnt * tcp_keepintvl;
|
||||
INP_INFO_WLOCK(&tcbinfo);
|
||||
(void) tcp_timer_2msl_tw(0);
|
||||
(void) tcp_tw_2msl_scan(0);
|
||||
INP_INFO_WUNLOCK(&tcbinfo);
|
||||
}
|
||||
|
||||
@ -468,59 +468,6 @@ tcp_timer_2msl(struct tcpcb *tp, struct inpcb *inp)
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* The timed wait queue contains references to each of the TCP sessions
|
||||
* currently in the TIME_WAIT state. The queue pointers, including the
|
||||
* queue pointers in each tcptw structure, are protected using the global
|
||||
* tcbinfo lock, which must be held over queue iteration and modification.
|
||||
*/
|
||||
static TAILQ_HEAD(, tcptw) twq_2msl;
|
||||
|
||||
void
|
||||
tcp_timer_init(void)
|
||||
{
|
||||
|
||||
TAILQ_INIT(&twq_2msl);
|
||||
}
|
||||
|
||||
void
|
||||
tcp_timer_2msl_reset(struct tcptw *tw, int rearm)
|
||||
{
|
||||
|
||||
INP_INFO_WLOCK_ASSERT(&tcbinfo);
|
||||
INP_LOCK_ASSERT(tw->tw_inpcb);
|
||||
if (rearm)
|
||||
TAILQ_REMOVE(&twq_2msl, tw, tw_2msl);
|
||||
tw->tw_time = ticks + 2 * tcp_msl;
|
||||
TAILQ_INSERT_TAIL(&twq_2msl, tw, tw_2msl);
|
||||
}
|
||||
|
||||
void
|
||||
tcp_timer_2msl_stop(struct tcptw *tw)
|
||||
{
|
||||
|
||||
INP_INFO_WLOCK_ASSERT(&tcbinfo);
|
||||
TAILQ_REMOVE(&twq_2msl, tw, tw_2msl);
|
||||
}
|
||||
|
||||
struct tcptw *
|
||||
tcp_timer_2msl_tw(int reuse)
|
||||
{
|
||||
struct tcptw *tw;
|
||||
|
||||
INP_INFO_WLOCK_ASSERT(&tcbinfo);
|
||||
for (;;) {
|
||||
tw = TAILQ_FIRST(&twq_2msl);
|
||||
if (tw == NULL || (!reuse && tw->tw_time > ticks))
|
||||
break;
|
||||
INP_LOCK(tw->tw_inpcb);
|
||||
tcp_twclose(tw, reuse);
|
||||
if (reuse)
|
||||
return (tw);
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
static int
|
||||
tcp_timer_keep(struct tcpcb *tp, struct inpcb *inp)
|
||||
{
|
||||
|
@ -175,10 +175,10 @@ extern int tcp_finwait2_timeout;
|
||||
extern int tcp_fast_finwait2_recycle;
|
||||
|
||||
void tcp_timer_init(void);
|
||||
struct tcptw *
|
||||
tcp_timer_2msl_tw(int _reuse); /* XXX temporary */
|
||||
void tcp_timer_2msl_reset(struct tcptw *_tw, int rearm);
|
||||
void tcp_timer_2msl_stop(struct tcptw *_tw);
|
||||
struct tcptw *
|
||||
tcp_tw_2msl_scan(int _reuse); /* XXX temporary */
|
||||
|
||||
#endif /* _KERNEL */
|
||||
|
||||
|
@ -93,6 +93,17 @@
|
||||
static uma_zone_t tcptw_zone;
|
||||
static int maxtcptw;
|
||||
|
||||
/*
|
||||
* The timed wait queue contains references to each of the TCP sessions
|
||||
* currently in the TIME_WAIT state. The queue pointers, including the
|
||||
* queue pointers in each tcptw structure, are protected using the global
|
||||
* tcbinfo lock, which must be held over queue iteration and modification.
|
||||
*/
|
||||
static TAILQ_HEAD(, tcptw) twq_2msl;
|
||||
|
||||
static void tcp_tw_2msl_reset(struct tcptw *, int);
|
||||
static void tcp_tw_2msl_stop(struct tcptw *);
|
||||
|
||||
static int
|
||||
tcptw_auto_size(void)
|
||||
{
|
||||
@ -156,6 +167,7 @@ tcp_tw_init(void)
|
||||
uma_zone_set_max(tcptw_zone, tcptw_auto_size());
|
||||
else
|
||||
uma_zone_set_max(tcptw_zone, maxtcptw);
|
||||
TAILQ_INIT(&twq_2msl);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -171,7 +183,7 @@ tcp_twstart(struct tcpcb *tp)
|
||||
int acknow;
|
||||
struct socket *so;
|
||||
|
||||
INP_INFO_WLOCK_ASSERT(&tcbinfo); /* tcp_timer_2msl_reset(). */
|
||||
INP_INFO_WLOCK_ASSERT(&tcbinfo); /* tcp_tw_2msl_reset(). */
|
||||
INP_LOCK_ASSERT(inp);
|
||||
|
||||
if (nolocaltimewait && in_localip(inp->inp_faddr)) {
|
||||
@ -183,7 +195,7 @@ tcp_twstart(struct tcpcb *tp)
|
||||
|
||||
tw = uma_zalloc(tcptw_zone, M_NOWAIT);
|
||||
if (tw == NULL) {
|
||||
tw = tcp_timer_2msl_tw(1);
|
||||
tw = tcp_tw_2msl_scan(1);
|
||||
if (tw == NULL) {
|
||||
tp = tcp_close(tp);
|
||||
if (tp != NULL)
|
||||
@ -243,7 +255,7 @@ tcp_twstart(struct tcpcb *tp)
|
||||
tcp_twrespond(tw, TH_ACK);
|
||||
inp->inp_ppcb = tw;
|
||||
inp->inp_vflag |= INP_TIMEWAIT;
|
||||
tcp_timer_2msl_reset(tw, 0);
|
||||
tcp_tw_2msl_reset(tw, 0);
|
||||
|
||||
/*
|
||||
* If the inpcb owns the sole reference to the socket, then we can
|
||||
@ -295,6 +307,145 @@ tcp_twrecycleable(struct tcptw *tw)
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Returns 1 if the TIME_WAIT state was killed and we should start over,
|
||||
* looking for a pcb in the listen state. Returns 0 otherwise.
|
||||
*/
|
||||
int
|
||||
tcp_twcheck(struct inpcb *inp, struct tcpopt *to, struct tcphdr *th,
|
||||
struct mbuf *m, int tlen)
|
||||
{
|
||||
struct tcptw *tw;
|
||||
int thflags;
|
||||
tcp_seq seq;
|
||||
#ifdef INET6
|
||||
int isipv6 = (mtod(m, struct ip *)->ip_v == 6) ? 1 : 0;
|
||||
#else
|
||||
const int isipv6 = 0;
|
||||
#endif
|
||||
|
||||
/* tcbinfo lock required for tcp_twclose(), tcp_tw_2msl_reset(). */
|
||||
INP_INFO_WLOCK_ASSERT(&tcbinfo);
|
||||
INP_LOCK_ASSERT(inp);
|
||||
|
||||
/*
|
||||
* XXXRW: Time wait state for inpcb has been recycled, but inpcb is
|
||||
* still present. This is undesirable, but temporarily necessary
|
||||
* until we work out how to handle inpcb's who's timewait state has
|
||||
* been removed.
|
||||
*/
|
||||
tw = intotw(inp);
|
||||
if (tw == NULL)
|
||||
goto drop;
|
||||
|
||||
thflags = th->th_flags;
|
||||
|
||||
/*
|
||||
* NOTE: for FIN_WAIT_2 (to be added later),
|
||||
* must validate sequence number before accepting RST
|
||||
*/
|
||||
|
||||
/*
|
||||
* If the segment contains RST:
|
||||
* Drop the segment - see Stevens, vol. 2, p. 964 and
|
||||
* RFC 1337.
|
||||
*/
|
||||
if (thflags & TH_RST)
|
||||
goto drop;
|
||||
|
||||
#if 0
|
||||
/* PAWS not needed at the moment */
|
||||
/*
|
||||
* RFC 1323 PAWS: If we have a timestamp reply on this segment
|
||||
* and it's less than ts_recent, drop it.
|
||||
*/
|
||||
if ((to.to_flags & TOF_TS) != 0 && tp->ts_recent &&
|
||||
TSTMP_LT(to.to_tsval, tp->ts_recent)) {
|
||||
if ((thflags & TH_ACK) == 0)
|
||||
goto drop;
|
||||
goto ack;
|
||||
}
|
||||
/*
|
||||
* ts_recent is never updated because we never accept new segments.
|
||||
*/
|
||||
#endif
|
||||
|
||||
/*
|
||||
* If a new connection request is received
|
||||
* while in TIME_WAIT, drop the old connection
|
||||
* and start over if the sequence numbers
|
||||
* are above the previous ones.
|
||||
*/
|
||||
if ((thflags & TH_SYN) && SEQ_GT(th->th_seq, tw->rcv_nxt)) {
|
||||
tcp_twclose(tw, 0);
|
||||
return (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Drop the the segment if it does not contain an ACK.
|
||||
*/
|
||||
if ((thflags & TH_ACK) == 0)
|
||||
goto drop;
|
||||
|
||||
/*
|
||||
* Reset the 2MSL timer if this is a duplicate FIN.
|
||||
*/
|
||||
if (thflags & TH_FIN) {
|
||||
seq = th->th_seq + tlen + (thflags & TH_SYN ? 1 : 0);
|
||||
if (seq + 1 == tw->rcv_nxt)
|
||||
tcp_tw_2msl_reset(tw, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Acknowledge the segment if it has data or is not a duplicate ACK.
|
||||
*/
|
||||
if (thflags != TH_ACK || tlen != 0 ||
|
||||
th->th_seq != tw->rcv_nxt || th->th_ack != tw->snd_nxt)
|
||||
tcp_twrespond(tw, TH_ACK);
|
||||
goto drop;
|
||||
|
||||
/*
|
||||
* Generate a RST, dropping incoming segment.
|
||||
* Make ACK acceptable to originator of segment.
|
||||
* Don't bother to respond if destination was broadcast/multicast.
|
||||
*/
|
||||
if (m->m_flags & (M_BCAST|M_MCAST))
|
||||
goto drop;
|
||||
if (isipv6) {
|
||||
struct ip6_hdr *ip6;
|
||||
|
||||
/* IPv6 anycast check is done at tcp6_input() */
|
||||
ip6 = mtod(m, struct ip6_hdr *);
|
||||
if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst) ||
|
||||
IN6_IS_ADDR_MULTICAST(&ip6->ip6_src))
|
||||
goto drop;
|
||||
} else {
|
||||
struct ip *ip;
|
||||
|
||||
ip = mtod(m, struct ip *);
|
||||
if (IN_MULTICAST(ntohl(ip->ip_dst.s_addr)) ||
|
||||
IN_MULTICAST(ntohl(ip->ip_src.s_addr)) ||
|
||||
ip->ip_src.s_addr == htonl(INADDR_BROADCAST) ||
|
||||
in_broadcast(ip->ip_dst, m->m_pkthdr.rcvif))
|
||||
goto drop;
|
||||
}
|
||||
if (thflags & TH_ACK) {
|
||||
tcp_respond(NULL,
|
||||
mtod(m, void *), th, m, 0, th->th_ack, TH_RST);
|
||||
} else {
|
||||
seq = th->th_seq + (thflags & TH_SYN ? 1 : 0);
|
||||
tcp_respond(NULL,
|
||||
mtod(m, void *), th, m, seq, 0, TH_RST|TH_ACK);
|
||||
}
|
||||
INP_UNLOCK(inp);
|
||||
return (0);
|
||||
|
||||
drop:
|
||||
INP_UNLOCK(inp);
|
||||
m_freem(m);
|
||||
return (0);
|
||||
}
|
||||
|
||||
void
|
||||
tcp_twclose(struct tcptw *tw, int reuse)
|
||||
{
|
||||
@ -313,11 +464,11 @@ tcp_twclose(struct tcptw *tw, int reuse)
|
||||
inp = tw->tw_inpcb;
|
||||
KASSERT((inp->inp_vflag & INP_TIMEWAIT), ("tcp_twclose: !timewait"));
|
||||
KASSERT(intotw(inp) == tw, ("tcp_twclose: inp_ppcb != tw"));
|
||||
INP_INFO_WLOCK_ASSERT(&tcbinfo); /* tcp_timer_2msl_stop(). */
|
||||
INP_INFO_WLOCK_ASSERT(&tcbinfo); /* tcp_tw_2msl_stop(). */
|
||||
INP_LOCK_ASSERT(inp);
|
||||
|
||||
tw->tw_inpcb = NULL;
|
||||
tcp_timer_2msl_stop(tw);
|
||||
tcp_tw_2msl_stop(tw);
|
||||
inp->inp_ppcb = NULL;
|
||||
in_pcbdrop(inp);
|
||||
|
||||
@ -454,3 +605,41 @@ tcp_twrespond(struct tcptw *tw, int flags)
|
||||
tcpstat.tcps_sndtotal++;
|
||||
return (error);
|
||||
}
|
||||
|
||||
static void
|
||||
tcp_tw_2msl_reset(struct tcptw *tw, int rearm)
|
||||
{
|
||||
|
||||
INP_INFO_WLOCK_ASSERT(&tcbinfo);
|
||||
INP_LOCK_ASSERT(tw->tw_inpcb);
|
||||
if (rearm)
|
||||
TAILQ_REMOVE(&twq_2msl, tw, tw_2msl);
|
||||
tw->tw_time = ticks + 2 * tcp_msl;
|
||||
TAILQ_INSERT_TAIL(&twq_2msl, tw, tw_2msl);
|
||||
}
|
||||
|
||||
static void
|
||||
tcp_tw_2msl_stop(struct tcptw *tw)
|
||||
{
|
||||
|
||||
INP_INFO_WLOCK_ASSERT(&tcbinfo);
|
||||
TAILQ_REMOVE(&twq_2msl, tw, tw_2msl);
|
||||
}
|
||||
|
||||
struct tcptw *
|
||||
tcp_tw_2msl_scan(int reuse)
|
||||
{
|
||||
struct tcptw *tw;
|
||||
|
||||
INP_INFO_WLOCK_ASSERT(&tcbinfo);
|
||||
for (;;) {
|
||||
tw = TAILQ_FIRST(&twq_2msl);
|
||||
if (tw == NULL || (!reuse && tw->tw_time > ticks))
|
||||
break;
|
||||
INP_LOCK(tw->tw_inpcb);
|
||||
tcp_twclose(tw, reuse);
|
||||
if (reuse)
|
||||
return (tw);
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
@ -538,6 +538,8 @@ void tcp_respond(struct tcpcb *, void *,
|
||||
struct tcphdr *, struct mbuf *, tcp_seq, tcp_seq, int);
|
||||
void tcp_tw_init(void);
|
||||
void tcp_tw_zone_change(void);
|
||||
int tcp_twcheck(struct inpcb *, struct tcpopt *, struct tcphdr *,
|
||||
struct mbuf *, int);
|
||||
int tcp_twrespond(struct tcptw *, int);
|
||||
void tcp_setpersist(struct tcpcb *);
|
||||
#ifdef TCP_SIGNATURE
|
||||
|
Loading…
Reference in New Issue
Block a user