The `subrequest` keyword creates a child request. The child request
is empty, and contains no attributes. Attributes in the child can be
copied from the parent via xref:unlang/edit.adoc[editing] statements.
-Please see the xref:unlang/list.adoc[list] syntax for a description of how to
-refer to parent requests.
+Please see the xref:unlang/list.adoc[list] syntax for a description of
+how to refer to parent requests.
The `subrequest` keyword allows the server to receive one type of
-packet, and then to create a different type of packet. `subrequest`
-is most commonly used in conjunction with the
+packet, and then to create a different type of packet. For example,
+receive a RADIUS `Accounting-Request`, and then create a child
+`Disconnect-Request` to kick the user offline. The `subrequest`
+keyword can also be used to change protocols. For example, receive a
+DHCP `Discover` packet, and then send a RADIUS `Access-Request` packet
+to see if the MAC address is authorized.
+
+`subrequest` is most commonly used in conjunction with the
xref:unlang/call.adoc[call] keyword to change packet types or
-protocols, and then run a different virtual server. See the
-"Operation" section below for more information.
+protocols, and then xref:unlang/call.adoc[call] a different virtual
+server. See the "Operation" section below for more information.
-<type>:: The type of the child request being created.
+<type>:: The packet type of the child request being created.
+
-The _<type>_ field is a either an enumerated packet name such as `::Access-Request`,
-or a protocol name followed by a packet name, such as
-`dhcpv4::Discover`. Please see the protocol dictionaries for a
-complete list of packet types for each protocol.
+If the _<type>_ field is omitted, then a child request is created
+using the same packet type as the current request.
++
+If the _<type>_ field contains a protocol reference such as `@dhcpv4`,
+then this creates a new child request of a different protocol. The
+rest of the _<type>_ field is then parsed to see what kind of packet
+is created.
++
+The _<type> field is then parsed as an enumerated packet name such as
+`::Access-Request`, Please see the protocol dictionaries for a
+complete list of allowed packet types for each protocol.
+
The _<type>_ field cannot be a dynamic expansion.
+
-The _<type> field can, however, be an attribute reference. When an
+As an exception, the _<type>_ field can be an attribute reference. When an
attribute reference is used, the attribute must be of an integer type
(8, 16, 32, or 64-bit), or of type `string`. Integer types are
resolved to known / allowed values of the `Packet-Type` attribute.
+
When the _<type>_ field is an attribute reference, it is not
possible to change the dictionary.
++
+We do not suggest using attribute references. The functionality will
+be removed in a future release, and will be replaced with full support
+for dynamic expansions.
[ statements ]:: The `unlang` commands which will be executed.
.Example
[source,unlang]
----
-subrequest dhcpv4::Discover {
+subrequest @dhcpv4::Discover {
&Your-IP-Address := &parent.request.Framed-IP-Address
...
}
----
-// Copyright (C) 2021 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
+// Copyright (C) 2025 Network RADIUS SAS. Licenced under CC-by-NC 4.0.
// This documentation was developed by Network RADIUS SAS.
return NULL;
}
+ dict = unlang_ctx->rules->attr.dict_def;
+
+ /*
+ * @foo is "dictionary foo", as with references in the dictionaries.
+ *
+ * @foo::bar is "dictionary foo, Packet-Type = ::bar"
+ *
+ * foo::bar is "dictionary foo, Packet-Type = ::bar"
+ *
+ * ::bar is "this dictionary, Packet-Type = ::bar", BUT
+ * we don't try to parse the new dictionary name, as it
+ * doesn't exist.
+ */
+ if ((name2[0] == '@') ||
+ ((name2[0] != ':') && (name2[0] != '&') && (strchr(name2 + 1, ':') != NULL))) {
+ char *q;
+
+ if (name2[0] == '@') name2++;
+
+ MEM(namespace = talloc_strdup(parent, name2));
+ q = namespace;
+
+ while (fr_dict_attr_allowed_chars[(unsigned int) *q]) {
+ q++;
+ }
+ *q = '\0';
+
+ dict = fr_dict_by_protocol_name(namespace);
+ if (!dict) {
+ dict_ref = fr_dict_autoload_talloc(NULL, &dict, namespace);
+ if (!dict_ref) {
+ cf_log_err(cs, "Unknown namespace in '%s'", name2);
+ talloc_free(namespace);
+ return NULL;
+ }
+ }
+
+ /*
+ * Skip the dictionary name, and go to the thing
+ * right after it.
+ */
+ name2 += (q - namespace);
+ TALLOC_FREE(namespace);
+ }
+
/*
+ * @dict::enum is "other dictionary, Packet-Type = ::enum"
* ::enum is this dictionary, "Packet-Type = ::enum"
*/
if ((name2[0] == ':') && (name2[1] == ':')) {
- dict = unlang_ctx->rules->attr.dict_def;
packet_name = name2;
goto get_packet_type;
}
/*
- * If !tmpl_require_enum_prefix, '&' means "attribute reference".
+ * Can't do foo.bar.baz::foo, the enums are only used for Packet-Type.
+ */
+ if (strchr(name2, ':') != NULL) {
+ cf_log_err(cs, "Reference cannot contain enum value in '%s'", name2);
+ return NULL;
+ }
+
+ /*
+ * '&' means "attribute reference"
*
- * Or, bare word means "attribute reference".
+ * Or, bare word an require_enum_prefix means "attribute reference".
*/
- if ((name2[0] == '&') ||
- (tmpl_require_enum_prefix && ((p = strchr(name2, ':')) == NULL))) {
+ if ((name2[0] == '&') || tmpl_require_enum_prefix) {
ssize_t slen;
slen = tmpl_afrom_attr_substr(parent, NULL, &vpt,
}
/*
- * subrequest foo::bar { ... }
- *
- * Change to dictionary "foo", packet type "bar".
- */
- if (p) {
- if (p[1] != ':') {
- cf_log_err(cs, "Invalid syntax in namespace::enum");
- return NULL;
- }
-
- MEM(namespace = talloc_strdup(parent, name2)); /* get a modifiable copy */
-
- p = namespace + (p - name2);
- *p = '\0';
- p += 2;
- packet_name = p;
-
- dict = fr_dict_by_protocol_name(namespace);
- if (!dict) {
- dict_ref = fr_dict_autoload_talloc(NULL, &dict, namespace);
- if (!dict_ref) {
- cf_log_err(cs, "Unknown namespace '%s'", namespace);
- talloc_free(namespace);
- return NULL;
- }
- }
-
- goto get_packet_type;
- }
-
- /*
- * subrequest foo { ... }
- *
- * Change packet types without changing dictionaries.
+ * foo.bar without '&' and NOT require_enum_prefix is fugly, we should disallow it.
*/
p = strchr(name2, '.');
if (!p) {
+ cf_log_warn(cs, "Please upgrade configuration to use '::%s' when changing packet types",
+ name2);
+
dict = unlang_ctx->rules->attr.dict_def;
packet_name = name2;
- } else if (!tmpl_require_enum_prefix) {
+ } else {
+ cf_log_warn(cs, "Please upgrade configuration to use '@' when changing dictionaries");
+
/*
* subrequest foo.bar { ... }
*
WARN("Deprecated syntax 'subrequest %s ...'", name2);
WARN(" please switch to 'subrequest %s::%s ...", namespace, packet_name);
-
- } else {
- /*
- * We have tmpl_require_enum prefix, so bare word foo.bar is "attribute foo,
- * sub-attribute bar"
- */
- dict = unlang_ctx->rules->attr.dict_def;
- packet_name = name2;
}
/*