Skip to content

Commit

Permalink
af_unix: Link struct unix_edge when queuing skb.
Browse files Browse the repository at this point in the history
Just before queuing skb with inflight fds, we call scm_stat_add(),
which is a good place to set up the preallocated struct unix_vertex
and struct unix_edge in UNIXCB(skb).fp.

Then, we call unix_add_edges() and construct the directed graph
as follows:

  1. Set the inflight socket's unix_sock to unix_edge.predecessor.
  2. Set the receiver's unix_sock to unix_edge.successor.
  3. Set the preallocated vertex to inflight socket's unix_sock.vertex.
  4. Link inflight socket's unix_vertex.entry to unix_unvisited_vertices.
  5. Link unix_edge.vertex_entry to the inflight socket's unix_vertex.edges.

Let's say we pass the fd of AF_UNIX socket A to B and the fd of B
to C.  The graph looks like this:

  +-------------------------+
  | unix_unvisited_vertices | <-------------------------.
  +-------------------------+                           |
  +                                                     |
  |     +--------------+             +--------------+   |         +--------------+
  |     |  unix_sock A | <---. .---> |  unix_sock B | <-|-. .---> |  unix_sock C |
  |     +--------------+     | |     +--------------+   | | |     +--------------+
  | .-+ |    vertex    |     | | .-+ |    vertex    |   | | |     |    vertex    |
  | |   +--------------+     | | |   +--------------+   | | |     +--------------+
  | |                        | | |                      | | |
  | |   +--------------+     | | |   +--------------+   | | |
  | '-> |  unix_vertex |     | | '-> |  unix_vertex |   | | |
  |     +--------------+     | |     +--------------+   | | |
  `---> |    entry     | +---------> |    entry     | +-' | |
        |--------------|     | |     |--------------|     | |
        |    edges     | <-. | |     |    edges     | <-. | |
        +--------------+   | | |     +--------------+   | | |
                           | | |                        | | |
    .----------------------' | | .----------------------' | |
    |                        | | |                        | |
    |   +--------------+     | | |   +--------------+     | |
    |   |   unix_edge  |     | | |   |   unix_edge  |     | |
    |   +--------------+     | | |   +--------------+     | |
    `-> | vertex_entry |     | | `-> | vertex_entry |     | |
        |--------------|     | |     |--------------|     | |
        |  predecessor | +---' |     |  predecessor | +---' |
        |--------------|       |     |--------------|       |
        |   successor  | +-----'     |   successor  | +-----'
        +--------------+             +--------------+

Henceforth, we denote such a graph as A -> B (-> C).

Now, we can express all inflight fd graphs that do not contain
embryo sockets.  We will support the particular case later.

Signed-off-by: Kuniyuki Iwashima <kuniyu@amazon.com>
Acked-by: Paolo Abeni <pabeni@redhat.com>
Link: https://lore.kernel.org/r/20240325202425.60930-4-kuniyu@amazon.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
  • Loading branch information
Kuniyuki Iwashima authored and Jakub Kicinski committed Mar 29, 2024
1 parent 29b64e3 commit 42f298c
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 3 deletions.
2 changes: 2 additions & 0 deletions include/net/af_unix.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ extern unsigned int unix_tot_inflight;

void unix_inflight(struct user_struct *user, struct file *fp);
void unix_notinflight(struct user_struct *user, struct file *fp);
void unix_add_edges(struct scm_fp_list *fpl, struct unix_sock *receiver);
void unix_del_edges(struct scm_fp_list *fpl);
int unix_prepare_fpl(struct scm_fp_list *fpl);
void unix_destroy_fpl(struct scm_fp_list *fpl);
void unix_gc(void);
Expand Down
1 change: 1 addition & 0 deletions include/net/scm.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ struct scm_fp_list {
short count_unix;
short max;
#ifdef CONFIG_UNIX
bool inflight;
struct list_head vertices;
struct unix_edge *edges;
#endif
Expand Down
2 changes: 2 additions & 0 deletions net/core/scm.c
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ static int scm_fp_copy(struct cmsghdr *cmsg, struct scm_fp_list **fplp)
fpl->max = SCM_MAX_FD;
fpl->user = NULL;
#if IS_ENABLED(CONFIG_UNIX)
fpl->inflight = false;
fpl->edges = NULL;
INIT_LIST_HEAD(&fpl->vertices);
#endif
Expand Down Expand Up @@ -384,6 +385,7 @@ struct scm_fp_list *scm_fp_dup(struct scm_fp_list *fpl)
new_fpl->max = new_fpl->count;
new_fpl->user = get_uid(fpl->user);
#if IS_ENABLED(CONFIG_UNIX)
new_fpl->inflight = false;
new_fpl->edges = NULL;
INIT_LIST_HEAD(&new_fpl->vertices);
#endif
Expand Down
8 changes: 6 additions & 2 deletions net/unix/af_unix.c
Original file line number Diff line number Diff line change
Expand Up @@ -1943,17 +1943,21 @@ static void scm_stat_add(struct sock *sk, struct sk_buff *skb)
struct scm_fp_list *fp = UNIXCB(skb).fp;
struct unix_sock *u = unix_sk(sk);

if (unlikely(fp && fp->count))
if (unlikely(fp && fp->count)) {
atomic_add(fp->count, &u->scm_stat.nr_fds);
unix_add_edges(fp, u);
}
}

static void scm_stat_del(struct sock *sk, struct sk_buff *skb)
{
struct scm_fp_list *fp = UNIXCB(skb).fp;
struct unix_sock *u = unix_sk(sk);

if (unlikely(fp && fp->count))
if (unlikely(fp && fp->count)) {
atomic_sub(fp->count, &u->scm_stat.nr_fds);
unix_del_edges(fp);
}
}

/*
Expand Down
90 changes: 89 additions & 1 deletion net/unix/garbage.c
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,38 @@ struct unix_sock *unix_get_socket(struct file *filp)
return NULL;
}

static LIST_HEAD(unix_unvisited_vertices);

static void unix_add_edge(struct scm_fp_list *fpl, struct unix_edge *edge)
{
struct unix_vertex *vertex = edge->predecessor->vertex;

if (!vertex) {
vertex = list_first_entry(&fpl->vertices, typeof(*vertex), entry);
vertex->out_degree = 0;
INIT_LIST_HEAD(&vertex->edges);

list_move_tail(&vertex->entry, &unix_unvisited_vertices);
edge->predecessor->vertex = vertex;
}

vertex->out_degree++;
list_add_tail(&edge->vertex_entry, &vertex->edges);
}

static void unix_del_edge(struct scm_fp_list *fpl, struct unix_edge *edge)
{
struct unix_vertex *vertex = edge->predecessor->vertex;

list_del(&edge->vertex_entry);
vertex->out_degree--;

if (!vertex->out_degree) {
edge->predecessor->vertex = NULL;
list_move_tail(&vertex->entry, &fpl->vertices);
}
}

static void unix_free_vertices(struct scm_fp_list *fpl)
{
struct unix_vertex *vertex, *next_vertex;
Expand All @@ -111,6 +143,60 @@ static void unix_free_vertices(struct scm_fp_list *fpl)
}
}

DEFINE_SPINLOCK(unix_gc_lock);

void unix_add_edges(struct scm_fp_list *fpl, struct unix_sock *receiver)
{
int i = 0, j = 0;

spin_lock(&unix_gc_lock);

if (!fpl->count_unix)
goto out;

do {
struct unix_sock *inflight = unix_get_socket(fpl->fp[j++]);
struct unix_edge *edge;

if (!inflight)
continue;

edge = fpl->edges + i++;
edge->predecessor = inflight;
edge->successor = receiver;

unix_add_edge(fpl, edge);
} while (i < fpl->count_unix);

out:
spin_unlock(&unix_gc_lock);

fpl->inflight = true;

unix_free_vertices(fpl);
}

void unix_del_edges(struct scm_fp_list *fpl)
{
int i = 0;

spin_lock(&unix_gc_lock);

if (!fpl->count_unix)
goto out;

do {
struct unix_edge *edge = fpl->edges + i++;

unix_del_edge(fpl, edge);
} while (i < fpl->count_unix);

out:
spin_unlock(&unix_gc_lock);

fpl->inflight = false;
}

int unix_prepare_fpl(struct scm_fp_list *fpl)
{
struct unix_vertex *vertex;
Expand Down Expand Up @@ -141,11 +227,13 @@ int unix_prepare_fpl(struct scm_fp_list *fpl)

void unix_destroy_fpl(struct scm_fp_list *fpl)
{
if (fpl->inflight)
unix_del_edges(fpl);

kvfree(fpl->edges);
unix_free_vertices(fpl);
}

DEFINE_SPINLOCK(unix_gc_lock);
unsigned int unix_tot_inflight;
static LIST_HEAD(gc_candidates);
static LIST_HEAD(gc_inflight_list);
Expand Down

0 comments on commit 42f298c

Please sign in to comment.