A => LICENSE +374 -0
@@ 1,374 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
+
A => README.md +11 -0
@@ 1,11 @@
+# Netlink library for Hare
+
+This is a highly WIP library for [Hare](https://harelang.org/). Still
+experimenting with Hare code and API design.
+
+This may or may not work with latest Hare stdlib, since it's still changing
+often.
+
+# License
+
+See [LICENSE](LICENSE).
A => cmd/iproute2/main.ha +178 -0
@@ 1,178 @@
+use bufio;
+use fmt;
+use io;
+use net;
+use net::ip;
+use nl = netlink;
+use netlink::core;
+use nla = netlink::route::addr;
+use netlink::serde;
+use os;
+use rt;
+use strconv;
+use strings;
+use log;
+
+fn lifetime_str(lft: u32) str = {
+ if (lft == 0xFFFFFFFF) {
+ return fmt::asprint("forever");
+ } else {
+ return fmt::asprint(lft);
+ };
+};
+
+fn links_free(links: []*nl::link) void = {
+ for (let i = 0z; i < len(links); i += 1) {
+ nl::link_free(*links[i]);
+ free(links[i]);
+ };
+ free(links);
+};
+
+export fn main() void = {
+ const links = nl::link_list()!;
+ defer links_free(links);
+
+ log::printfln("found {} links", len(links));
+ for (let i = 0z; i < len(links); i += 1) {
+ const link = links[i];
+ log::printfln("\t{}: {}", link.index, link.name);
+ };
+ add_interface();
+ // del_interface();
+ // list_addresses();
+ // add_address();
+};
+
+fn del_interface() void = {
+ const hdl = core::open(core::family::NETLINK_ROUTE)!;
+ defer core::close(&hdl)!;
+
+ const index = strconv::stou64(os::args[1]) as u64;
+ log::printfln("deleting interface {}", index);
+ const link = nl::link {
+ index = index: u32,
+ ...
+ };
+ match (nl::hlink_del(&hdl, link)) {
+ case let err: core::error =>
+ log::fatalf("failed to delete interface {}: {}", index, nl::strerror(err));
+ case void => void;
+ };
+};
+
+fn add_interface() void = {
+ const hdl = core::open(core::family::NETLINK_ROUTE)!;
+ defer core::close(&hdl)!;
+
+ const name = os::args[1];
+ log::printfln("creating interface {}", name);
+ const link = nl::link {
+ name = name,
+ txqueuelen = 100,
+ ...
+ };
+ match (nl::hlink_add(&hdl, link)) {
+ case let err: nl::error =>
+ log::fatalf("failed to create interface {}: {}", name, nl::strerror(err));
+ case void => void;
+ };
+};
+
+fn add_address() void = {
+ const hdl = core::open(core::family::NETLINK_ROUTE)!;
+ defer core::close(&hdl)!;
+
+ static let wbuf: [65535]u8 = [0...];
+ const req = core::newrequest(wbuf, core::nl_type_t::RTM_NEWADDR,
+ core::nl_flags_t::NLM_F_REQUEST|
+ core::nl_flags_t::NLM_F_ACK|
+ core::nl_flags_t::NLM_F_CREATE|
+ core::nl_flags_t::NLM_F_EXCL, 0, 0);
+
+ const ifa = nla::ifaddrmsg {
+ ifa_family = rt::AF_INET: u8,
+ ifa_prefixlen = 25,
+ ifa_flags = 0,
+ ifa_scope = nla::rt_scope_t::RT_SCOPE_UNIVERSE,
+ ifa_index = 10,
+ };
+ core::request_write(&req, ifa);
+
+ const addr = [172u8, 0, 0, 4]: nla::rta_ifa_address;
+ const local = [172u8, 0, 0, 4]: nla::rta_ifa_local;
+ core::request_write(&req, addr);
+ core::request_write(&req, local);
+
+ const sendlen = core::send_request(&hdl, &req) as size;
+ fmt::println("sent bytes", sendlen)!;
+
+ let iter = core::response_iter(&hdl);
+
+ match (core::response_next(&iter)) {
+ case core::ACK =>
+ fmt::println("success!")!;
+ case void =>
+ fmt::println("expected ACK, not EOF")!;
+ case let err: core::error =>
+ fmt::println("error while adding address:", core::strerror(err))!;
+ };
+};
+
+fn list_addresses() void = {
+ const hdl = core::open(core::family::NETLINK_ROUTE)!;
+ defer core::close(&hdl)!;
+
+ static let wbuf: [65535]u8 = [0...];
+ const req = core::newrequest(wbuf, core::nl_type_t::RTM_GETADDR,
+ core::nl_flags_t::NLM_F_REQUEST|
+ core::nl_flags_t::NLM_F_ROOT, 0, 0);
+
+ const ifa = nla::ifaddrmsg {
+ ...
+ };
+ core::request_write(&req, ifa);
+
+ const sendlen = core::send_request(&hdl, &req) as size;
+ fmt::println("sent bytes", sendlen)!;
+
+ let iter = core::response_iter(&hdl);
+
+ for (true) {
+ const resp = match (core::response_next(&iter)) {
+ case let resp: core::response => yield resp;
+ case void =>
+ fmt::println("done listing addresses")!;
+ return;
+ case let err: core::error =>
+ fmt::println("error while listing addresses", core::strerror(err))!;
+ return;
+ };
+
+ let hdr = resp.hdr;
+ if (hdr.nlmsg_type != core::nl_type_t::RTM_NEWADDR) {
+ fmt::println("expected address")!;
+ return;
+ };
+
+ const rd = resp.rd;
+ const ifaddr = nla::read_ifaddrmsg(&rd);
+ const rest = serde::borrowend(&rd);
+ fmt::printf("family: {}, index: {}, prefix length: {}\n", ifaddr.ifa_family, ifaddr.ifa_index, ifaddr.ifa_prefixlen)!;
+
+ let addr = nl::addr { ... };
+ let iter = nla::rtattr_iter(rest);
+ for (true) {
+ const attr = match (nla::rtattr_next(&iter)) {
+ case let attr: nla::rtattr =>
+ yield attr;
+ case void =>
+ break;
+ };
+
+ nl::fill_addr_from_rtattr(&addr, ifaddr, attr);
+ };
+ fmt::printfln("address is {}", net::ip::string(addr.prefix))!;
+ fmt::println("")!;
+ };
+};
A => mac/buffer.ha +46 -0
@@ 1,46 @@
+use bufio;
+use endian;
+use fmt;
+use io;
+use strconv;
+use strings;
+
+export type buffer = [6]u8;
+
+export type invalid = !void;
+
+export fn fmt(h: io::handle, m: buffer) (io::error | size) = {
+ let z = 0z;
+ z += fmt::fprintf(h, "{:02x}", m[0])?;
+ for (let i = 1; i < 6; i += 1) {
+ z += fmt::fprintf(h, ":{:02x}", m[i])?;
+ };
+ return z;
+};
+
+export fn parse(s: str) (buffer | invalid) = {
+ if (len(s) != 17) {
+ return invalid;
+ };
+
+ let buf: buffer = [0u8...];
+ let i = 0;
+
+ let tok = strings::tokenize(s, ":");
+ for (true) {
+ let s = match (strings::next_token(&tok)) {
+ case let s: str =>
+ yield s;
+ case void =>
+ break;
+ };
+
+ match (strconv::stou8b(s, 8)) {
+ case let val: u8 =>
+ buf[i] = val;
+ i += 1;
+ case => return invalid;
+ };
+ };
+ return buf;
+};
A => netlink/addr.ha +59 -0
@@ 1,59 @@
+use endian;
+use fmt;
+use net::ip;
+use nla = netlink::route::addr;
+use strings;
+
+export type addr = struct {
+ prefix: ip::subnet,
+ label: str,
+ flags: u32,
+ scope: nla::rt_scope_t,
+ broadcast: ip::addr,
+ // Prefered lifetime
+ prefered_lft: u32,
+ // Valid lifetime
+ valid_lft: u32,
+};
+
+fn makeprefix(addr: ip::addr, prefixlen: u8) ip::subnet = {
+ match (addr) {
+ case let addr4: ip::addr4 =>
+ const subnet = ip::subnet {
+ addr = addr4,
+ mask = [0...]: ip::addr4,
+ };
+ ip::fillmask(subnet.mask as ip::addr4, prefixlen);
+ return subnet;
+ case let addr6: ip::addr6 =>
+ const subnet = ip::subnet {
+ addr = addr6,
+ mask = [0...]: ip::addr6,
+ };
+ ip::fillmask(subnet.mask as ip::addr6, prefixlen);
+ return subnet;
+ };
+};
+
+export fn fill_addr_from_rtattr(a: *addr, ifaddr: nla::ifaddrmsg, attr: nla::rtattr) void = {
+ a.scope = ifaddr.ifa_scope;
+ if (a.flags == 0) {
+ a.flags = ifaddr.ifa_flags;
+ };
+ match (attr) {
+ case let addr: nla::rta_ifa_address =>
+ const prefix = makeprefix(addr, ifaddr.ifa_prefixlen);
+ a.prefix = prefix;
+ case let label: nla::rta_ifa_label =>
+ a.label = label;
+ case let broadcast: nla::rta_ifa_broadcast =>
+ a.broadcast = broadcast;
+ case let ci: nla::rta_ifa_cacheinfo =>
+ a.prefered_lft = ci.ifa_prefered;
+ a.valid_lft = ci.ifa_valid;
+ case let flags: nla::rta_ifa_flags =>
+ a.flags = flags;
+ case =>
+ void;
+ };
+};
A => netlink/core/+freebsd/netlink.ha +4 -0
@@ 1,4 @@
+export type family = enum int {
+ NETLINK_ROUTE = 0, // Routing/device hook
+ NETLINK_GENERIC = 16, // Generic netlink (dynamic families)
+};
A => netlink/core/+linux/if.ha +43 -0
@@ 1,43 @@
+export type net_device_flags = enum u32 {
+ // Interface is up.
+ IFF_UP = 1 << 0,
+ // Broadcast address valid.
+ IFF_BROADCAST = 1 << 1,
+ // Turn on debugging.
+ IFF_DEBUG = 1 << 2,
+ // Is a loopback interface.
+ IFF_LOOPBACK = 1 << 3,
+ // Is a point to point link.
+ IFF_POINTTOPOINT = 1 << 4,
+ // Avoid use of trailers.
+ IFF_NOTRAILERS = 1 << 5,
+ // OPER_UP
+ IFF_RUNNING = 1 << 6,
+ IFF_NOARP = 1 << 7,
+ // Receive all packets.
+ IFF_PROMISC = 1 << 8,
+ IFF_ALLMULTI = 1 << 9,
+ // Master of a load balancer.
+ IFF_MASTER = 1 << 10,
+ // Slave of a load balancer.
+ IFF_SLAVE = 1 << 11,
+ IFF_MULTICAST = 1 << 12,
+ IFF_PORTSEL = 1 << 13,
+ IFF_AUTOMEDIA = 1 << 14,
+ IFF_DYNAMIC = 1 << 15,
+ // Driver signals L1 up.
+ IFF_LOWER_UP = 1 << 16,
+ // Driver signals dormant.
+ IFF_DORMANT = 1 << 17,
+ IFF_ECHO = 1 << 18,
+};
+
+export type operstate = enum {
+ IF_OPER_UNKNOWN = 0,
+ IF_OPER_NOTPRESENT = 1,
+ IF_OPER_DOWN = 2,
+ IF_OPER_LOWERLAYERDOWN = 3,
+ IF_OPER_TESTING = 4,
+ IF_OPER_DORMANT = 5,
+ IF_OPER_UP = 6,
+};
A => netlink/core/+linux/if_addr.ha +68 -0
@@ 1,68 @@
+export def IFA_UNSPEC: u16 = 0;
+
+// The prefix address. Same as IFA_LOCAL except for point-to-point interfaces,
+// then IFA_ADDRESS is DESTINATION address.
+export def IFA_ADDRESS: u16 = 1;
+export def IFA_LOCAL: u16 = 2; // Local address
+export def IFA_LABEL: u16 = 3; // Name of the interface
+export def IFA_BROADCAST: u16 = 4; // Broadcast address
+export def IFA_ANYCAST: u16 = 5; // Anycast address
+export def IFA_CACHEINFO: u16 = 6; // Address information
+export def IFA_MULTICAST: u16 = 7;
+// If IFA_FLAGS is present, then the value ifaddrmsg.ifa_flags will be ignored.
+export def IFA_FLAGS: u16 = 8;
+export def IFA_RT_PRIORITY: u32 = 9; // u32, priority/metric for prefix route
+export def IFA_TARGET_NETNSID: u16 = 10;
+export def IFA_PROTO: u8 = 11; // u8, address protocol
+
+// ifa_flags
+
+export def IFA_F_SECONDARY: int = 0x01;
+export def IFA_F_TEMPORARY: int = IFA_F_SECONDARY;
+
+// (IPv6 only) Don't perform Duplicate Address Detection when adding this
+// address. See https://www.rfc-editor.org/rfc/rfc4862#page-12.
+export def IFA_F_NODAD: int = 0x02;
+
+// (IPv6 only) When performing Duplicate Address Detection, use the optimistic
+// variant as defined in https://www.rfc-editor.org/rfc/rfc4429.
+export def IFA_F_OPTIMISTIC: int = 0x04;
+
+// (IPv6 only) Address has failed duplicate address detection.
+export def IFA_F_DADFAILED: int = 0x08;
+
+// (IPv6 only) Designates this address the "home address" as defined in
+// https://www.rfc-editor.org/rfc/rfc6275.
+export def IFA_F_HOMEADDRESS: int = 0x10;
+
+// Address' prefered lifetime has expired.
+export def IFA_F_DEPRECATED: int = 0x20;
+
+// (IPv6 only) Address has not passed duplicate address detection.
+export def IFA_F_TENTATIVE: int = 0x40;
+export def IFA_F_PERMANENT: int = 0x80;
+export def IFA_F_MANAGETEMPADDR: int = 0x100;
+
+// Don't create a route for the network prefix of the added address, and don't
+// search for one to delete when removing the address.
+export def IFA_F_NOPREFIXROUTE: int = 0x200;
+export def IFA_F_MCAUTOJOIN: int = 0x400;
+export def IFA_F_STABLE_PRIVACY: int = 0x800;
+
+// See here for lifetime: https://www.rfc-editor.org/rfc/rfc4862#section-5.5.4
+export type ifa_cacheinfo = struct {
+ ifa_prefered: u32, // Prefered lifetime
+ ifa_valid: u32, // Valid lifetime
+ cstamp: u32, // created timestamp, hunderdths of seconds
+ tstamp: u32, // updated timestamp, hundredths of seconds
+};
+
+// ifa_proto
+
+export def IFAPROT_UNSPEC: int = 0;
+// Loopback
+export def IFAPROT_KERNEL_LO: int = 0;
+// Set by kernel from router annoucement
+export def IFAPROT_KERNEL_RA: int = 0;
+// Link-local set by kernel
+export def IFAPROT_KERNEL_LL: int = 0;
A => netlink/core/+linux/if_arp.ha +15 -0
@@ 1,15 @@
+export def ARPHRD_NETROM: int = 0; // from KA9Q: NET/ROM pseudo
+export def ARPHRD_ETHER: int = 1; // Ethernet 10Mbps
+export def ARPHRD_EETHER: int = 2; // Experimental Ethernet
+export def ARPHRD_AX25: int = 3; // AX.25 Level 2
+export def ARPHRD_PRONET: int = 4; // PROnet token ring
+export def ARPHRD_CHAOS: int = 5; // Chaosnet
+export def ARPHRD_IEEE802: int = 6; // IEEE 802.2 Ethernet/TR/TB
+export def ARPHRD_ARCNET: int = 7; // ARCnet
+export def ARPHRD_APPLETLK: int = 8; // APPLEtalk
+export def ARPHRD_DLCI: int = 15; // Frame Relay DLCI
+export def ARPHRD_ATM: int = 19; // ATM
+export def ARPHRD_METRICOM: int = 23; // Metricom STRIP (new IANA id)
+export def ARPHRD_IEEE1394: int = 24; // IEEE 1394 IPv4 - RFC 2734
+export def ARPHRD_EUI64: int = 27; // EUI-64
+export def ARPHRD_INFINIBAND: int = 32; // InfiniBand
A => netlink/core/+linux/netlink.ha +24 -0
@@ 1,24 @@
+export type family = enum int {
+ NETLINK_ROUTE = 0, // Routing/device hook
+ NETLINK_UNUSED = 1, // Unused number
+ NETLINK_USERSOCK = 2, // Reserved for user mode socket protocols
+ NETLINK_FIREWALL = 3, // Unused number, formerly ip_queue
+ NETLINK_SOCK_DIAG = 4, // socket monitoring
+ NETLINK_NFLOG = 5, // netfilter/iptables ULOG
+ NETLINK_XFRM = 6, // ipsec
+ NETLINK_SELINUX = 7, // SELinux event notifications
+ NETLINK_ISCSI = 8, // Open-iSCSI
+ NETLINK_AUDIT = 9, // auditing
+ NETLINK_FIB_LOOKUP = 10,
+ NETLINK_CONNECTOR = 11,
+ NETLINK_NETFILTER = 12, // netfilter subsystem
+ NETLINK_IP6_FW = 13,
+ NETLINK_DNRTMSG = 14, // DECnet routing messages (obsolete)
+ NETLINK_KOBJECT_UEVENT = 15, // Kernel messages to userspace
+ NETLINK_GENERIC = 16,
+ NETLINK_SCSITRANSPORT = 18, // SCSI Transports
+ NETLINK_ECRYPTFS = 19,
+ NETLINK_RDMA = 20,
+ NETLINK_CRYPTO = 21, // Crypto layer
+ NETLINK_SMC = 22, // SMC monitoring
+};
A => netlink/core/error.ha +15 -0
@@ 1,15 @@
+use errors;
+use net;
+
+export type truncated = !void;
+export type error = !(net::error | nlmsgerr | truncated);
+
+export fn strerror(err: error) str = match (err) {
+ case let err: net::error =>
+ return net::strerror(err);
+ case truncated =>
+ return "message truncated";
+ case let nle: nlmsgerr =>
+ return errors::strerror(errors::errno(-nle.error));
+};
+
A => netlink/core/handle.ha +117 -0
@@ 1,117 @@
+use bufio;
+use errors;
+use io;
+use net;
+use netlink::serde;
+use rt;
+
+export type handle = struct {
+ sock: net::socket,
+ msghdr: net::msghdr,
+ sa: *rt::sockaddr,
+};
+
+// Opens a Netlink socket connection.
+//
+// The user must call [[close]] when they are done using this handle.
+export fn open(family: family) (handle | error) = {
+ const sa = alloc(rt::sockaddr {
+ nl = rt::sockaddr_nl {
+ nl_family = rt::AF_NETLINK,
+ // TODO pid and groups
+ // nl_groups = 1 << (RTNLGRP_LINK - 1),
+ ...
+ },
+ });
+ const sockfd = match (rt::socket(rt::AF_NETLINK: int, rt::SOCK_RAW, family)) {
+ case let err: rt::errno =>
+ return errors::errno(err);
+ case let fd: int =>
+ yield fd;
+ };
+
+ match (rt::bind(sockfd, sa, size(rt::sockaddr_nl): u32)) {
+ case int => void;
+ case let err: rt::errno => return errors::errno(err);
+ };
+
+ // TODO: Connect?
+
+ const msghdr = net::newmsg();
+ net::setname(&msghdr, &sa.nl, size(rt::sockaddr_nl));
+
+ return handle {
+ sock = io::fdopen(sockfd),
+ msghdr = msghdr,
+ sa = sa,
+ };
+};
+
+// Frees resources associated with a [[handle]].
+export fn close(hdl: *handle) (void | net::error) = {
+ net::finish(&hdl.msghdr);
+ free(hdl.sa);
+ return net::close(hdl.sock);
+};
+
+// Sends a [[[]u8]] to the Netlink socket.
+export fn send_raw(hdl: *handle, buf: []u8) (size | net::error) = {
+ net::reset(&hdl.msghdr);
+ net::addvector(&hdl.msghdr, buf);
+ return net::sendmsg(hdl.sock, &hdl.msghdr);
+};
+
+// Sends a [[request]] to the Netlink socket.
+export fn send_request(hdl: *handle, req: *request) (size | net::error) = {
+ return send_raw(hdl, request_buffer(req));
+};
+
+// Receives a single message from the Netlink socket.
+// XXX: This should probably be returning a response iterator of some kind
+export fn recv_raw(hdl: *handle, buf: []u8) (size | net::error) = {
+ net::reset(&hdl.msghdr);
+ net::addvector(&hdl.msghdr, buf);
+ return net::recvmsg(hdl.sock, &hdl.msghdr);
+};
+
+export type response_iterator = struct {
+ hdl: *handle,
+ rd: bufio::memstream,
+};
+
+export fn response_iter(hdl: *handle) response_iterator = {
+ static let buf: []u8 = [];
+ return response_iterator {
+ hdl = hdl,
+ rd = bufio::fixed(buf, io::mode::READ),
+ };
+};
+
+export fn response_next(iter: *response_iterator) (response | ACK | void | error) = {
+ if (serde::is_at_end(&iter.rd)) {
+ static let buf: [65535]u8 = [0...];
+ const rlen = recv_raw(iter.hdl, buf)?;
+
+ // NOTE: If the buffer is too small, the reply will have MSG_TRUNC which
+ // means the data will be discarded (with no way of getting it back
+ // really) The next recvmsg will give the next nlmsg instead of
+ // continuing..
+ const flags = net::getflags(&iter.hdl.msghdr);
+ if ((flags & rt::MSG_TRUNC) > 0) {
+ return truncated;
+ };
+
+ iter.rd = bufio::fixed(buf[..rlen], io::mode::READ);
+ };
+
+ match (read_response(&iter.rd)) {
+ case let resp: response =>
+ return resp;
+ case ACK =>
+ return ACK;
+ case void =>
+ return void;
+ case let err: nlmsgerr =>
+ return err;
+ };
+};
A => netlink/core/msg.ha +163 -0
@@ 1,163 @@
+use bufio;
+use io;
+use nla = netlink::route::addr;
+use nll = netlink::route::link;
+use netlink::serde;
+
+use fmt;
+
+// Various flags set on a message.
+//
+// Manpage: netlink(7).
+// Linux: include/linux/netlink.h
+// Freebsd: /sys/netlink/netlink.h
+export type nl_flags_t = enum u16 {
+ // Standard flag bits
+ NLM_F_REQUEST = 0x01, // It is request message
+ NLM_F_MULTI = 0x02, // The message is part of a multipart message terminated by NLMSG_DONE
+ NLM_F_ACK = 0x04, // Request for an acknowledgement on success
+ NLM_F_ECHO = 0x08, // Echo this request
+ NLM_F_DUMP_INTR = 0x10, // Dump was inconsistent due to sequence change
+ NLM_F_DUMP_FILTERED = 0x20, // Dump was filtered as requested
+
+ // Modifiers to GET request
+ NLM_F_ROOT = 0x100, // Specify tree root
+ NLM_F_MATCH = 0x200, // Return all matching
+ NLM_F_ATOMIC = 0x400, // Atomic GET
+ NLM_F_DUMP = NLM_F_ROOT|NLM_F_MATCH,
+
+ // Modifiers to NEW request
+ NLM_F_REPLACE = 0x100, // Override existing
+ NLM_F_EXCL = 0x200, // Do not touch, if it exists
+ NLM_F_CREATE = 0x400, // Create, if it does not exist
+ NLM_F_APPEND = 0x800, // Add to end of list
+
+ // Modifiers to DELETE request
+ NLM_F_NONREC = 0x100, // Do not delete recursively
+ NLM_F_BULK = 0x200, // Delete multiple objects
+
+ // Flags for ACK message
+ NLM_F_CAPPED = 0x100, // Request was capped
+ NLM_F_ACK_TLVS = 0x200, // extended ACK TVLs were included
+
+};
+
+export type nlmsgerr = struct {
+ // If nlmsg_type == NLMSG_ERROR and error == 0, then it's an ACK.
+ error: i32,
+ msg: nlmsghdr,
+};
+
+export type nl_type_t = enum u16 {
+ NLMSG_NOOP = 1, // Nothing
+ NLMSG_ERROR = 2, // Error
+ NLMSG_DONE = 3, // End of a dump
+ NLMSG_OVERRUN = 4, // Data lost
+
+ // Netlink route messages
+ RTM_NEWLINK = 16, // Create a network interface.
+ RTM_DELLINK = 17, // Delete a network interface.
+ RTM_GETLINK = 18, // Get information from one or multiple network interface(s).
+ RTM_NEWADDR = 20, // Add an IP address
+ RTM_DELADDR = 21, // Delete an IP address
+ RTM_GETADDR = 22, // Get information about an IP address
+};
+
+export type nlmsghdr = struct {
+ // Length of message including header
+ nlmsg_len: u32,
+ // Type of message content
+ nlmsg_type: nl_type_t,
+ // Additional flags
+ nlmsg_flags: u16,
+ // Sequence number
+ nlmsg_seq: u32,
+ // Sender port ID
+ nlmsg_pid: u32,
+};
+
+export type item = (
+ nla::ifaddrmsg |
+ nla::rtattr |
+ nll::ifinfomsg |
+ nll::rtattr
+);
+
+export type request = struct {
+ rd: bufio::memstream,
+ hdr: *nlmsghdr,
+};
+
+export fn newrequest(buf: []u8, typ: nl_type_t, flags: nl_flags_t, seq: u32, pid: u32) request = {
+ const rd = bufio::fixed(buf, io::mode::RDWR);
+ const hdr = borrowmsghdr(&rd);
+ *hdr = nlmsghdr {
+ nlmsg_type = typ,
+ nlmsg_flags = flags,
+ nlmsg_seq = seq,
+ nlmsg_pid = pid,
+ ...
+ };
+ return request {
+ rd = rd,
+ hdr = hdr,
+ };
+};
+
+export fn request_write(req: *request, item: item) void = {
+ match (item) {
+ case let ifa: nla::ifaddrmsg =>
+ nla::write_ifaddrmsg(&req.rd, ifa);
+ case let rtattr: nla::rtattr =>
+ nla::write_rtattr(&req.rd, rtattr);
+ case let ifi: nll::ifinfomsg =>
+ nll::write_ifinfomsg(&req.rd, ifi);
+ case let rtattr: nll::rtattr =>
+ nll::write_rtattr(&req.rd, rtattr);
+ };
+ req.hdr.nlmsg_len = io::tell(&req.rd)!: u32;
+};
+
+export fn request_buffer(req: *request) []u8 = {
+ return bufio::buffer(&req.rd)[..req.hdr.nlmsg_len];
+};
+
+export type response = struct {
+ hdr: nlmsghdr,
+ rd: bufio::memstream,
+};
+
+export type ACK = void;
+
+export fn read_response(rd: *bufio::memstream) (response | ACK | void | nlmsgerr) = {
+ const hdr = *borrowmsghdr(rd);
+ if (hdr.nlmsg_type == nl_type_t::NLMSG_DONE) {
+ return void;
+ };
+
+ // 4-bytes aligned
+ assert(hdr.nlmsg_len % 4 == 0);
+
+ const payload = serde::borrow(rd, hdr.nlmsg_len-size(nlmsghdr));
+ if (hdr.nlmsg_type == nl_type_t::NLMSG_ERROR) {
+ fmt::printfln("HI {} {}", hdr.nlmsg_len, len(bufio::buffer(rd)))!;
+ const nle = borrowmsgerr(rd);
+ if (nle.error == 0) {
+ return ACK;
+ };
+ return *nle;
+ };
+
+ return response {
+ hdr = hdr,
+ rd = bufio::fixed(payload, io::mode::READ),
+ };
+};
+
+fn borrowmsgerr(rd: *bufio::memstream) *nlmsgerr = {
+ return serde::borrowptr(rd, size(nlmsgerr)): *nlmsgerr;
+};
+
+fn borrowmsghdr(rd: *bufio::memstream) *nlmsghdr = {
+ return serde::borrowptr(rd, size(nlmsghdr)): *nlmsghdr;
+};
A => netlink/error.ha +21 -0
@@ 1,21 @@
+use net;
+use netlink::core;
+
+// Invalid reply
+export type invalid = !void;
+export type emptyname = !void;
+
+export type error = !(invalid | emptyname | net::error | core::error);
+
+export fn strerror(err: error) str = {
+ match (err) {
+ case let err: net::error =>
+ return net::strerror(err);
+ case let err: core::error =>
+ return core::strerror(err);
+ case invalid =>
+ return "reply is invalid";
+ case emptyname =>
+ return "name is empty";
+ };
+};
A => netlink/link.ha +249 -0
@@ 1,249 @@
+use bufio;
+use io;
+use endian;
+use fmt;
+use net;
+use net::ip;
+use netlink::core;
+use nll = netlink::route::link;
+use netlink::serde;
+use strconv;
+use strings;
+use strio;
+
+export type dummy_link = struct {
+ attrs: link,
+};
+
+// TODO: Generic link attributes vs interface specific attributes.
+export type link = struct {
+ index: u32,
+ name: str,
+ mtu: int,
+ mac: ip::mac,
+ broadcast: ip::mac,
+ txqueuelen: int,
+ numtxqueues: int,
+ numrxqueues: int,
+ // operstate: operstate,
+ // flags: net_device_flags,
+ rawflags: u32,
+};
+
+// Frees allocated resources.
+export fn link_free(link: link) void = {
+ free(link.mac);
+ free(link.broadcast);
+ free(link.name);
+};
+
+// The caller must free each individual links with [[link_free]] and free the
+// underlying array.
+export fn link_list() ([]*link | error) = {
+ const hdl = core::open(core::family::NETLINK_ROUTE)!;
+ defer core::close(&hdl)!;
+ return hlink_list(&hdl);
+};
+
+export fn link_add(link: link) (void | error) = {
+ const hdl = core::open(core::family::NETLINK_ROUTE)!;
+ defer core::close(&hdl)!;
+ return hlink_add(&hdl, link);
+};
+
+export fn link_del(link: link) (void | error) = {
+ const hdl = core::open(core::family::NETLINK_ROUTE)!;
+ defer core::close(&hdl)!;
+ return hlink_del(&hdl, link);
+};
+
+// The caller must free each individual links with [[link_free]] and free the
+// underlying array.
+export fn hlink_list(hdl: *core::handle) ([]*link | error) = {
+ static let wbuf: [65535]u8 = [0...];
+ const req = core::newrequest(wbuf, core::nl_type_t::RTM_GETLINK,
+ core::nl_flags_t::NLM_F_REQUEST|
+ core::nl_flags_t::NLM_F_ROOT, 0, 0);
+
+ const ifi = nll::ifinfomsg {
+ ...
+ };
+ core::request_write(&req, ifi);
+
+
+ match (core::send_request(hdl, &req)) {
+ case size => void;
+ case let err: net::error =>
+ return err;
+ };
+
+ let iter = core::response_iter(hdl);
+
+ let links: []*link = [];
+ for (true) {
+ const resp = match (core::response_next(&iter)) {
+ case let resp: core::response => yield resp;
+ case void =>
+ return links;
+ case let err: core::nlmsgerr =>
+ return err;
+ };
+
+ const hdr = resp.hdr;
+ if (hdr.nlmsg_type != core::nl_type_t::RTM_NEWLINK) {
+ return invalid;
+ };
+
+ const rd = resp.rd;
+ const rifi = nll::read_ifinfomsg(&rd);
+ const rest = serde::borrowend(&rd);
+
+ let link = alloc(link { ... });
+ let iter = nll::rtattr_iter(rest);
+ for (true) {
+ const attr = match (nll::rtattr_next(&iter)) {
+ case let attr: nll::rtattr =>
+ yield attr;
+ case void =>
+ break;
+ };
+
+ fill_link_from_rtattr(link, rifi, attr);
+ };
+ append(links, link);
+ };
+ return links;
+};
+
+export fn hlink_add(hdl: *core::handle, link: link) (void | error) = {
+ static let wbuf: [65535]u8 = [0...];
+ const req = core::newrequest(wbuf, core::nl_type_t::RTM_NEWLINK,
+ core::nl_flags_t::NLM_F_REQUEST|
+ core::nl_flags_t::NLM_F_ACK|
+ core::nl_flags_t::NLM_F_CREATE|
+ core::nl_flags_t::NLM_F_EXCL, 0, 0);
+
+ const ifi = nll::ifinfomsg {
+ ifi_index = link.index: int,
+ ...
+ };
+ core::request_write(&req, ifi);
+
+ if (len(link.name) == 0) {
+ return emptyname;
+ };
+
+ core::request_write(&req, link.name: nll::rta_ifla_ifname: nll::rtattr);
+
+ if (link.mtu > 0) {
+ const mtu = link.mtu: nll::rta_ifla_mtu: nll::rtattr;
+ core::request_write(&req, mtu);
+ };
+
+ if (link.txqueuelen > 0) {
+ const txqlen = link.txqueuelen: nll::rta_ifla_txqlen: nll::rtattr;
+ core::request_write(&req, txqlen);
+ };
+
+ if (link.numtxqueues > 0) {
+ const numtxqueues = link.numtxqueues: nll::rta_ifla_num_tx_queues: nll::rtattr;
+ core::request_write(&req, numtxqueues);
+ };
+
+ if (link.numrxqueues > 0) {
+ const numrxqueues = link.numrxqueues: nll::rta_ifla_num_rx_queues: nll::rtattr;
+ core::request_write(&req, numrxqueues);
+ };
+
+ // XXX: Heh, could be made simpler perhaps
+ let bufinfo: [1024]u8 = [0...];
+ let rdinfo = bufio::fixed(bufinfo, io::mode::WRITE);
+ const kind = "dummy": nll::rta_ifla_info_kind;
+ nll::write_rtattr_linkinfo(&rdinfo, kind);
+ const infolength = io::tell(&rdinfo)!;
+ const linkinfo = nll::rtattr_linkinfo_iter(bufinfo[..infolength]);
+ core::request_write(&req, linkinfo);
+
+ match (core::send_request(hdl, &req)) {
+ case size => void;
+ case let err: net::error =>
+ return err;
+ };
+
+ const iter = core::response_iter(hdl);
+
+ match (core::response_next(&iter)) {
+ case core::ACK => void;
+ case void =>
+ return invalid;
+ case let err: core::error =>
+ return err;
+ };
+};
+
+export fn hlink_del(hdl: *core::handle, link: link) (void | error) = {
+ static let wbuf: [65535]u8 = [0...];
+ const req = core::newrequest(wbuf, core::nl_type_t::RTM_DELLINK,
+ core::nl_flags_t::NLM_F_REQUEST|
+ core::nl_flags_t::NLM_F_ACK, 0, 0);
+
+ const ifi = nll::ifinfomsg {
+ ifi_index = link.index: int,
+ ...
+ };
+ core::request_write(&req, ifi);
+
+ match (core::send_request(hdl, &req)) {
+ case size => void;
+ case let err: net::error =>
+ return err;
+ };
+
+ const iter = core::response_iter(hdl);
+
+ match (core::response_next(&iter)) {
+ case core::ACK => void;
+ case void =>
+ return invalid;
+ case let err: core::error =>
+ return err;
+ };
+};
+
+export fn fill_link_from_rtattr(link: *link, ifi: nll::ifinfomsg, attr: nll::rtattr) void = {
+ link.index = ifi.ifi_index: u32;
+ // if (link.flags == 0) {
+ // link.flags = ifi.ifi_flags: net_device_flags;
+ // };
+ match (attr) {
+ case let addr: nll::rta_ifla_address =>
+ fmt::printfln("TODO address")!;
+ case let broadcast: nll::rta_ifla_broadcast =>
+ fmt::printfln("TODO broadcast")!;
+ case let ifname: nll::rta_ifla_ifname =>
+ link.name = strings::dup(ifname);
+ case let mtu: nll::rta_ifla_mtu =>
+ link.mtu = mtu: int;
+ case let linkinfo: nll::rta_ifla_linkinfo =>
+ for (true) {
+ const nested = match (nll::rtattr_linkinfo_next(&linkinfo)) {
+ case let attr: nll::rtattr_linkinfo =>
+ yield attr;
+ case void =>
+ break;
+ };
+
+ match (nested) {
+ case let kind: nll::rta_ifla_info_kind =>
+ fmt::println(kind)!;
+ case let unknown: nll::rta_unknown =>
+ fmt::println("unknown")!;
+ continue;
+ case =>
+ abort();
+ };
+ };
+ case =>
+ return;
+ };
+};
A => netlink/route/addr/+test.ha +64 -0
@@ 1,64 @@
+use bufio;
+use bytes;
+use io;
+use net::ip;
+
+@test fn rtattr() void = {
+ let buf: [4096]u8 = [0u8...];
+ let rd = bufio::fixed(buf, io::mode::RDWR);
+
+ const addr = [127u8, 0, 0, 1]: rta_ifa_address;
+ const local = [127u8, 0, 0, 1]: rta_ifa_local;
+ const label = "mylabel": rta_ifa_label;
+ const broadcast = [127u8, 0, 0, 255]: rta_ifa_broadcast;
+ const flags = 1: rta_ifa_flags;
+ const unknown = rta_unknown {
+ typ = 10,
+ data = [1,2,3],
+ };
+ write_rtattr(&rd, addr);
+ write_rtattr(&rd, local);
+ write_rtattr(&rd, label);
+ write_rtattr(&rd, broadcast);
+ write_rtattr(&rd, flags);
+ write_rtattr(&rd, unknown);
+
+ const end = io::tell(&rd)!;
+
+ io::seek(&rd, 0, io::whence::SET)!;
+
+ const out_addr = read_rtattr(&rd) as rta_ifa_address;
+ const out_local = read_rtattr(&rd) as rta_ifa_local;
+ const out_label = read_rtattr(&rd) as rta_ifa_label;
+ const out_broadcast = read_rtattr(&rd) as rta_ifa_broadcast;
+ const out_flags = read_rtattr(&rd) as rta_ifa_flags;
+ const out_unknown = read_rtattr(&rd) as rta_unknown;
+
+ assert(ip::equal(addr, out_addr));
+ assert(ip::equal(local, out_local));
+ assert(label == out_label);
+ assert(ip::equal(broadcast, out_broadcast));
+ assert(flags == out_flags);
+ assert(unknown.typ == out_unknown.typ);
+ assert(bytes::equal(unknown.data, out_unknown.data));
+
+ io::seek(&rd, 0, io::whence::SET)!;
+
+ let iter = rtattr_iter(buf[..end]);
+
+ const out_addr = rtattr_next(&iter) as rta_ifa_address;
+ const out_local = rtattr_next(&iter) as rta_ifa_local;
+ const out_label = rtattr_next(&iter) as rta_ifa_label;
+ const out_broadcast = rtattr_next(&iter) as rta_ifa_broadcast;
+ const out_flags = rtattr_next(&iter) as rta_ifa_flags;
+ const out_unknown = rtattr_next(&iter) as rta_unknown;
+ assert(rtattr_next(&iter) is void);
+
+ assert(ip::equal(addr, out_addr));
+ assert(ip::equal(local, out_local));
+ assert(label == out_label);
+ assert(ip::equal(broadcast, out_broadcast));
+ assert(flags == out_flags);
+ assert(unknown.typ == out_unknown.typ);
+ assert(bytes::equal(unknown.data, out_unknown.data));
+};
A => netlink/route/addr/addr.ha +31 -0
@@ 1,31 @@
+use bufio;
+use netlink::serde;
+
+export type rt_scope_t = enum u8 {
+ RT_SCOPE_UNIVERSE = 0,
+ // User defined values
+ RT_SCOPE_SITE = 200,
+ RT_SCOPE_LINK = 253,
+ RT_SCOPE_HOST = 254,
+ RT_SCOPE_NOWHERE = 255,
+};
+
+// From <linux/if_addr.h>
+export type ifaddrmsg = struct {
+ // AF_*
+ ifa_family: u8,
+ ifa_prefixlen: u8, // The prefix length
+ ifa_flags: u8, // Flags
+ ifa_scope: rt_scope_t, // Address scope
+ ifa_index: u32, // Link index
+};
+
+export fn write_ifaddrmsg(rd: *bufio::memstream, ifa: ifaddrmsg) void = {
+ const ptr = serde::borrowptr(rd, size(ifaddrmsg)): *ifaddrmsg;
+ *ptr = ifa;
+};
+
+export fn read_ifaddrmsg(rd: *bufio::memstream) ifaddrmsg = {
+ const ptr = serde::borrowptr(rd, size(ifaddrmsg)): *ifaddrmsg;
+ return *ptr;
+};
A => netlink/route/addr/debug.ha +22 -0
@@ 1,22 @@
+use fmt;
+use io;
+use mac;
+use strio;
+
+export fn debugstr_rtattr(rta: rtattr) str = {
+ static let buf: [1024]u8 = [0...];
+ const s = strio::fixed(buf);
+ debugfmt_rtattr(&s, rta)!;
+ return strio::string(&s);
+};
+
+export fn debugfmt_rtattr(h: io::handle, rta: rtattr) (size | io::error) = {
+ match (rta) {
+ case let flags: rta_ifa_flags =>
+ return fmt::fprintf(h, `(IFA_FLAGS, {})`, flags: u32);
+ case let unknown: rta_unknown =>
+ return fmt::fprintf(h, `({}, [{}]u8)`, unknown.typ: uint, len(unknown.data));
+ case =>
+ abort();
+ };
+};
A => netlink/route/addr/rtattr.ha +186 -0
@@ 1,186 @@
+use bufio;
+use io;
+use net::ip;
+use netlink::serde;
+use types::c;
+
+export type rta_type = enum {
+ IFA_UNSPEC = 0,
+ // The prefix address. Same as IFA_LOCAL except for point-to-point interfaces,
+ // then IFA_ADDRESS is DESTINATION address.
+ IFA_ADDRESS = 1,
+ IFA_LOCAL = 2, // Local address
+ IFA_LABEL = 3, // Name of the interface
+ IFA_BROADCAST = 4, // Broadcast address
+ IFA_ANYCAST = 5, // Anycast address
+ IFA_CACHEINFO = 6, // Address information
+ IFA_MULTICAST = 7,
+ // If IFA_FLAGS is present, then the value ifaddrmsg.ifa_flags will be ignored.
+ IFA_FLAGS = 8,
+ IFA_RT_PRIORITY = 9, // u32, priority/metric for prefix route
+ IFA_TARGET_NETNSID = 10,
+ IFA_PROTO= 11, // u8, address protocol
+};
+
+export type rtattr = (
+ rta_ifa_address |
+ rta_ifa_local |
+ rta_ifa_label |
+ rta_ifa_broadcast |
+ rta_ifa_cacheinfo |
+ rta_ifa_flags |
+ rta_unknown
+);
+export type rta_ifa_address = ip::addr;
+export type rta_ifa_local = ip::addr;
+export type rta_ifa_label = str;
+export type rta_ifa_broadcast = ip::addr;
+// See here for lifetime: https://www.rfc-editor.org/rfc/rfc4862#section-5.5.4
+export type rta_ifa_cacheinfo = struct {
+ ifa_prefered: u32, // Prefered lifetime
+ ifa_valid: u32, // Valid lifetime
+ cstamp: u32, // created timestamp, hundredths of seconds
+ tstamp: u32, // updated timestamp, hundredths of seconds
+};
+// XXX: enum?
+export type rta_ifa_flags = u32;
+export type rta_unknown = struct {
+ typ: c::ushort,
+ data: []u8,
+};
+
+export fn write_rtattr(rd: *bufio::memstream, rta: rtattr) void = {
+ const start = rd.pos;
+ const lenptr = serde::borrowptr(rd, size(c::ushort)): *c::ushort;
+
+ const typ = get_rta_type(rta): c::ushort;
+ serde::write_ushort(rd, typ);
+
+ // padding 1
+ const hdr_len = size(c::ushort) + size(c::ushort);
+ const padding1 = rta_align(hdr_len) - hdr_len;
+ io::seek(rd, padding1: io::off, io::whence::CUR)!;
+
+ match (rta) {
+ case let addr: rta_ifa_address =>
+ serde::write_ipaddr(rd, addr);
+ case let local: rta_ifa_local =>
+ serde::write_ipaddr(rd, local);
+ case let label: rta_ifa_label =>
+ serde::write_str0(rd, label);
+ case let broadcast: rta_ifa_broadcast =>
+ serde::write_ipaddr(rd, broadcast);
+ case let ci: rta_ifa_cacheinfo =>
+ // XXX: generic write bytes here?
+ serde::write_u32(rd, ci.ifa_prefered);
+ serde::write_u32(rd, ci.ifa_valid);
+ serde::write_u32(rd, ci.cstamp);
+ serde::write_u32(rd, ci.tstamp);
+ case let flags: rta_ifa_flags =>
+ serde::write_u32(rd, flags);
+ case let unknown: rta_unknown =>
+ serde::write_bytes(rd, unknown.data);
+ };
+
+ const rta_len = rd.pos - start;
+ *lenptr = rta_len: c::ushort;
+
+ // padding 2
+ const padding2 = rta_align(rta_len) - rta_len;
+ io::seek(rd, padding2: io::off, io::whence::CUR)!;
+};
+
+export fn read_rtattr(rd: *bufio::memstream) rtattr = {
+ const rta_len = serde::read_ushort(rd);
+ const typ = serde::read_ushort(rd);
+
+ // padding 1
+ const hdr_len = size(c::ushort) + size(c::ushort);
+ const padding1 = rta_align(hdr_len) - hdr_len;
+ io::seek(rd, padding1: io::off, io::whence::CUR)!;
+
+ const length = rta_len - rta_align(hdr_len);
+
+ const start = io::tell(rd)!;
+
+ const attr = switch (typ: rta_type) {
+ case rta_type::IFA_ADDRESS =>
+ yield serde::read_ipaddr(rd, length): rta_ifa_address;
+ case rta_type::IFA_LOCAL =>
+ yield serde::read_ipaddr(rd, length): rta_ifa_local;
+ case rta_type::IFA_LABEL =>
+ yield serde::read_str0(rd, length): rta_ifa_label;
+ case rta_type::IFA_BROADCAST =>
+ yield serde::read_ipaddr(rd, length): rta_ifa_broadcast;
+ case rta_type::IFA_CACHEINFO =>
+ yield rta_ifa_cacheinfo {
+ ifa_prefered = serde::read_u32(rd),
+ ifa_valid = serde::read_u32(rd),
+ cstamp = serde::read_u32(rd),
+ tstamp = serde::read_u32(rd),
+ };
+ case rta_type::IFA_FLAGS =>
+ yield serde::read_u32(rd): rta_ifa_flags;
+ case =>
+ const data = serde::read_bytes(rd, length);
+ yield rta_unknown {
+ typ = typ,
+ data = data,
+ };
+ };
+
+ // sanity check to verify we read the correct length
+ const end = io::tell(rd)!;
+ assert(end - start == length: i64);
+
+ // padding 2
+ const padding2 = rta_align(rta_len) - rta_len;
+ io::seek(rd, padding2: io::off, io::whence::CUR)!;
+
+ return attr;
+};
+
+export type rtattr_iterator = struct {
+ rd: bufio::memstream,
+};
+
+export fn rtattr_iter(buf: []u8) rtattr_iterator = {
+ return rtattr_iterator {
+ rd = bufio::fixed(buf, io::mode::READ),
+ };
+};
+
+export fn rtattr_next(iter: *rtattr_iterator) (rtattr | void) = {
+ if (serde::is_at_end(&iter.rd)) {
+ return void;
+ };
+
+ return read_rtattr(&iter.rd);
+};
+
+fn get_rta_type(rta: rtattr) rta_type = {
+ match (rta) {
+ case rta_ifa_address =>
+ return rta_type::IFA_ADDRESS;
+ case rta_ifa_local =>
+ return rta_type::IFA_LOCAL;
+ case rta_ifa_label =>
+ return rta_type::IFA_LABEL;
+ case rta_ifa_broadcast =>
+ return rta_type::IFA_BROADCAST;
+ case rta_ifa_cacheinfo =>
+ return rta_type::IFA_CACHEINFO;
+ case rta_ifa_flags =>
+ return rta_type::IFA_FLAGS;
+ case let u: rta_unknown =>
+ return u.typ: rta_type;
+ case =>
+ abort();
+ };
+};
+
+def ALIGN_TO: size = 4;
+
+fn rta_align(length: size) size = {
+ return (length+ALIGN_TO-1) & (~(ALIGN_TO-1));
+};
A => netlink/route/link/+test.ha +79 -0
@@ 1,79 @@
+use bufio;
+use bytes;
+use io;
+
+@test fn rtattr() void = {
+ let buf: [4096]u8 = [0u8...];
+ let rd = bufio::fixed(buf, io::mode::RDWR);
+
+ // ifname is not 4 bytes aligned to test rta_align
+ const ifname = "hi": rta_ifla_ifname;
+ const mtu = 1400: rta_ifla_mtu;
+ const link = 2: rta_ifla_link;
+ const qdisc = "qdisc": rta_ifla_qdisc;
+ const unknown = rta_unknown {
+ typ = 10,
+ data = [1,2,3],
+ };
+ write_rtattr(&rd, ifname);
+ write_rtattr(&rd, mtu);
+ write_rtattr(&rd, link);
+ write_rtattr(&rd, qdisc);
+ write_rtattr(&rd, unknown);
+
+ const end = io::tell(&rd)!;
+
+ io::seek(&rd, 0, io::whence::SET)!;
+
+ const out_ifname = read_rtattr(&rd) as rta_ifla_ifname;
+ const out_mtu = read_rtattr(&rd) as rta_ifla_mtu;
+ const out_link = read_rtattr(&rd) as rta_ifla_link;
+ const out_qdisc = read_rtattr(&rd) as rta_ifla_qdisc;
+ const out_unknown = read_rtattr(&rd) as rta_unknown;
+
+ assert(ifname == out_ifname);
+ assert(mtu == out_mtu);
+ assert(link == out_link);
+ assert(qdisc == out_qdisc);
+ assert(unknown.typ == out_unknown.typ);
+ assert(bytes::equal(unknown.data, out_unknown.data));
+
+ io::seek(&rd, 0, io::whence::SET)!;
+
+ let iter = rtattr_iter(buf[..end]);
+
+ const out_ifname = rtattr_next(&iter) as rta_ifla_ifname;
+ const out_mtu = rtattr_next(&iter) as rta_ifla_mtu;
+ const out_link = rtattr_next(&iter) as rta_ifla_link;
+ const out_qdisc = rtattr_next(&iter) as rta_ifla_qdisc;
+ const out_unknown = rtattr_next(&iter) as rta_unknown;
+ assert(rtattr_next(&iter) is void);
+
+ assert(ifname == out_ifname);
+ assert(mtu == out_mtu);
+ assert(link == out_link);
+ assert(qdisc == out_qdisc);
+ assert(unknown.typ == out_unknown.typ);
+ assert(bytes::equal(unknown.data, out_unknown.data));
+};
+
+@test fn rtattr_linkinfo() void = {
+ let buf: [4096]u8 = [0...];
+ let rd = bufio::fixed(buf, io::mode::RDWR);
+
+ let bufinfo: [4096]u8 = [0...];
+ let rdinfo = bufio::fixed(bufinfo, io::mode::WRITE);
+
+ const kind = "bridge": rta_ifla_info_kind;
+ write_rtattr_linkinfo(&rdinfo, kind);
+ const infolength = io::tell(&rdinfo)!;
+
+ const linkinfo = rtattr_linkinfo_iter(bufinfo[..infolength]);
+ write_rtattr(&rd, linkinfo);
+
+ io::seek(&rd, 0, io::whence::SET)!;
+
+ const out_linkinfo = read_rtattr(&rd) as rta_ifla_linkinfo;
+ const out_kind = rtattr_linkinfo_next(&out_linkinfo) as rta_ifla_info_kind;
+ assert(kind == out_kind);
+};
A => netlink/route/link/debug.ha +79 -0
@@ 1,79 @@
+use fmt;
+use io;
+use mac;
+use strio;
+
+export fn debugstr_rtattr(rta: rtattr) str = {
+ static let buf: [1024]u8 = [0...];
+ const s = strio::fixed(buf);
+ debugfmt_rtattr(&s, rta)!;
+ return strio::string(&s);
+};
+
+export fn debugfmt_rtattr(h: io::handle, rta: rtattr) (size | io::error) = {
+ match (rta) {
+ case let addr: rta_ifla_address =>
+ let z = fmt::fprintf(h, `(IFLA_ADDRESS, `)?;
+ z += mac::fmt(h, addr)?;
+ z += fmt::fprintf(h, `)`)?;
+ return z;
+ case let addr: rta_ifla_broadcast =>
+ let z = fmt::fprintf(h, `(IFLA_BROADCAST, `)?;
+ z += mac::fmt(h, addr)?;
+ z += fmt::fprintf(h, `)`)?;
+ return z;
+ case let ifname: rta_ifla_ifname =>
+ return fmt::fprintf(h, `(IFLA_IFNAME, "{}")`, ifname: str);
+ case let mtu: rta_ifla_mtu =>
+ return fmt::fprintf(h, `(IFLA_MTU, {})`, mtu: u32);
+ case let link: rta_ifla_link =>
+ return fmt::fprintf(h, `(IFLA_LINK, {})`, link: i32);
+ case let qdisc: rta_ifla_qdisc =>
+ return fmt::fprintf(h, `(IFLA_QDISC, "{}")`, qdisc: str);
+ case let txqlen: rta_ifla_txqlen =>
+ return fmt::fprintf(h, `(IFLA_TXQLEN, {})`, txqlen: u32);
+ case let linkinfo: rta_ifla_linkinfo =>
+ let iter = rtattr_linkinfo_clone(&linkinfo);
+ let z = fmt::fprintf(h, `(IFLA_LINKINFO, [`)?;
+ const attr = match (rtattr_linkinfo_next(&iter)) {
+ case let attr: rtattr_linkinfo =>
+ yield attr;
+ case void =>
+ z += fmt::fprintf(h, `])`)?;
+ return z;
+ };
+ z += debugfmt_rtattr_linkinfo(h, attr)?;
+ for (true) {
+ const attr = match (rtattr_linkinfo_next(&iter)) {
+ case let attr: rtattr_linkinfo =>
+ yield attr;
+ case void =>
+ break;
+ };
+
+ z += fmt::fprintf(h, `, `)?;
+ z += debugfmt_rtattr_linkinfo(h, attr)?;
+ };
+ z += fmt::fprintf(h, `])`)?;
+ return z;
+ case let num_tx_queues: rta_ifla_num_tx_queues =>
+ return fmt::fprintf(h, `(IFLA_NUM_TX_QUEUES, {})`, num_tx_queues: u32);
+ case let num_rx_queues: rta_ifla_num_rx_queues =>
+ return fmt::fprintf(h, `(IFLA_NUM_RX_QUEUES, {})`, num_rx_queues: u32);
+ case let unknown: rta_unknown =>
+ return fmt::fprintf(h, `({}, [{}]u8)`, unknown.typ: uint, len(unknown.data));
+ case =>
+ abort();
+ };
+};
+
+export fn debugfmt_rtattr_linkinfo(h: io::handle, rta: rtattr_linkinfo) (size | io::error) = {
+ match (rta) {
+ case let kind: rta_ifla_info_kind =>
+ return fmt::fprintf(h, `(IFLA_INFO_KIND, "{}")`, kind: str);
+ case let unknown: rta_unknown =>
+ return fmt::fprintf(h, `({}, [{}]u8)`, unknown.typ: uint, len(unknown.data));
+ case =>
+ abort();
+ };
+};
A => netlink/route/link/link.ha +23 -0
@@ 1,23 @@
+use bufio;
+use netlink::serde;
+use types::c;
+
+// From <linux/rtnetlink.h>
+export type ifinfomsg = struct {
+ ifi_family: c::uchar,
+ __ifi_pad: c::uchar,
+ ifi_type: c::ushort,
+ ifi_index: int,
+ ifi_flags: uint,
+ ifi_change: uint,
+};
+
+export fn write_ifinfomsg(rd: *bufio::memstream, ifi: ifinfomsg) void = {
+ const ptr = serde::borrowptr(rd, size(ifinfomsg)): *ifinfomsg;
+ *ptr = ifi;
+};
+
+export fn read_ifinfomsg(rd: *bufio::memstream) ifinfomsg = {
+ const ptr = serde::borrowptr(rd, size(ifinfomsg)): *ifinfomsg;
+ return *ptr;
+};
A => netlink/route/link/rtattr.ha +324 -0
@@ 1,324 @@
+use bufio;
+use io;
+use mac;
+use netlink::serde;
+use types::c;
+
+export type rta_type = enum {
+ IFLA_UNSPEC = 0,
+ IFLA_ADDRESS = 1,
+ IFLA_BROADCAST = 2,
+ IFLA_IFNAME = 3,
+ IFLA_MTU = 4,
+ IFLA_LINK = 5,
+ IFLA_QDISC = 6,
+ IFLA_TXQLEN = 13,
+ IFLA_LINKINFO = 18,
+ IFLA_NUM_TX_QUEUES = 31,
+ IFLA_NUM_RX_QUEUES = 32,
+};
+
+// rtattr is serialized in the following way:
+// | rta_len(ushort) | rta_type(ushort) | padding1 | data([]u8) | padding2 |
+// where
+// rta_len = size(rta_len) + size(rta_type) + padding1 + data
+export type rtattr = (
+ rta_ifla_address |
+ rta_ifla_broadcast |
+ rta_ifla_ifname |
+ rta_ifla_mtu |
+ rta_ifla_link |
+ rta_ifla_qdisc |
+ rta_ifla_txqlen |
+ rta_ifla_linkinfo |
+ rta_ifla_num_tx_queues |
+ rta_ifla_num_rx_queues |
+ rta_unknown
+);
+export type rta_ifla_address = mac::buffer;
+export type rta_ifla_broadcast = mac::buffer;
+export type rta_ifla_ifname = str;
+export type rta_ifla_mtu = u32;
+// int or i32? (rtnetlink(7))
+export type rta_ifla_link = i32;
+export type rta_ifla_qdisc = str;
+export type rta_ifla_txqlen = u32;
+export type rta_ifla_linkinfo = rtattr_linkinfo_iterator;
+export type rta_ifla_num_tx_queues = u32;
+export type rta_ifla_num_rx_queues = u32;
+export type rta_unknown = struct {
+ typ: c::ushort,
+ data: []u8,
+};
+
+export fn write_rtattr(rd: *bufio::memstream, rta: rtattr) void = {
+ const start = rd.pos;
+ const lenptr = serde::borrowptr(rd, size(c::ushort)): *c::ushort;
+
+ const typ = get_rta_type(rta): c::ushort;
+ serde::write_ushort(rd, typ);
+
+ // padding 1
+ const hdr_len = size(c::ushort) + size(c::ushort);
+ const padding1 = rta_align(hdr_len) - hdr_len;
+ io::seek(rd, padding1: io::off, io::whence::CUR)!;
+
+ match (rta) {
+ // case let addr: rta_ifla_address =>
+ // serde::write_bytes(rd, addr[..]);
+ // case let addr: rta_ifla_broadcast =>
+ // serde::write_bytes(rd, addr[..]);
+ case let ifname: rta_ifla_ifname =>
+ serde::write_str0(rd, ifname);
+ case let mtu: rta_ifla_mtu =>
+ serde::write_u32(rd, mtu);
+ case let link: rta_ifla_link =>
+ serde::write_i32(rd, link: i32);
+ case let qdisc: rta_ifla_qdisc =>
+ serde::write_str0(rd, qdisc);
+ case let txqlen: rta_ifla_txqlen =>
+ serde::write_u32(rd, txqlen);
+ case let linkinfo: rta_ifla_linkinfo =>
+ let iter = rtattr_linkinfo_clone(&linkinfo);
+ for (true) {
+ const attr = match (rtattr_linkinfo_next(&iter)) {
+ case let attr: rtattr_linkinfo =>
+ yield attr;
+ case void =>
+ break;
+ };
+
+ write_rtattr_linkinfo(rd, attr);
+ };
+ case let num_tx_queues: rta_ifla_num_tx_queues =>
+ serde::write_u32(rd, num_tx_queues);
+ case let num_rx_queues: rta_ifla_num_rx_queues =>
+ serde::write_u32(rd, num_rx_queues);
+ case let unknown: rta_unknown =>
+ serde::write_bytes(rd, unknown.data);
+ };
+
+ const rta_len = rd.pos - start;
+ *lenptr = rta_len: c::ushort;
+
+ // padding 2
+ const padding2 = rta_align(rta_len) - rta_len;
+ io::seek(rd, padding2: io::off, io::whence::CUR)!;
+};
+
+export fn read_rtattr(rd: *bufio::memstream) rtattr = {
+ const rta_len = serde::read_ushort(rd);
+ const typ = serde::read_ushort(rd);
+
+ // padding 1
+ const hdr_len = size(c::ushort) + size(c::ushort);
+ const padding1 = rta_align(hdr_len) - hdr_len;
+ io::seek(rd, padding1: io::off, io::whence::CUR)!;
+
+ const length = rta_len-rta_align(hdr_len);
+
+ const start = io::tell(rd)!;
+
+ const attr = switch (typ: rta_type) {
+ // case rta_type::IFLA_ADDRESS =>
+ // abort("todo");
+ // case rta_type::IFLA_BROADCAST =>
+ // abort("todo");
+ case rta_type::IFLA_IFNAME =>
+ yield serde::read_str0(rd, length): rta_ifla_ifname;
+ case rta_type::IFLA_MTU =>
+ yield serde::read_u32(rd): rta_ifla_mtu;
+ case rta_type::IFLA_LINK =>
+ yield serde::read_i32(rd): rta_ifla_link;
+ case rta_type::IFLA_QDISC =>
+ yield serde::read_str0(rd, length): rta_ifla_qdisc;
+ case rta_type::IFLA_TXQLEN =>
+ yield serde::read_u32(rd): rta_ifla_txqlen;
+ case rta_type::IFLA_LINKINFO =>
+ const data = serde::read_bytes(rd, length);
+ yield rtattr_iter(data): rta_ifla_linkinfo;
+ case rta_type::IFLA_NUM_TX_QUEUES =>
+ yield serde::read_u32(rd): rta_ifla_num_tx_queues;
+ case rta_type::IFLA_NUM_RX_QUEUES =>
+ yield serde::read_u32(rd): rta_ifla_num_rx_queues;
+ case =>
+ const data = serde::read_bytes(rd, length);
+ yield rta_unknown {
+ typ = typ,
+ data = data,
+ };
+ };
+
+ // sanity check to verify we read the correct length
+ const end = io::tell(rd)!;
+ assert(end - start == length: i64);
+
+ // padding 2
+ const padding2 = rta_align(rta_len) - rta_len;
+ io::seek(rd, padding2: io::off, io::whence::CUR)!;
+
+ return attr;
+};
+
+export type rtattr_iterator = struct {
+ rd: bufio::memstream,
+};
+
+export fn rtattr_iter(buf: []u8) rtattr_iterator = {
+ return rtattr_iterator {
+ rd = bufio::fixed(buf, io::mode::READ),
+ };
+};
+
+export fn rtattr_next(iter: *rtattr_iterator) (rtattr | void) = {
+ if (len(bufio::buffer(&iter.rd)) == io::tell(&iter.rd)!: size) {
+ return void;
+ };
+
+ return read_rtattr(&iter.rd);
+};
+
+fn get_rta_type(rta: rtattr) rta_type = {
+ match (rta) {
+ case rta_ifla_address =>
+ return rta_type::IFLA_ADDRESS;
+ case rta_ifla_broadcast =>
+ return rta_type::IFLA_ADDRESS;
+ case rta_ifla_ifname =>
+ return rta_type::IFLA_IFNAME;
+ case rta_ifla_mtu =>
+ return rta_type::IFLA_MTU;
+ case rta_ifla_link =>
+ return rta_type::IFLA_LINK;
+ case rta_ifla_qdisc =>
+ return rta_type::IFLA_QDISC;
+ case rta_ifla_txqlen =>
+ return rta_type::IFLA_TXQLEN;
+ case rta_ifla_linkinfo =>
+ return rta_type::IFLA_LINKINFO;
+ case rta_ifla_num_tx_queues =>
+ return rta_type::IFLA_NUM_TX_QUEUES;
+ case rta_ifla_num_rx_queues =>
+ return rta_type::IFLA_NUM_RX_QUEUES;
+ case let u: rta_unknown =>
+ return u.typ: rta_type;
+ case =>
+ abort();
+ };
+};
+
+export type rta_info_type = enum {
+ IFLA_INFO_UNSPEC = 0,
+ IFLA_INFO_KIND = 1,
+ IFLA_INFO_DATA = 2,
+ IFLA_INFO_XSTATS = 3,
+ IFLA_INFO_SLAVE_KIND = 4,
+ IFLA_INFO_SLAVE_DATA = 5,
+};
+
+// For IFLA_LINKINFO
+export type rtattr_linkinfo = (rta_ifla_info_kind | rta_unknown);
+export type rta_ifla_info_kind = str;
+
+export fn write_rtattr_linkinfo(rd: *bufio::memstream, rta: rtattr_linkinfo) void = {
+ const start = rd.pos;
+ const lenptr = serde::borrowptr(rd, size(c::ushort)): *c::ushort;
+
+ const typ = get_rta_info_type(rta): c::ushort;
+ serde::write_ushort(rd, typ);
+
+ // padding 1
+ const hdr_len = size(c::ushort) + size(c::ushort);
+ const padding1 = rta_align(hdr_len) - hdr_len;
+ io::seek(rd, padding1: io::off, io::whence::CUR)!;
+
+ match (rta) {
+ case let kind: rta_ifla_info_kind =>
+ serde::write_str0(rd, kind);
+ case let unknown: rta_unknown =>
+ serde::write_bytes(rd, unknown.data);
+ };
+
+ const rta_len = rd.pos - start;
+ *lenptr = rta_len: c::ushort;
+
+ // padding 2
+ const padding2 = rta_align(rta_len) - rta_len;
+ io::seek(rd, padding2: io::off, io::whence::CUR)!;
+};
+
+export fn read_rtattr_linkinfo(rd: *bufio::memstream) rtattr_linkinfo = {
+ const rta_len = serde::read_ushort(rd);
+ const typ = serde::read_ushort(rd);
+
+ // padding 1
+ const hdr_len = size(c::ushort) + size(c::ushort);
+ const padding1 = rta_align(hdr_len) - hdr_len;
+ io::seek(rd, padding1: io::off, io::whence::CUR)!;
+
+ const length = rta_len-rta_align(hdr_len);
+
+ const start = io::tell(rd)!;
+
+ const attr = switch (typ: rta_info_type) {
+ case rta_info_type::IFLA_INFO_KIND =>
+ yield serde::read_str0(rd, length): rta_ifla_info_kind;
+ case =>
+ const data = serde::read_bytes(rd, length);
+ yield rta_unknown {
+ typ = typ,
+ data = data,
+ };
+ };
+
+ // sanity check to verify we read the correct length
+ const end = io::tell(rd)!;
+ assert(end - start == length: i64);
+
+ // padding 2
+ const padding2 = rta_align(rta_len) - rta_len;
+ io::seek(rd, padding2: io::off, io::whence::CUR)!;
+
+ return attr;
+};
+
+export type rtattr_linkinfo_iterator = struct {
+ rd: bufio::memstream,
+};
+
+export fn rtattr_linkinfo_clone(iter: *rtattr_linkinfo_iterator) rtattr_linkinfo_iterator = {
+ return rtattr_linkinfo_iterator {
+ rd = bufio::fixed(bufio::buffer(&iter.rd), io::mode::READ),
+ };
+};
+
+export fn rtattr_linkinfo_iter(buf: []u8) rtattr_linkinfo_iterator = {
+ return rtattr_linkinfo_iterator {
+ rd = bufio::fixed(buf, io::mode::READ),
+ };
+};
+
+export fn rtattr_linkinfo_next(iter: *rtattr_linkinfo_iterator) (rtattr_linkinfo | void) = {
+ if (len(bufio::buffer(&iter.rd)) == io::tell(&iter.rd)!: size) {
+ return void;
+ };
+
+ return read_rtattr_linkinfo(&iter.rd);
+};
+
+fn get_rta_info_type(rta: rtattr_linkinfo) rta_info_type = {
+ match (rta) {
+ case rta_ifla_info_kind =>
+ return rta_info_type::IFLA_INFO_KIND;
+ case let u: rta_unknown =>
+ return u.typ: rta_info_type;
+ case =>
+ abort();
+ };
+};
+
+def ALIGN_TO: size = 4;
+
+fn rta_align(length: size) size = {
+ return (length+ALIGN_TO-1) & (~(ALIGN_TO-1));
+};
A => netlink/serde/README +2 -0
@@ 1,2 @@
+This module has functions to serialize and deserialize to and from
+Netlink messages. The functions operate on a [[bufio::memstream]].
A => netlink/serde/utils.ha +137 -0
@@ 1,137 @@
+use bufio;
+use endian;
+use io;
+use net::ip;
+use strings;
+use types::c;
+
+export fn write_str0(rd: *bufio::memstream, val: str) void = {
+ write_str(rd, val);
+ write_str(rd, "\0");
+};
+
+export fn write_str(rd: *bufio::memstream, val: str) void = {
+ write_bytes(rd, strings::toutf8(val));
+};
+
+export fn write_bytes(rd: *bufio::memstream, val: []u8) void = {
+ match (io::writeall(rd, val)) {
+ case let z: size =>
+ assert(z == len(val));
+ case io::error =>
+ abort();
+ };
+};
+
+export fn write_u32(rd: *bufio::memstream, val: u32) void = {
+ const buf = borrow(rd, 4);
+ endian::host.putu32(buf, val);
+};
+
+export fn write_u16(rd: *bufio::memstream, val: u16) void = {
+ const buf = borrow(rd, 2);
+ endian::host.putu16(buf, val);
+};
+
+export fn write_i32(rd: *bufio::memstream, val: i32) void = {
+ write_u32(rd, val: u32);
+};
+
+export fn write_i16(rd: *bufio::memstream, val: i16) void = {
+ write_u16(rd, val: u16);
+};
+
+export fn write_ushort(rd: *bufio::memstream, val: c::ushort) void = {
+ const ptr = borrow(rd, size(c::ushort)): *[*]u8: *c::ushort;
+ *ptr = val;
+};
+
+export fn write_ipaddr(rd: *bufio::memstream, addr: ip::addr) void = {
+ match (addr) {
+ case let addr4: ip::addr4 =>
+ write_bytes(rd, addr4);
+ case let addr6: ip::addr6 =>
+ write_bytes(rd, addr6);
+ };
+};
+
+export fn read_len(rd: *bufio::memstream) u16 = {
+ const length = read_u16(rd);
+ assert(length >= 4);
+ return length-4;
+};
+
+export fn read_u32(rd: *bufio::memstream) u32 = {
+ const buf = borrow(rd, 4);
+ return endian::host.getu32(buf);
+};
+
+export fn read_i32(rd: *bufio::memstream) i32 = {
+ return read_u32(rd): i32;
+};
+
+export fn read_u16(rd: *bufio::memstream) u16 = {
+ const buf = borrow(rd, 2);
+ return endian::host.getu16(buf);
+};
+
+export fn read_ushort(rd: *bufio::memstream) c::ushort = {
+ const ptr = borrow(rd, size(c::ushort)): *[*]u8: *c::ushort;
+ return *ptr;
+};
+
+export fn read_str0(rd: *bufio::memstream, length: size) str = {
+ const s = read_str(rd, length-1);
+ read_str(rd, 1);
+ return s;
+};
+
+export fn read_str(rd: *bufio::memstream, length: size) str = {
+ return strings::fromutf8_unsafe(borrow(rd, length));
+};
+
+export fn read_bytes(rd: *bufio::memstream, length: size) []u8 = {
+ return borrow(rd, length);
+};
+
+export fn read_ipaddr(rd: *bufio::memstream, length: size) ip::addr = {
+ assert(length == 4 || length == 16);
+ if (length == 4) {
+ let addr: ip::addr4 = [0...];
+ const data = read_bytes(rd, length);
+ addr[..] = data[..];
+ return addr;
+ };
+
+ let addr: ip::addr6 = [0...];
+ const data = read_bytes(rd, length);
+ addr[..] = data[..];
+ return addr;
+};
+
+export fn borrow(rd: *bufio::memstream, amt: size) []u8 = {
+ match (bufio::borrowedread(rd, amt)) {
+ case let buf: []u8 =>
+ assert(len(buf) == amt);
+ return buf;
+ case io::EOF =>
+ abort();
+ };
+};
+
+export fn borrowend(rd: *bufio::memstream) []u8 = {
+ const length = len(bufio::buffer(rd)) - rd.pos;
+ return borrow(rd, length);
+};
+
+export fn borrowptr(rd: *bufio::memstream, amt: size) *void = {
+ return borrow(rd, amt): *[*]u8: *void;
+};
+
+fn available(rd: *bufio::memstream) size = {
+ return len(bufio::buffer(rd)) - io::tell(rd)!: size;
+};
+
+export fn is_at_end(rd: *bufio::memstream) bool = {
+ return available(rd) == 0;
+};