+static struct device_source_table*
+find_source_table(const struct device_route *route)
+{
+ struct device_source_table key = {
+ .v6 = (route->flags & DEVADDR_FAMILY) == DEVADDR_INET6,
+ .addr = route->source,
+ .mask = route->sourcemask
+ };
+ struct device_source_table *c;
+ list_for_each_entry(c, &source_tables, head)
+ if (!memcmp(&c->v6, &key.v6, sizeof(key) -
+ offsetof(struct device_source_table, v6)))
+ return c;
+ return NULL;
+}
+
+static uint32_t
+get_source_table(const struct device_route *route)
+{
+ if (route->table || route->sourcemask == 0)
+ return route->table;
+
+ struct device_source_table *tbl = find_source_table(route);
+
+ if (!tbl) {
+ tbl = calloc(1, sizeof(*tbl));
+ tbl->addr = route->source;
+ tbl->mask = route->sourcemask;
+ tbl->v6 = (route->flags & DEVADDR_FAMILY) == DEVADDR_INET6;
+ tbl->table = IPRULE_PRIORITY_SOURCE | (((~tbl->mask) & 0x7f) << 20);
+
+ struct list_head *before = NULL;
+ struct device_source_table *c;
+ list_for_each_entry(c, &source_tables, head) {
+ if (c->table > tbl->table) {
+ before = &c->head;
+ break;
+ } else if (c->table == tbl->table) {
+ ++tbl->table;
+ }
+ }
+
+ if (!before)
+ before = &source_tables;
+
+ list_add_tail(&tbl->head, before);
+ set_ip_source_policy(true, tbl->v6, tbl->table, &tbl->addr,
+ tbl->mask, tbl->table, NULL, NULL);
+ }
+
+ ++tbl->refcount;
+ return tbl->table;
+}
+
+static void
+put_source_table(const struct device_route *route)
+{
+ struct device_source_table *tbl = find_source_table(route);
+ if (tbl && tbl->table == route->table && --tbl->refcount == 0) {
+ set_ip_source_policy(false, tbl->v6, tbl->table, &tbl->addr,
+ tbl->mask, tbl->table, NULL, NULL);
+ list_del(&tbl->head);
+ free(tbl);
+ }
+}
+