diff --git a/README.md b/README.md index 52581dd7..dc816417 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ A [precompiled version is available](https://github.com/bettercap/bettercap/releases) for each release, alternatively you can use the latest version of the source code from this repository in order to build your own binary. -Make sure you have a correctly configured **Go >= 1.8** environment, that `$GOPATH/bin` is in `$PATH` and the `libpcap-dev` package installed for your system, then: +Make sure you have a correctly configured **Go >= 1.8** environment, that `$GOPATH/bin` is in `$PATH`, that the `libpcap-dev` and `libnetfilter-queue-dev` package installed for your system and then: $ go get github.com/bettercap/bettercap diff --git a/glide.lock b/glide.lock index 4df5663a..7a98beb8 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ hash: fbde0d2452ce166fdbca5d911aa533bef347f420e172f0801a98b90ec6ccf9be -updated: 2018-03-23T15:14:51.394587797+01:00 +updated: 2018-03-28T13:41:06.165992863+02:00 imports: - name: github.com/adrianmo/go-nmea version: 22095aa1b48050243d3eb9a001ca80eb91a0c6fa @@ -16,6 +16,10 @@ imports: - xpc - name: github.com/bettercap/readline version: 62c6fe6193755f722b8b8788aa7357be55a50ff1 +- name: github.com/chifflier/nfqueue-go + version: 61ca646babef3bd4dea1deb610bfb0005c0a1298 + subpackages: + - nfqueue - name: github.com/dustin/go-humanize version: bb3d318650d48840a39aa21a027c6630e198e626 - name: github.com/elazarl/goproxy diff --git a/main.go b/main.go index 53902a94..89c967ed 100644 --- a/main.go +++ b/main.go @@ -41,10 +41,11 @@ func main() { sess.Register(modules.NewDHCP6Spoofer(sess)) sess.Register(modules.NewDNSSpoofer(sess)) sess.Register(modules.NewSniffer(sess)) - sess.Register(modules.NewHttpServer(sess)) + sess.Register(modules.NewPacketProxy(sess)) + sess.Register(modules.NewTcpProxy(sess)) sess.Register(modules.NewHttpProxy(sess)) sess.Register(modules.NewHttpsProxy(sess)) - sess.Register(modules.NewTcpProxy(sess)) + sess.Register(modules.NewHttpServer(sess)) sess.Register(modules.NewRestAPI(sess)) sess.Register(modules.NewWOL(sess)) sess.Register(modules.NewWiFiModule(sess)) diff --git a/modules/packet_proxy.go b/modules/packet_proxy.go new file mode 100644 index 00000000..0d462fc7 --- /dev/null +++ b/modules/packet_proxy.go @@ -0,0 +1,217 @@ +// +build !windows +// +build !darwin + +package modules + +import ( + "fmt" + "io/ioutil" + golog "log" + "plugin" + "strings" + "syscall" + + "github.com/bettercap/bettercap/core" + "github.com/bettercap/bettercap/log" + "github.com/bettercap/bettercap/session" + + "github.com/chifflier/nfqueue-go/nfqueue" +) + +type PacketProxy struct { + session.SessionModule + done chan bool + chainName string + rule string + queue *nfqueue.Queue + queueNum int + queueCb nfqueue.Callback + pluginPath string + plugin *plugin.Plugin +} + +// this is ugly, but since we can only pass a function +// (not a struct function) as a callback to nfqueue, +// we need this in order to recover the state. +var mod *PacketProxy + +func NewPacketProxy(s *session.Session) *PacketProxy { + mod = &PacketProxy{ + SessionModule: session.NewSessionModule("packet.proxy", s), + done: make(chan bool), + queue: nil, + queueCb: nil, + queueNum: 0, + chainName: "OUTPUT", + } + + mod.AddHandler(session.NewModuleHandler("packet.proxy on", "", + "Start the NFQUEUE based packet proxy.", + func(args []string) error { + return mod.Start() + })) + + mod.AddHandler(session.NewModuleHandler("packet.proxy off", "", + "Stop the NFQUEUE based packet proxy.", + func(args []string) error { + return mod.Stop() + })) + + mod.AddParam(session.NewIntParameter("packet.proxy.queue.num", + "0", + "NFQUEUE number to bind to.")) + + mod.AddParam(session.NewStringParameter("packet.proxy.chain", + "OUTPUT", + "", + "Chain name of the iptables rule.")) + + mod.AddParam(session.NewStringParameter("packet.proxy.plugin", + "", + "", + "Go plugin file to load and call for every packet.")) + + mod.AddParam(session.NewStringParameter("packet.proxy.rule", + "", + "", + "Any additional iptables rule to make the queue more selective (ex. --destination 8.8.8.8).")) + + return mod +} + +func (pp PacketProxy) Name() string { + return "packet.proxy" +} + +func (pp PacketProxy) Description() string { + return "A Linux only module that relies on NFQUEUEs in order to filter packets." +} + +func (pp PacketProxy) Author() string { + return "Simone Margaritelli " +} + +func (pp *PacketProxy) destroyQueue() { + if pp.queue == nil { + return + } + + pp.queue.DestroyQueue() + pp.queue.Close() + pp.queue = nil +} + +func (pp *PacketProxy) runRule(enable bool) (err error) { + action := "-A" + if enable == false { + action = "-D" + } + + args := []string{ + action, pp.chainName, + } + + if pp.rule != "" { + rule := strings.Split(pp.rule, " ") + args = append(args, rule...) + } + + args = append(args, []string{ + "-j", "NFQUEUE", + "--queue-num", fmt.Sprintf("%d", pp.queueNum), + }...) + + log.Debug("iptables %s", args) + + _, err = core.Exec("iptables", args) + return +} + +func (pp *PacketProxy) Configure() (err error) { + golog.SetOutput(ioutil.Discard) + + pp.destroyQueue() + + if err, pp.queueNum = pp.IntParam("packet.proxy.queue.num"); err != nil { + return + } else if err, pp.chainName = pp.StringParam("packet.proxy.chain"); err != nil { + return + } else if err, pp.rule = pp.StringParam("packet.proxy.rule"); err != nil { + return + } else if err, pp.pluginPath = pp.StringParam("packet.proxy.plugin"); err != nil { + return + } + + if pp.pluginPath == "" { + return fmt.Errorf("The parameter %s can not be empty.", core.Bold("packet.proxy.plugin")) + } else if core.Exists(pp.pluginPath) == false { + return fmt.Errorf("%s does not exist.", pp.pluginPath) + } + + log.Info("Loading packet proxy plugin from %s ...", pp.pluginPath) + + var ok bool + var sym plugin.Symbol + + if pp.plugin, err = plugin.Open(pp.pluginPath); err != nil { + return + } else if sym, err = pp.plugin.Lookup("OnPacket"); err != nil { + return + } else if pp.queueCb, ok = sym.(func(*nfqueue.Payload) int); ok == false { + return fmt.Errorf("Symbol OnPacket is not a valid callback function.") + } + + pp.queue = new(nfqueue.Queue) + if err = pp.queue.SetCallback(dummyCallback); err != nil { + return + } else if err = pp.queue.Init(); err != nil { + return + } else if err = pp.queue.Unbind(syscall.AF_INET); err != nil { + return + } else if err = pp.queue.Bind(syscall.AF_INET); err != nil { + return + } else if err = pp.queue.CreateQueue(pp.queueNum); err != nil { + return + } else if err = pp.queue.SetMode(nfqueue.NFQNL_COPY_PACKET); err != nil { + return + } else if err = pp.runRule(true); err != nil { + return + } + + return nil +} + +// we need this because for some reason we can't directly +// pass the symbol loaded from the plugin as a direct +// CGO callback ... ¯\_(ツ)_/¯ +func dummyCallback(payload *nfqueue.Payload) int { + return mod.queueCb(payload) +} + +func (pp *PacketProxy) Start() error { + if pp.Running() == true { + return session.ErrAlreadyStarted + } else if err := pp.Configure(); err != nil { + return err + } + + return pp.SetRunning(true, func() { + log.Info("%s started on queue number %d", core.Green("packet.proxy"), pp.queueNum) + + defer pp.destroyQueue() + + pp.queue.Loop() + + pp.done <- true + }) + + return nil +} + +func (pp *PacketProxy) Stop() error { + return pp.SetRunning(false, func() { + pp.queue.StopLoop() + pp.runRule(false) + <-pp.done + }) +} diff --git a/modules/packet_proxy_unsupported.go b/modules/packet_proxy_unsupported.go new file mode 100644 index 00000000..c2bee27c --- /dev/null +++ b/modules/packet_proxy_unsupported.go @@ -0,0 +1,47 @@ +// +build windows darwin + +package modules + +import ( + "errors" + + "github.com/bettercap/bettercap/session" +) + +var ( + notSupported = errors.New("packet.proxy is not supported on this OS") +) + +type PacketProxy struct { + session.SessionModule +} + +func NewPacketProxy(s *session.Session) *PacketProxy { + return &PacketProxy{ + SessionModule: session.NewSessionModule("packet.proxy", s), + } +} + +func (pp PacketProxy) Name() string { + return "packet.proxy" +} + +func (pp PacketProxy) Description() string { + return "Not supported on this OS" +} + +func (pp PacketProxy) Author() string { + return "Simone Margaritelli " +} + +func (pp *PacketProxy) Configure() (err error) { + return notSupported +} + +func (pp *PacketProxy) Start() error { + return notSupported +} + +func (pp *PacketProxy) Stop() error { + return notSupported +} diff --git a/vendor/github.com/chifflier/nfqueue-go/.travis.yml b/vendor/github.com/chifflier/nfqueue-go/.travis.yml new file mode 100644 index 00000000..43d4d526 --- /dev/null +++ b/vendor/github.com/chifflier/nfqueue-go/.travis.yml @@ -0,0 +1,12 @@ +language: go + +go: + - 1.6 + - tip +install: + - go get github.com/google/gopacket + - go get github.com/google/gopacket/layers + - go get github.com/chifflier/nfqueue-go/nfqueue +before_install: + - sudo apt-get -qq update + - sudo apt-get install -y pkg-config libnfnetlink-dev libnetfilter-queue-dev diff --git a/vendor/github.com/chifflier/nfqueue-go/COPYING b/vendor/github.com/chifflier/nfqueue-go/COPYING new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/vendor/github.com/chifflier/nfqueue-go/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/vendor/github.com/chifflier/nfqueue-go/README.md b/vendor/github.com/chifflier/nfqueue-go/README.md new file mode 100644 index 00000000..808dd9fa --- /dev/null +++ b/vendor/github.com/chifflier/nfqueue-go/README.md @@ -0,0 +1,54 @@ +# nfqueue-go + + +[![Build Status](https://travis-ci.org/chifflier/nfqueue-go.svg?branch=master)](https://travis-ci.org/chifflier/nfqueue-go) +[![GoDoc](https://godoc.org/github.com/chifflier/nfqueue-go?status.svg)](https://godoc.org/github.com/chifflier/nfqueue-go/nfqueue) + +nfqueue-go is a wrapper library for +[libnetfilter-queue](http://www.netfilter.org/projects/libnetfilter_queue/). The goal is to provide a library to gain access to packets queued by the kernel packet filter. + +It is important to note that these bindings will not follow blindly libnetfilter_queue API. For ex., some higher-level wrappers will be provided for the open/bind/create mechanism (using one function call instead of three). + +**The API is not yet stable.** + +To use the library, a program must +- open a queue +- bind to a network family (`AF_PACKET` for IPv4) +- provide a callback function, which will be automatically called when a packet is received. The callback must return a verdict +- create the queue, providing the queue number (which must match the `--queue-num` from the iptables rules, see below +- run a loop, waiting for events. The program should also provide a clean way to exit the loop (for ex on `SIGINT`) + +## Using library + +``` +import "github.com/chifflier/nfqueue-go/nfqueue" +``` + +## Example + +See [test_nfqueue](nfqueue/test_nfqueue/test_nfqueue.go) for a minimal example, and [test_nfqueue_gopacket](nfqueue/test_nfqueue_gopacket/test_nfqueue.go) for an example using the [gopacket](https://github.com/google/gopacket) library to decode the packets. + +## IPtables + +You must add rules in netfilter to send packets to the userspace queue. +The number of the queue (--queue-num option in netfilter) must match the +number provided to create_queue(). + +Example of iptables rules: + + iptables -A OUTPUT --destination 1.2.3.4 -j NFQUEUE --queue-num 0 + +Of course, you should be more restrictive, depending on your needs. + +## Privileges + +nfqueue-go does not require root privileges, but needs to open a netlink socket and send/receive packets to the kernel. + +You have several options: +- Use the CAP_NET_ADMIN capability in order to allow your application to receive from and to send packets to kernel-space: +```setcap 'cap_net_admin=+ep' /path/to/program``` +- Run your program as `root` and drop privileges + +## License + +This library is licensed under the GNU General Public License version 2, or (at your option) any later version. diff --git a/vendor/github.com/chifflier/nfqueue-go/nfqueue/libnfqueue1.go b/vendor/github.com/chifflier/nfqueue-go/nfqueue/libnfqueue1.go new file mode 100644 index 00000000..2635af54 --- /dev/null +++ b/vendor/github.com/chifflier/nfqueue-go/nfqueue/libnfqueue1.go @@ -0,0 +1,50 @@ +// +build libnfqueue1 + +package nfqueue + +// This file contains code specific to versions >= 1.0 of libnetfilter_queue + +/* +#include +#include +#include +#include +#include +*/ +import "C" + +import ( + "log" + "unsafe" +) + +// SetVerdictMark issues a verdict for a packet, but a mark can be set +// +// Every queued packet _must_ have a verdict specified by userspace. +func (p *Payload) SetVerdictMark(verdict int, mark uint32) error { + log.Printf("Setting verdict for packet %d: %d mark %lx\n",p.Id,verdict,mark) + C.nfq_set_verdict2( + p.c_qh, + C.u_int32_t(p.Id), + C.u_int32_t(verdict), + C.u_int32_t(mark), + 0,nil) + return nil +} + +// SetVerdictMarkModified issues a verdict for a packet, but replaces the +// packet with the provided one, and a mark can be set. +// +// Every queued packet _must_ have a verdict specified by userspace. +func (p *Payload) SetVerdictMarkModified(verdict int, mark uint32, data []byte) error { + log.Printf("Setting verdict for NEW packet %d: %d mark %lx\n",p.Id,verdict,mark) + C.nfq_set_verdict2( + p.c_qh, + C.u_int32_t(p.Id), + C.u_int32_t(verdict), + C.u_int32_t(mark), + C.u_int32_t(len(data)), + (*C.uchar)(unsafe.Pointer(&data[0])), + ) + return nil +} diff --git a/vendor/github.com/chifflier/nfqueue-go/nfqueue/nfq_cb.go b/vendor/github.com/chifflier/nfqueue-go/nfqueue/nfq_cb.go new file mode 100644 index 00000000..bf9e50dd --- /dev/null +++ b/vendor/github.com/chifflier/nfqueue-go/nfqueue/nfq_cb.go @@ -0,0 +1,35 @@ +package nfqueue + +import ( + "unsafe" +) + +import "C" + +/* +Cast argument to Queue* before calling the real callback + +Notes: + - export cannot be done in the same file (nfqueue.go) else it + fails to build (multiple definitions of C functions) + See https://github.com/golang/go/issues/3497 + See https://github.com/golang/go/wiki/cgo + - this cast is caused by the fact that cgo does not support + exporting structs + See https://github.com/golang/go/wiki/cgo + +This function must _nerver_ be called directly. +*/ +/* +BUG(GoCallbackWrapper): The return value from the Go callback is used as a +verdict. This works, and avoids packets without verdict to be queued, but +prevents using out-of-order replies. +*/ +//export GoCallbackWrapper +func GoCallbackWrapper(ptr_q *unsafe.Pointer, ptr_nfad *unsafe.Pointer) int { + q := (*Queue)(unsafe.Pointer(ptr_q)) + payload := build_payload(q.c_qh, ptr_nfad) + return q.cb(payload) +} + + diff --git a/vendor/github.com/chifflier/nfqueue-go/nfqueue/nfqueue.go b/vendor/github.com/chifflier/nfqueue-go/nfqueue/nfqueue.go new file mode 100644 index 00000000..dd8b08bf --- /dev/null +++ b/vendor/github.com/chifflier/nfqueue-go/nfqueue/nfqueue.go @@ -0,0 +1,380 @@ +// Go bindings for the NFQUEUE netfilter target +// libnetfilter_queue is a userspace library providing an API to access packets +// that have been queued by the Linux kernel packet filter. +// +// This provides an easy way to filter packets from userspace, and use tools +// or libraries that are not accessible from kernelspace. +// +// BUG(nfqueue): This package currently displays lots of debug information +package nfqueue + +// XXX we should use something like +// pkg-config --libs libnetfilter_queue + +// #cgo pkg-config: libnetfilter_queue +/* +#include +#include +#include +#include +#include +#include + +extern int GoCallbackWrapper(void *data, void *nfad); +static inline ssize_t recv_to(int sockfd, void *buf, size_t len, int flags, int to); + +int _process_loop(struct nfq_handle *h, + int *fd, + int flags, + int max_count) { + int rv; + char buf[65535]; + int count; + + count = 0; + + (*fd) = nfq_fd(h); + if (fd < 0) { + return -1; + } + + //avoid ENOBUFS on read() operation, otherwise the while loop is interrupted. + int opt = 1; + rv = setsockopt(*fd, SOL_NETLINK, NETLINK_NO_ENOBUFS, &opt, sizeof(int)); + if (rv == -1) { + return -1; + } + + while (h && *fd != -1) { + rv = recv_to(*fd, buf, sizeof(buf), flags, 500); + if (rv > 0) { + nfq_handle_packet(h, buf, rv); + count++; + if (max_count > 0 && count >= max_count) { + break; + } + } else if (rv < 0){ + return rv; + } + } + return count; +} + +void _stop_loop(int *fd) { + (*fd) = -1; +} + +// recv with timeout using select +static inline ssize_t recv_to(int sockfd, void *buf, size_t len, int flags, int to) { + int rv; + ssize_t result; + fd_set readset; + + // Initialize timeval struct + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = to * 1000; + + // Initialize socket set + FD_ZERO(&readset); + FD_SET(sockfd, &readset); + + rv = select(sockfd+1, &readset, (fd_set *) 0, (fd_set *) 0, &timeout); + // Check status + if (rv < 0) { + return -1; + } else if (rv > 0 && FD_ISSET(sockfd, &readset)) { + // Receive (ensure that the socket is set to non blocking mode!) + result = recv(sockfd, buf, len, flags); + return result; + } + + return 0; +} + +int c_nfq_cb(struct nfq_q_handle *qh, + struct nfgenmsg *nfmsg, + struct nfq_data *nfad, void *data) { + return GoCallbackWrapper(data, nfad); +} + +// wrap nfq_get_payload so cgo always have the same prototype +// (libnetfilter_queue 0.17 uses a signed char) +static int _c_get_payload (struct nfq_data *nfad, unsigned char **data) +{ + return nfq_get_payload (nfad, data); +} +*/ +import "C" + +import ( + "errors" + "log" + "unsafe" +) + +var ErrNotInitialized = errors.New("nfqueue: queue not initialized") +var ErrOpenFailed = errors.New("nfqueue: open failed") +var ErrRuntime = errors.New("nfqueue: runtime error") + +var NF_DROP = C.NF_DROP +var NF_ACCEPT = C.NF_ACCEPT +var NF_QUEUE = C.NF_QUEUE +var NF_REPEAT = C.NF_REPEAT +var NF_STOP = C.NF_STOP + +var NFQNL_COPY_NONE uint8 = C.NFQNL_COPY_NONE +var NFQNL_COPY_META uint8 = C.NFQNL_COPY_META +var NFQNL_COPY_PACKET uint8 = C.NFQNL_COPY_PACKET + +// Prototype for a NFQUEUE callback. +// The callback receives the NFQUEUE ID of the packet, and +// the packet payload. +// Packet data start from the IP layer (ethernet information are not included). +// It must return the verdict for the packet. +type Callback func(*Payload) int + +// Queue is an opaque structure describing a connection to a kernel NFQUEUE, +// and the associated Go callback. +type Queue struct { + c_h (*C.struct_nfq_handle) + c_qh (*C.struct_nfq_q_handle) + c_fd (*C.int) + + cb Callback +} + +// Init creates a netfilter queue which can be used to receive packets +// from the kernel. +func (q *Queue) Init() error { + log.Println("Opening queue") + q.c_h = C.nfq_open() + if (q.c_h == nil) { + log.Println("nfq_open failed") + return ErrOpenFailed + } + q.c_fd = (*C.int)(C.malloc(C.sizeof_int)) + return nil +} + +// SetCallback sets the callback function, fired when a packet is received. +func (q *Queue) SetCallback(cb Callback) error { + q.cb = cb + return nil +} + +func (q *Queue) Close() { + if (q.c_h != nil) { + log.Println("Closing queue") + C.nfq_close(q.c_h) + q.c_h = nil + } + C.free(unsafe.Pointer(q.c_fd)) +} + +// Bind binds a Queue to a given protocol family. +// +// Usually, the family is syscall.AF_INET for IPv4, and syscall.AF_INET6 for IPv6 +func (q *Queue) Bind(af_family int) error { + if (q.c_h == nil) { + return ErrNotInitialized + } + log.Println("Binding to selected family") + /* Errors in nfq_bind_pf are non-fatal ... + * This function just tells the kernel that nfnetlink_queue is + * the chosen module to queue packets to userspace. + */ + _ = C.nfq_bind_pf(q.c_h,C.u_int16_t(af_family)) + return nil +} + +// Unbind a queue from the given protocol family. +// +// Note that errors from this function can usually be ignored. +func (q *Queue) Unbind(af_family int) error { + if (q.c_h == nil) { + return ErrNotInitialized + } + log.Println("Unbinding to selected family") + rc := C.nfq_unbind_pf(q.c_h,C.u_int16_t(af_family)) + if (rc < 0) { + log.Println("nfq_unbind_pf failed") + return ErrRuntime + } + return nil +} + +// Create a new queue handle +// +// The queue must be initialized (using Init) and bound (using Bind), and +// a callback function must be set (using SetCallback). +func (q *Queue) CreateQueue(queue_num int) error { + if (q.c_h == nil) { + return ErrNotInitialized + } + if (q.cb == nil) { + return ErrNotInitialized + } + log.Println("Creating queue") + q.c_qh = C.nfq_create_queue(q.c_h,C.u_int16_t(queue_num),(*C.nfq_callback)(C.c_nfq_cb),unsafe.Pointer(q)) + if (q.c_qh == nil) { + log.Println("nfq_create_queue failed") + return ErrRuntime + } + // Default mode + C.nfq_set_mode(q.c_qh,C.NFQNL_COPY_PACKET,0xffff) + return nil +} + +// Destroy a queue handle +// +// This also unbind from the nfqueue handler, so you don't have to call Unbind() +// Note that errors from this function can usually be ignored. +func (q *Queue) DestroyQueue() error { + if (q.c_qh == nil) { + return ErrNotInitialized + } + log.Println("Destroy queue") + rc := C.nfq_destroy_queue(q.c_qh) + if (rc < 0) { + log.Println("nfq_destroy_queue failed") + return ErrRuntime + } + q.c_qh = nil + return nil +} + +// SetMode sets the amount of packet data that nfqueue copies to userspace +// +// Default mode is NFQNL_COPY_PACKET +func (q *Queue) SetMode(mode uint8) error { + if (q.c_h == nil) { + return ErrNotInitialized + } + if (q.c_qh == nil) { + return ErrNotInitialized + } + C.nfq_set_mode(q.c_qh,C.u_int8_t(mode),0xffff) + return nil +} + +// SetQueueMaxLen fixes the number of packets the kernel will store before internally before dropping upcoming packets +func (q *Queue) SetQueueMaxLen(maxlen uint32) error { + if (q.c_h == nil) { + return ErrNotInitialized + } + if (q.c_qh == nil) { + return ErrNotInitialized + } + C.nfq_set_queue_maxlen(q.c_qh,C.u_int32_t(maxlen)) + return nil +} + +// Main loop: Loop starts a loop, receiving kernel events +// and processing packets using the callback function. +func (q *Queue) Loop() error { + if (q.c_h == nil) { + return ErrNotInitialized + } + if (q.c_qh == nil) { + return ErrNotInitialized + } + if (q.cb == nil) { + return ErrNotInitialized + } + + log.Println("Start Loop") + ret := C._process_loop(q.c_h, q.c_fd, 0, -1) + if ret < 0 { + return ErrRuntime + } + return nil +} + +func (q *Queue) StopLoop() { + log.Println("Stop Loop") + C._stop_loop(q.c_fd) +} + +// Payload is a structure describing a packet received from the kernel +type Payload struct { + c_qh (*C.struct_nfq_q_handle) + nfad *C.struct_nfq_data + + // NFQueue ID of the packet + Id uint32 + // Packet data + Data []byte +} + +func build_payload(c_qh *C.struct_nfq_q_handle, ptr_nfad *unsafe.Pointer) *Payload { + var payload_data *C.uchar + var data []byte + + nfad := (*C.struct_nfq_data)(unsafe.Pointer(ptr_nfad)) + + ph := C.nfq_get_msg_packet_hdr(nfad) + id := C.ntohl(C.uint32_t(ph.packet_id)) + payload_len := C._c_get_payload(nfad, &payload_data) + if (payload_len >= 0) { + data = C.GoBytes(unsafe.Pointer(payload_data), C.int(payload_len)) + } + + p := new(Payload) + p.c_qh = c_qh + p.nfad = nfad + p.Id = uint32(id) + p.Data = data + + return p +} + +// SetVerdict issues a verdict for a packet. +// +// Every queued packet _must_ have a verdict specified by userspace. +func (p *Payload) SetVerdict(verdict int) error { + log.Printf("Setting verdict for packet %d: %d\n",p.Id,verdict) + C.nfq_set_verdict(p.c_qh,C.u_int32_t(p.Id),C.u_int32_t(verdict),0,nil) + return nil +} + +// SetVerdictModified issues a verdict for a packet, but replaces the packet +// with the provided one. +// +// Every queued packet _must_ have a verdict specified by userspace. +func (p *Payload) SetVerdictModified(verdict int, data []byte) error { + log.Printf("Setting verdict for NEW packet %d: %d\n",p.Id,verdict) + C.nfq_set_verdict( + p.c_qh, + C.u_int32_t(p.Id), + C.u_int32_t(verdict), + C.u_int32_t(len(data)), + (*C.uchar)(unsafe.Pointer(&data[0])), + ) + return nil +} + +// Returns the packet mark +func (p *Payload) GetNFMark() uint32 { + return uint32(C.nfq_get_nfmark(p.nfad)) +} + +// Returns the interface that the packet was received through +func (p *Payload) GetInDev() uint32 { + return uint32(C.nfq_get_indev(p.nfad)) +} + +// Returns the interface that the packet will be routed out +func (p *Payload) GetOutDev() uint32 { + return uint32(C.nfq_get_outdev(p.nfad)) +} + +// Returns the physical interface that the packet was received through +func (p *Payload) GetPhysInDev() uint32 { + return uint32(C.nfq_get_physindev(p.nfad)) +} + +// Returns the physical interface that the packet will be routed out +func (p *Payload) GetPhysOutDev() uint32 { + return uint32(C.nfq_get_physoutdev(p.nfad)) +} diff --git a/vendor/github.com/chifflier/nfqueue-go/nfqueue/test_nfqueue/test_nfqueue.go b/vendor/github.com/chifflier/nfqueue-go/nfqueue/test_nfqueue/test_nfqueue.go new file mode 100644 index 00000000..9f832ac8 --- /dev/null +++ b/vendor/github.com/chifflier/nfqueue-go/nfqueue/test_nfqueue/test_nfqueue.go @@ -0,0 +1,54 @@ +package main + +import ( + "encoding/hex" + "fmt" + "github.com/chifflier/nfqueue-go/nfqueue" + "os" + "os/signal" + "syscall" +) + +func real_callback(payload *nfqueue.Payload) int { + fmt.Println("Real callback") + fmt.Printf(" id: %d\n", payload.Id) + fmt.Printf(" mark: %d\n", payload.GetNFMark()) + fmt.Printf(" in %d out %d\n", payload.GetInDev(), payload.GetOutDev()) + fmt.Printf(" Φin %d Φout %d\n", payload.GetPhysInDev(), payload.GetPhysOutDev()) + fmt.Println(hex.Dump(payload.Data)) + fmt.Println("-- ") + payload.SetVerdict(nfqueue.NF_ACCEPT) + return 0 +} + +func main() { + q := new(nfqueue.Queue) + + q.SetCallback(real_callback) + + q.Init() + defer q.Close() + + q.Unbind(syscall.AF_INET) + q.Bind(syscall.AF_INET) + + q.CreateQueue(0) + q.SetMode(nfqueue.NFQNL_COPY_PACKET) + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + go func(){ + for sig := range c { + // sig is a ^C, handle it + _ = sig + q.StopLoop() + } + }() + + // XXX Drop privileges here + + q.Loop() + q.DestroyQueue() + q.Close() + os.Exit(0) +} diff --git a/vendor/github.com/chifflier/nfqueue-go/nfqueue/test_nfqueue_gopacket/test_nfqueue.go b/vendor/github.com/chifflier/nfqueue-go/nfqueue/test_nfqueue_gopacket/test_nfqueue.go new file mode 100644 index 00000000..beac36ac --- /dev/null +++ b/vendor/github.com/chifflier/nfqueue-go/nfqueue/test_nfqueue_gopacket/test_nfqueue.go @@ -0,0 +1,66 @@ +package main + +import ( + "encoding/hex" + "fmt" + "github.com/chifflier/nfqueue-go/nfqueue" + "os" + "os/signal" + "syscall" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" +) + +func real_callback(payload *nfqueue.Payload) int { + fmt.Println("Real callback") + fmt.Printf(" id: %d\n", payload.Id) + fmt.Println(hex.Dump(payload.Data)) + // Decode a packet + packet := gopacket.NewPacket(payload.Data, layers.LayerTypeIPv4, gopacket.Default) + // Get the TCP layer from this packet + if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil { + fmt.Println("This is a TCP packet!") + // Get actual TCP data from this layer + tcp, _ := tcpLayer.(*layers.TCP) + fmt.Printf("From src port %d to dst port %d\n", tcp.SrcPort, tcp.DstPort) + } + // Iterate over all layers, printing out each layer type + for _, layer := range packet.Layers() { + fmt.Println("PACKET LAYER:", layer.LayerType()) + fmt.Println(gopacket.LayerDump(layer)) + } + fmt.Println("-- ") + payload.SetVerdict(nfqueue.NF_ACCEPT) + return 0 +} + +func main() { + q := new(nfqueue.Queue) + + q.SetCallback(real_callback) + + q.Init() + + q.Unbind(syscall.AF_INET) + q.Bind(syscall.AF_INET) + + q.CreateQueue(0) + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + go func(){ + for sig := range c { + // sig is a ^C, handle it + _ = sig + q.StopLoop() + } + }() + + // XXX Drop privileges here + + q.Loop() + q.DestroyQueue() + q.Close() + os.Exit(0) +}