]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
manual and pktinfo.
authorWouter Wijngaards <wouter@nlnetlabs.nl>
Thu, 17 Jan 2008 15:35:34 +0000 (15:35 +0000)
committerWouter Wijngaards <wouter@nlnetlabs.nl>
Thu, 17 Jan 2008 15:35:34 +0000 (15:35 +0000)
git-svn-id: file:///svn/unbound/trunk@873 be551aaa-1e26-0410-a405-d3ace91eadb9

doc/Changelog
doc/TODO
doc/libunbound.3 [new file with mode: 0644]
libunbound/context.h
libunbound/unbound.h
services/listen_dnsport.c
util/netevent.c
util/netevent.h

index d561239869ecb659715d065b8b3e2b9308b04711..8124899019a6b0ad70e8202236f70ad8f1f1c198 100644 (file)
@@ -3,6 +3,8 @@
        - fixup a couple of doxygen warnings, about enum variables.
        - interface-automatic now copies the interface address from the
          PKT_INFO structure as well.
+       - manual page with library API, all on one page 'man libunbound'.
+       - rewrite of PKTINFO structure, it also captures IP4 PKTINFO.
 
 16 January 2008: Wouter
        - incoming queries to the server with TC bit on are replied FORMERR.
index d9b73477f685e8ce84dc37091474e8be31493afb..e30318e71fb51cadd77d5aec8744094d181e9324 100644 (file)
--- a/doc/TODO
+++ b/doc/TODO
@@ -56,3 +56,4 @@ o in an ipv6 connected only environment unbound cannot use outgoing IP6
   V6ONLY socket option.
 o support multiple dns messages in a TCP query stream for the unbound server.
 o SIG(0) and TSIG.
+o examine errno threaded trouble with EAGAIN in netevent.
diff --git a/doc/libunbound.3 b/doc/libunbound.3
new file mode 100644 (file)
index 0000000..e147d83
--- /dev/null
@@ -0,0 +1,264 @@
+.TH "libunbound" "3" "@date@" "NLnet Labs" "unbound @version@"
+.\"
+.\" libunbound.3 -- unbound library functions manual
+.\"
+.\" Copyright (c) 2007, NLnet Labs. All rights reserved.
+.\"
+.\" See LICENSE for the license.
+.\"
+.\"
+.SH "NAME"
+.LP
+.B libunbound,
+.B unbound.h,
+.B ub_val_ctx,
+.B ub_val_result,
+.B ub_val_callback_t,
+.B ub_val_ctx_create,
+.B ub_val_ctx_delete,
+.B ub_val_ctx_config,
+.B ub_val_ctx_add_ta,
+.B ub_val_ctx_add_ta_file,
+.B ub_val_ctx_trustedkeys,
+.B ub_val_ctx_debuglevel,
+.B ub_val_ctx_async,
+.B ub_val_ctx_poll,
+.B ub_val_ctx_wait,
+.B ub_val_ctx_fd,
+.B ub_val_ctx_process,
+.B ub_val_resolve,
+.B ub_val_resolve_async,
+.B ub_val_cancel,
+.B ub_val_result_free,
+.B ub_val_strerror
+\- Unbound DNS validating resolver @version@ functions.
+.SH "SYNOPSIS"
+.LP
+.B #include <unbound.h>
+.LP
+\fIstruct ub_val_ctx *\fR
+\fBub_val_ctx_create\fR(\fIvoid\fR);
+.LP
+\fIvoid\fR
+\fBub_val_ctx_delete\fR(\fIstruct ub_val_ctx*\fR ctx);
+.LP
+\fIint\fR
+\fBub_val_ctx_config\fR(\fIstruct ub_val_ctx*\fR ctx, \fIchar*\fR fname);
+.LP
+\fIint\fR
+\fBub_val_ctx_add_ta\fR(\fIstruct ub_val_ctx*\fR ctx, \fIchar*\fR ta);
+.LP
+\fIint\fR
+\fBub_val_ctx_add_ta_file\fR(\fIstruct ub_val_ctx*\fR ctx, \fIchar*\fR fname);
+.LP
+\fIint\fR
+\fBub_val_ctx_trustedkeys\fR(\fIstruct ub_val_ctx*\fR ctx, \fIchar*\fR fname);
+.LP
+\fIint\fR
+\fBub_val_ctx_debuglevel\fR(\fIstruct ub_val_ctx*\fR ctx, \fIint\fR d);
+.LP
+\fIint\fR
+\fBub_val_ctx_async\fR(\fIstruct ub_val_ctx*\fR ctx, \fIint\fR dothread);
+.LP
+\fIint\fR
+\fBub_val_ctx_poll\fR(\fIstruct ub_val_ctx*\fR ctx);
+.LP
+\fIint\fR
+\fBub_val_ctx_wait\fR(\fIstruct ub_val_ctx*\fR ctx);
+.LP
+\fIint\fR
+\fBub_val_ctx_fd\fR(\fIstruct ub_val_ctx*\fR ctx);
+.LP
+\fIint\fR
+\fBub_val_ctx_process\fR(\fIstruct ub_val_ctx*\fR ctx);
+.LP
+\fIint\fR
+\fBub_val_resolve\fR(\fIstruct ub_val_ctx*\fR ctx, \fIchar*\fR name, 
+.br
+                   \fIint\fR rrtype, \fIint\fR rrclass, \fIint*\fR secure, 
+.br
+                   \fIint*\fR data, \fIstruct ub_val_result**\fR result);
+.LP
+\fIint\fR
+\fBub_val_resolve_async\fR(\fIstruct ub_val_ctx*\fR ctx, \fIchar*\fR name, 
+.br
+                         \fIint\fR rrtype, \fIint\fR rrclass, \fIvoid*\fR mydata, 
+.br
+                         \fIub_val_callback_t\fR callback, \fIint*\fR async_id);
+.LP
+\fIint\fR
+\fBub_val_cancel\fR(\fIstruct ub_val_ctx*\fR ctx, \fIint\fR async_id);
+.LP
+\fIvoid\fR
+\fBub_val_result_free\fR(\fIstruct ub_val_result*\fR result);
+.LP
+\fIconst char *\fR
+\fBub_val_strerror\fR(\fIint\fR err);
+.SH "DESCRIPTION"
+.LP
+.B Unbound 
+is an implementation of a DNS resolver, that does caching and 
+DNSSEC validation. This is the library API, for using the \-lunbound library.
+The server daemon is described in \fIunbound\fR(8).
+The library can be used to convert hostnames to ip addresses, and back,
+and obtain other information from the DNS. The library performs public\-key
+validation of results with DNSSEC.
+.P
+The library uses a variable of type \fIstruct ub_val_ctx\fR to keep context
+between calls. The user must maintain it, creating it with
+.B ub_val_ctx_create
+and deleting it with
+.B ub_val_ctx_delete\fR.
+It can be created and deleted at any time. Creating it anew removes any 
+previous configuration (such as trusted keys) and clears any cached results.
+.P
+The functions are thread\-safe, and a context an be used in a threaded (as 
+well as in a non\-threaded) environment. Also resolution (and validation) 
+can be performed blocking and non\-blocking (also called asynchronous). 
+The async method returns from the call immediately, so that processing 
+can go on, while the results become available later. 
+.P
+The functions are discussed in turn below.
+.SH "FUNCTIONS"
+.TP 
+.B ub_val_ctx_create
+Create a new context, initialised with defaults.
+.TP
+.B ub_val_ctx_delete
+Delete validation context and free associated resources.
+Outstanding async queries are killed and callbacks are not called for them.
+.TP
+.B ub_val_ctx_config
+A power\-user interface that lets you specify an unbound config file, see
+\fIunbound.conf\fR(5), which is read for configuration. Not all options are
+relevant. For some specific options, such as adding trust anchors, special
+routines exist.
+.TP
+.B
+ub_val_ctx_add_ta
+Add a trust anchor to the given context.
+At this time it is only possible to add trusted keys before the
+first resolve is done.
+The format is a string, similar to the zone-file format,
+[domainname] [type] [rdata contents]. Both DS and DNSKEY records are accepted.
+.TP
+.B ub_val_ctx_add_ta_file
+Add trust anchors to the given context.
+Pass name of a file with DS and DNSKEY records in zone file format.
+At this time it is only possible to add trusted keys before the
+first resolve is done.
+.TP
+.B ub_val_ctx_trustedkeys
+Add trust anchors to the given context.
+Pass the name of a bind-style config file with trusted-keys{}.
+At this time it is only possible to add trusted keys before the
+first resolve is done.
+.TP
+.B ub_val_ctx_debuglevel
+Set debug verbosity for the context. Output is directed to stderr.
+Higher debug level gives more output.
+.TP
+.B ub_val_ctx_async
+Set a context behaviour for asynchronous action.
+if set to true, enables threading and a call to resolve_async() 
+creates a thread to handle work in the background.
+If false, a process is forked to handle work in the background.
+Changes to this setting after async() calls have been made have 
+no effect (delete and re\-create the context to change).
+.TP
+.B ub_val_ctx_poll
+Poll a context to see if it has any new results.
+Do not poll in a loop, instead extract the fd below to poll for readiness,
+and then check, or wait using the wait routine.
+Returns 0 if nothing to read, or nonzero if a result is available.
+If nonzero, call 
+.B ctx_process 
+to do callbacks.
+.TP
+.B ub_val_ctx_wait
+Wait for a context to finish with results. Calls 
+.B ub_val_ctx_process after
+the wait for you. After the wait, there are no more outstanding asynchronous
+queries.
+.TP
+.B ub_val_ctx_fd
+Get file descriptor. Wait for it to become readable, at this point
+answers are returned from the asynchronous validating resolver.
+Then call the \fBub_val_ctx_process\fR to continue processing.
+.TP
+.B ub_val_ctx_process
+Call this routine to continue processing results from the validating
+resolver (when the fd becomes readable).
+Will perform necessary callbacks.
+.TP
+.B ub_val_resolve
+Perform resolution and validation of the target name.
+The name is a domain name in a zero terminated text string.
+The rrtype and rrclass are DNS type and class codes.
+The value secure returns true if the answer validated securely.
+The value data returns true if there was data.
+The result structure is newly allocated with the resulting data.
+.TP
+.B ub_val_resolve_async
+Perform asynchronous resolution and validation of the target name.
+Arguments mean the same as for \fBub_val_resolve\fR except no
+data is returned immediately, instead a callback is called later.
+The callback receives a copy of the mydata point, that you can use to pass
+information to the callback. The callback type is a function pointer to
+a function declared as
+.IP
+void my_callback_function(void* my_arg, int err, 
+.br
+                          int secure, int havedata, 
+.br
+                          struct ub_val_result* result);
+.IP
+The async_id is returned so you can (at your option) decide to track it
+and cancel the request if needed.
+.TP
+.B ub_val_cancel
+Cancel an async query in progress.
+.TP
+.B ub_val_result_free
+Free struct ub_val_result contents after use.
+.TP
+.B ub_val_strerror
+Convert error value from one of the unbound library functions 
+to a human readable string.
+.SH "RESULT DATA STRUCTURE"
+.LP
+The result of the DNS resolution and validation is returned as 
+\fIstruct ub_val_result\fR. The result structure contains the following entries.
+.P
+.nf
+       struct ub_val_result {
+               char* qname; /* text string, original question */
+               int qtype;   /* type code asked for */
+               int qclass;  /* class code asked for */
+               char** data; /* array of rdata items, NULL terminated*/
+               int* len;    /* array with lengths of rdata items */
+               char* canonname; /* canonical name of result */
+               int rcode;   /* additional error code in case of error */
+               int nxdomain; /* if nodata because no domain */
+               int bogus;   /* if not secure due to security failure */
+       };
+.fi
+.SH "RETURN VALUES"
+Many routines return an error code. The value 0 (zero) denotes no error
+happened. Other values can be passed to
+.B ub_val_strerror
+to obtain a readable error string.
+.B ub_val_strerror
+returns a zero terminated string.
+.B ub_val_ctx_create
+returns NULL on an error (a malloc failure).
+.B ub_val_ctx_poll
+returns true if some information may be available, false otherwise.
+.B ub_val_ctx_fd
+returns a file descriptor or -1 on error.
+.SH "SEE ALSO"
+\fIunbound.conf\fR(5), 
+\fIunbound\fR(8).
+.SH "AUTHORS"
+.B Unbound
+developers are mentioned in the CREDITS file in the distribution.
index e42509c74a6f3bfd9d743fdfa5ad43b0ce31ae8a..8a472d63d119477bdb77f06641ced8c3b4e1e499 100644 (file)
@@ -155,19 +155,19 @@ enum ub_ctx_err {
        /** no error */
        UB_NOERROR = 0,
        /** alloc failure */
-       UB_NOMEM,
+       UB_NOMEM = -1,
        /** socket operation */
-       UB_SOCKET,
+       UB_SOCKET = -2,
        /** syntax error */
-       UB_SYNTAX,
+       UB_SYNTAX = -3,
        /** DNS service failed */
-       UB_SERVFAIL,
+       UB_SERVFAIL = -4,
        /** fork() failed */
-       UB_FORKFAIL,
+       UB_FORKFAIL = -5,
        /** cfg change after finalize() */
-       UB_AFTERFINAL,
+       UB_AFTERFINAL = -6,
        /** initialization failed (bad settings) */
-       UB_INITFAIL
+       UB_INITFAIL = -7
 };
 
 /** 
index 7d60cd4748f79cb1de78aaf644518f9ef122d981..711caefa3be0232daeb0b7402ae66a601b0e0a93 100644 (file)
@@ -266,13 +266,14 @@ int ub_val_ctx_async(struct ub_val_ctx* ctx, int dothread);
  * and then check, or wait using the wait routine.
  * @param ctx: context.
  * @return: 0 if nothing to read, or nonzero if a result is available.
- *     If nonzero, call ctx_process() to get do any callbacks.
+ *     If nonzero, call ctx_process() to do callbacks.
  */
 int ub_val_ctx_poll(struct ub_val_ctx* ctx);
 
 /**
  * Wait for a context to finish with results. Calls ctx_process() after
- * the wait for you. After the wait, there are no more outstanding queries.
+ * the wait for you. After the wait, there are no more outstanding 
+ * asynchronous queries.
  * @param ctx: context.
  * @return: 0 if OK, else error.
  */
index 22ccd6de8b22616309a23bd1f3e60811d801f5d2..6cd65c4de31308ca2d1e1640620ee86d4cee5669 100644 (file)
@@ -263,6 +263,18 @@ set_ip6_recvpktinfo(int s)
                "disable interface-automatic in config");
        return 0;
 #endif /* defined IPV6_RECVPKTINFO */
+
+#ifdef IP_PKTINFO
+       if(setsockopt(s, IPPROTO_IP, IP_PKTINFO,
+               &on, (socklen_t)sizeof(on)) < 0) {
+               log_err("setsockopt(..., IP_PKTINFO, ...) failed: %s",
+                       strerror(errno));
+       }
+#else
+       log_err("no IP_PKTINFO option, please disable "
+               "interface-automatic in config");
+       return 0;
+#endif /* IP_PKTINFO */
        return 1;
 }
 
index 37735e058286cf7ea7e0f9c4204af9c8ee26ef25..43bd1917f31e18a0b1f480c81110316ad4c4e830 100644 (file)
@@ -178,12 +178,46 @@ comm_point_send_udp_msg(struct comm_point *c, ldns_buffer* packet,
        return 1;
 }
 
+/** print debug ancillary info */
+void p_ancil(const char* str, struct comm_reply* r)
+{
+#if defined(AF_INET6) && defined(IPV6_PKTINFO) && defined(IP_PKTINFO)
+       if(r->srctype != 4 && r->srctype != 6) {
+               log_info("%s: unknown srctype %d", str, r->srctype);
+               return;
+       }
+       if(r->srctype == 6) {
+               char buf[1024];
+               if(inet_ntop(AF_INET6, &r->pktinfo.v6info.ipi6_addr, 
+                       buf, (socklen_t)sizeof(buf)) == 0) {
+                       strncpy(buf, "(inet_ntop error)", sizeof(buf));
+               }
+               buf[sizeof(buf)-1]=0;
+               log_info("%s: %s %d", str, buf, r->pktinfo.v6info.ipi6_ifindex);
+       } else if(r->srctype == 4) {
+               char buf1[1024], buf2[1024];
+               if(inet_ntop(AF_INET, &r->pktinfo.v4info.ipi_addr, 
+                       buf1, (socklen_t)sizeof(buf1)) == 0) {
+                       strncpy(buf1, "(inet_ntop error)", sizeof(buf1));
+               }
+               buf1[sizeof(buf1)-1]=0;
+               if(inet_ntop(AF_INET, &r->pktinfo.v4info.ipi_spec_dst, 
+                       buf2, (socklen_t)sizeof(buf2)) == 0) {
+                       strncpy(buf2, "(inet_ntop error)", sizeof(buf2));
+               }
+               buf2[sizeof(buf2)-1]=0;
+               log_info("%s: %d %s %s", str, r->pktinfo.v4info.ipi_ifindex,
+                       buf1, buf2);
+       }
+#endif
+}
+
 /** send a UDP reply over specified interface*/
 int
 comm_point_send_udp_msg_if(struct comm_point *c, ldns_buffer* packet,
-       struct sockaddr* addr, socklen_t addrlen, void* ifaddr, int ifnum
+       struct sockaddr* addr, socklen_t addrlen, struct comm_reply* r
 {
-#if defined(AF_INET6) && defined(IPV6_PKTINFO)
+#if defined(AF_INET6) && defined(IPV6_PKTINFO) && defined(IP_PKTINFO)
        ssize_t sent;
        struct msghdr msg;
        struct iovec iov[1];
@@ -210,15 +244,29 @@ comm_point_send_udp_msg_if(struct comm_point *c, ldns_buffer* packet,
 
 #ifndef S_SPLINT_S
        cmsg = CMSG_FIRSTHDR(&msg);
-       cmsg->cmsg_level = IPPROTO_IPV6;
-       cmsg->cmsg_type = IPV6_PKTINFO;
-       cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
-       memmove(&((struct in6_pktinfo*)CMSG_DATA(cmsg))->ipi6_addr, 
-               ifaddr, sizeof(struct in6_addr));
-       ((struct in6_pktinfo*)CMSG_DATA(cmsg))->ipi6_ifindex = ifnum;
+       if(r->srctype == 4) {
+               cmsg->cmsg_level = IPPROTO_IP;
+               cmsg->cmsg_type = IP_PKTINFO;
+               memmove(CMSG_DATA(cmsg), &r->pktinfo.v4info,
+                       sizeof(struct in_pktinfo));
+               cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
+       } else if(r->srctype == 6) {
+               cmsg->cmsg_level = IPPROTO_IPV6;
+               cmsg->cmsg_type = IPV6_PKTINFO;
+               memmove(CMSG_DATA(cmsg), &r->pktinfo.v6info,
+                       sizeof(struct in6_pktinfo));
+               cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+       } else {
+               /* try to pass all 0 to use default route */
+               cmsg->cmsg_level = IPPROTO_IPV6;
+               cmsg->cmsg_type = IPV6_PKTINFO;
+               memset(CMSG_DATA(cmsg), 0, sizeof(struct in6_pktinfo));
+               cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
+       }
        msg.msg_controllen = cmsg->cmsg_len;
 #endif /* S_SPLINT_S */
 
+       p_ancil("send_udp over interface", r);
        sent = sendmsg(c->fd, &msg, 0);
        if(sent == -1) {
                verbose(VERB_OPS, "sendmsg failed: %s", strerror(errno));
@@ -238,7 +286,7 @@ comm_point_send_udp_msg_if(struct comm_point *c, ldns_buffer* packet,
 void 
 comm_point_udp_ancil_callback(int fd, short event, void* arg)
 {
-#if defined(AF_INET6) && defined(IPV6_PKTINFO)
+#if defined(AF_INET6) && defined(IPV6_PKTINFO) && defined(IP_PKTINFO)
        struct comm_reply rep;
        struct msghdr msg;
        struct iovec iov[1];
@@ -279,26 +327,35 @@ comm_point_udp_ancil_callback(int fd, short event, void* arg)
        rep.addrlen = msg.msg_namelen;
        ldns_buffer_skip(rep.c->buffer, recv);
        ldns_buffer_flip(rep.c->buffer);
-       rep.ifnum = 0;
+       rep.srctype = 0;
 #ifndef S_SPLINT_S
        for(cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
                cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+               log_info("looking at hdr %d %d (need %d %d or %d %d)",
+                       cmsg->cmsg_level, cmsg->cmsg_type,
+                       IPPROTO_IPV6, IPV6_PKTINFO,
+                       IPPROTO_IP, IP_PKTINFO);
                if( cmsg->cmsg_level == IPPROTO_IPV6 &&
                        cmsg->cmsg_type == IPV6_PKTINFO) {
-                       rep.ifnum = ((struct in6_pktinfo*)CMSG_DATA(cmsg))->
-                               ipi6_ifindex;
-                       memmove(&rep.ifaddr, &((struct in6_pktinfo*)
-                               CMSG_DATA(cmsg))->ipi6_addr, 
-                               sizeof(struct in6_addr));
+                       rep.srctype = 6;
+                       memmove(&rep.pktinfo.v6info, CMSG_DATA(cmsg),
+                               sizeof(struct in6_pktinfo));
+                       break;
+               } else if( cmsg->cmsg_level == IPPROTO_IP &&
+                       cmsg->cmsg_type == IP_PKTINFO) {
+                       rep.srctype = 4;
+                       memmove(&rep.pktinfo.v4info, CMSG_DATA(cmsg),
+                               sizeof(struct in_pktinfo));
+                       break;
                }
        }
+       p_ancil("receive_udp on interface", &rep);
 #endif /* S_SPLINT_S */
        log_assert(fptr_whitelist_comm_point(rep.c->callback));
        if((*rep.c->callback)(rep.c, rep.c->cb_arg, NETEVENT_NOERROR, &rep)) {
                /* send back immediate reply */
                (void)comm_point_send_udp_msg_if(rep.c, rep.c->buffer,
-                       (struct sockaddr*)&rep.addr, rep.addrlen, 
-                       &rep.ifaddr, rep.ifnum);
+                       (struct sockaddr*)&rep.addr, rep.addrlen, &rep);
        }
 #else
        fatal_exit("recvmsg: No support for IPV6_PKTINFO. "
@@ -333,7 +390,7 @@ comm_point_udp_callback(int fd, short event, void* arg)
        }
        ldns_buffer_skip(rep.c->buffer, recv);
        ldns_buffer_flip(rep.c->buffer);
-       rep.ifnum = -1;
+       rep.srctype = 0;
        log_assert(fptr_whitelist_comm_point(rep.c->callback));
        if((*rep.c->callback)(rep.c, rep.c->cb_arg, NETEVENT_NOERROR, &rep)) {
                /* send back immediate reply */
@@ -1007,10 +1064,10 @@ comm_point_send_reply(struct comm_reply *repinfo)
 {
        log_assert(repinfo && repinfo->c);
        if(repinfo->c->type == comm_udp) {
-               if(repinfo->ifnum != -1)
+               if(repinfo->srctype)
                        comm_point_send_udp_msg_if(repinfo->c, 
                        repinfo->c->buffer, (struct sockaddr*)&repinfo->addr, 
-                       repinfo->addrlen, &repinfo->ifaddr, repinfo->ifnum);
+                       repinfo->addrlen, repinfo);
                else
                        comm_point_send_udp_msg(repinfo->c, repinfo->c->buffer,
                        (struct sockaddr*)&repinfo->addr, repinfo->addrlen);
index 15d59218827139de958c51a959a2c39130ddf77c..bff08ebd7d632b912735d378208536fb996ee399 100644 (file)
@@ -98,12 +98,19 @@ struct comm_reply {
        struct sockaddr_storage addr;
        /** length of address */
        socklen_t addrlen;
-#ifdef AF_INET6
-       /** the interface address */
-       struct in6_addr ifaddr;
+       /** return type 0 (none), 4(IP4), 6(IP6) */
+       int srctype;
+       /** the return source interface data */
+       union {
+#ifdef IPV6_PKTINFO
+               struct in6_pktinfo v6info;
 #endif
-       /** the interface received (for UDPautomaticinterface) or 0 */
-       int ifnum;
+#ifdef IP_PKTINFO
+               struct in_pktinfo v4info;
+#endif
+       }       
+               /** variable with return source data */
+               pktinfo;
 };
 
 /**