Hi, This question is mostly for bluhm@. Should the gettdbbyflow() grab the extra reference on returned `tdbp' like other other gettdb*() do? I'm pointing this because we are going to not rely on the netlock when doing `tdbp' dereference.
Also could this block be rewritten? It looks a little ugly. > + if (ipo->ipo_ids != NULL) { > + if ((tdbp->tdb_flags & TDBF_TUNNELING) == 0 && > + (tdbp->tdb_flags & TDBF_UDPENCAP) != 0) { > + /* > + * Skip IDs check for transport mode > + * with NAT-T. Multiple clients (IDs) > + * can use a same policy. > + */ > + } else if (tdbp->tdb_ids == NULL && > + !ipsp_ids_match(ipo->ipo_ids, > + tdbp->tdb_ids)) > goto nomatchin; > + } On Tue, Nov 30, 2021 at 01:50:21PM +0900, YASUOKA Masahiko wrote: > Hi, > > Let me update the diff. Previous has a problem in ipsp_spd_lookup() > which uses "rn" without initialization. > > On Sat, 20 Nov 2021 21:44:20 +0900 (JST) > YASUOKA Masahiko <yasu...@openbsd.org> wrote: > > On Wed, 12 May 2021 19:11:09 +0900 (JST) > > YASUOKA Masahiko <yasu...@openbsd.org> wrote: > >> Radek reported a problem to misc@ that multiple Windows clients behind > >> a NAT cannot use a L2TP/IPsec server simultaneously. > >> > >> https://marc.info/?t=160996816100001&r=1&w=2 > >> > >> There is two problems. First is pipex(4) doesn't pass the proper > >> ipsecflowinfo to ip_output(). Second is the IPsec policy check which > >> is done by ipsp_spd_lookup() returns -1 (EINVAL) if the given tdb is > >> not cached. This happens when its flow is shared by another tdb (for > >> another client of the same NAT). > > > > This problem is not fixed yet. The diff for the second problem was > > not committed in. It was to fix the check in ipsp_spd_lookup() by > > making a IPsec policy have a list of IDs. > > > > Also my colleague Kawai pointed out there is another problem if there > > is a Linux client among with Windows clients behind a NAT. Windows > > uses 1701/udp for its local ID, but the Linux uses ANY/udp for its > > local ID. > > > > In the situation, policies will be overlapped. > > > > (a) Windows: REMOTE_IP:1701/udp <=> LOCAL_IP:1701/udp > > (b) Linux: REMOTE_IP:ANY/udp <=> LOCAL_IP:1701/udp > > > > Since we use a radix tree for the policies, when rn_match() is used to > > find a policy, as it's best match, (b) is never selected. > > > > Let me update the diff. > > > > As for the incomming, we know the tdb when is used. The diff uses the > > tdb to find the proper policy. > > > > As for the outgoing, other than using "ipsecflowinfo" there is no way > > to select a proper policy. So only when "ipsecflowinfo" is used, get > > a tdb from the packet flow and the IDs (retributed by the > > ipsecflowinfo), then we can find the proper policy by the tdb. > > > > Also the diff skips the IDs check against the policy only if it is > > transport mode and using NAT-T. Since when NAT-T is used for a policy > > for transport mode is shared by multiple clients which has a different > > IDs, checking the IDs is difficult and I think the checks other than > > is enough. > > > > ok? comments? > > > > Fix some problems when accepting IPsec transport mode connections from > > multiple clients behind a NAT. In the situation, policies can be > > overlapped, but previous could not choice a proper policy both for > > incoming and outgoing. To solve this problem, use > > tdb->tdb_filter{,mask} to find a proper policy for incoming and find the > > tdb by the given ipsecflowinfo and use it for outgoing. Also skip > > checking IDs of the policy since a policy is shared by multiple clients > > in the situation. > > Index: sys/netinet/ip_ipsp.c > =================================================================== > RCS file: /disk/cvs/openbsd/src/sys/netinet/ip_ipsp.c,v > retrieving revision 1.258 > diff -u -p -r1.258 ip_ipsp.c > --- sys/netinet/ip_ipsp.c 29 Nov 2021 19:19:00 -0000 1.258 > +++ sys/netinet/ip_ipsp.c 30 Nov 2021 04:44:48 -0000 > @@ -90,6 +90,8 @@ void tdb_firstuse(void *); > void tdb_soft_timeout(void *); > void tdb_soft_firstuse(void *); > int tdb_hash(u_int32_t, union sockaddr_union *, u_int8_t); > +int sockaddr_encap_match(struct sockaddr_encap *, > + struct sockaddr_encap *, struct sockaddr_encap *); > > int ipsec_in_use = 0; > u_int64_t ipsec_last_added = 0; > @@ -507,6 +509,76 @@ gettdbbysrc(u_int rdomain, union sockadd > tdb_ref(tdbp); > mtx_leave(&tdb_sadb_mtx); > return tdbp; > +} > + > +/* > + * Get an SA given the flow, the direction, the security protocol type, and > + * the desired IDs. > + */ > +struct tdb * > +gettdbbyflow(u_int rdomain, int direction, struct sockaddr_encap *senflow, > + u_int8_t sproto, struct ipsec_ids *ids) > +{ > + u_int32_t hashval; > + struct tdb *tdbp; > + union sockaddr_union srcdst; > + > + if (ids == NULL) /* ids is mandatory */ > + return NULL; > + > + memset(&srcdst, 0, sizeof(srcdst)); > + switch (senflow->sen_type) { > + case SENT_IP4: > + srcdst.sin.sin_len = sizeof(srcdst.sin); > + srcdst.sin.sin_family = AF_INET; > + if (direction == IPSP_DIRECTION_OUT) > + srcdst.sin.sin_addr = senflow->Sen.Sip4.Dst; > + else > + srcdst.sin.sin_addr = senflow->Sen.Sip4.Src; > + break; > + case SENT_IP6: > + srcdst.sin6.sin6_len = sizeof(srcdst.sin6); > + srcdst.sin6.sin6_family = AF_INET6; > + if (direction == IPSP_DIRECTION_OUT) > + srcdst.sin6.sin6_addr = senflow->Sen.Sip6.Dst; > + else > + srcdst.sin6.sin6_addr = senflow->Sen.Sip6.Src; > + break; > + } > + > + mtx_enter(&tdb_sadb_mtx); > + hashval = tdb_hash(0, &srcdst, sproto); > + > + for (tdbp = tdbdst[hashval]; tdbp != NULL; tdbp = tdbp->tdb_dnext) > + if (tdbp->tdb_sproto == sproto && > + tdbp->tdb_rdomain == rdomain && > + (tdbp->tdb_flags & TDBF_INVALID) == 0 && > + ((direction == IPSP_DIRECTION_OUT && > + !memcmp(&tdbp->tdb_dst, &srcdst, srcdst.sa.sa_len)) || > + (direction == IPSP_DIRECTION_IN && > + !memcmp(&tdbp->tdb_src, &srcdst, srcdst.sa.sa_len)))) { > + if (sockaddr_encap_match(&tdbp->tdb_filter, > + &tdbp->tdb_filtermask, senflow)) > + break; > + } > + > + mtx_leave(&tdb_sadb_mtx); > + return tdbp; > +} > + > +int > +sockaddr_encap_match(struct sockaddr_encap *addr, struct sockaddr_encap > *mask, > + struct sockaddr_encap *dest) > +{ > + size_t off; > + > + for (off = offsetof(struct sockaddr_encap, sen_type); > + off < dest->sen_len; off++) { > + if ((*((u_char *)addr + off) & *((u_char *)mask + off)) != > + (*((u_char *)dest + off) & *((u_char *)mask + off))) > + break; > + } > + return (off < dest->sen_len)? 0 : 1; > } > > #ifdef DDB > Index: sys/netinet/ip_ipsp.h > =================================================================== > RCS file: /disk/cvs/openbsd/src/sys/netinet/ip_ipsp.h,v > retrieving revision 1.223 > diff -u -p -r1.223 ip_ipsp.h > --- sys/netinet/ip_ipsp.h 26 Nov 2021 16:16:35 -0000 1.223 > +++ sys/netinet/ip_ipsp.h 30 Nov 2021 04:44:48 -0000 > @@ -565,6 +565,8 @@ struct tdb *gettdbbysrcdst_dir(u_int, u_ > union sockaddr_union *, u_int8_t, int); > #define gettdbbysrcdst(a,b,c,d,e) gettdbbysrcdst_dir((a),(b),(c),(d),(e),0) > #define gettdbbysrcdst_rev(a,b,c,d,e) > gettdbbysrcdst_dir((a),(b),(c),(d),(e),1) > +struct tdb *gettdbbyflow(u_int, int, struct sockaddr_encap *, u_int8_t, > + struct ipsec_ids *); > void puttdb(struct tdb *); > void puttdb_locked(struct tdb *); > void tdb_delete(struct tdb *); > Index: sys/netinet/ip_spd.c > =================================================================== > RCS file: /disk/cvs/openbsd/src/sys/netinet/ip_spd.c,v > retrieving revision 1.105 > diff -u -p -r1.105 ip_spd.c > --- sys/netinet/ip_spd.c 25 Nov 2021 13:46:02 -0000 1.105 > +++ sys/netinet/ip_spd.c 30 Nov 2021 04:44:48 -0000 > @@ -147,7 +147,7 @@ ipsp_spd_lookup(struct mbuf *m, int af, > struct tdb *tdbp, struct inpcb *inp, u_int32_t ipsecflowinfo) > { > struct radix_node_head *rnh; > - struct radix_node *rn; > + struct radix_node *rn = NULL; > union sockaddr_union sdst, ssrc; > struct sockaddr_encap *ddst, dst; > struct ipsec_policy *ipo; > @@ -177,6 +177,8 @@ ipsp_spd_lookup(struct mbuf *m, int af, > return NULL; > } > > + if (ipsecflowinfo != 0) > + ids = ipsp_ids_lookup(ipsecflowinfo); > memset(&dst, 0, sizeof(dst)); > memset(&sdst, 0, sizeof(union sockaddr_union)); > memset(&ssrc, 0, sizeof(union sockaddr_union)); > @@ -299,9 +301,28 @@ ipsp_spd_lookup(struct mbuf *m, int af, > return NULL; > } > > + /* > + * When there are multiple clients behind a NAT, policies can be > + * overlapped. When it happens it's impossible to find a correct one > + * only by the flow. Use ipsecflowinfo and get an SA first to solve > + * the situation. > + */ > + if (ipsecflowinfo != 0 && ids != NULL) { > + KASSERT(tdbp == NULL); > + KASSERT(direction == IPSP_DIRECTION_OUT); > + tdbp = gettdbbyflow(rdomain, direction, &dst, IPPROTO_ESP, ids); > + } > + > /* Actual SPD lookup. */ > - if ((rnh = spd_table_get(rdomain)) == NULL || > - (rn = rn_match((caddr_t)&dst, rnh)) == NULL) { > + rnh = spd_table_get(rdomain); > + if (rnh != NULL) { > + if (tdbp != NULL) > + rn = rn_lookup((caddr_t)&tdbp->tdb_filter, > + (caddr_t)&tdbp->tdb_filtermask, rnh); > + else > + rn = rn_match((caddr_t)&dst, rnh); > + } > + if (rn == NULL) { > /* > * Return whatever the socket requirements are, there are no > * system-wide policies. > @@ -396,9 +417,6 @@ ipsp_spd_lookup(struct mbuf *m, int af, > } > } > > - if (ipsecflowinfo) > - ids = ipsp_ids_lookup(ipsecflowinfo); > - > /* Check that the cached TDB (if present), is appropriate. */ > if (ipo->ipo_tdb != NULL) { > if ((ipo->ipo_last_searched <= ipsec_last_added) || > @@ -517,10 +535,19 @@ ipsp_spd_lookup(struct mbuf *m, int af, > goto nomatchin; > > /* Match source/dest IDs. */ > - if (ipo->ipo_ids) > - if (tdbp->tdb_ids == NULL || > - !ipsp_ids_match(ipo->ipo_ids, > tdbp->tdb_ids)) > + if (ipo->ipo_ids != NULL) { > + if ((tdbp->tdb_flags & TDBF_TUNNELING) == 0 && > + (tdbp->tdb_flags & TDBF_UDPENCAP) != 0) { > + /* > + * Skip IDs check for transport mode > + * with NAT-T. Multiple clients (IDs) > + * can use a same policy. > + */ > + } else if (tdbp->tdb_ids == NULL && > + !ipsp_ids_match(ipo->ipo_ids, > + tdbp->tdb_ids)) > goto nomatchin; > + } > > /* Add it to the cache. */ > if (ipo->ipo_tdb != NULL) {