├── .gitignore ├── .travis.yml ├── CHANGES.md ├── LICENSE.md ├── Makefile ├── README.md ├── bin ├── dune ├── rawcap.ml ├── rawcap_eio.ml ├── rawcap_lwt.ml └── test_arp.ml ├── dune-project ├── lib ├── dune ├── eio_rawlink.ml ├── eio_rawlink.mli ├── lwt_rawlink.ml ├── lwt_rawlink.mli ├── rawlink.ml ├── rawlink.mli ├── rawlink_lowlevel.ml └── rawlink_stubs.c ├── rawlink-eio.opam ├── rawlink-lwt.opam └── rawlink.opam /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | *.install 3 | .merlin 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | sudo: false 3 | services: 4 | - docker 5 | install: wget https://raw.githubusercontent.com/ocaml/ocaml-travisci-skeleton/master/.travis-docker.sh 6 | script: bash -ex ./.travis-docker.sh 7 | env: 8 | global: 9 | - PACKAGE="rawlink" 10 | matrix: 11 | - DISTRO="centos-7" OCAML_VERSION="4.03" 12 | - DISTRO="ubuntu-14.04" OCAML_VERSION="4.03" 13 | - DISTRO="ubuntu-16.04" OCAML_VERSION="4.03" 14 | - DISTRO="alpine" OCAML_VERSION="4.04" 15 | - DISTRO="debian-stable" OCAML_VERSION="4.05" 16 | - DISTRO="ubuntu-lts" OCAML_VERSION="4.06" 17 | - DISTRO="fedora" OCAML_VERSION="4.07" 18 | - DISTRO="opensuse" OCAML_VERSION="4.07" 19 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## v2.1 (2022-08-26) 2 | 3 | * Fix broken dune files 4 | * Include `dhcp_client_filter` and `dhcp_server_filter` in Eio 5 | * Include an actual interface for Eio 6 | * Fix Eio types on `send_packet` 7 | 8 | ## v2.0 (2022-08-26) 9 | 10 | * Eio support via rawlink-eio 11 | * Lwt split into rawlink-lwt 12 | * More and better testing binaries 13 | * Update to dune 3.2 format 14 | * Split common bits in Rawlink_lowlevel 15 | 16 | ## v1.2 (2022-04-14) 17 | 18 | * Fix linking against non-lwt rawlink 19 | * Implement promiscuous mode 20 | * Update caml_ calls in rawlink_stubs.c 21 | 22 | ## v1.1 (2022-04-01) 23 | 24 | * Use Host_endian instead of Little_endian, requires Cstruct >= 6.1.0, 25 | this will make rawlink work correctly on big endian machines 26 | * Adapt to modern Cstruct 27 | * const/deconstify string/bytes 28 | 29 | ## v1.0 (2019-01-17) 30 | 31 | This release splits the Lwt package into a separate 32 | `rawlink.lwt` ocamlfind package. Existing users of `Lwt_rawlink` 33 | can just rename the ocamlfind package `rawlink` to `rawlink.lwt` 34 | to get the previous functionality. 35 | 36 | * Add a `dhcp_client_filter` for DHCP client port (#8 by @yomimono) 37 | * Rename `dhcp_filter` to `dhcp_server_filter` (#8 by @yomimono) 38 | * Support Lwt 4.0 (#10 by @hannesm) 39 | * Port build system to Dune (#11 by @avsm) 40 | 41 | ## 0.7 (2017-11-26) 42 | 43 | * Fix compilation on POSIX-like systems (such as FreeBSD) by including netinet/in.h 44 | 45 | ## 0.6 (2017-11-24) 46 | 47 | * Support ocaml 4.06 48 | * Fix cstruct linking 49 | 50 | ## 0.5 (2017-04-16) 51 | 52 | * Convert to topkg 53 | 54 | ## 0.4 (2016-12-17) 55 | 56 | * Convert to ppx 57 | 58 | ## 0.3 (2015-08-30) 59 | 60 | * Fix fd leak in bpf_open 61 | * Fix linux send function 62 | 63 | ## 0.2 (2015-08-28) 64 | 65 | * Fix to bpf_split_buf 66 | 67 | ## 0.1 (2015-10-09) 68 | 69 | * Initial release 70 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Christiano F. Haesbaert 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY : clean all doc test 2 | all: 3 | dune build 4 | 5 | doc: 6 | dune build @doc 7 | 8 | test: 9 | dune runtest 10 | 11 | clean: 12 | dune clean 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Rawlink - portable library to read and write raw packets. 2 | 3 | [![Build Status](https://travis-ci.org/haesbaert/rawlink.svg)](https://travis-ci.org/haesbaert/rawlink) 4 | 5 | Rawlink is an ocaml library for sending and receiving raw packets at the link 6 | layer level. Sometimes you need to have full control of the packet, including 7 | building the full ethernet frame. 8 | 9 | The API is platform independent, it uses BPF on real UNIXes and AF_SOCKET on 10 | linux. Some functionality is sacrificed so that the API is portable enough. 11 | 12 | Currently BPF and AF_PACKET are implemented, including filtering capabilities. 13 | Writing a BPF program is a pain in the ass, so no facilities are provided for 14 | it. If you need a BPF filter, I suggest you write a small .c file with a 15 | function that returns the BPF program as a string, check `rawlink_stubs.c` for 16 | an example. You can leverage [`dumpcap -d`](https://tshark.dev/packetcraft/arcana/bpf_instructions/) 17 | to generate BPF programs from human readable filters. 18 | 19 | Both normal blocking functions as well as `Eio` and `Lwt` bindings are provided. 20 | 21 | A typical code for receiving all packets and just sending them back on a 22 | specific interface is detailed below: 23 | 24 | ```ocaml 25 | let link = Rawlink.open_link "eth0" in 26 | let buf = Rawlink.read_packet link in 27 | Printf.printf "got a packet with %d bytes.\n%!" (Cstruct.len buf); 28 | Rawlink.send_packet link buf 29 | ``` 30 | 31 | Check the mli interface for more options. 32 | -------------------------------------------------------------------------------- /bin/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name rawcap) 3 | (modules rawcap) 4 | (libraries rawlink)) 5 | 6 | (executable 7 | (name rawcap_lwt) 8 | (modules rawcap_lwt) 9 | (libraries lwt_rawlink)) 10 | 11 | (executable 12 | (name rawcap_eio) 13 | (modules rawcap_eio) 14 | (libraries eio_rawlink eio_main)) 15 | 16 | (executable 17 | (name test_arp) 18 | (modules test_arp) 19 | (libraries eio_rawlink eio_main arp ethernet)) 20 | -------------------------------------------------------------------------------- /bin/rawcap.ml: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2015-2022 Christiano F. Haesbaert 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | *) 16 | 17 | (* Useful for livetesting *) 18 | 19 | let () = 20 | if Array.length Sys.argv <> 2 then invalid_arg "usage: rawcap ifname"; 21 | let ifname = Sys.argv.(1) in 22 | let link = Rawlink.open_link ~promisc:true ifname in 23 | let rec loop () = 24 | let pkt = Rawlink.read_packet link in 25 | Printf.printf "got packet with %d bytes\n%!" (Cstruct.length pkt); 26 | loop () 27 | in 28 | loop () 29 | -------------------------------------------------------------------------------- /bin/rawcap_eio.ml: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2022 Christiano F. Haesbaert 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | *) 16 | 17 | (* Useful for livetesting *) 18 | 19 | let () = 20 | Eio_main.run @@ fun _env -> 21 | if Array.length Sys.argv <> 2 then invalid_arg "usage: rawcap ifname"; 22 | let ifname = Sys.argv.(1) in 23 | Eio.Switch.run (fun sw -> 24 | let link = Eio_rawlink.open_link ~sw ~promisc:true ifname in 25 | let rec loop () = 26 | let pkt = Eio_rawlink.read_packet link in 27 | Printf.printf "got packet with %d bytes\n%!" (Cstruct.length pkt); 28 | loop () 29 | in 30 | loop () 31 | ) 32 | -------------------------------------------------------------------------------- /bin/rawcap_lwt.ml: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2015-2022 Christiano F. Haesbaert 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | *) 16 | 17 | (* Useful for livetesting *) 18 | open Lwt.Infix 19 | 20 | let () = 21 | if Array.length Sys.argv <> 2 then invalid_arg "usage: rawcap ifname"; 22 | let ifname = Sys.argv.(1) in 23 | let link = Lwt_rawlink.open_link ~promisc:true ifname in 24 | let rec loop () = 25 | Lwt_rawlink.read_packet link 26 | >>= fun pkt -> 27 | Printf.printf "got packet with %d bytes\n%!" (Cstruct.length pkt); 28 | loop () 29 | in 30 | Lwt_main.run (loop ()) 31 | -------------------------------------------------------------------------------- /bin/test_arp.ml: -------------------------------------------------------------------------------- 1 | let a_mac = Macaddr.of_string_exn "18:18:18:18:18:18" 2 | let a_ip = Ipaddr.V4.of_string_exn "18.18.18.18" 3 | let b_mac = Macaddr.of_string_exn "29:29:29:29:29:29" 4 | let b_ip = Ipaddr.V4.of_string_exn "29.29.29.29" 5 | 6 | let a_arp_request = 7 | let source_mac = a_mac in 8 | let source_ip = a_ip in 9 | let target_mac = Macaddr.of_string_exn "00:00:00:00:00:00" in 10 | let target_ip = b_ip in 11 | let arp = Arp_packet.{ operation = Request; source_mac; source_ip; target_mac; target_ip } in 12 | let eth = Ethernet.Packet.{ source = source_mac; destination = Macaddr.broadcast; ethertype = `ARP } in 13 | Cstruct.append (Ethernet.Packet.make_cstruct eth) (Arp_packet.encode arp) 14 | 15 | let b_arp_reply = 16 | let source_mac = b_mac in 17 | let source_ip = b_ip in 18 | let target_mac = b_mac in 19 | let target_ip = a_ip in 20 | let arp = Arp_packet.{ operation = Reply; source_mac; source_ip; target_mac; target_ip } in 21 | let eth = Ethernet.Packet.{ source = source_mac; destination = Macaddr.broadcast; ethertype = `ARP } in 22 | Cstruct.append (Ethernet.Packet.make_cstruct eth) (Arp_packet.encode arp) 23 | 24 | let pp hdr buf = 25 | print_string hdr; 26 | Cstruct.hexdump buf; 27 | print_newline () 28 | 29 | let send link hdr pkt = 30 | Eio_rawlink.send_packet link pkt; 31 | pp hdr pkt 32 | 33 | let recv link hdr = 34 | let pkt = Eio_rawlink.read_packet link in 35 | pp hdr pkt; 36 | pkt 37 | 38 | let rec wait_pkt link hdr pkt = 39 | let pkt' = recv link hdr in 40 | if not (Cstruct.equal pkt pkt') then 41 | wait_pkt link hdr pkt 42 | 43 | let () = 44 | Eio_main.run @@ fun env -> 45 | Eio.Switch.run @@ fun sw -> 46 | Eio.Time.with_timeout_exn (Eio.Stdenv.clock env) 3.0 @@ fun () -> 47 | 48 | let a_link = Eio_rawlink.open_link ~sw ~promisc:true "lo" in 49 | let b_link = Eio_rawlink.open_link ~sw ~promisc:true "lo" in 50 | 51 | send a_link "A sent:" a_arp_request; 52 | wait_pkt b_link "B got:" a_arp_request; 53 | Printf.printf "request match !\n%!"; 54 | 55 | send b_link "B sent:" b_arp_reply; 56 | wait_pkt a_link "A got:" b_arp_reply; 57 | Printf.printf "reply match !\n%!" 58 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 3.2) 2 | (name rawlink) 3 | -------------------------------------------------------------------------------- /lib/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name rawlink) 3 | (synopsis "Rawlink is a portable interface to BPF/AF_SOCKET") 4 | (public_name rawlink) 5 | (modules rawlink) 6 | (libraries rawlink_lowlevel)) 7 | 8 | (library 9 | (name rawlink_lowlevel) 10 | (synopsis "Rawlink lowlevel/common bits") 11 | (public_name rawlink.lowlevel) 12 | (modules rawlink_lowlevel) 13 | (libraries cstruct unix) 14 | (preprocess (pps ppx_cstruct)) 15 | (foreign_stubs (language c) (names rawlink_stubs))) 16 | 17 | (library 18 | (name lwt_rawlink) 19 | (synopsis "Lwt_rawlink is a Lwt interface to BPF/AF_SOCKET") 20 | (modules lwt_rawlink) 21 | (public_name rawlink-lwt) 22 | (libraries rawlink rawlink.lowlevel lwt lwt.unix)) 23 | 24 | (library 25 | (name eio_rawlink) 26 | (synopsis "Eio_rawlink is a Eio interface to BPF/AF_SOCKET") 27 | (modules eio_rawlink) 28 | (public_name rawlink-eio) 29 | (libraries rawlink rawlink.lowlevel eio eio.unix)) 30 | -------------------------------------------------------------------------------- /lib/eio_rawlink.ml: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2022 Christiano F. Haesbaert 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | *) 16 | 17 | module Lowlevel = Rawlink_lowlevel 18 | 19 | type t = { 20 | flow : Eio_unix.socket; 21 | fd : Unix.file_descr; 22 | packets : Cstruct.t list ref; 23 | buffer : Cstruct.t; 24 | } 25 | 26 | let dhcp_server_filter = Lowlevel.dhcp_server_filter 27 | let dhcp_client_filter = Lowlevel.dhcp_client_filter 28 | 29 | let open_link ?filter ?(promisc=false) ifname ~sw = 30 | let fd = Lowlevel.opensock ?filter:filter ~promisc ifname in 31 | let flow = Eio_unix.FD.as_socket ~sw ~close_unix:true fd in 32 | { flow; fd; packets = ref []; buffer = (Cstruct.create 65536) } 33 | 34 | let close_link t = t.flow#close 35 | 36 | let send_packet t buf = t.flow#copy @@ Eio.Flow.cstruct_source [ buf ] 37 | 38 | let rec read_packet t = 39 | match !(t.packets) with 40 | | hd :: tl -> t.packets := tl; hd 41 | | [] -> 42 | let n = Eio.Flow.read t.flow t.buffer in 43 | t.packets := Lowlevel.process_input t.buffer n; 44 | read_packet t 45 | -------------------------------------------------------------------------------- /lib/eio_rawlink.mli: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2015-2022 Christiano F. Haesbaert 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | *) 16 | 17 | (** A portable API to send and receive raw packets. 18 | 19 | There are times when one needs to construct the full ethernet frame to 20 | send/receive. Most unixes support BPF (BSD Packet Filter) to achieve it, but 21 | linux provides the same functionality via AF_SOCKET. This API works with 22 | either a BPF or AF_SOCKET backend, so it should work on every usable UNIX, 23 | as well as linux out there. 24 | *) 25 | 26 | type t 27 | (** Type of a rawlink. *) 28 | 29 | val open_link : ?filter:string -> ?promisc:bool -> string -> sw:Eio.Switch.t -> t 30 | (** [open_link ~filter ~promisc interface sw]. Creates a rawlink on the 31 | specified [interface], a BPF program [filter] can be passed to 32 | filter out incoming packets. If [promisc] is true, sets [interface] 33 | to promiscuous mode, defaults to false.*) 34 | 35 | val close_link : t -> unit 36 | (** [close_link]. Closes a rawlink. *) 37 | 38 | val read_packet : t -> Cstruct.t 39 | (** [read_packet t]. Reads a full packet, may raise Unix.Unix_error. *) 40 | 41 | val send_packet : t -> Cstruct.t -> unit 42 | (** [send_packet t]. Sends a full packet, may raise Unix.Unix_error. *) 43 | 44 | val dhcp_server_filter : unit -> string 45 | (** [dhcp_server_filter]. Returns a BPF program suitable to be passed in 46 | [open_link ~filter], it accepts UDP packets destined to 47 | port 67 (DHCP server). *) 48 | 49 | val dhcp_client_filter : unit -> string 50 | (** [dhcp_client_filter]. Returns a BPF program suitable to be passed in 51 | [open_link ~filter], it accepts UDP packets destined to 52 | port 68 (DHCP client). *) 53 | -------------------------------------------------------------------------------- /lib/lwt_rawlink.ml: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2015-2022 Christiano F. Haesbaert 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | *) 16 | 17 | [@@@warning "-32-37"] 18 | 19 | module Lowlevel = Rawlink_lowlevel 20 | 21 | open Lwt.Infix 22 | 23 | type t = { 24 | fd : Lwt_unix.file_descr; 25 | packets : Cstruct.t list ref; 26 | buffer : Cstruct.t; 27 | } 28 | 29 | let dhcp_server_filter = Lowlevel.dhcp_server_filter 30 | let dhcp_client_filter = Lowlevel.dhcp_client_filter 31 | 32 | let open_link ?filter ?(promisc=false) ifname = 33 | let fd = Lwt_unix.of_unix_file_descr (Lowlevel.opensock ?filter:filter ~promisc ifname) in 34 | let () = Lwt_unix.set_blocking fd false in 35 | { fd; packets = ref []; buffer = (Cstruct.create 65536) } 36 | 37 | let close_link t = Lwt_unix.close t.fd 38 | 39 | let rec read_packet t = 40 | match !(t.packets) with 41 | | hd :: tl -> t.packets := tl; Lwt.return hd 42 | | [] -> 43 | Lwt_bytes.read t.fd t.buffer.Cstruct.buffer 0 t.buffer.Cstruct.len 44 | >>= (fun n -> 45 | if n = 0 then 46 | failwith "Link socket closed"; 47 | t.packets := Lowlevel.process_input t.buffer n; 48 | read_packet t) 49 | 50 | let send_packet t buf = 51 | let len = Cstruct.length buf in 52 | Lwt_bytes.write t.fd buf.Cstruct.buffer 0 len 53 | >>= (fun n -> 54 | if n = 0 then 55 | Lwt.fail (Unix.Unix_error(Unix.EPIPE, "send_packet: socket closed", "")) 56 | else if n <> len then 57 | Lwt.fail (Unix.Unix_error(Unix.ENOBUFS, "send_packet: short write", "")) 58 | else 59 | Lwt.return_unit) 60 | -------------------------------------------------------------------------------- /lib/lwt_rawlink.mli: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2015-2022 Christiano F. Haesbaert 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | *) 16 | 17 | (** A portable API to send and receive raw packets. 18 | 19 | There are times when one needs to construct the full ethernet frame to 20 | send/receive. Most unixes support BPF (BSD Packet Filter) to achieve it, but 21 | linux provides the same functionality via AF_SOCKET. This API works with 22 | either a BPF or AF_SOCKET backend, so it should work on every usable UNIX, 23 | as well as linux out there. The module is designed to work with Lwt threads. 24 | *) 25 | 26 | type t 27 | 28 | val open_link : ?filter:string -> ?promisc:bool -> string -> t 29 | (** [open_link ~filter ~promisc interface]. Creates a rawlink on the 30 | specified [interface], a BPF program [filter] can be passed to 31 | filter out incoming packets. If [promisc] is true, sets [interface] 32 | to promiscuous mode, defaults to false. *) 33 | 34 | val close_link : t -> unit Lwt.t 35 | (** [close_link]. Closes a rawlink. *) 36 | 37 | val read_packet : t -> Cstruct.t Lwt.t 38 | (** [read_packet t]. Reads a full packet, may raise Unix.Unix_error. *) 39 | 40 | val send_packet : t -> Cstruct.t -> unit Lwt.t 41 | (** [send_packet t]. Sends a full packet, may raise Unix.Unix_error. *) 42 | 43 | val dhcp_server_filter : unit -> string 44 | (** [dhcp_server_filter]. Returns a BPF program suitable to be passed in 45 | [open_link ~filter], it accepts UDP packets destined to 46 | port 67 (DHCP client). *) 47 | 48 | val dhcp_client_filter : unit -> string 49 | (** [dhcp_client_filter]. Returns a BPF program suitable to be passed in 50 | [open_link ~filter], it accepts UDP packets destined to 51 | port 68 (DHCP server). *) 52 | -------------------------------------------------------------------------------- /lib/rawlink.ml: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2015-2022 Christiano F. Haesbaert 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | *) 16 | 17 | [@@@warning "-32-37"] 18 | 19 | module Lowlevel = Rawlink_lowlevel 20 | 21 | type t = { 22 | fd : Unix.file_descr; 23 | packets : Cstruct.t list ref; 24 | buffer : Cstruct.t; 25 | } 26 | 27 | let dhcp_server_filter = Lowlevel.dhcp_server_filter 28 | let dhcp_client_filter = Lowlevel.dhcp_client_filter 29 | 30 | let open_link ?filter ?(promisc=false) ifname = 31 | { fd = Lowlevel.opensock ?filter ~promisc ifname; 32 | packets = ref []; 33 | buffer = Cstruct.create 65536 } 34 | 35 | let close_link t = Unix.close t.fd 36 | 37 | let send_packet t buf = 38 | let len = Cstruct.length buf in 39 | let n = Unix.write t.fd (Cstruct.to_bytes buf) 0 len in 40 | if n = 0 then 41 | raise (Unix.Unix_error(Unix.EPIPE, "send_packet: socket closed", "")) 42 | else if n <> len then 43 | raise (Unix.Unix_error(Unix.ENOBUFS, "send_packet: short write", "")) 44 | 45 | let rec read_packet t = 46 | match !(t.packets) with 47 | | hd :: tl -> t.packets := tl; hd 48 | | [] -> 49 | let n = Lowlevel.unix_bytes_read t.fd t.buffer.Cstruct.buffer 0 t.buffer.Cstruct.len in 50 | if n = 0 then 51 | failwith "Link socket closed"; 52 | t.packets := Lowlevel.process_input t.buffer n; 53 | read_packet t 54 | -------------------------------------------------------------------------------- /lib/rawlink.mli: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2015-2022 Christiano F. Haesbaert 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | *) 16 | 17 | (** A portable API to send and receive raw packets. 18 | 19 | There are times when one needs to construct the full ethernet frame to 20 | send/receive. Most unixes support BPF (BSD Packet Filter) to achieve it, but 21 | linux provides the same functionality via AF_SOCKET. This API works with 22 | either a BPF or AF_SOCKET backend, so it should work on every usable UNIX, 23 | as well as linux out there. 24 | *) 25 | 26 | type t 27 | (** Type of a rawlink. *) 28 | 29 | val open_link : ?filter:string -> ?promisc:bool -> string -> t 30 | (** [open_link ~filter ~promisc interface]. Creates a rawlink on the 31 | specified [interface], a BPF program [filter] can be passed to 32 | filter out incoming packets. If [promisc] is true, sets [interface] 33 | to promiscuous mode, defaults to false.*) 34 | 35 | val close_link : t -> unit 36 | (** [close_link]. Closes a rawlink. *) 37 | 38 | val read_packet : t -> Cstruct.t 39 | (** [read_packet t]. Reads a full packet, may raise Unix.Unix_error. *) 40 | 41 | val send_packet : t -> Cstruct.t -> unit 42 | (** [send_packet t]. Sends a full packet, may raise Unix.Unix_error. *) 43 | 44 | val dhcp_server_filter : unit -> string 45 | (** [dhcp_server_filter]. Returns a BPF program suitable to be passed in 46 | [open_link ~filter], it accepts UDP packets destined to 47 | port 67 (DHCP server). *) 48 | 49 | val dhcp_client_filter : unit -> string 50 | (** [dhcp_client_filter]. Returns a BPF program suitable to be passed in 51 | [open_link ~filter], it accepts UDP packets destined to 52 | port 68 (DHCP client). *) 53 | -------------------------------------------------------------------------------- /lib/rawlink_lowlevel.ml: -------------------------------------------------------------------------------- 1 | (* 2 | * Copyright (c) 2022 Christiano F. Haesbaert 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | *) 16 | 17 | (* This module is supposed to be used internally only *) 18 | 19 | [%%cstruct 20 | type bpf_hdr = { 21 | bh_sec: uint32_t; 22 | bh_usec: uint32_t; 23 | bh_caplen: uint32_t; 24 | bh_datalen: uint32_t; 25 | bh_hdrlen: uint16_t; 26 | } [@@host_endian]] 27 | 28 | type driver = 29 | | AF_PACKET 30 | | BPF 31 | 32 | external opensock: ?filter:string -> ?promisc:bool -> string -> Unix.file_descr = "caml_rawlink_open" 33 | external dhcp_server_filter: unit -> string = "caml_dhcp_server_filter" 34 | external dhcp_client_filter: unit -> string = "caml_dhcp_client_filter" 35 | external driver: unit -> driver = "caml_driver" 36 | external unix_bytes_read: Unix.file_descr -> Cstruct.buffer -> int -> int -> int = 37 | "caml_unix_bytes_read" 38 | external bpf_align: int -> int -> int = "caml_bpf_align" 39 | 40 | let bpf_split_buffer buffer len = 41 | let rec loop buffer n packets = 42 | if n <= 0 then 43 | List.rev packets 44 | else 45 | let bh_caplen = Int32.to_int (get_bpf_hdr_bh_caplen buffer) in 46 | let bh_datalen = Int32.to_int (get_bpf_hdr_bh_datalen buffer) in 47 | let bh_hdrlen = get_bpf_hdr_bh_hdrlen buffer in 48 | let nextoff = bpf_align bh_hdrlen bh_caplen in 49 | if bh_caplen <> bh_datalen then 50 | loop (Cstruct.shift buffer nextoff) (n - nextoff) packets 51 | else 52 | let pkt = Cstruct.create bh_datalen in 53 | Cstruct.blit buffer bh_hdrlen pkt 0 bh_datalen; 54 | loop (Cstruct.shift buffer nextoff) (n - nextoff) (pkt :: packets) 55 | in 56 | loop buffer len [] 57 | 58 | let process_input buf len = 59 | match driver () with 60 | | AF_PACKET -> 61 | let dst = Cstruct.create len in 62 | Cstruct.blit buf 0 dst 0 len; 63 | [ dst ] 64 | | BPF -> bpf_split_buffer buf len 65 | -------------------------------------------------------------------------------- /lib/rawlink_stubs.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Christiano F. Haesbaert 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #ifdef __linux__ 18 | #define USE_AF_PACKET 19 | #else 20 | #define USE_BPF /* Best bet */ 21 | #endif 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | #ifdef USE_AF_PACKET 30 | #include 31 | #include 32 | #endif /* USE_AF_PACKET */ 33 | 34 | #ifdef USE_BPF 35 | #include 36 | #endif /* USE_BPF */ 37 | 38 | #include 39 | 40 | #include 41 | 42 | #include 43 | 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | #include 50 | 51 | #include "caml/memory.h" 52 | #include "caml/fail.h" 53 | #include "caml/unixsupport.h" 54 | #include "caml/signals.h" 55 | #include "caml/alloc.h" 56 | #include "caml/custom.h" 57 | #include "caml/bigarray.h" 58 | 59 | #ifdef USE_BPF 60 | 61 | #define FILTER bpf_insn 62 | 63 | int 64 | bpf_open(void) 65 | { 66 | int i, fd = -1; 67 | char path[16]; 68 | 69 | for (i = 0; i < 10; i++) { 70 | snprintf(path, sizeof(path), "/dev/bpf%d", i); 71 | caml_enter_blocking_section(); 72 | fd = open(path, O_RDWR); 73 | caml_leave_blocking_section(); 74 | if (fd == -1 && errno == EBUSY) 75 | continue; 76 | break; 77 | } 78 | 79 | if (fd == -1) 80 | uerror("bpf_open", Nothing); 81 | 82 | return (fd); 83 | } 84 | 85 | int 86 | bpf_seesent(int fd, u_int opt) 87 | { 88 | int r; 89 | 90 | caml_enter_blocking_section(); 91 | r = ioctl(fd, BIOCSSEESENT, &opt); 92 | caml_leave_blocking_section(); 93 | if (r == -1) 94 | uerror("bpf_seesent", Nothing); 95 | 96 | return (r); 97 | } 98 | 99 | int 100 | bpf_setblen(int fd, u_int len) 101 | { 102 | int r; 103 | 104 | caml_enter_blocking_section(); 105 | r = ioctl(fd, BIOCSBLEN, &len); 106 | caml_leave_blocking_section(); 107 | if (r == -1) 108 | uerror("bpf_setblen", Nothing); 109 | 110 | return (r); 111 | } 112 | 113 | int 114 | bpf_setif(int fd, char *ifname) 115 | { 116 | struct ifreq ifreq; 117 | int r; 118 | 119 | bzero(&ifreq, sizeof(ifreq)); 120 | strlcpy(ifreq.ifr_name, ifname, sizeof (ifreq.ifr_name)); 121 | caml_enter_blocking_section(); 122 | r = ioctl(fd, BIOCSETIF, &ifreq); 123 | caml_leave_blocking_section(); 124 | if (r == -1) 125 | uerror("bpf_setif", Nothing); 126 | 127 | return (r); 128 | } 129 | 130 | int 131 | bpf_setimmediate(int fd, u_int opt) 132 | { 133 | int r; 134 | 135 | caml_enter_blocking_section(); 136 | r = ioctl(fd, BIOCIMMEDIATE, &opt); 137 | caml_leave_blocking_section(); 138 | if (r == -1) 139 | uerror("bpf_setimmediate", Nothing); 140 | 141 | return (r); 142 | } 143 | 144 | int 145 | bpf_setfilter(int fd, value vfilter) 146 | { 147 | int r; 148 | struct bpf_program prog; 149 | 150 | if (vfilter == Val_int(0)) 151 | return (0); 152 | prog.bf_len = caml_string_length(Field(vfilter, 0)) / 153 | sizeof(struct bpf_insn); 154 | prog.bf_insns = (struct bpf_insn *) String_val(Field(vfilter, 0)); 155 | 156 | caml_enter_blocking_section(); 157 | r = ioctl(fd, BIOCSETF, &prog); 158 | caml_leave_blocking_section(); 159 | 160 | if (r == -1) 161 | uerror("bpf_setfilter", Nothing); 162 | 163 | return (r); 164 | } 165 | 166 | int 167 | bpf_setpromisc(int fd, int promisc) 168 | { 169 | int r = 0; 170 | 171 | if (promisc) { 172 | caml_enter_blocking_section(); 173 | r = ioctl(fd, BIOCPROMISC, NULL); 174 | caml_leave_blocking_section(); 175 | } 176 | 177 | if (r == -1) 178 | uerror("bpf_setpromisc", Nothing); 179 | 180 | return (0); 181 | } 182 | 183 | CAMLprim value 184 | caml_rawlink_open(value vfilter, value vpromisc, value vifname) 185 | { 186 | CAMLparam3(vfilter, vpromisc, vifname); 187 | int fd; 188 | 189 | if ((fd = bpf_open()) == -1) 190 | CAMLreturn(Val_unit); 191 | if (bpf_seesent(fd, 0) == -1) 192 | CAMLreturn(Val_unit); 193 | if (bpf_setblen(fd, UNIX_BUFFER_SIZE) == -1) 194 | CAMLreturn(Val_unit); 195 | if (bpf_setfilter(fd, vfilter) == -1) 196 | CAMLreturn(Val_unit); 197 | if (bpf_setif(fd, String_val(vifname)) == -1) 198 | CAMLreturn(Val_unit); 199 | if (bpf_setimmediate(fd, 1) == -1) 200 | CAMLreturn(Val_unit); 201 | if (bpf_setpromisc(fd, Bool_val(vpromisc)) == -1) 202 | CAMLreturn(Val_unit); 203 | 204 | CAMLreturn (Val_int(fd)); 205 | } 206 | 207 | CAMLprim value 208 | caml_bpf_align(value va, value vb) 209 | { 210 | CAMLparam2(va, vb); 211 | CAMLlocal1(v); 212 | uint32_t a, b; 213 | 214 | a = Int_val(va); 215 | b = Int_val(vb); 216 | 217 | v = Val_int(BPF_WORDALIGN (a + b)); 218 | 219 | CAMLreturn (v); 220 | } 221 | 222 | #endif /* USE_BPF */ 223 | 224 | #ifdef USE_AF_PACKET 225 | 226 | /* 227 | * Welcome to linux where glibc insists in not providing strlcpy. 228 | */ 229 | size_t 230 | strlcpy(char *dst, const char *src, size_t dsize) 231 | { 232 | const char *osrc = src; 233 | size_t nleft = dsize; 234 | 235 | /* Copy as many bytes as will fit. */ 236 | if (nleft != 0) { 237 | while (--nleft != 0) { 238 | if ((*dst++ = *src++) == '\0') 239 | break; 240 | } 241 | } 242 | 243 | /* Not enough room in dst, add NUL and traverse rest of src. */ 244 | if (nleft == 0) { 245 | if (dsize != 0) 246 | *dst = '\0'; /* NUL-terminate dst */ 247 | while (*src++) 248 | ; 249 | } 250 | 251 | return(src - osrc - 1); /* count does not include NUL */ 252 | } 253 | 254 | #define FILTER sock_filter 255 | 256 | int 257 | af_packet_open(void) 258 | { 259 | int fd; 260 | 261 | caml_enter_blocking_section(); 262 | fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); 263 | caml_leave_blocking_section(); 264 | 265 | if (fd == -1) 266 | uerror("af_packet_open", Nothing); 267 | 268 | return (fd); 269 | } 270 | 271 | int 272 | af_packet_setif(int fd, const char *ifname) 273 | { 274 | int r, ifidx; 275 | struct sockaddr_ll sll; 276 | 277 | ifidx = if_nametoindex(ifname); 278 | if (ifidx == 0) { 279 | uerror("af_packet_setif: if_nametoindex", Nothing); 280 | return (-1); 281 | } 282 | 283 | bzero(&sll, sizeof(sll)); 284 | sll.sll_family = AF_PACKET; 285 | sll.sll_ifindex = ifidx; 286 | sll.sll_protocol = htons(ETH_P_ALL); 287 | 288 | caml_enter_blocking_section(); 289 | r = bind(fd, (struct sockaddr *) &sll, sizeof(sll)); 290 | caml_leave_blocking_section(); 291 | 292 | if (r == -1) 293 | uerror("af_packet_setif: bind", Nothing); 294 | 295 | return (r); 296 | } 297 | 298 | int 299 | af_packet_setfilter(int fd, value vfilter) 300 | { 301 | int r; 302 | struct sock_fprog prog; 303 | 304 | if (vfilter == Val_int(0)) 305 | return (0); 306 | 307 | prog.len = caml_string_length(Field(vfilter, 0)) / 308 | sizeof(struct sock_filter); 309 | prog.filter = (struct sock_filter *) String_val(Field(vfilter, 0)); 310 | 311 | caml_enter_blocking_section(); 312 | r = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &prog, sizeof(prog)); 313 | caml_leave_blocking_section(); 314 | 315 | if (r == -1) 316 | uerror("af_packet_setfilter", Nothing); 317 | 318 | return (r); 319 | } 320 | 321 | int 322 | af_packet_setpromisc(int fd, int promisc, const char *ifname) 323 | { 324 | int r = 0; 325 | struct packet_mreq mr; 326 | int ifidx; 327 | 328 | ifidx = if_nametoindex(ifname); 329 | if (ifidx == 0) 330 | uerror("af_set_promisc: if_nametoindex", Nothing); 331 | 332 | if (promisc) { 333 | bzero(&mr, sizeof(mr)); 334 | mr.mr_ifindex = ifidx; 335 | mr.mr_type = PACKET_MR_PROMISC; 336 | caml_enter_blocking_section(); 337 | r = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, 338 | &mr, sizeof(mr)); 339 | caml_leave_blocking_section(); 340 | if (r == -1) 341 | uerror("af_set_promisc: PACKET_ADD_MEMBERSHIP", 342 | Nothing); 343 | } 344 | 345 | return (r); 346 | } 347 | 348 | CAMLprim value 349 | caml_rawlink_open(value vfilter, value vpromisc, value vifname) 350 | { 351 | CAMLparam3(vfilter, vpromisc, vifname); 352 | int fd; 353 | 354 | if ((fd = af_packet_open()) == -1) 355 | CAMLreturn (Val_unit); 356 | if (af_packet_setfilter(fd, vfilter) == -1) 357 | CAMLreturn (Val_unit); 358 | if (af_packet_setif(fd, String_val(vifname)) == -1) 359 | CAMLreturn (Val_unit); 360 | if (af_packet_setpromisc(fd, Bool_val(vpromisc), 361 | String_val(vifname)) == -1) 362 | CAMLreturn (Val_unit); 363 | 364 | CAMLreturn (Val_int(fd)); 365 | } 366 | 367 | /* dummy, not called */ 368 | CAMLprim value 369 | caml_bpf_align(value va, value vb) 370 | { 371 | CAMLparam2(va, vb); 372 | CAMLreturn (Val_int(0)); 373 | } 374 | 375 | #endif /* USE_AF_PACKET */ 376 | 377 | CAMLprim value 378 | caml_driver(value vunit) 379 | { 380 | CAMLparam0(); 381 | #ifdef AF_PACKET 382 | CAMLreturn (Val_int(0)); 383 | #else 384 | CAMLreturn (Val_int(1)); 385 | #endif 386 | } 387 | 388 | /* Filters */ 389 | CAMLprim value 390 | caml_dhcp_server_filter(value vunit) 391 | { 392 | CAMLparam0(); 393 | CAMLlocal1(vfilter); 394 | struct FILTER dhcp_bpf_filter[] = { 395 | /* Make sure this is an IP packet... */ 396 | BPF_STMT (BPF_LD + BPF_H + BPF_ABS, 12), 397 | BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8), 398 | 399 | /* Make sure it's a UDP packet... */ 400 | BPF_STMT (BPF_LD + BPF_B + BPF_ABS, 23), 401 | BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6), 402 | 403 | /* Make sure this isn't a fragment... */ 404 | BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20), 405 | BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0), 406 | 407 | /* Get the IP header length... */ 408 | BPF_STMT (BPF_LDX + BPF_B + BPF_MSH, 14), 409 | 410 | /* Make sure it's to the right port... */ 411 | BPF_STMT (BPF_LD + BPF_H + BPF_IND, 16), 412 | BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, 67, 0, 1), /* patch */ 413 | 414 | /* If we passed all the tests, ask for the whole packet. */ 415 | BPF_STMT(BPF_RET+BPF_K, (u_int)-1), 416 | 417 | /* Otherwise, drop it. */ 418 | BPF_STMT(BPF_RET+BPF_K, 0), 419 | }; 420 | 421 | vfilter = caml_alloc_string(sizeof(dhcp_bpf_filter)); 422 | memcpy(Bp_val(vfilter), dhcp_bpf_filter, sizeof(dhcp_bpf_filter)); 423 | 424 | CAMLreturn (vfilter); 425 | } 426 | 427 | CAMLprim value 428 | caml_dhcp_client_filter(value vunit) 429 | { 430 | CAMLparam0(); 431 | CAMLlocal1(vfilter); 432 | struct FILTER dhcp_bpf_filter[] = { 433 | /* Make sure this is an IP packet... */ 434 | BPF_STMT (BPF_LD + BPF_H + BPF_ABS, 12), 435 | BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8), 436 | 437 | /* Make sure it's a UDP packet... */ 438 | BPF_STMT (BPF_LD + BPF_B + BPF_ABS, 23), 439 | BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6), 440 | 441 | /* Make sure this isn't a fragment... */ 442 | BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20), 443 | BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0), 444 | 445 | /* Get the IP header length... */ 446 | BPF_STMT (BPF_LDX + BPF_B + BPF_MSH, 14), 447 | 448 | /* Make sure it's to the right port... */ 449 | BPF_STMT (BPF_LD + BPF_H + BPF_IND, 16), 450 | BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, 68, 0, 1), /* patch */ 451 | 452 | /* If we passed all the tests, ask for the whole packet. */ 453 | BPF_STMT(BPF_RET+BPF_K, (u_int)-1), 454 | 455 | /* Otherwise, drop it. */ 456 | BPF_STMT(BPF_RET+BPF_K, 0), 457 | }; 458 | 459 | vfilter = caml_alloc_string(sizeof(dhcp_bpf_filter)); 460 | memcpy(Bp_val(vfilter), dhcp_bpf_filter, sizeof(dhcp_bpf_filter)); 461 | 462 | CAMLreturn (vfilter); 463 | } 464 | 465 | /* From lwt, so we can read into a Cstruct.t */ 466 | CAMLprim value caml_unix_bytes_read(value val_fd, value val_buf, value val_ofs, 467 | value val_len) 468 | { 469 | long ret; 470 | ret = read(Int_val(val_fd), 471 | (char *)Caml_ba_array_val(val_buf)->data + Long_val(val_ofs), 472 | Long_val(val_len)); 473 | if (ret == -1) uerror("read", Nothing); 474 | return Val_long(ret); 475 | } 476 | -------------------------------------------------------------------------------- /rawlink-eio.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "Christiano F. Haesbaert " 3 | authors: "Christiano F. Haesbaert " 4 | license: "ISC" 5 | homepage: "https://github.com/haesbaert/rawlink" 6 | bug-reports: "https://github.com/haesbaert/rawlink/issues" 7 | dev-repo: "git+https://github.com/haesbaert/rawlink.git" 8 | doc: "https://haesbaert.github.io/rawlink/" 9 | build: [ 10 | [ "dune" "subst" ] {dev} 11 | [ "dune" "build" "-p" name "-j" jobs ] ] 12 | depends: [ 13 | "ocaml" {>= "5.0.0"} 14 | "dune" {>= "3.2"} 15 | "rawlink" {>= "2.1"} 16 | "eio" {>= "0.4"} 17 | ] 18 | depexts: [ 19 | ["linux-headers"] {os-distribution = "alpine"} 20 | ] 21 | synopsis: "Portable library to read and write raw packets with EIO bindings" 22 | description: """ 23 | Rawlink is an ocaml library for sending and receiving raw packets at the link 24 | layer level. Sometimes you need to have full control of the packet, including 25 | building the full ethernet frame. 26 | 27 | The API is platform independent, it uses BPF on real UNIXes and AF_SOCKET on 28 | linux. Some functionality is sacrificed so that the API is portable enough. 29 | 30 | Currently BPF and AF_PACKET are implemented, including filtering capabilities. 31 | Writing a BPF program is a pain in the ass, so no facilities are provided for 32 | it. If you need a BPF filter, I suggest you write a small .c file with a 33 | function that returns the BPF program as a string, check `rawlink_stubs.c` for 34 | an example. 35 | 36 | This version of Rawlink is to be used with Eio, the IO functions will produce 37 | EIO effects. 38 | """ 39 | -------------------------------------------------------------------------------- /rawlink-lwt.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "Christiano F. Haesbaert " 3 | authors: "Christiano F. Haesbaert " 4 | license: "ISC" 5 | homepage: "https://github.com/haesbaert/rawlink" 6 | bug-reports: "https://github.com/haesbaert/rawlink/issues" 7 | dev-repo: "git+https://github.com/haesbaert/rawlink.git" 8 | doc: "https://haesbaert.github.io/rawlink/" 9 | build: [ 10 | [ "dune" "subst" ] {dev} 11 | [ "dune" "build" "-p" name "-j" jobs ] ] 12 | depends: [ 13 | "ocaml" {>= "4.09.0"} 14 | "dune" {>= "3.2"} 15 | "rawlink" {>= "2.1"} 16 | "lwt" {>= "2.4.7"} 17 | ] 18 | depexts: [ 19 | ["linux-headers"] {os-distribution = "alpine"} 20 | ] 21 | synopsis: "Portable library to read and write raw packets with Lwt bindings" 22 | description: """ 23 | Rawlink is an ocaml library for sending and receiving raw packets at the link 24 | layer level. Sometimes you need to have full control of the packet, including 25 | building the full ethernet frame. 26 | 27 | The API is platform independent, it uses BPF on real UNIXes and AF_SOCKET on 28 | linux. Some functionality is sacrificed so that the API is portable enough. 29 | 30 | Currently BPF and AF_PACKET are implemented, including filtering capabilities. 31 | Writing a BPF program is a pain in the ass, so no facilities are provided for 32 | it. If you need a BPF filter, I suggest you write a small .c file with a 33 | function that returns the BPF program as a string, check `rawlink_stubs.c` for 34 | an example. 35 | 36 | This version of Rawlink is to be used with Lwt, the IO functions will produce 37 | Lwt threads (promises). 38 | """ 39 | -------------------------------------------------------------------------------- /rawlink.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "Christiano F. Haesbaert " 3 | authors: "Christiano F. Haesbaert " 4 | license: "ISC" 5 | homepage: "https://github.com/haesbaert/rawlink" 6 | bug-reports: "https://github.com/haesbaert/rawlink/issues" 7 | dev-repo: "git+https://github.com/haesbaert/rawlink.git" 8 | doc: "https://haesbaert.github.io/rawlink/" 9 | build: [ 10 | [ "dune" "subst" ] {dev} 11 | [ "dune" "build" "-p" name "-j" jobs ] ] 12 | depends: [ 13 | "ocaml" {>= "4.09.0"} 14 | "dune" {>= "3.2"} 15 | "cstruct" {>= "6.1.0"} 16 | "ppx_cstruct" 17 | ] 18 | depexts: [ 19 | ["linux-headers"] {os-distribution = "alpine"} 20 | ] 21 | synopsis: "Portable library to read and write raw packets" 22 | description: """ 23 | Rawlink is an ocaml library for sending and receiving raw packets at the link 24 | layer level. Sometimes you need to have full control of the packet, including 25 | building the full ethernet frame. 26 | 27 | The API is platform independent, it uses BPF on real UNIXes and AF_SOCKET on 28 | linux. Some functionality is sacrificed so that the API is portable enough. 29 | 30 | Currently BPF and AF_PACKET are implemented, including filtering capabilities. 31 | Writing a BPF program is a pain in the ass, so no facilities are provided for 32 | it. If you need a BPF filter, I suggest you write a small .c file with a 33 | function that returns the BPF program as a string, check `rawlink_stubs.c` for 34 | an example. 35 | """ 36 | --------------------------------------------------------------------------------