3838class ActionItem
3939{
4040public:
41- virtual int SNIAction (Continuation *cont) const = 0;
41+ /* *
42+ * Context should contain extra data needed to be passed to the actual SNIAction.
43+ */
44+ struct Context {
45+ /* *
46+ * if any, fqdn_wildcard_captured_groups will hold the captured groups from the `fqdn`
47+ * match which will be used to construct the tunnel destination.
48+ */
49+ std::optional<std::vector<std::string>> _fqdn_wildcard_captured_groups;
50+ };
51+
52+ virtual int SNIAction (Continuation *cont, const Context &ctx) const = 0;
4253
4354 /* *
4455 This method tests whether this action would have been triggered by a
@@ -61,7 +72,7 @@ class ControlH2 : public ActionItem
6172 ~ControlH2 () override {}
6273
6374 int
64- SNIAction (Continuation *cont) const override
75+ SNIAction (Continuation *cont, const Context &ctx ) const override
6576 {
6677 auto ssl_vc = dynamic_cast <SSLNetVConnection *>(cont);
6778 if (ssl_vc) {
@@ -81,21 +92,98 @@ class ControlH2 : public ActionItem
8192class TunnelDestination : public ActionItem
8293{
8394public:
84- TunnelDestination (const std::string_view &dest, bool decrypt) : destination(dest), tunnel_decrypt(decrypt) {}
95+ TunnelDestination (const std::string_view &dest, bool decrypt) : destination(dest), tunnel_decrypt(decrypt)
96+ {
97+ need_fix = (destination.find_first_of (' $' ) != std::string::npos);
98+ }
8599 ~TunnelDestination () override {}
86100
87101 int
88- SNIAction (Continuation *cont) const override
102+ SNIAction (Continuation *cont, const Context &ctx ) const override
89103 {
90104 // Set the netvc option?
91105 SSLNetVConnection *ssl_netvc = dynamic_cast <SSLNetVConnection *>(cont);
92106 if (ssl_netvc) {
93- ssl_netvc->set_tunnel_destination (destination, tunnel_decrypt);
107+ // If needed, we will try to amend the tunnel destination.
108+ if (ctx._fqdn_wildcard_captured_groups && need_fix) {
109+ const auto &fixed_dst = replace_match_groups (destination, *ctx._fqdn_wildcard_captured_groups );
110+ ssl_netvc->set_tunnel_destination (fixed_dst, tunnel_decrypt);
111+ Debug (" TunnelDestination" , " Destination now is [%s], configured [%s]" , fixed_dst.c_str (), destination.c_str ());
112+ } else {
113+ ssl_netvc->set_tunnel_destination (destination, tunnel_decrypt);
114+ }
94115 }
95116 return SSL_TLSEXT_ERR_OK;
96117 }
97118 std::string destination;
98119 bool tunnel_decrypt = false ;
120+
121+ private:
122+ bool
123+ is_number (const std::string &s) const
124+ {
125+ return !s.empty () &&
126+ std::find_if (std::begin (s), std::end (s), [](std::string::value_type c) { return !std::isdigit (c); }) == std::end (s);
127+ }
128+
129+ /* *
130+ * `tunnel_route` may contain matching groups ie: `$1` which needs to be replaced by the corresponding
131+ * captured group from the `fqdn`, this function will replace them using proper group string. Matching
132+ * groups could be at any order.
133+ */
134+ std::string
135+ replace_match_groups (const std::string &dst, const std::vector<std::string> &groups) const
136+ {
137+ if (dst.empty () || groups.empty ()) {
138+ return dst;
139+ }
140+ std::string real_dst;
141+ std::string::size_type pos{0 };
142+
143+ const auto end = std::end (dst);
144+ // We need to split the tunnel string and place each corresponding match on the
145+ // configured one, so we need to first, get the match, then get the match number
146+ // making sure that it does exist in the captured group.
147+ for (auto c = std::begin (dst); c != end; c++, pos++) {
148+ if (*c == ' $' ) {
149+ // find the next '.' so we can get the group number.
150+ const auto dot = dst.find (' .' , pos);
151+ std::string::size_type to = std::string::npos;
152+ if (dot != std::string::npos) {
153+ to = dot - (pos + 1 );
154+ } else {
155+ // It may not have a dot, which could be because it's the last part. In that case
156+ // we should check for the port separator.
157+ if (const auto port = dst.find (' :' , pos); port != std::string::npos) {
158+ to = (port - pos) - 1 ;
159+ }
160+ }
161+ const auto &number_str = dst.substr (pos + 1 , to);
162+ if (!is_number (number_str)) {
163+ // it may be some issue on the configured string, place the char and keep going.
164+ real_dst += *c;
165+ continue ;
166+ }
167+ const std::size_t group_index = std::stoi (number_str);
168+ if ((group_index - 1 ) < groups.size ()) {
169+ // place the captured group.
170+ real_dst += groups[group_index - 1 ];
171+ // if it was the last match, then ...
172+ if (dot == std::string::npos && to == std::string::npos) {
173+ // that's it.
174+ break ;
175+ }
176+ pos += number_str.size () + 1 ;
177+ std::advance (c, number_str.size () + 1 );
178+ }
179+ }
180+ real_dst += *c;
181+ }
182+
183+ return real_dst;
184+ }
185+
186+ bool need_fix;
99187};
100188
101189class VerifyClient : public ActionItem
@@ -107,7 +195,7 @@ class VerifyClient : public ActionItem
107195 VerifyClient (uint8_t param) : mode(param) {}
108196 ~VerifyClient () override {}
109197 int
110- SNIAction (Continuation *cont) const override
198+ SNIAction (Continuation *cont, const Context &ctx ) const override
111199 {
112200 auto ssl_vc = dynamic_cast <SSLNetVConnection *>(cont);
113201 Debug (" ssl_sni" , " action verify param %d" , this ->mode );
@@ -131,7 +219,7 @@ class HostSniPolicy : public ActionItem
131219 HostSniPolicy (uint8_t param) : policy(param) {}
132220 ~HostSniPolicy () override {}
133221 int
134- SNIAction (Continuation *cont) const override
222+ SNIAction (Continuation *cont, const Context &ctx ) const override
135223 {
136224 // On action this doesn't do anything
137225 return SSL_TLSEXT_ERR_OK;
@@ -160,7 +248,7 @@ class TLSValidProtocols : public ActionItem
160248 TLSValidProtocols () : protocol_mask(max_mask) {}
161249 TLSValidProtocols (unsigned long protocols) : unset(false ), protocol_mask(protocols) {}
162250 int
163- SNIAction (Continuation *cont) const override
251+ SNIAction (Continuation *cont, const Context &ctx ) const override
164252 {
165253 if (!unset) {
166254 auto ssl_vc = dynamic_cast <SSLNetVConnection *>(cont);
@@ -205,7 +293,7 @@ class SNI_IpAllow : public ActionItem
205293 } // end function SNI_IpAllow
206294
207295 int
208- SNIAction (Continuation *cont) const override
296+ SNIAction (Continuation *cont, const Context &ctx ) const override
209297 {
210298 // i.e, ip filtering is not required
211299 if (ip_map.count () == 0 ) {
0 commit comments