aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander V. Chernikov <melifaro@FreeBSD.org>2022-08-08 19:24:26 +0000
committerAlexander V. Chernikov <melifaro@FreeBSD.org>2022-08-29 10:07:58 +0000
commita1b59379db7d879551118b921f6e9692b4bf200c (patch)
treed141c82429260917c97bc16875e6bbc3757e4b07
parent827622937efa741d5d84d72968cf1580a8c597d8 (diff)
downloadsrc-a1b59379db7d879551118b921f6e9692b4bf200c.tar.gz
src-a1b59379db7d879551118b921f6e9692b4bf200c.zip
routing: install prefix and loopback routes using new nhop-based KPI.
Construct the desired hexthops directly instead of using the "translation" layer in form of filling rt_addrinfo data. Simplify V_rt_add_addr_allfibs handling by using recently-added rib_copy_route() to propagate the routes to the non-primary address fibs. MFC after: 1 month Differential Revision: https://reviews.freebsd.org/D36166
-rw-r--r--sys/net/route/route_ctl.h8
-rw-r--r--sys/net/route/route_ifaddrs.c190
-rw-r--r--sys/netinet/in.c62
-rw-r--r--sys/netinet6/in6.c54
-rw-r--r--sys/netinet6/nd6_rtr.c70
5 files changed, 210 insertions, 174 deletions
diff --git a/sys/net/route/route_ctl.h b/sys/net/route/route_ctl.h
index 19fe5362b6ec..2d43679ad183 100644
--- a/sys/net/route/route_ctl.h
+++ b/sys/net/route/route_ctl.h
@@ -75,7 +75,13 @@ int rib_change_route(uint32_t fibnum, struct rt_addrinfo *info,
struct rib_cmd_info *rc);
int rib_action(uint32_t fibnum, int action, struct rt_addrinfo *info,
struct rib_cmd_info *rc);
-int rib_handle_ifaddr_info(uint32_t fibnum, int cmd, struct rt_addrinfo *info);
+int rib_add_kernel_px(uint32_t fibnum, struct sockaddr *dst, int plen,
+ struct route_nhop_data *rnd, int op_flags);
+int rib_del_kernel_px(uint32_t fibnum, struct sockaddr *dst, int plen,
+ rib_filter_f_t *filter_func, void *filter_arg, int op_flags);
+
+int rib_match_gw(const struct rtentry *rt, const struct nhop_object *nh,
+ void *_data);
int rib_match_gw(const struct rtentry *rt, const struct nhop_object *nh,
void *gw_sa);
diff --git a/sys/net/route/route_ifaddrs.c b/sys/net/route/route_ifaddrs.c
index a456ffa28696..d88284c2e008 100644
--- a/sys/net/route/route_ifaddrs.c
+++ b/sys/net/route/route_ifaddrs.c
@@ -64,112 +64,103 @@ VNET_DEFINE(u_int, rt_add_addr_allfibs) = 0;
SYSCTL_UINT(_net, OID_AUTO, add_addr_allfibs, CTLFLAG_RWTUN | CTLFLAG_VNET,
&VNET_NAME(rt_add_addr_allfibs), 0, "");
-/*
- * Executes routing tables change specified by @cmd and @info for the fib
- * @fibnum. Generates routing message on success.
- * Note: it assumes there is only single route (interface route) for the
- * provided prefix.
- * Returns 0 on success or errno.
- */
-static int
-rib_handle_ifaddr_one(uint32_t fibnum, int cmd, struct rt_addrinfo *info)
+static void
+report_operation(uint32_t fibnum, struct rib_cmd_info *rc)
{
- struct rib_cmd_info rc;
struct nhop_object *nh;
- int error;
- error = rib_action(fibnum, cmd, info, &rc);
- if (error == 0) {
- if (cmd == RTM_ADD)
- nh = nhop_select(rc.rc_nh_new, 0);
- else
- nh = nhop_select(rc.rc_nh_old, 0);
- rt_routemsg(cmd, rc.rc_rt, nh, fibnum);
+ if (rc->rc_cmd == RTM_DELETE)
+ nh = nhop_select(rc->rc_nh_old, 0);
+ else
+ nh = nhop_select(rc->rc_nh_new, 0);
+ rt_routemsg(rc->rc_cmd, rc->rc_rt, nh, fibnum);
+}
+
+int
+rib_add_kernel_px(uint32_t fibnum, struct sockaddr *dst, int plen,
+ struct route_nhop_data *rnd, int op_flags)
+{
+ struct rib_cmd_info rc = {};
+
+ NET_EPOCH_ASSERT();
+
+ int error = rib_add_route_px(fibnum, dst, plen, rnd, op_flags, &rc);
+ if (error != 0)
+ return (error);
+ report_operation(fibnum, &rc);
+
+ if (V_rt_add_addr_allfibs != 0) {
+ for (int i = 0; i < V_rt_numfibs; i++) {
+ if (i == fibnum)
+ continue;
+ struct rib_head *rnh = rt_tables_get_rnh(fibnum, dst->sa_family);
+ /* Don't care much about the errors in non-primary fib */
+ if (rnh != NULL) {
+ if (rib_copy_route(rc.rc_rt, rnd, rnh, &rc) == 0)
+ report_operation(i, &rc);
+ }
+ }
}
return (error);
}
-/*
- * Adds/deletes interface prefix specified by @info to the routing table.
- * If V_rt_add_addr_allfibs is set, iterates over all existing routing
- * tables, otherwise uses fib in @fibnum. Generates routing message for
- * each table.
- * Returns 0 on success or errno.
- */
int
-rib_handle_ifaddr_info(uint32_t fibnum, int cmd, struct rt_addrinfo *info)
+rib_del_kernel_px(uint32_t fibnum, struct sockaddr *dst, int plen,
+ rib_filter_f_t *filter_func, void *filter_arg, int op_flags)
{
- int error = 0, last_error = 0;
- bool didwork = false;
-
- if (V_rt_add_addr_allfibs == 0) {
- error = rib_handle_ifaddr_one(fibnum, cmd, info);
- didwork = (error == 0);
- } else {
- for (fibnum = 0; fibnum < V_rt_numfibs; fibnum++) {
- error = rib_handle_ifaddr_one(fibnum, cmd, info);
- if (error == 0)
- didwork = true;
- else
- last_error = error;
- }
- }
+ struct rib_cmd_info rc = {};
- if (cmd == RTM_DELETE) {
- if (didwork) {
- error = 0;
- } else {
- /* we only give an error if it wasn't in any table */
- error = ((info->rti_flags & RTF_HOST) ?
- EHOSTUNREACH : ENETUNREACH);
- }
- } else {
- if (last_error != 0) {
- /* return an error if any of them failed */
- error = last_error;
+ NET_EPOCH_ASSERT();
+
+ int error = rib_del_route_px(fibnum, dst, plen, filter_func, filter_arg,
+ op_flags, &rc);
+ if (error != 0)
+ return (error);
+ report_operation(fibnum, &rc);
+
+ if (V_rt_add_addr_allfibs != 0) {
+ for (int i = 0; i < V_rt_numfibs; i++) {
+ if (i == fibnum)
+ continue;
+ /* Don't care much about the errors in non-primary fib */
+ if (rib_del_route_px(fibnum, dst, plen, filter_func, filter_arg,
+ op_flags, &rc) == 0)
+ report_operation(i, &rc);
}
}
+
return (error);
}
static int
-ifa_maintain_loopback_route(int cmd, const char *otype, struct ifaddr *ifa,
- struct sockaddr *ia)
+add_loopback_route_flags(struct ifaddr *ifa, struct sockaddr *ia, int op_flags)
{
struct rib_cmd_info rc;
- struct epoch_tracker et;
int error;
- struct rt_addrinfo info;
- struct sockaddr_dl null_sdl;
- struct ifnet *ifp;
-
- ifp = ifa->ifa_ifp;
- NET_EPOCH_ENTER(et);
- bzero(&info, sizeof(info));
- if (cmd != RTM_DELETE)
- info.rti_ifp = V_loif;
- if (cmd == RTM_ADD) {
- /* explicitly specify (loopback) ifa */
- if (info.rti_ifp != NULL)
- info.rti_ifa = ifaof_ifpforaddr(ifa->ifa_addr, info.rti_ifp);
- }
- info.rti_flags = ifa->ifa_flags | RTF_HOST | RTF_STATIC | RTF_PINNED;
- info.rti_info[RTAX_DST] = ia;
- info.rti_info[RTAX_GATEWAY] = (struct sockaddr *)&null_sdl;
- link_init_sdl(ifp, (struct sockaddr *)&null_sdl, ifp->if_type);
+ NET_EPOCH_ASSERT();
- error = rib_action(ifp->if_fib, cmd, &info, &rc);
- NET_EPOCH_EXIT(et);
+ struct ifnet *ifp = ifa->ifa_ifp;
+ struct nhop_object *nh = nhop_alloc(ifp->if_fib, ia->sa_family);
+ struct route_nhop_data rnd = { .rnd_weight = RT_DEFAULT_WEIGHT };
+ if (nh == NULL)
+ return (ENOMEM);
- if (error == 0 ||
- (cmd == RTM_ADD && error == EEXIST) ||
- (cmd == RTM_DELETE && (error == ENOENT || error == ESRCH)))
+ nhop_set_direct_gw(nh, ifp);
+ nhop_set_transmit_ifp(nh, V_loif);
+ nhop_set_src(nh, ifaof_ifpforaddr(ifa->ifa_addr, ifp));
+ nhop_set_pinned(nh, true);
+ nhop_set_rtflags(nh, RTF_STATIC);
+ nhop_set_pxtype_flag(nh, NHF_HOST);
+ rnd.rnd_nhop = nhop_get_nhop(nh, &error);
+ if (error != 0)
return (error);
+ error = rib_add_route_px(ifp->if_fib, ia, -1, &rnd, op_flags, &rc);
- log(LOG_DEBUG, "%s: %s failed for interface %s: %u\n",
- __func__, otype, if_name(ifp), error);
+ if (error != 0)
+ log(LOG_DEBUG, "%s: failed to update interface %s route: %u\n",
+ __func__, if_name(ifp), error);
return (error);
}
@@ -177,22 +168,49 @@ ifa_maintain_loopback_route(int cmd, const char *otype, struct ifaddr *ifa,
int
ifa_add_loopback_route(struct ifaddr *ifa, struct sockaddr *ia)
{
+ struct epoch_tracker et;
- return (ifa_maintain_loopback_route(RTM_ADD, "insertion", ifa, ia));
+ NET_EPOCH_ENTER(et);
+ int error = add_loopback_route_flags(ifa, ia, RTM_F_CREATE | RTM_F_FORCE);
+ NET_EPOCH_EXIT(et);
+
+ return (error);
}
int
-ifa_del_loopback_route(struct ifaddr *ifa, struct sockaddr *ia)
+ifa_switch_loopback_route(struct ifaddr *ifa, struct sockaddr *ia)
{
+ struct epoch_tracker et;
+
+ NET_EPOCH_ENTER(et);
+ int error = add_loopback_route_flags(ifa, ia, RTM_F_REPLACE | RTM_F_FORCE);
+ NET_EPOCH_EXIT(et);
- return (ifa_maintain_loopback_route(RTM_DELETE, "deletion", ifa, ia));
+ return (error);
}
int
-ifa_switch_loopback_route(struct ifaddr *ifa, struct sockaddr *ia)
+ifa_del_loopback_route(struct ifaddr *ifa, struct sockaddr *ia)
{
+ struct ifnet *ifp = ifa->ifa_ifp;
+ struct sockaddr_dl link_sdl;
+ struct sockaddr *gw = (struct sockaddr *)&link_sdl;
+ struct rib_cmd_info rc;
+ struct epoch_tracker et;
+ int error;
- return (ifa_maintain_loopback_route(RTM_CHANGE, "switch", ifa, ia));
+ NET_EPOCH_ENTER(et);
+
+ link_init_sdl(ifp, gw, ifp->if_type);
+ error = rib_del_route_px_gw(ifp->if_fib, ia, -1, gw, RTM_F_FORCE, &rc);
+
+ NET_EPOCH_EXIT(et);
+
+ if (error != 0)
+ log(LOG_DEBUG, "%s: failed to delete interface %s route: %u\n",
+ __func__, if_name(ifp), error);
+
+ return (error);
}
static bool
diff --git a/sys/netinet/in.c b/sys/netinet/in.c
index 790740456160..e097698c27dd 100644
--- a/sys/netinet/in.c
+++ b/sys/netinet/in.c
@@ -821,36 +821,34 @@ in_match_ifaddr(const struct rtentry *rt, const struct nhop_object *nh, void *ar
}
static int
-in_handle_prefix_route(uint32_t fibnum, int cmd,
- struct sockaddr_in *dst, struct sockaddr_in *netmask, struct ifaddr *ifa,
- struct ifnet *ifp)
+in_handle_prefix_route(uint32_t fibnum, int cmd, struct sockaddr *dst,
+ int plen, struct ifaddr *ifa, struct ifnet *ifp)
{
+ int error = 0;
NET_EPOCH_ASSERT();
- /* Prepare gateway */
- struct sockaddr_dl_short sdl = {
- .sdl_family = AF_LINK,
- .sdl_len = sizeof(struct sockaddr_dl_short),
- .sdl_type = ifa->ifa_ifp->if_type,
- .sdl_index = ifa->ifa_ifp->if_index,
- };
-
- struct rt_addrinfo info = {
- .rti_ifa = ifa,
- .rti_ifp = ifp,
- .rti_flags = RTF_PINNED | ((netmask != NULL) ? 0 : RTF_HOST),
- .rti_info = {
- [RTAX_DST] = (struct sockaddr *)dst,
- [RTAX_NETMASK] = (struct sockaddr *)netmask,
- [RTAX_GATEWAY] = (struct sockaddr *)&sdl,
- },
- /* Ensure we delete the prefix IFF prefix ifa matches */
- .rti_filter = in_match_ifaddr,
- .rti_filterdata = ifa,
- };
-
- return (rib_handle_ifaddr_info(fibnum, cmd, &info));
+ if (cmd == RTM_DELETE) {
+ error = rib_del_kernel_px(fibnum, dst, plen, in_match_ifaddr, ifa,
+ RTM_F_FORCE);
+ } else {
+ struct nhop_object *nh = nhop_alloc(fibnum, dst->sa_family);
+ struct route_nhop_data rnd = { .rnd_weight = RT_DEFAULT_WEIGHT };
+
+ if (nh == NULL)
+ return (ENOMEM);
+ nhop_set_direct_gw(nh, ifa->ifa_ifp);
+ nhop_set_transmit_ifp(nh, ifp);
+ nhop_set_src(nh, ifa);
+ nhop_set_pinned(nh, true);
+ nhop_set_pxtype_flag(nh, plen == 32 ? NHF_HOST : 0);
+ rnd.rnd_nhop = nhop_get_nhop(nh, &error);
+ if (error != 0)
+ return (error);
+ int op_flags = RTM_F_CREATE | RTM_F_REPLACE | RTM_F_FORCE;
+ error = rib_add_kernel_px(fibnum, dst, plen, &rnd, op_flags);
+ }
+ return (error);
}
/*
@@ -959,19 +957,12 @@ in_handle_ifaddr_route(int cmd, struct in_ifaddr *ia)
{
struct ifaddr *ifa = &ia->ia_ifa;
struct in_addr daddr, maddr;
- struct sockaddr_in *pmask;
struct epoch_tracker et;
int error;
ia_getrtprefix(ia, &daddr, &maddr);
- struct sockaddr_in mask = {
- .sin_family = AF_INET,
- .sin_len = sizeof(struct sockaddr_in),
- .sin_addr = maddr,
- };
-
- pmask = (maddr.s_addr != INADDR_BROADCAST) ? &mask : NULL;
+ int plen = bitcount32(maddr.s_addr);
struct sockaddr_in dst = {
.sin_family = AF_INET,
@@ -989,7 +980,8 @@ in_handle_ifaddr_route(int cmd, struct in_ifaddr *ia)
uint32_t fibnum = ifa->ifa_ifp->if_fib;
NET_EPOCH_ENTER(et);
- error = in_handle_prefix_route(fibnum, cmd, &dst, pmask, ifa, ifp);
+ error = in_handle_prefix_route(fibnum, cmd, (struct sockaddr *)&dst,
+ plen, ifa, ifp);
NET_EPOCH_EXIT(et);
return (error);
diff --git a/sys/netinet6/in6.c b/sys/netinet6/in6.c
index ccdf71cfc01c..4b3f30a7ee7c 100644
--- a/sys/netinet6/in6.c
+++ b/sys/netinet6/in6.c
@@ -1267,6 +1267,29 @@ in6_broadcast_ifa(struct ifnet *ifp, struct in6_aliasreq *ifra,
return (error);
}
+static int
+in6_handle_dstaddr_rtadd(uint32_t fibnum, struct sockaddr *dst, struct ifaddr *ifa)
+{
+ struct nhop_object *nh = nhop_alloc(fibnum, dst->sa_family);
+ struct route_nhop_data rnd = { .rnd_weight = RT_DEFAULT_WEIGHT };
+ int error = 0;
+
+ if (nh == NULL)
+ return (ENOMEM);
+ nhop_set_direct_gw(nh, ifa->ifa_ifp);
+ nhop_set_transmit_ifp(nh, ifa->ifa_ifp);
+ nhop_set_src(nh, ifa);
+ nhop_set_pinned(nh, true);
+ nhop_set_pxtype_flag(nh, NHF_HOST);
+ rnd.rnd_nhop = nhop_get_nhop(nh, &error);
+ if (error == 0) {
+ int op_flags = RTM_F_CREATE | RTM_F_REPLACE | RTM_F_FORCE;
+ error = rib_add_kernel_px(fibnum, dst, -1, &rnd, op_flags);
+ }
+
+ return (error);
+}
+
/*
* Adds or deletes interface route for p2p ifa.
* Returns 0 on success or errno.
@@ -1276,35 +1299,26 @@ in6_handle_dstaddr_rtrequest(int cmd, struct in6_ifaddr *ia)
{
struct epoch_tracker et;
struct ifaddr *ifa = &ia->ia_ifa;
+ uint32_t fibnum = ifa->ifa_ifp->if_fib;
int error;
- /* Prepare gateway */
- struct sockaddr_dl_short sdl = {
- .sdl_family = AF_LINK,
- .sdl_len = sizeof(struct sockaddr_dl_short),
- .sdl_type = ifa->ifa_ifp->if_type,
- .sdl_index = ifa->ifa_ifp->if_index,
- };
-
struct sockaddr_in6 dst = {
.sin6_family = AF_INET6,
.sin6_len = sizeof(struct sockaddr_in6),
.sin6_addr = ia->ia_dstaddr.sin6_addr,
};
- struct rt_addrinfo info = {
- .rti_ifa = ifa,
- .rti_ifp = ifa->ifa_ifp,
- .rti_flags = RTF_PINNED | RTF_HOST,
- .rti_info = {
- [RTAX_DST] = (struct sockaddr *)&dst,
- [RTAX_GATEWAY] = (struct sockaddr *)&sdl,
- },
- };
- /* Don't set additional per-gw filters on removal */
-
NET_EPOCH_ENTER(et);
- error = rib_handle_ifaddr_info(ifa->ifa_ifp->if_fib, cmd, &info);
+ if (cmd == RTM_DELETE) {
+ struct sockaddr_dl link_sdl;
+ struct sockaddr *gw = (struct sockaddr *)&link_sdl;
+ struct ifnet *ifp = ifa->ifa_ifp;
+
+ link_init_sdl(ifp, gw, ifp->if_type);
+ error = rib_del_kernel_px(fibnum, (struct sockaddr *)&dst, 128,
+ rib_match_gw, gw, RTM_F_FORCE);
+ } else
+ error = in6_handle_dstaddr_rtadd(fibnum, (struct sockaddr *)&dst, ifa);
NET_EPOCH_EXIT(et);
return (error);
diff --git a/sys/netinet6/nd6_rtr.c b/sys/netinet6/nd6_rtr.c
index 9b33d0ea9b24..032d019702ac 100644
--- a/sys/netinet6/nd6_rtr.c
+++ b/sys/netinet6/nd6_rtr.c
@@ -2014,41 +2014,55 @@ restart:
ND6_ONLINK_UNLOCK();
}
+static int
+nd6_prefix_rtadd(uint32_t fibnum, struct sockaddr *dst, int plen,
+ struct ifnet *ifp, struct ifaddr *ifa)
+{
+ struct route_nhop_data rnd = { .rnd_weight = RT_DEFAULT_WEIGHT };
+ int error = 0;
+
+ struct nhop_object *nh = nhop_alloc(fibnum, dst->sa_family);
+ if (nh == NULL)
+ return (ENOMEM);
+ nhop_set_direct_gw(nh, ifp);
+ nhop_set_transmit_ifp(nh, ifp);
+ nhop_set_src(nh, ifa);
+ nhop_set_pinned(nh, true);
+ nhop_set_pxtype_flag(nh, plen == 128 ? NHF_HOST : 0);
+ rnd.rnd_nhop = nhop_get_nhop(nh, &error);
+ if (error == 0) {
+ int op_flags = RTM_F_CREATE | RTM_F_REPLACE | RTM_F_FORCE;
+ error = rib_add_kernel_px(fibnum, dst, plen, &rnd, op_flags);
+ }
+
+ return (error);
+}
+
/*
* Add or remove interface route specified by @dst, @netmask and @ifp.
* ifa can be NULL.
* Returns 0 on success
*/
static int
-nd6_prefix_rtrequest(uint32_t fibnum, int cmd, struct sockaddr_in6 *dst,
- struct sockaddr_in6 *netmask, struct ifnet *ifp, struct ifaddr *ifa)
+nd6_prefix_rtrequest(uint32_t fibnum, int cmd, struct sockaddr *dst,
+ int plen, struct ifnet *ifp, struct ifaddr *ifa)
{
struct epoch_tracker et;
int error;
- /* Prepare gateway */
- struct sockaddr_dl_short sdl = {
- .sdl_family = AF_LINK,
- .sdl_len = sizeof(struct sockaddr_dl_short),
- .sdl_type = ifp->if_type,
- .sdl_index = ifp->if_index,
- };
+ NET_EPOCH_ENTER(et);
+ if (cmd == RTM_DELETE) {
+ struct sockaddr_dl link_sdl;
+ struct sockaddr *gw = (struct sockaddr *)&link_sdl;
- struct rt_addrinfo info = {
- .rti_ifa = ifa,
- .rti_ifp = ifp,
- .rti_flags = RTF_PINNED | ((netmask != NULL) ? 0 : RTF_HOST),
- .rti_info = {
- [RTAX_DST] = (struct sockaddr *)dst,
- [RTAX_NETMASK] = (struct sockaddr *)netmask,
- [RTAX_GATEWAY] = (struct sockaddr *)&sdl,
- },
- };
- /* Don't set additional per-gw filters on removal */
+ link_init_sdl(ifp, gw, ifp->if_type);
+ error = rib_del_kernel_px(fibnum, dst, plen,
+ rib_match_gw, gw, RTM_F_FORCE);
+ } else
+ error = nd6_prefix_rtadd(fibnum, dst, plen, ifp, ifa);
- NET_EPOCH_ENTER(et);
- error = rib_handle_ifaddr_info(fibnum, cmd, &info);
NET_EPOCH_EXIT(et);
+
return (error);
}
@@ -2057,15 +2071,8 @@ nd6_prefix_onlink_rtrequest(struct nd_prefix *pr, struct ifaddr *ifa)
{
int error;
- struct sockaddr_in6 mask6 = {
- .sin6_family = AF_INET6,
- .sin6_len = sizeof(struct sockaddr_in6),
- .sin6_addr = pr->ndpr_mask,
- };
- struct sockaddr_in6 *pmask6 = (pr->ndpr_plen != 128) ? &mask6 : NULL;
-
error = nd6_prefix_rtrequest(pr->ndpr_ifp->if_fib, RTM_ADD,
- &pr->ndpr_prefix, pmask6, pr->ndpr_ifp, ifa);
+ (struct sockaddr *)&pr->ndpr_prefix, pr->ndpr_plen, pr->ndpr_ifp, ifa);
if (error == 0)
pr->ndpr_stateflags |= NDPRF_ONLINK;
@@ -2176,10 +2183,9 @@ nd6_prefix_offlink(struct nd_prefix *pr)
.sin6_len = sizeof(struct sockaddr_in6),
.sin6_addr = pr->ndpr_mask,
};
- struct sockaddr_in6 *pmask6 = (pr->ndpr_plen != 128) ? &mask6 : NULL;
error = nd6_prefix_rtrequest(ifp->if_fib, RTM_DELETE,
- &pr->ndpr_prefix, pmask6, ifp, NULL);
+ (struct sockaddr *)&pr->ndpr_prefix, pr->ndpr_plen, ifp, NULL);
a_failure = 1;
if (error == 0) {