From 4ce4e978c6254cb17d1c00eb0a3d17ad51dbea3a Mon Sep 17 00:00:00 2001 From: David Scott Date: Sun, 3 Apr 2016 11:14:36 +0100 Subject: [PATCH 01/12] Add skeleton /sbin/proxy program This will eventually contain the `docker-proxy` replacement. Signed-off-by: David Scott --- alpine/Dockerfile | 1 + alpine/packages/Makefile | 3 +++ alpine/packages/proxy/.gitignore | 1 + alpine/packages/proxy/Dockerfile | 13 +++++++++++++ alpine/packages/proxy/Makefile | 10 ++++++++++ alpine/packages/proxy/README.md | 11 +++++++++++ alpine/packages/proxy/main.go | 4 ++++ 7 files changed, 43 insertions(+) create mode 100644 alpine/packages/proxy/.gitignore create mode 100644 alpine/packages/proxy/Dockerfile create mode 100644 alpine/packages/proxy/Makefile create mode 100644 alpine/packages/proxy/README.md create mode 100644 alpine/packages/proxy/main.go diff --git a/alpine/Dockerfile b/alpine/Dockerfile index 851641b26..1de4df159 100644 --- a/alpine/Dockerfile +++ b/alpine/Dockerfile @@ -26,6 +26,7 @@ COPY kernel/kernel-source-info /etc/ COPY packages/9pudc/9pudc /sbin/ COPY packages/9pudc/etc /etc/ +COPY packages/proxy/proxy /sbin/ COPY packages/transfused/transfused /sbin/ COPY packages/transfused/etc /etc/ COPY packages/mdnstool/mdnstool /sbin/ diff --git a/alpine/packages/Makefile b/alpine/packages/Makefile index 36426e059..82ea7ae7e 100644 --- a/alpine/packages/Makefile +++ b/alpine/packages/Makefile @@ -1,5 +1,6 @@ all: $(MAKE) -C 9pudc OS=linux + $(MAKE) -C proxy OS=linux $(MAKE) -C transfused OS=linux $(MAKE) -C mdnstool OS=linux $(MAKE) -C hupper OS=linux @@ -8,6 +9,7 @@ all: arm: $(MAKE) -C 9pudc OS=linux ARCH=arm + $(MAKE) -C proxy OS=linux ARCH=arm $(MAKE) -C transfused OS=linux ARCH=arm $(MAKE) -C mdnstool OS=linux ARCH=arm $(MAKE) -C hupper OS=linux ARCH=arm @@ -15,6 +17,7 @@ arm: clean: $(MAKE) -C 9pudc clean + $(MAKE) -C proxy clean $(MAKE) -C transfused clean $(MAKE) -C mdnstool clean $(MAKE) -C docker clean diff --git a/alpine/packages/proxy/.gitignore b/alpine/packages/proxy/.gitignore new file mode 100644 index 000000000..c659c4f4f --- /dev/null +++ b/alpine/packages/proxy/.gitignore @@ -0,0 +1 @@ +/proxy diff --git a/alpine/packages/proxy/Dockerfile b/alpine/packages/proxy/Dockerfile new file mode 100644 index 000000000..4cb2c4947 --- /dev/null +++ b/alpine/packages/proxy/Dockerfile @@ -0,0 +1,13 @@ +FROM golang:alpine + +RUN mkdir -p /go/src/proxy +WORKDIR /go/src/proxy + +COPY . /go/src/proxy/ + +ARG GOARCH +ARG GOOS + +RUN go install + +RUN [ -f /go/bin/*/proxy ] && mv /go/bin/*/proxy /go/bin/ || true diff --git a/alpine/packages/proxy/Makefile b/alpine/packages/proxy/Makefile new file mode 100644 index 000000000..40e9381e6 --- /dev/null +++ b/alpine/packages/proxy/Makefile @@ -0,0 +1,10 @@ +all: proxy + +proxy: Dockerfile main.go + docker build --build-arg GOOS=$(OS) --build-arg GOARCH=$(ARCH) -t proxy:build . + docker run --rm proxy:build cat /go/bin/proxy > proxy + chmod 755 proxy + +clean: + rm -f proxy + docker images -q proxy:build | xargs docker rmi -f diff --git a/alpine/packages/proxy/README.md b/alpine/packages/proxy/README.md new file mode 100644 index 000000000..e325cf049 --- /dev/null +++ b/alpine/packages/proxy/README.md @@ -0,0 +1,11 @@ +docker-proxy which can set up tunnels into the VM +================================================= + +This is a replacement for the built-in `docker-proxy` command, which +proxies data from external ports to internal container ports. + +This program uses the 9P filesystem under /port to extend the port +forward from the host running the Moby VM all the way to the container. + +docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8080 -container-ip 172.17.0.2 -container-port 8080 + diff --git a/alpine/packages/proxy/main.go b/alpine/packages/proxy/main.go new file mode 100644 index 000000000..da29a2cad --- /dev/null +++ b/alpine/packages/proxy/main.go @@ -0,0 +1,4 @@ +package main + +func main() { +} From 1c0b7f87745742fc0483c795701c7823bfc2b506 Mon Sep 17 00:00:00 2001 From: David Scott Date: Sun, 3 Apr 2016 12:16:25 +0100 Subject: [PATCH 02/12] Import docker/libnetwork/portmapper/proxy.go Signed-off-by: David Scott --- alpine/packages/proxy/Dockerfile | 7 +- alpine/packages/proxy/LICENSE | 202 ++++++++++ alpine/packages/proxy/Makefile | 2 +- alpine/packages/proxy/main.go | 1 + alpine/packages/proxy/proxy.go | 72 ++++ .../github.com/Sirupsen/logrus/.gitignore | 1 + .../github.com/Sirupsen/logrus/.travis.yml | 8 + .../github.com/Sirupsen/logrus/CHANGELOG.md | 47 +++ .../vendor/github.com/Sirupsen/logrus/LICENSE | 21 ++ .../github.com/Sirupsen/logrus/README.md | 357 ++++++++++++++++++ .../vendor/github.com/Sirupsen/logrus/doc.go | 26 ++ .../github.com/Sirupsen/logrus/entry.go | 264 +++++++++++++ .../Sirupsen/logrus/examples/basic/basic.go | 50 +++ .../Sirupsen/logrus/examples/hook/hook.go | 30 ++ .../github.com/Sirupsen/logrus/exported.go | 193 ++++++++++ .../github.com/Sirupsen/logrus/formatter.go | 48 +++ .../logrus/formatters/logstash/logstash.go | 56 +++ .../github.com/Sirupsen/logrus/hooks.go | 34 ++ .../logrus/hooks/airbrake/airbrake.go | 54 +++ .../Sirupsen/logrus/hooks/bugsnag/bugsnag.go | 68 ++++ .../logrus/hooks/papertrail/README.md | 28 ++ .../logrus/hooks/papertrail/papertrail.go | 55 +++ .../Sirupsen/logrus/hooks/sentry/README.md | 111 ++++++ .../Sirupsen/logrus/hooks/sentry/sentry.go | 137 +++++++ .../Sirupsen/logrus/hooks/syslog/README.md | 20 + .../Sirupsen/logrus/hooks/syslog/syslog.go | 59 +++ .../Sirupsen/logrus/json_formatter.go | 41 ++ .../github.com/Sirupsen/logrus/logger.go | 206 ++++++++++ .../github.com/Sirupsen/logrus/logrus.go | 98 +++++ .../Sirupsen/logrus/terminal_bsd.go | 9 + .../Sirupsen/logrus/terminal_linux.go | 12 + .../Sirupsen/logrus/terminal_notwindows.go | 21 ++ .../Sirupsen/logrus/terminal_windows.go | 27 ++ .../Sirupsen/logrus/text_formatter.go | 159 ++++++++ .../github.com/Sirupsen/logrus/writer.go | 31 ++ 35 files changed, 2553 insertions(+), 2 deletions(-) create mode 100644 alpine/packages/proxy/LICENSE create mode 100644 alpine/packages/proxy/proxy.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/.gitignore create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/.travis.yml create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/CHANGELOG.md create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/LICENSE create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/README.md create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/doc.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/entry.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/examples/basic/basic.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/examples/hook/hook.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/exported.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/formatter.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/formatters/logstash/logstash.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/papertrail/README.md create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/sentry/README.md create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/sentry/sentry.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/syslog/README.md create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/syslog/syslog.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/json_formatter.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/logger.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/logrus.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/terminal_bsd.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/terminal_linux.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/terminal_notwindows.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/terminal_windows.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/text_formatter.go create mode 100644 alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/writer.go diff --git a/alpine/packages/proxy/Dockerfile b/alpine/packages/proxy/Dockerfile index 4cb2c4947..98054e0cf 100644 --- a/alpine/packages/proxy/Dockerfile +++ b/alpine/packages/proxy/Dockerfile @@ -3,7 +3,12 @@ FROM golang:alpine RUN mkdir -p /go/src/proxy WORKDIR /go/src/proxy -COPY . /go/src/proxy/ +COPY * /go/src/proxy/ + +RUN mkdir -p /go/src/pkg/proxy +COPY pkg/* /go/src/pkg/proxy/ +RUN mkdir -p /go/src/vendor/github.com/Sirupsen/logrus +COPY vendor/github.com/Sirupsen/logrus/* /go/src/vendor/github.com/Sirupsen/logrus/ ARG GOARCH ARG GOOS diff --git a/alpine/packages/proxy/LICENSE b/alpine/packages/proxy/LICENSE new file mode 100644 index 000000000..e06d20818 --- /dev/null +++ b/alpine/packages/proxy/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/alpine/packages/proxy/Makefile b/alpine/packages/proxy/Makefile index 40e9381e6..5c041595d 100644 --- a/alpine/packages/proxy/Makefile +++ b/alpine/packages/proxy/Makefile @@ -1,6 +1,6 @@ all: proxy -proxy: Dockerfile main.go +proxy: Dockerfile main.go proxy.go docker build --build-arg GOOS=$(OS) --build-arg GOARCH=$(ARCH) -t proxy:build . docker run --rm proxy:build cat /go/bin/proxy > proxy chmod 755 proxy diff --git a/alpine/packages/proxy/main.go b/alpine/packages/proxy/main.go index da29a2cad..c709a4996 100644 --- a/alpine/packages/proxy/main.go +++ b/alpine/packages/proxy/main.go @@ -1,4 +1,5 @@ package main func main() { + execProxy() } diff --git a/alpine/packages/proxy/proxy.go b/alpine/packages/proxy/proxy.go new file mode 100644 index 000000000..64937c50d --- /dev/null +++ b/alpine/packages/proxy/proxy.go @@ -0,0 +1,72 @@ +package main + +import ( + "flag" + "fmt" + "log" + "net" + "os" + "os/signal" + "syscall" + + "pkg/proxy" +) + +// From docker/libnetwork/portmapper/proxy.go + +// execProxy is the reexec function that is registered to start the userland proxies +func execProxy() { + f := os.NewFile(3, "signal-parent") + host, container := parseHostContainerAddrs() + + p, err := proxy.NewProxy(host, container) + if err != nil { + fmt.Fprintf(f, "1\n%s", err) + f.Close() + os.Exit(1) + } + go handleStopSignals(p) + fmt.Fprint(f, "0\n") + f.Close() + + // Run will block until the proxy stops + p.Run() +} + +// parseHostContainerAddrs parses the flags passed on reexec to create the TCP or UDP +// net.Addrs to map the host and container ports +func parseHostContainerAddrs() (host net.Addr, container net.Addr) { + var ( + proto = flag.String("proto", "tcp", "proxy protocol") + hostIP = flag.String("host-ip", "", "host ip") + hostPort = flag.Int("host-port", -1, "host port") + containerIP = flag.String("container-ip", "", "container ip") + containerPort = flag.Int("container-port", -1, "container port") + ) + + flag.Parse() + + switch *proto { + case "tcp": + host = &net.TCPAddr{IP: net.ParseIP(*hostIP), Port: *hostPort} + container = &net.TCPAddr{IP: net.ParseIP(*containerIP), Port: *containerPort} + case "udp": + host = &net.UDPAddr{IP: net.ParseIP(*hostIP), Port: *hostPort} + container = &net.UDPAddr{IP: net.ParseIP(*containerIP), Port: *containerPort} + default: + log.Fatalf("unsupported protocol %s", *proto) + } + + return host, container +} + +func handleStopSignals(p proxy.Proxy) { + s := make(chan os.Signal, 10) + signal.Notify(s, os.Interrupt, syscall.SIGTERM, syscall.SIGSTOP) + + for range s { + p.Close() + + os.Exit(0) + } +} diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/.gitignore b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/.gitignore new file mode 100644 index 000000000..66be63a00 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/.gitignore @@ -0,0 +1 @@ +logrus diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/.travis.yml b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/.travis.yml new file mode 100644 index 000000000..2d8c08661 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/.travis.yml @@ -0,0 +1,8 @@ +language: go +go: + - 1.2 + - 1.3 + - 1.4 + - tip +install: + - go get -t ./... diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/CHANGELOG.md b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/CHANGELOG.md new file mode 100644 index 000000000..78f98959b --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/CHANGELOG.md @@ -0,0 +1,47 @@ +# 0.8.7 + +* logrus/core: fix possible race (#216) +* logrus/doc: small typo fixes and doc improvements + + +# 0.8.6 + +* hooks/raven: allow passing an initialized client + +# 0.8.5 + +* logrus/core: revert #208 + +# 0.8.4 + +* formatter/text: fix data race (#218) + +# 0.8.3 + +* logrus/core: fix entry log level (#208) +* logrus/core: improve performance of text formatter by 40% +* logrus/core: expose `LevelHooks` type +* logrus/core: add support for DragonflyBSD and NetBSD +* formatter/text: print structs more verbosely + +# 0.8.2 + +* logrus: fix more Fatal family functions + +# 0.8.1 + +* logrus: fix not exiting on `Fatalf` and `Fatalln` + +# 0.8.0 + +* logrus: defaults to stderr instead of stdout +* hooks/sentry: add special field for `*http.Request` +* formatter/text: ignore Windows for colors + +# 0.7.3 + +* formatter/\*: allow configuration of timestamp layout + +# 0.7.2 + +* formatter/text: Add configuration option for time format (#158) diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/LICENSE b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/LICENSE new file mode 100644 index 000000000..f090cb42f --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Simon Eskildsen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/README.md b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/README.md new file mode 100644 index 000000000..6fa6e2062 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/README.md @@ -0,0 +1,357 @@ +# Logrus :walrus: [![Build Status](https://travis-ci.org/Sirupsen/logrus.svg?branch=master)](https://travis-ci.org/Sirupsen/logrus) [![godoc reference](https://godoc.org/github.com/Sirupsen/logrus?status.png)][godoc] + +Logrus is a structured logger for Go (golang), completely API compatible with +the standard library logger. [Godoc][godoc]. **Please note the Logrus API is not +yet stable (pre 1.0). Logrus itself is completely stable and has been used in +many large deployments. The core API is unlikely to change much but please +version control your Logrus to make sure you aren't fetching latest `master` on +every build.** + +Nicely color-coded in development (when a TTY is attached, otherwise just +plain text): + +![Colored](http://i.imgur.com/PY7qMwd.png) + +With `log.Formatter = new(logrus.JSONFormatter)`, for easy parsing by logstash +or Splunk: + +```json +{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the +ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"} + +{"level":"warning","msg":"The group's number increased tremendously!", +"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"} + +{"animal":"walrus","level":"info","msg":"A giant walrus appears!", +"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"} + +{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.", +"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"} + +{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true, +"time":"2014-03-10 19:57:38.562543128 -0400 EDT"} +``` + +With the default `log.Formatter = new(&log.TextFormatter{})` when a TTY is not +attached, the output is compatible with the +[logfmt](http://godoc.org/github.com/kr/logfmt) format: + +```text +time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8 +time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10 +time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true +time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4 +time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009 +time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true +exit status 1 +``` + +#### Example + +The simplest way to use Logrus is simply the package-level exported logger: + +```go +package main + +import ( + log "github.com/Sirupsen/logrus" +) + +func main() { + log.WithFields(log.Fields{ + "animal": "walrus", + }).Info("A walrus appears") +} +``` + +Note that it's completely api-compatible with the stdlib logger, so you can +replace your `log` imports everywhere with `log "github.com/Sirupsen/logrus"` +and you'll now have the flexibility of Logrus. You can customize it all you +want: + +```go +package main + +import ( + "os" + log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus/hooks/airbrake" +) + +func init() { + // Log as JSON instead of the default ASCII formatter. + log.SetFormatter(&log.JSONFormatter{}) + + // Use the Airbrake hook to report errors that have Error severity or above to + // an exception tracker. You can create custom hooks, see the Hooks section. + log.AddHook(airbrake.NewHook("https://example.com", "xyz", "development")) + + // Output to stderr instead of stdout, could also be a file. + log.SetOutput(os.Stderr) + + // Only log the warning severity or above. + log.SetLevel(log.WarnLevel) +} + +func main() { + log.WithFields(log.Fields{ + "animal": "walrus", + "size": 10, + }).Info("A group of walrus emerges from the ocean") + + log.WithFields(log.Fields{ + "omg": true, + "number": 122, + }).Warn("The group's number increased tremendously!") + + log.WithFields(log.Fields{ + "omg": true, + "number": 100, + }).Fatal("The ice breaks!") + + // A common pattern is to re-use fields between logging statements by re-using + // the logrus.Entry returned from WithFields() + contextLogger := log.WithFields(log.Fields{ + "common": "this is a common field", + "other": "I also should be logged always", + }) + + contextLogger.Info("I'll be logged with common and other field") + contextLogger.Info("Me too") +} +``` + +For more advanced usage such as logging to multiple locations from the same +application, you can also create an instance of the `logrus` Logger: + +```go +package main + +import ( + "github.com/Sirupsen/logrus" +) + +// Create a new instance of the logger. You can have any number of instances. +var log = logrus.New() + +func main() { + // The API for setting attributes is a little different than the package level + // exported logger. See Godoc. + log.Out = os.Stderr + + log.WithFields(logrus.Fields{ + "animal": "walrus", + "size": 10, + }).Info("A group of walrus emerges from the ocean") +} +``` + +#### Fields + +Logrus encourages careful, structured logging though logging fields instead of +long, unparseable error messages. For example, instead of: `log.Fatalf("Failed +to send event %s to topic %s with key %d")`, you should log the much more +discoverable: + +```go +log.WithFields(log.Fields{ + "event": event, + "topic": topic, + "key": key, +}).Fatal("Failed to send event") +``` + +We've found this API forces you to think about logging in a way that produces +much more useful logging messages. We've been in countless situations where just +a single added field to a log statement that was already there would've saved us +hours. The `WithFields` call is optional. + +In general, with Logrus using any of the `printf`-family functions should be +seen as a hint you should add a field, however, you can still use the +`printf`-family functions with Logrus. + +#### Hooks + +You can add hooks for logging levels. For example to send errors to an exception +tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to +multiple places simultaneously, e.g. syslog. + +Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in +`init`: + +```go +import ( + log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus/hooks/airbrake" + logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog" + "log/syslog" +) + +func init() { + log.AddHook(airbrake.NewHook("https://example.com", "xyz", "development")) + + hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") + if err != nil { + log.Error("Unable to connect to local syslog daemon") + } else { + log.AddHook(hook) + } +} +``` + + +| Hook | Description | +| ----- | ----------- | +| [Airbrake](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go) | Send errors to an exception tracking service compatible with the Airbrake API. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. | +| [Papertrail](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go) | Send errors to the Papertrail hosted logging service via UDP. | +| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. | +| [BugSnag](https://github.com/Sirupsen/logrus/blob/master/hooks/bugsnag/bugsnag.go) | Send errors to the Bugsnag exception tracking service. | +| [Sentry](https://github.com/Sirupsen/logrus/blob/master/hooks/sentry/sentry.go) | Send errors to the Sentry error logging and aggregation service. | +| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. | +| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) | +| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. | +| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` | +| [Graylog](https://github.com/gemnasium/logrus-hooks/tree/master/graylog) | Hook for logging to [Graylog](http://graylog2.org/) | +| [Raygun](https://github.com/squirkle/logrus-raygun-hook) | Hook for logging to [Raygun.io](http://raygun.io/) | +| [LFShook](https://github.com/rifflock/lfshook) | Hook for logging to the local filesystem | +| [Honeybadger](https://github.com/agonzalezro/logrus_honeybadger) | Hook for sending exceptions to Honeybadger | +| [Mail](https://github.com/zbindenren/logrus_mail) | Hook for sending exceptions via mail | +| [Rollrus](https://github.com/heroku/rollrus) | Hook for sending errors to rollbar | +| [Fluentd](https://github.com/evalphobia/logrus_fluent) | Hook for logging to fluentd | +| [Mongodb](https://github.com/weekface/mgorus) | Hook for logging to mongodb | + +#### Level logging + +Logrus has six logging levels: Debug, Info, Warning, Error, Fatal and Panic. + +```go +log.Debug("Useful debugging information.") +log.Info("Something noteworthy happened!") +log.Warn("You should probably take a look at this.") +log.Error("Something failed but I'm not quitting.") +// Calls os.Exit(1) after logging +log.Fatal("Bye.") +// Calls panic() after logging +log.Panic("I'm bailing.") +``` + +You can set the logging level on a `Logger`, then it will only log entries with +that severity or anything above it: + +```go +// Will log anything that is info or above (warn, error, fatal, panic). Default. +log.SetLevel(log.InfoLevel) +``` + +It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose +environment if your application has that. + +#### Entries + +Besides the fields added with `WithField` or `WithFields` some fields are +automatically added to all logging events: + +1. `time`. The timestamp when the entry was created. +2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after + the `AddFields` call. E.g. `Failed to send event.` +3. `level`. The logging level. E.g. `info`. + +#### Environments + +Logrus has no notion of environment. + +If you wish for hooks and formatters to only be used in specific environments, +you should handle that yourself. For example, if your application has a global +variable `Environment`, which is a string representation of the environment you +could do: + +```go +import ( + log "github.com/Sirupsen/logrus" +) + +init() { + // do something here to set environment depending on an environment variable + // or command-line flag + if Environment == "production" { + log.SetFormatter(&log.JSONFormatter{}) + } else { + // The TextFormatter is default, you don't actually have to do this. + log.SetFormatter(&log.TextFormatter{}) + } +} +``` + +This configuration is how `logrus` was intended to be used, but JSON in +production is mostly only useful if you do log aggregation with tools like +Splunk or Logstash. + +#### Formatters + +The built-in logging formatters are: + +* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise + without colors. + * *Note:* to force colored output when there is no TTY, set the `ForceColors` + field to `true`. To force no colored output even if there is a TTY set the + `DisableColors` field to `true` +* `logrus.JSONFormatter`. Logs fields as JSON. +* `logrus_logstash.LogstashFormatter`. Logs fields as Logstash Events (http://logstash.net). + + ```go + logrus.SetFormatter(&logrus_logstash.LogstashFormatter{Type: “application_name"}) + ``` + +Third party logging formatters: + +* [`zalgo`](https://github.com/aybabtme/logzalgo): invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦. + +You can define your formatter by implementing the `Formatter` interface, +requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a +`Fields` type (`map[string]interface{}`) with all your fields as well as the +default ones (see Entries section above): + +```go +type MyJSONFormatter struct { +} + +log.SetFormatter(new(MyJSONFormatter)) + +func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) { + // Note this doesn't include Time, Level and Message which are available on + // the Entry. Consult `godoc` on information about those fields or read the + // source of the official loggers. + serialized, err := json.Marshal(entry.Data) + if err != nil { + return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) + } + return append(serialized, '\n'), nil +} +``` + +#### Logger as an `io.Writer` + +Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it. + +```go +w := logger.Writer() +defer w.Close() + +srv := http.Server{ + // create a stdlib log.Logger that writes to + // logrus.Logger. + ErrorLog: log.New(w, "", 0), +} +``` + +Each line written to that writer will be printed the usual way, using formatters +and hooks. The level for those entries is `info`. + +#### Rotation + +Log rotation is not provided with Logrus. Log rotation should be done by an +external program (like `logrotate(8)`) that can compress and delete old log +entries. It should not be a feature of the application-level logger. + + +[godoc]: https://godoc.org/github.com/Sirupsen/logrus diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/doc.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/doc.go new file mode 100644 index 000000000..dddd5f877 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/doc.go @@ -0,0 +1,26 @@ +/* +Package logrus is a structured logger for Go, completely API compatible with the standard library logger. + + +The simplest way to use Logrus is simply the package-level exported logger: + + package main + + import ( + log "github.com/Sirupsen/logrus" + ) + + func main() { + log.WithFields(log.Fields{ + "animal": "walrus", + "number": 1, + "size": 10, + }).Info("A walrus appears") + } + +Output: + time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10 + +For a full guide visit https://github.com/Sirupsen/logrus +*/ +package logrus diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/entry.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/entry.go new file mode 100644 index 000000000..9ae900bc5 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/entry.go @@ -0,0 +1,264 @@ +package logrus + +import ( + "bytes" + "fmt" + "io" + "os" + "time" +) + +// Defines the key when adding errors using WithError. +var ErrorKey = "error" + +// An entry is the final or intermediate Logrus logging entry. It contains all +// the fields passed with WithField{,s}. It's finally logged when Debug, Info, +// Warn, Error, Fatal or Panic is called on it. These objects can be reused and +// passed around as much as you wish to avoid field duplication. +type Entry struct { + Logger *Logger + + // Contains all the fields set by the user. + Data Fields + + // Time at which the log entry was created + Time time.Time + + // Level the log entry was logged at: Debug, Info, Warn, Error, Fatal or Panic + Level Level + + // Message passed to Debug, Info, Warn, Error, Fatal or Panic + Message string +} + +func NewEntry(logger *Logger) *Entry { + return &Entry{ + Logger: logger, + // Default is three fields, give a little extra room + Data: make(Fields, 5), + } +} + +// Returns a reader for the entry, which is a proxy to the formatter. +func (entry *Entry) Reader() (*bytes.Buffer, error) { + serialized, err := entry.Logger.Formatter.Format(entry) + return bytes.NewBuffer(serialized), err +} + +// Returns the string representation from the reader and ultimately the +// formatter. +func (entry *Entry) String() (string, error) { + reader, err := entry.Reader() + if err != nil { + return "", err + } + + return reader.String(), err +} + +// Add an error as single field (using the key defined in ErrorKey) to the Entry. +func (entry *Entry) WithError(err error) *Entry { + return entry.WithField(ErrorKey, err) +} + +// Add a single field to the Entry. +func (entry *Entry) WithField(key string, value interface{}) *Entry { + return entry.WithFields(Fields{key: value}) +} + +// Add a map of fields to the Entry. +func (entry *Entry) WithFields(fields Fields) *Entry { + data := Fields{} + for k, v := range entry.Data { + data[k] = v + } + for k, v := range fields { + data[k] = v + } + return &Entry{Logger: entry.Logger, Data: data} +} + +// This function is not declared with a pointer value because otherwise +// race conditions will occur when using multiple goroutines +func (entry Entry) log(level Level, msg string) { + entry.Time = time.Now() + entry.Level = level + entry.Message = msg + + if err := entry.Logger.Hooks.Fire(level, &entry); err != nil { + entry.Logger.mu.Lock() + fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err) + entry.Logger.mu.Unlock() + } + + reader, err := entry.Reader() + if err != nil { + entry.Logger.mu.Lock() + fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err) + entry.Logger.mu.Unlock() + } + + entry.Logger.mu.Lock() + defer entry.Logger.mu.Unlock() + + _, err = io.Copy(entry.Logger.Out, reader) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err) + } + + // To avoid Entry#log() returning a value that only would make sense for + // panic() to use in Entry#Panic(), we avoid the allocation by checking + // directly here. + if level <= PanicLevel { + panic(&entry) + } +} + +func (entry *Entry) Debug(args ...interface{}) { + if entry.Logger.Level >= DebugLevel { + entry.log(DebugLevel, fmt.Sprint(args...)) + } +} + +func (entry *Entry) Print(args ...interface{}) { + entry.Info(args...) +} + +func (entry *Entry) Info(args ...interface{}) { + if entry.Logger.Level >= InfoLevel { + entry.log(InfoLevel, fmt.Sprint(args...)) + } +} + +func (entry *Entry) Warn(args ...interface{}) { + if entry.Logger.Level >= WarnLevel { + entry.log(WarnLevel, fmt.Sprint(args...)) + } +} + +func (entry *Entry) Warning(args ...interface{}) { + entry.Warn(args...) +} + +func (entry *Entry) Error(args ...interface{}) { + if entry.Logger.Level >= ErrorLevel { + entry.log(ErrorLevel, fmt.Sprint(args...)) + } +} + +func (entry *Entry) Fatal(args ...interface{}) { + if entry.Logger.Level >= FatalLevel { + entry.log(FatalLevel, fmt.Sprint(args...)) + } + os.Exit(1) +} + +func (entry *Entry) Panic(args ...interface{}) { + if entry.Logger.Level >= PanicLevel { + entry.log(PanicLevel, fmt.Sprint(args...)) + } + panic(fmt.Sprint(args...)) +} + +// Entry Printf family functions + +func (entry *Entry) Debugf(format string, args ...interface{}) { + if entry.Logger.Level >= DebugLevel { + entry.Debug(fmt.Sprintf(format, args...)) + } +} + +func (entry *Entry) Infof(format string, args ...interface{}) { + if entry.Logger.Level >= InfoLevel { + entry.Info(fmt.Sprintf(format, args...)) + } +} + +func (entry *Entry) Printf(format string, args ...interface{}) { + entry.Infof(format, args...) +} + +func (entry *Entry) Warnf(format string, args ...interface{}) { + if entry.Logger.Level >= WarnLevel { + entry.Warn(fmt.Sprintf(format, args...)) + } +} + +func (entry *Entry) Warningf(format string, args ...interface{}) { + entry.Warnf(format, args...) +} + +func (entry *Entry) Errorf(format string, args ...interface{}) { + if entry.Logger.Level >= ErrorLevel { + entry.Error(fmt.Sprintf(format, args...)) + } +} + +func (entry *Entry) Fatalf(format string, args ...interface{}) { + if entry.Logger.Level >= FatalLevel { + entry.Fatal(fmt.Sprintf(format, args...)) + } + os.Exit(1) +} + +func (entry *Entry) Panicf(format string, args ...interface{}) { + if entry.Logger.Level >= PanicLevel { + entry.Panic(fmt.Sprintf(format, args...)) + } +} + +// Entry Println family functions + +func (entry *Entry) Debugln(args ...interface{}) { + if entry.Logger.Level >= DebugLevel { + entry.Debug(entry.sprintlnn(args...)) + } +} + +func (entry *Entry) Infoln(args ...interface{}) { + if entry.Logger.Level >= InfoLevel { + entry.Info(entry.sprintlnn(args...)) + } +} + +func (entry *Entry) Println(args ...interface{}) { + entry.Infoln(args...) +} + +func (entry *Entry) Warnln(args ...interface{}) { + if entry.Logger.Level >= WarnLevel { + entry.Warn(entry.sprintlnn(args...)) + } +} + +func (entry *Entry) Warningln(args ...interface{}) { + entry.Warnln(args...) +} + +func (entry *Entry) Errorln(args ...interface{}) { + if entry.Logger.Level >= ErrorLevel { + entry.Error(entry.sprintlnn(args...)) + } +} + +func (entry *Entry) Fatalln(args ...interface{}) { + if entry.Logger.Level >= FatalLevel { + entry.Fatal(entry.sprintlnn(args...)) + } + os.Exit(1) +} + +func (entry *Entry) Panicln(args ...interface{}) { + if entry.Logger.Level >= PanicLevel { + entry.Panic(entry.sprintlnn(args...)) + } +} + +// Sprintlnn => Sprint no newline. This is to get the behavior of how +// fmt.Sprintln where spaces are always added between operands, regardless of +// their type. Instead of vendoring the Sprintln implementation to spare a +// string allocation, we do the simplest thing. +func (entry *Entry) sprintlnn(args ...interface{}) string { + msg := fmt.Sprintln(args...) + return msg[:len(msg)-1] +} diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/examples/basic/basic.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/examples/basic/basic.go new file mode 100644 index 000000000..a1623ec00 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/examples/basic/basic.go @@ -0,0 +1,50 @@ +package main + +import ( + "github.com/Sirupsen/logrus" +) + +var log = logrus.New() + +func init() { + log.Formatter = new(logrus.JSONFormatter) + log.Formatter = new(logrus.TextFormatter) // default + log.Level = logrus.DebugLevel +} + +func main() { + defer func() { + err := recover() + if err != nil { + log.WithFields(logrus.Fields{ + "omg": true, + "err": err, + "number": 100, + }).Fatal("The ice breaks!") + } + }() + + log.WithFields(logrus.Fields{ + "animal": "walrus", + "number": 8, + }).Debug("Started observing beach") + + log.WithFields(logrus.Fields{ + "animal": "walrus", + "size": 10, + }).Info("A group of walrus emerges from the ocean") + + log.WithFields(logrus.Fields{ + "omg": true, + "number": 122, + }).Warn("The group's number increased tremendously!") + + log.WithFields(logrus.Fields{ + "temperature": -4, + }).Debug("Temperature changes") + + log.WithFields(logrus.Fields{ + "animal": "orca", + "size": 9009, + }).Panic("It's over 9000!") +} diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/examples/hook/hook.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/examples/hook/hook.go new file mode 100644 index 000000000..cb5759a35 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/examples/hook/hook.go @@ -0,0 +1,30 @@ +package main + +import ( + "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus/hooks/airbrake" +) + +var log = logrus.New() + +func init() { + log.Formatter = new(logrus.TextFormatter) // default + log.Hooks.Add(airbrake.NewHook("https://example.com", "xyz", "development")) +} + +func main() { + log.WithFields(logrus.Fields{ + "animal": "walrus", + "size": 10, + }).Info("A group of walrus emerges from the ocean") + + log.WithFields(logrus.Fields{ + "omg": true, + "number": 122, + }).Warn("The group's number increased tremendously!") + + log.WithFields(logrus.Fields{ + "omg": true, + "number": 100, + }).Fatal("The ice breaks!") +} diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/exported.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/exported.go new file mode 100644 index 000000000..9a0120ac1 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/exported.go @@ -0,0 +1,193 @@ +package logrus + +import ( + "io" +) + +var ( + // std is the name of the standard logger in stdlib `log` + std = New() +) + +func StandardLogger() *Logger { + return std +} + +// SetOutput sets the standard logger output. +func SetOutput(out io.Writer) { + std.mu.Lock() + defer std.mu.Unlock() + std.Out = out +} + +// SetFormatter sets the standard logger formatter. +func SetFormatter(formatter Formatter) { + std.mu.Lock() + defer std.mu.Unlock() + std.Formatter = formatter +} + +// SetLevel sets the standard logger level. +func SetLevel(level Level) { + std.mu.Lock() + defer std.mu.Unlock() + std.Level = level +} + +// GetLevel returns the standard logger level. +func GetLevel() Level { + std.mu.Lock() + defer std.mu.Unlock() + return std.Level +} + +// AddHook adds a hook to the standard logger hooks. +func AddHook(hook Hook) { + std.mu.Lock() + defer std.mu.Unlock() + std.Hooks.Add(hook) +} + +// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key. +func WithError(err error) *Entry { + return std.WithField(ErrorKey, err) +} + +// WithField creates an entry from the standard logger and adds a field to +// it. If you want multiple fields, use `WithFields`. +// +// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal +// or Panic on the Entry it returns. +func WithField(key string, value interface{}) *Entry { + return std.WithField(key, value) +} + +// WithFields creates an entry from the standard logger and adds multiple +// fields to it. This is simply a helper for `WithField`, invoking it +// once for each field. +// +// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal +// or Panic on the Entry it returns. +func WithFields(fields Fields) *Entry { + return std.WithFields(fields) +} + +// Debug logs a message at level Debug on the standard logger. +func Debug(args ...interface{}) { + std.Debug(args...) +} + +// Print logs a message at level Info on the standard logger. +func Print(args ...interface{}) { + std.Print(args...) +} + +// Info logs a message at level Info on the standard logger. +func Info(args ...interface{}) { + std.Info(args...) +} + +// Warn logs a message at level Warn on the standard logger. +func Warn(args ...interface{}) { + std.Warn(args...) +} + +// Warning logs a message at level Warn on the standard logger. +func Warning(args ...interface{}) { + std.Warning(args...) +} + +// Error logs a message at level Error on the standard logger. +func Error(args ...interface{}) { + std.Error(args...) +} + +// Panic logs a message at level Panic on the standard logger. +func Panic(args ...interface{}) { + std.Panic(args...) +} + +// Fatal logs a message at level Fatal on the standard logger. +func Fatal(args ...interface{}) { + std.Fatal(args...) +} + +// Debugf logs a message at level Debug on the standard logger. +func Debugf(format string, args ...interface{}) { + std.Debugf(format, args...) +} + +// Printf logs a message at level Info on the standard logger. +func Printf(format string, args ...interface{}) { + std.Printf(format, args...) +} + +// Infof logs a message at level Info on the standard logger. +func Infof(format string, args ...interface{}) { + std.Infof(format, args...) +} + +// Warnf logs a message at level Warn on the standard logger. +func Warnf(format string, args ...interface{}) { + std.Warnf(format, args...) +} + +// Warningf logs a message at level Warn on the standard logger. +func Warningf(format string, args ...interface{}) { + std.Warningf(format, args...) +} + +// Errorf logs a message at level Error on the standard logger. +func Errorf(format string, args ...interface{}) { + std.Errorf(format, args...) +} + +// Panicf logs a message at level Panic on the standard logger. +func Panicf(format string, args ...interface{}) { + std.Panicf(format, args...) +} + +// Fatalf logs a message at level Fatal on the standard logger. +func Fatalf(format string, args ...interface{}) { + std.Fatalf(format, args...) +} + +// Debugln logs a message at level Debug on the standard logger. +func Debugln(args ...interface{}) { + std.Debugln(args...) +} + +// Println logs a message at level Info on the standard logger. +func Println(args ...interface{}) { + std.Println(args...) +} + +// Infoln logs a message at level Info on the standard logger. +func Infoln(args ...interface{}) { + std.Infoln(args...) +} + +// Warnln logs a message at level Warn on the standard logger. +func Warnln(args ...interface{}) { + std.Warnln(args...) +} + +// Warningln logs a message at level Warn on the standard logger. +func Warningln(args ...interface{}) { + std.Warningln(args...) +} + +// Errorln logs a message at level Error on the standard logger. +func Errorln(args ...interface{}) { + std.Errorln(args...) +} + +// Panicln logs a message at level Panic on the standard logger. +func Panicln(args ...interface{}) { + std.Panicln(args...) +} + +// Fatalln logs a message at level Fatal on the standard logger. +func Fatalln(args ...interface{}) { + std.Fatalln(args...) +} diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/formatter.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/formatter.go new file mode 100644 index 000000000..104d689f1 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/formatter.go @@ -0,0 +1,48 @@ +package logrus + +import "time" + +const DefaultTimestampFormat = time.RFC3339 + +// The Formatter interface is used to implement a custom Formatter. It takes an +// `Entry`. It exposes all the fields, including the default ones: +// +// * `entry.Data["msg"]`. The message passed from Info, Warn, Error .. +// * `entry.Data["time"]`. The timestamp. +// * `entry.Data["level"]. The level the entry was logged at. +// +// Any additional fields added with `WithField` or `WithFields` are also in +// `entry.Data`. Format is expected to return an array of bytes which are then +// logged to `logger.Out`. +type Formatter interface { + Format(*Entry) ([]byte, error) +} + +// This is to not silently overwrite `time`, `msg` and `level` fields when +// dumping it. If this code wasn't there doing: +// +// logrus.WithField("level", 1).Info("hello") +// +// Would just silently drop the user provided level. Instead with this code +// it'll logged as: +// +// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."} +// +// It's not exported because it's still using Data in an opinionated way. It's to +// avoid code duplication between the two default formatters. +func prefixFieldClashes(data Fields) { + _, ok := data["time"] + if ok { + data["fields.time"] = data["time"] + } + + _, ok = data["msg"] + if ok { + data["fields.msg"] = data["msg"] + } + + _, ok = data["level"] + if ok { + data["fields.level"] = data["level"] + } +} diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/formatters/logstash/logstash.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/formatters/logstash/logstash.go new file mode 100644 index 000000000..8ea93ddf2 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/formatters/logstash/logstash.go @@ -0,0 +1,56 @@ +package logstash + +import ( + "encoding/json" + "fmt" + + "github.com/Sirupsen/logrus" +) + +// Formatter generates json in logstash format. +// Logstash site: http://logstash.net/ +type LogstashFormatter struct { + Type string // if not empty use for logstash type field. + + // TimestampFormat sets the format used for timestamps. + TimestampFormat string +} + +func (f *LogstashFormatter) Format(entry *logrus.Entry) ([]byte, error) { + entry.Data["@version"] = 1 + + if f.TimestampFormat == "" { + f.TimestampFormat = logrus.DefaultTimestampFormat + } + + entry.Data["@timestamp"] = entry.Time.Format(f.TimestampFormat) + + // set message field + v, ok := entry.Data["message"] + if ok { + entry.Data["fields.message"] = v + } + entry.Data["message"] = entry.Message + + // set level field + v, ok = entry.Data["level"] + if ok { + entry.Data["fields.level"] = v + } + entry.Data["level"] = entry.Level.String() + + // set type field + if f.Type != "" { + v, ok = entry.Data["type"] + if ok { + entry.Data["fields.type"] = v + } + entry.Data["type"] = f.Type + } + + serialized, err := json.Marshal(entry.Data) + if err != nil { + return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) + } + return append(serialized, '\n'), nil +} diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks.go new file mode 100644 index 000000000..3f151cdc3 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks.go @@ -0,0 +1,34 @@ +package logrus + +// A hook to be fired when logging on the logging levels returned from +// `Levels()` on your implementation of the interface. Note that this is not +// fired in a goroutine or a channel with workers, you should handle such +// functionality yourself if your call is non-blocking and you don't wish for +// the logging calls for levels returned from `Levels()` to block. +type Hook interface { + Levels() []Level + Fire(*Entry) error +} + +// Internal type for storing the hooks on a logger instance. +type LevelHooks map[Level][]Hook + +// Add a hook to an instance of logger. This is called with +// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface. +func (hooks LevelHooks) Add(hook Hook) { + for _, level := range hook.Levels() { + hooks[level] = append(hooks[level], hook) + } +} + +// Fire all the hooks for the passed level. Used by `entry.log` to fire +// appropriate hooks for a log entry. +func (hooks LevelHooks) Fire(level Level, entry *Entry) error { + for _, hook := range hooks[level] { + if err := hook.Fire(entry); err != nil { + return err + } + } + + return nil +} diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go new file mode 100644 index 000000000..b0502c335 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/airbrake/airbrake.go @@ -0,0 +1,54 @@ +package airbrake + +import ( + "errors" + "fmt" + + "github.com/Sirupsen/logrus" + "github.com/tobi/airbrake-go" +) + +// AirbrakeHook to send exceptions to an exception-tracking service compatible +// with the Airbrake API. +type airbrakeHook struct { + APIKey string + Endpoint string + Environment string +} + +func NewHook(endpoint, apiKey, env string) *airbrakeHook { + return &airbrakeHook{ + APIKey: apiKey, + Endpoint: endpoint, + Environment: env, + } +} + +func (hook *airbrakeHook) Fire(entry *logrus.Entry) error { + airbrake.ApiKey = hook.APIKey + airbrake.Endpoint = hook.Endpoint + airbrake.Environment = hook.Environment + + var notifyErr error + err, ok := entry.Data["error"].(error) + if ok { + notifyErr = err + } else { + notifyErr = errors.New(entry.Message) + } + + airErr := airbrake.Notify(notifyErr) + if airErr != nil { + return fmt.Errorf("Failed to send error to Airbrake: %s", airErr) + } + + return nil +} + +func (hook *airbrakeHook) Levels() []logrus.Level { + return []logrus.Level{ + logrus.ErrorLevel, + logrus.FatalLevel, + logrus.PanicLevel, + } +} diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag.go new file mode 100644 index 000000000..d20a0f54a --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/bugsnag/bugsnag.go @@ -0,0 +1,68 @@ +package logrus_bugsnag + +import ( + "errors" + + "github.com/Sirupsen/logrus" + "github.com/bugsnag/bugsnag-go" +) + +type bugsnagHook struct{} + +// ErrBugsnagUnconfigured is returned if NewBugsnagHook is called before +// bugsnag.Configure. Bugsnag must be configured before the hook. +var ErrBugsnagUnconfigured = errors.New("bugsnag must be configured before installing this logrus hook") + +// ErrBugsnagSendFailed indicates that the hook failed to submit an error to +// bugsnag. The error was successfully generated, but `bugsnag.Notify()` +// failed. +type ErrBugsnagSendFailed struct { + err error +} + +func (e ErrBugsnagSendFailed) Error() string { + return "failed to send error to Bugsnag: " + e.err.Error() +} + +// NewBugsnagHook initializes a logrus hook which sends exceptions to an +// exception-tracking service compatible with the Bugsnag API. Before using +// this hook, you must call bugsnag.Configure(). The returned object should be +// registered with a log via `AddHook()` +// +// Entries that trigger an Error, Fatal or Panic should now include an "error" +// field to send to Bugsnag. +func NewBugsnagHook() (*bugsnagHook, error) { + if bugsnag.Config.APIKey == "" { + return nil, ErrBugsnagUnconfigured + } + return &bugsnagHook{}, nil +} + +// Fire forwards an error to Bugsnag. Given a logrus.Entry, it extracts the +// "error" field (or the Message if the error isn't present) and sends it off. +func (hook *bugsnagHook) Fire(entry *logrus.Entry) error { + var notifyErr error + err, ok := entry.Data["error"].(error) + if ok { + notifyErr = err + } else { + notifyErr = errors.New(entry.Message) + } + + bugsnagErr := bugsnag.Notify(notifyErr) + if bugsnagErr != nil { + return ErrBugsnagSendFailed{bugsnagErr} + } + + return nil +} + +// Levels enumerates the log levels on which the error should be forwarded to +// bugsnag: everything at or above the "Error" level. +func (hook *bugsnagHook) Levels() []logrus.Level { + return []logrus.Level{ + logrus.ErrorLevel, + logrus.FatalLevel, + logrus.PanicLevel, + } +} diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/papertrail/README.md b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/papertrail/README.md new file mode 100644 index 000000000..ae61e9229 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/papertrail/README.md @@ -0,0 +1,28 @@ +# Papertrail Hook for Logrus :walrus: + +[Papertrail](https://papertrailapp.com) provides hosted log management. Once stored in Papertrail, you can [group](http://help.papertrailapp.com/kb/how-it-works/groups/) your logs on various dimensions, [search](http://help.papertrailapp.com/kb/how-it-works/search-syntax) them, and trigger [alerts](http://help.papertrailapp.com/kb/how-it-works/alerts). + +In most deployments, you'll want to send logs to Papertrail via their [remote_syslog](http://help.papertrailapp.com/kb/configuration/configuring-centralized-logging-from-text-log-files-in-unix/) daemon, which requires no application-specific configuration. This hook is intended for relatively low-volume logging, likely in managed cloud hosting deployments where installing `remote_syslog` is not possible. + +## Usage + +You can find your Papertrail UDP port on your [Papertrail account page](https://papertrailapp.com/account/destinations). Substitute it below for `YOUR_PAPERTRAIL_UDP_PORT`. + +For `YOUR_APP_NAME`, substitute a short string that will readily identify your application or service in the logs. + +```go +import ( + "log/syslog" + "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus/hooks/papertrail" +) + +func main() { + log := logrus.New() + hook, err := logrus_papertrail.NewPapertrailHook("logs.papertrailapp.com", YOUR_PAPERTRAIL_UDP_PORT, YOUR_APP_NAME) + + if err == nil { + log.Hooks.Add(hook) + } +} +``` diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go new file mode 100644 index 000000000..c0f10c1bd --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/papertrail/papertrail.go @@ -0,0 +1,55 @@ +package logrus_papertrail + +import ( + "fmt" + "net" + "os" + "time" + + "github.com/Sirupsen/logrus" +) + +const ( + format = "Jan 2 15:04:05" +) + +// PapertrailHook to send logs to a logging service compatible with the Papertrail API. +type PapertrailHook struct { + Host string + Port int + AppName string + UDPConn net.Conn +} + +// NewPapertrailHook creates a hook to be added to an instance of logger. +func NewPapertrailHook(host string, port int, appName string) (*PapertrailHook, error) { + conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", host, port)) + return &PapertrailHook{host, port, appName, conn}, err +} + +// Fire is called when a log event is fired. +func (hook *PapertrailHook) Fire(entry *logrus.Entry) error { + date := time.Now().Format(format) + msg, _ := entry.String() + payload := fmt.Sprintf("<22> %s %s: %s", date, hook.AppName, msg) + + bytesWritten, err := hook.UDPConn.Write([]byte(payload)) + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to send log line to Papertrail via UDP. Wrote %d bytes before error: %v", bytesWritten, err) + return err + } + + return nil +} + +// Levels returns the available logging levels. +func (hook *PapertrailHook) Levels() []logrus.Level { + return []logrus.Level{ + logrus.PanicLevel, + logrus.FatalLevel, + logrus.ErrorLevel, + logrus.WarnLevel, + logrus.InfoLevel, + logrus.DebugLevel, + } +} diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/sentry/README.md b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/sentry/README.md new file mode 100644 index 000000000..31de6540a --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/sentry/README.md @@ -0,0 +1,111 @@ +# Sentry Hook for Logrus :walrus: + +[Sentry](https://getsentry.com) provides both self-hosted and hosted +solutions for exception tracking. +Both client and server are +[open source](https://github.com/getsentry/sentry). + +## Usage + +Every sentry application defined on the server gets a different +[DSN](https://www.getsentry.com/docs/). In the example below replace +`YOUR_DSN` with the one created for your application. + +```go +import ( + "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus/hooks/sentry" +) + +func main() { + log := logrus.New() + hook, err := logrus_sentry.NewSentryHook(YOUR_DSN, []logrus.Level{ + logrus.PanicLevel, + logrus.FatalLevel, + logrus.ErrorLevel, + }) + + if err == nil { + log.Hooks.Add(hook) + } +} +``` + +If you wish to initialize a SentryHook with tags, you can use the `NewWithTagsSentryHook` constructor to provide default tags: + +```go +tags := map[string]string{ + "site": "example.com", +} +levels := []logrus.Level{ + logrus.PanicLevel, + logrus.FatalLevel, + logrus.ErrorLevel, +} +hook, err := logrus_sentry.NewWithTagsSentryHook(YOUR_DSN, tags, levels) + +``` + +If you wish to initialize a SentryHook with an already initialized raven client, you can use +the `NewWithClientSentryHook` constructor: + +```go +import ( + "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus/hooks/sentry" + "github.com/getsentry/raven-go" +) + +func main() { + log := logrus.New() + + client, err := raven.New(YOUR_DSN) + if err != nil { + log.Fatal(err) + } + + hook, err := logrus_sentry.NewWithClientSentryHook(client, []logrus.Level{ + logrus.PanicLevel, + logrus.FatalLevel, + logrus.ErrorLevel, + }) + + if err == nil { + log.Hooks.Add(hook) + } +} + +hook, err := NewWithClientSentryHook(client, []logrus.Level{ + logrus.ErrorLevel, +}) +``` + +## Special fields + +Some logrus fields have a special meaning in this hook, +these are `server_name`, `logger` and `http_request`. +When logs are sent to sentry these fields are treated differently. +- `server_name` (also known as hostname) is the name of the server which +is logging the event (hostname.example.com) +- `logger` is the part of the application which is logging the event. +In go this usually means setting it to the name of the package. +- `http_request` is the in-coming request(*http.Request). The detailed request data are sent to Sentry. + +## Timeout + +`Timeout` is the time the sentry hook will wait for a response +from the sentry server. + +If this time elapses with no response from +the server an error will be returned. + +If `Timeout` is set to 0 the SentryHook will not wait for a reply +and will assume a correct delivery. + +The SentryHook has a default timeout of `100 milliseconds` when created +with a call to `NewSentryHook`. This can be changed by assigning a value to the `Timeout` field: + +```go +hook, _ := logrus_sentry.NewSentryHook(...) +hook.Timeout = 20*time.Second +``` diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/sentry/sentry.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/sentry/sentry.go new file mode 100644 index 000000000..cf88098a8 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/sentry/sentry.go @@ -0,0 +1,137 @@ +package logrus_sentry + +import ( + "fmt" + "net/http" + "time" + + "github.com/Sirupsen/logrus" + "github.com/getsentry/raven-go" +) + +var ( + severityMap = map[logrus.Level]raven.Severity{ + logrus.DebugLevel: raven.DEBUG, + logrus.InfoLevel: raven.INFO, + logrus.WarnLevel: raven.WARNING, + logrus.ErrorLevel: raven.ERROR, + logrus.FatalLevel: raven.FATAL, + logrus.PanicLevel: raven.FATAL, + } +) + +func getAndDel(d logrus.Fields, key string) (string, bool) { + var ( + ok bool + v interface{} + val string + ) + if v, ok = d[key]; !ok { + return "", false + } + + if val, ok = v.(string); !ok { + return "", false + } + delete(d, key) + return val, true +} + +func getAndDelRequest(d logrus.Fields, key string) (*http.Request, bool) { + var ( + ok bool + v interface{} + req *http.Request + ) + if v, ok = d[key]; !ok { + return nil, false + } + if req, ok = v.(*http.Request); !ok || req == nil { + return nil, false + } + delete(d, key) + return req, true +} + +// SentryHook delivers logs to a sentry server. +type SentryHook struct { + // Timeout sets the time to wait for a delivery error from the sentry server. + // If this is set to zero the server will not wait for any response and will + // consider the message correctly sent + Timeout time.Duration + + client *raven.Client + levels []logrus.Level +} + +// NewSentryHook creates a hook to be added to an instance of logger +// and initializes the raven client. +// This method sets the timeout to 100 milliseconds. +func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) { + client, err := raven.New(DSN) + if err != nil { + return nil, err + } + return &SentryHook{100 * time.Millisecond, client, levels}, nil +} + +// NewWithTagsSentryHook creates a hook with tags to be added to an instance +// of logger and initializes the raven client. This method sets the timeout to +// 100 milliseconds. +func NewWithTagsSentryHook(DSN string, tags map[string]string, levels []logrus.Level) (*SentryHook, error) { + client, err := raven.NewWithTags(DSN, tags) + if err != nil { + return nil, err + } + return &SentryHook{100 * time.Millisecond, client, levels}, nil +} + +// NewWithClientSentryHook creates a hook using an initialized raven client. +// This method sets the timeout to 100 milliseconds. +func NewWithClientSentryHook(client *raven.Client, levels []logrus.Level) (*SentryHook, error) { + return &SentryHook{100 * time.Millisecond, client, levels}, nil +} + +// Called when an event should be sent to sentry +// Special fields that sentry uses to give more information to the server +// are extracted from entry.Data (if they are found) +// These fields are: logger, server_name and http_request +func (hook *SentryHook) Fire(entry *logrus.Entry) error { + packet := &raven.Packet{ + Message: entry.Message, + Timestamp: raven.Timestamp(entry.Time), + Level: severityMap[entry.Level], + Platform: "go", + } + + d := entry.Data + + if logger, ok := getAndDel(d, "logger"); ok { + packet.Logger = logger + } + if serverName, ok := getAndDel(d, "server_name"); ok { + packet.ServerName = serverName + } + if req, ok := getAndDelRequest(d, "http_request"); ok { + packet.Interfaces = append(packet.Interfaces, raven.NewHttp(req)) + } + packet.Extra = map[string]interface{}(d) + + _, errCh := hook.client.Capture(packet, nil) + timeout := hook.Timeout + if timeout != 0 { + timeoutCh := time.After(timeout) + select { + case err := <-errCh: + return err + case <-timeoutCh: + return fmt.Errorf("no response from sentry server in %s", timeout) + } + } + return nil +} + +// Levels returns the available logging levels. +func (hook *SentryHook) Levels() []logrus.Level { + return hook.levels +} diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/syslog/README.md b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/syslog/README.md new file mode 100644 index 000000000..4dbb8e729 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/syslog/README.md @@ -0,0 +1,20 @@ +# Syslog Hooks for Logrus :walrus: + +## Usage + +```go +import ( + "log/syslog" + "github.com/Sirupsen/logrus" + logrus_syslog "github.com/Sirupsen/logrus/hooks/syslog" +) + +func main() { + log := logrus.New() + hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "") + + if err == nil { + log.Hooks.Add(hook) + } +} +``` diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/syslog/syslog.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/syslog/syslog.go new file mode 100644 index 000000000..b6fa37462 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/hooks/syslog/syslog.go @@ -0,0 +1,59 @@ +package logrus_syslog + +import ( + "fmt" + "github.com/Sirupsen/logrus" + "log/syslog" + "os" +) + +// SyslogHook to send logs via syslog. +type SyslogHook struct { + Writer *syslog.Writer + SyslogNetwork string + SyslogRaddr string +} + +// Creates a hook to be added to an instance of logger. This is called with +// `hook, err := NewSyslogHook("udp", "localhost:514", syslog.LOG_DEBUG, "")` +// `if err == nil { log.Hooks.Add(hook) }` +func NewSyslogHook(network, raddr string, priority syslog.Priority, tag string) (*SyslogHook, error) { + w, err := syslog.Dial(network, raddr, priority, tag) + return &SyslogHook{w, network, raddr}, err +} + +func (hook *SyslogHook) Fire(entry *logrus.Entry) error { + line, err := entry.String() + if err != nil { + fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err) + return err + } + + switch entry.Level { + case logrus.PanicLevel: + return hook.Writer.Crit(line) + case logrus.FatalLevel: + return hook.Writer.Crit(line) + case logrus.ErrorLevel: + return hook.Writer.Err(line) + case logrus.WarnLevel: + return hook.Writer.Warning(line) + case logrus.InfoLevel: + return hook.Writer.Info(line) + case logrus.DebugLevel: + return hook.Writer.Debug(line) + default: + return nil + } +} + +func (hook *SyslogHook) Levels() []logrus.Level { + return []logrus.Level{ + logrus.PanicLevel, + logrus.FatalLevel, + logrus.ErrorLevel, + logrus.WarnLevel, + logrus.InfoLevel, + logrus.DebugLevel, + } +} diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/json_formatter.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/json_formatter.go new file mode 100644 index 000000000..2ad6dc5cf --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/json_formatter.go @@ -0,0 +1,41 @@ +package logrus + +import ( + "encoding/json" + "fmt" +) + +type JSONFormatter struct { + // TimestampFormat sets the format used for marshaling timestamps. + TimestampFormat string +} + +func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { + data := make(Fields, len(entry.Data)+3) + for k, v := range entry.Data { + switch v := v.(type) { + case error: + // Otherwise errors are ignored by `encoding/json` + // https://github.com/Sirupsen/logrus/issues/137 + data[k] = v.Error() + default: + data[k] = v + } + } + prefixFieldClashes(data) + + timestampFormat := f.TimestampFormat + if timestampFormat == "" { + timestampFormat = DefaultTimestampFormat + } + + data["time"] = entry.Time.Format(timestampFormat) + data["msg"] = entry.Message + data["level"] = entry.Level.String() + + serialized, err := json.Marshal(data) + if err != nil { + return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) + } + return append(serialized, '\n'), nil +} diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/logger.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/logger.go new file mode 100644 index 000000000..fd9804c64 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/logger.go @@ -0,0 +1,206 @@ +package logrus + +import ( + "io" + "os" + "sync" +) + +type Logger struct { + // The logs are `io.Copy`'d to this in a mutex. It's common to set this to a + // file, or leave it default which is `os.Stderr`. You can also set this to + // something more adventorous, such as logging to Kafka. + Out io.Writer + // Hooks for the logger instance. These allow firing events based on logging + // levels and log entries. For example, to send errors to an error tracking + // service, log to StatsD or dump the core on fatal errors. + Hooks LevelHooks + // All log entries pass through the formatter before logged to Out. The + // included formatters are `TextFormatter` and `JSONFormatter` for which + // TextFormatter is the default. In development (when a TTY is attached) it + // logs with colors, but to a file it wouldn't. You can easily implement your + // own that implements the `Formatter` interface, see the `README` or included + // formatters for examples. + Formatter Formatter + // The logging level the logger should log at. This is typically (and defaults + // to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be + // logged. `logrus.Debug` is useful in + Level Level + // Used to sync writing to the log. + mu sync.Mutex +} + +// Creates a new logger. Configuration should be set by changing `Formatter`, +// `Out` and `Hooks` directly on the default logger instance. You can also just +// instantiate your own: +// +// var log = &Logger{ +// Out: os.Stderr, +// Formatter: new(JSONFormatter), +// Hooks: make(LevelHooks), +// Level: logrus.DebugLevel, +// } +// +// It's recommended to make this a global instance called `log`. +func New() *Logger { + return &Logger{ + Out: os.Stderr, + Formatter: new(TextFormatter), + Hooks: make(LevelHooks), + Level: InfoLevel, + } +} + +// Adds a field to the log entry, note that you it doesn't log until you call +// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry. +// If you want multiple fields, use `WithFields`. +func (logger *Logger) WithField(key string, value interface{}) *Entry { + return NewEntry(logger).WithField(key, value) +} + +// Adds a struct of fields to the log entry. All it does is call `WithField` for +// each `Field`. +func (logger *Logger) WithFields(fields Fields) *Entry { + return NewEntry(logger).WithFields(fields) +} + +func (logger *Logger) Debugf(format string, args ...interface{}) { + if logger.Level >= DebugLevel { + NewEntry(logger).Debugf(format, args...) + } +} + +func (logger *Logger) Infof(format string, args ...interface{}) { + if logger.Level >= InfoLevel { + NewEntry(logger).Infof(format, args...) + } +} + +func (logger *Logger) Printf(format string, args ...interface{}) { + NewEntry(logger).Printf(format, args...) +} + +func (logger *Logger) Warnf(format string, args ...interface{}) { + if logger.Level >= WarnLevel { + NewEntry(logger).Warnf(format, args...) + } +} + +func (logger *Logger) Warningf(format string, args ...interface{}) { + if logger.Level >= WarnLevel { + NewEntry(logger).Warnf(format, args...) + } +} + +func (logger *Logger) Errorf(format string, args ...interface{}) { + if logger.Level >= ErrorLevel { + NewEntry(logger).Errorf(format, args...) + } +} + +func (logger *Logger) Fatalf(format string, args ...interface{}) { + if logger.Level >= FatalLevel { + NewEntry(logger).Fatalf(format, args...) + } + os.Exit(1) +} + +func (logger *Logger) Panicf(format string, args ...interface{}) { + if logger.Level >= PanicLevel { + NewEntry(logger).Panicf(format, args...) + } +} + +func (logger *Logger) Debug(args ...interface{}) { + if logger.Level >= DebugLevel { + NewEntry(logger).Debug(args...) + } +} + +func (logger *Logger) Info(args ...interface{}) { + if logger.Level >= InfoLevel { + NewEntry(logger).Info(args...) + } +} + +func (logger *Logger) Print(args ...interface{}) { + NewEntry(logger).Info(args...) +} + +func (logger *Logger) Warn(args ...interface{}) { + if logger.Level >= WarnLevel { + NewEntry(logger).Warn(args...) + } +} + +func (logger *Logger) Warning(args ...interface{}) { + if logger.Level >= WarnLevel { + NewEntry(logger).Warn(args...) + } +} + +func (logger *Logger) Error(args ...interface{}) { + if logger.Level >= ErrorLevel { + NewEntry(logger).Error(args...) + } +} + +func (logger *Logger) Fatal(args ...interface{}) { + if logger.Level >= FatalLevel { + NewEntry(logger).Fatal(args...) + } + os.Exit(1) +} + +func (logger *Logger) Panic(args ...interface{}) { + if logger.Level >= PanicLevel { + NewEntry(logger).Panic(args...) + } +} + +func (logger *Logger) Debugln(args ...interface{}) { + if logger.Level >= DebugLevel { + NewEntry(logger).Debugln(args...) + } +} + +func (logger *Logger) Infoln(args ...interface{}) { + if logger.Level >= InfoLevel { + NewEntry(logger).Infoln(args...) + } +} + +func (logger *Logger) Println(args ...interface{}) { + NewEntry(logger).Println(args...) +} + +func (logger *Logger) Warnln(args ...interface{}) { + if logger.Level >= WarnLevel { + NewEntry(logger).Warnln(args...) + } +} + +func (logger *Logger) Warningln(args ...interface{}) { + if logger.Level >= WarnLevel { + NewEntry(logger).Warnln(args...) + } +} + +func (logger *Logger) Errorln(args ...interface{}) { + if logger.Level >= ErrorLevel { + NewEntry(logger).Errorln(args...) + } +} + +func (logger *Logger) Fatalln(args ...interface{}) { + if logger.Level >= FatalLevel { + NewEntry(logger).Fatalln(args...) + } + os.Exit(1) +} + +func (logger *Logger) Panicln(args ...interface{}) { + if logger.Level >= PanicLevel { + NewEntry(logger).Panicln(args...) + } +} diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/logrus.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/logrus.go new file mode 100644 index 000000000..0c09fbc26 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/logrus.go @@ -0,0 +1,98 @@ +package logrus + +import ( + "fmt" + "log" +) + +// Fields type, used to pass to `WithFields`. +type Fields map[string]interface{} + +// Level type +type Level uint8 + +// Convert the Level to a string. E.g. PanicLevel becomes "panic". +func (level Level) String() string { + switch level { + case DebugLevel: + return "debug" + case InfoLevel: + return "info" + case WarnLevel: + return "warning" + case ErrorLevel: + return "error" + case FatalLevel: + return "fatal" + case PanicLevel: + return "panic" + } + + return "unknown" +} + +// ParseLevel takes a string level and returns the Logrus log level constant. +func ParseLevel(lvl string) (Level, error) { + switch lvl { + case "panic": + return PanicLevel, nil + case "fatal": + return FatalLevel, nil + case "error": + return ErrorLevel, nil + case "warn", "warning": + return WarnLevel, nil + case "info": + return InfoLevel, nil + case "debug": + return DebugLevel, nil + } + + var l Level + return l, fmt.Errorf("not a valid logrus Level: %q", lvl) +} + +// These are the different logging levels. You can set the logging level to log +// on your instance of logger, obtained with `logrus.New()`. +const ( + // PanicLevel level, highest level of severity. Logs and then calls panic with the + // message passed to Debug, Info, ... + PanicLevel Level = iota + // FatalLevel level. Logs and then calls `os.Exit(1)`. It will exit even if the + // logging level is set to Panic. + FatalLevel + // ErrorLevel level. Logs. Used for errors that should definitely be noted. + // Commonly used for hooks to send errors to an error tracking service. + ErrorLevel + // WarnLevel level. Non-critical entries that deserve eyes. + WarnLevel + // InfoLevel level. General operational entries about what's going on inside the + // application. + InfoLevel + // DebugLevel level. Usually only enabled when debugging. Very verbose logging. + DebugLevel +) + +// Won't compile if StdLogger can't be realized by a log.Logger +var ( + _ StdLogger = &log.Logger{} + _ StdLogger = &Entry{} + _ StdLogger = &Logger{} +) + +// StdLogger is what your logrus-enabled library should take, that way +// it'll accept a stdlib logger and a logrus logger. There's no standard +// interface, this is the closest we get, unfortunately. +type StdLogger interface { + Print(...interface{}) + Printf(string, ...interface{}) + Println(...interface{}) + + Fatal(...interface{}) + Fatalf(string, ...interface{}) + Fatalln(...interface{}) + + Panic(...interface{}) + Panicf(string, ...interface{}) + Panicln(...interface{}) +} diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/terminal_bsd.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/terminal_bsd.go new file mode 100644 index 000000000..71f8d67a5 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/terminal_bsd.go @@ -0,0 +1,9 @@ +// +build darwin freebsd openbsd netbsd dragonfly + +package logrus + +import "syscall" + +const ioctlReadTermios = syscall.TIOCGETA + +type Termios syscall.Termios diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/terminal_linux.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/terminal_linux.go new file mode 100644 index 000000000..a2c0b40db --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/terminal_linux.go @@ -0,0 +1,12 @@ +// Based on ssh/terminal: +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package logrus + +import "syscall" + +const ioctlReadTermios = syscall.TCGETS + +type Termios syscall.Termios diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/terminal_notwindows.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/terminal_notwindows.go new file mode 100644 index 000000000..4bb537602 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/terminal_notwindows.go @@ -0,0 +1,21 @@ +// Based on ssh/terminal: +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux darwin freebsd openbsd netbsd dragonfly + +package logrus + +import ( + "syscall" + "unsafe" +) + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal() bool { + fd := syscall.Stdout + var termios Termios + _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) + return err == 0 +} diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/terminal_windows.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/terminal_windows.go new file mode 100644 index 000000000..2e09f6f7e --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/terminal_windows.go @@ -0,0 +1,27 @@ +// Based on ssh/terminal: +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package logrus + +import ( + "syscall" + "unsafe" +) + +var kernel32 = syscall.NewLazyDLL("kernel32.dll") + +var ( + procGetConsoleMode = kernel32.NewProc("GetConsoleMode") +) + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal() bool { + fd := syscall.Stdout + var st uint32 + r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + return r != 0 && e == 0 +} diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/text_formatter.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/text_formatter.go new file mode 100644 index 000000000..17cc29848 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/text_formatter.go @@ -0,0 +1,159 @@ +package logrus + +import ( + "bytes" + "fmt" + "runtime" + "sort" + "strings" + "time" +) + +const ( + nocolor = 0 + red = 31 + green = 32 + yellow = 33 + blue = 34 + gray = 37 +) + +var ( + baseTimestamp time.Time + isTerminal bool +) + +func init() { + baseTimestamp = time.Now() + isTerminal = IsTerminal() +} + +func miniTS() int { + return int(time.Since(baseTimestamp) / time.Second) +} + +type TextFormatter struct { + // Set to true to bypass checking for a TTY before outputting colors. + ForceColors bool + + // Force disabling colors. + DisableColors bool + + // Disable timestamp logging. useful when output is redirected to logging + // system that already adds timestamps. + DisableTimestamp bool + + // Enable logging the full timestamp when a TTY is attached instead of just + // the time passed since beginning of execution. + FullTimestamp bool + + // TimestampFormat to use for display when a full timestamp is printed + TimestampFormat string + + // The fields are sorted by default for a consistent output. For applications + // that log extremely frequently and don't use the JSON formatter this may not + // be desired. + DisableSorting bool +} + +func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { + var keys []string = make([]string, 0, len(entry.Data)) + for k := range entry.Data { + keys = append(keys, k) + } + + if !f.DisableSorting { + sort.Strings(keys) + } + + b := &bytes.Buffer{} + + prefixFieldClashes(entry.Data) + + isColorTerminal := isTerminal && (runtime.GOOS != "windows") + isColored := (f.ForceColors || isColorTerminal) && !f.DisableColors + + timestampFormat := f.TimestampFormat + if timestampFormat == "" { + timestampFormat = DefaultTimestampFormat + } + if isColored { + f.printColored(b, entry, keys, timestampFormat) + } else { + if !f.DisableTimestamp { + f.appendKeyValue(b, "time", entry.Time.Format(timestampFormat)) + } + f.appendKeyValue(b, "level", entry.Level.String()) + f.appendKeyValue(b, "msg", entry.Message) + for _, key := range keys { + f.appendKeyValue(b, key, entry.Data[key]) + } + } + + b.WriteByte('\n') + return b.Bytes(), nil +} + +func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, timestampFormat string) { + var levelColor int + switch entry.Level { + case DebugLevel: + levelColor = gray + case WarnLevel: + levelColor = yellow + case ErrorLevel, FatalLevel, PanicLevel: + levelColor = red + default: + levelColor = blue + } + + levelText := strings.ToUpper(entry.Level.String())[0:4] + + if !f.FullTimestamp { + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message) + } else { + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), entry.Message) + } + for _, k := range keys { + v := entry.Data[k] + fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=%+v", levelColor, k, v) + } +} + +func needsQuoting(text string) bool { + for _, ch := range text { + if !((ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || + ch == '-' || ch == '.') { + return false + } + } + return true +} + +func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) { + + b.WriteString(key) + b.WriteByte('=') + + switch value := value.(type) { + case string: + if needsQuoting(value) { + b.WriteString(value) + } else { + fmt.Fprintf(b, "%q", value) + } + case error: + errmsg := value.Error() + if needsQuoting(errmsg) { + b.WriteString(errmsg) + } else { + fmt.Fprintf(b, "%q", value) + } + default: + fmt.Fprint(b, value) + } + + b.WriteByte(' ') +} diff --git a/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/writer.go b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/writer.go new file mode 100644 index 000000000..1e30b1c75 --- /dev/null +++ b/alpine/packages/proxy/vendor/github.com/Sirupsen/logrus/writer.go @@ -0,0 +1,31 @@ +package logrus + +import ( + "bufio" + "io" + "runtime" +) + +func (logger *Logger) Writer() *io.PipeWriter { + reader, writer := io.Pipe() + + go logger.writerScanner(reader) + runtime.SetFinalizer(writer, writerFinalizer) + + return writer +} + +func (logger *Logger) writerScanner(reader *io.PipeReader) { + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + logger.Print(scanner.Text()) + } + if err := scanner.Err(); err != nil { + logger.Errorf("Error while reading from Writer: %s", err) + } + reader.Close() +} + +func writerFinalizer(writer *io.PipeWriter) { + writer.Close() +} From cc52a5d55370128a8966ffd658819ce81d861786 Mon Sep 17 00:00:00 2001 From: David Scott Date: Sun, 3 Apr 2016 14:25:28 +0100 Subject: [PATCH 03/12] proxy: split the `execProxy` function into parts Previously `execProxy` would - create the proxy - signal success/failure to the parent - run forever (until signalled) Since we want to add more proxy setup and teardown, this patch removes the proxy creation from `execProxy` and renames it to `proxyForever`. Later patches will be able to perform the necessary side-effects before signalling success to the parent. Signed-off-by: David Scott --- alpine/packages/proxy/main.go | 8 +++++++- alpine/packages/proxy/proxy.go | 10 ++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/alpine/packages/proxy/main.go b/alpine/packages/proxy/main.go index c709a4996..654b6e667 100644 --- a/alpine/packages/proxy/main.go +++ b/alpine/packages/proxy/main.go @@ -1,5 +1,11 @@ package main +import ( + "pkg/proxy" +) + func main() { - execProxy() + host, container := parseHostContainerAddrs() + + proxyForever(proxy.NewProxy(host, container)) } diff --git a/alpine/packages/proxy/proxy.go b/alpine/packages/proxy/proxy.go index 64937c50d..d72e131ac 100644 --- a/alpine/packages/proxy/proxy.go +++ b/alpine/packages/proxy/proxy.go @@ -12,14 +12,10 @@ import ( "pkg/proxy" ) -// From docker/libnetwork/portmapper/proxy.go - -// execProxy is the reexec function that is registered to start the userland proxies -func execProxy() { +// proxyForever signals the parent success/failure and runs the proxy forever +func proxyForever(p proxy.Proxy, err error) { f := os.NewFile(3, "signal-parent") - host, container := parseHostContainerAddrs() - p, err := proxy.NewProxy(host, container) if err != nil { fmt.Fprintf(f, "1\n%s", err) f.Close() @@ -33,6 +29,8 @@ func execProxy() { p.Run() } +// From docker/libnetwork/portmapper/proxy.go: + // parseHostContainerAddrs parses the flags passed on reexec to create the TCP or UDP // net.Addrs to map the host and container ports func parseHostContainerAddrs() (host net.Addr, container net.Addr) { From 3d5767714c5accd2e39535b33a7b6343043b54e4 Mon Sep 17 00:00:00 2001 From: David Scott Date: Sun, 3 Apr 2016 14:42:17 +0100 Subject: [PATCH 04/12] proxy: on exit, return to main for cleanup Previously the proxy.go would directly call `os.Exit`. This patch causes control to return to `main` where we can tear down any port forward. Signed-off-by: David Scott --- alpine/packages/proxy/main.go | 9 ++++++++- alpine/packages/proxy/proxy.go | 7 +++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/alpine/packages/proxy/main.go b/alpine/packages/proxy/main.go index 654b6e667..ead8b92cb 100644 --- a/alpine/packages/proxy/main.go +++ b/alpine/packages/proxy/main.go @@ -1,11 +1,18 @@ package main import ( + "log" + "os" "pkg/proxy" ) func main() { host, container := parseHostContainerAddrs() - proxyForever(proxy.NewProxy(host, container)) + err := proxyForever(proxy.NewProxy(host, container)) + + if err != nil { + os.Exit(0) + } + os.Exit(1) } diff --git a/alpine/packages/proxy/proxy.go b/alpine/packages/proxy/proxy.go index d72e131ac..72b5d5734 100644 --- a/alpine/packages/proxy/proxy.go +++ b/alpine/packages/proxy/proxy.go @@ -13,13 +13,13 @@ import ( ) // proxyForever signals the parent success/failure and runs the proxy forever -func proxyForever(p proxy.Proxy, err error) { +func proxyForever(p proxy.Proxy, err error) error { f := os.NewFile(3, "signal-parent") if err != nil { fmt.Fprintf(f, "1\n%s", err) f.Close() - os.Exit(1) + return err } go handleStopSignals(p) fmt.Fprint(f, "0\n") @@ -27,6 +27,7 @@ func proxyForever(p proxy.Proxy, err error) { // Run will block until the proxy stops p.Run() + return nil } // From docker/libnetwork/portmapper/proxy.go: @@ -64,7 +65,5 @@ func handleStopSignals(p proxy.Proxy) { for range s { p.Close() - - os.Exit(0) } } From eab69ef58383bc50e8312dd48392208e1528db49 Mon Sep 17 00:00:00 2001 From: David Scott Date: Sun, 3 Apr 2016 14:57:35 +0100 Subject: [PATCH 05/12] proxy: add `exposePort` and `unexposePort` stubs This patch removes `proxyForever` and adds `sendError` and `sendOK` for signalling to the parent process. The main function now sequences these functions and calls stub functions `exposePort` and `unexposePort` which will be hooked up in a later patch. Signed-off-by: David Scott --- alpine/packages/proxy/main.go | 26 ++++++++++++++++++++++---- alpine/packages/proxy/proxy.go | 22 ++++++++++------------ 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/alpine/packages/proxy/main.go b/alpine/packages/proxy/main.go index ead8b92cb..1c5492d34 100644 --- a/alpine/packages/proxy/main.go +++ b/alpine/packages/proxy/main.go @@ -2,6 +2,7 @@ package main import ( "log" + "net" "os" "pkg/proxy" ) @@ -9,10 +10,27 @@ import ( func main() { host, container := parseHostContainerAddrs() - err := proxyForever(proxy.NewProxy(host, container)) - + err := exposePort(host) if err != nil { - os.Exit(0) + sendError(err) } - os.Exit(1) + p, err := proxy.NewProxy(host, container) + if err != nil { + unexposePort(host) + sendError(err) + } + go handleStopSignals(p) + sendOK() + p.Run() + unexposePort(host) + os.Exit(0) +} + +func exposePort(host net.Addr) error { + log.Printf("exposePort %#v\n", host) + return nil +} + +func unexposePort(host net.Addr) { + log.Printf("unexposePort %#v\n", host) } diff --git a/alpine/packages/proxy/proxy.go b/alpine/packages/proxy/proxy.go index 72b5d5734..b6fce56b2 100644 --- a/alpine/packages/proxy/proxy.go +++ b/alpine/packages/proxy/proxy.go @@ -12,22 +12,20 @@ import ( "pkg/proxy" ) -// proxyForever signals the parent success/failure and runs the proxy forever -func proxyForever(p proxy.Proxy, err error) error { +// sendError signals the error to the parent and quits the process. +func sendError(err error) { f := os.NewFile(3, "signal-parent") - if err != nil { - fmt.Fprintf(f, "1\n%s", err) - f.Close() - return err - } - go handleStopSignals(p) + fmt.Fprintf(f, "1\n%s", err) + f.Close() + os.Exit(1) +} + +// sendOK signals the parent that the forward is running. +func sendOK() { + f := os.NewFile(3, "signal-parent") fmt.Fprint(f, "0\n") f.Close() - - // Run will block until the proxy stops - p.Run() - return nil } // From docker/libnetwork/portmapper/proxy.go: From b29a44b4a8420c19f52e3b3bce37413e33d220a8 Mon Sep 17 00:00:00 2001 From: David Scott Date: Sun, 3 Apr 2016 15:28:20 +0100 Subject: [PATCH 06/12] proxy: set up and tear down host port forwards This patch uses the /port 9P filesystem to set up and tear down port forwards on the host. Signed-off-by: David Scott --- alpine/packages/proxy/main.go | 47 +++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/alpine/packages/proxy/main.go b/alpine/packages/proxy/main.go index 1c5492d34..383a580f2 100644 --- a/alpine/packages/proxy/main.go +++ b/alpine/packages/proxy/main.go @@ -1,10 +1,13 @@ package main import ( + "errors" + "fmt" "log" "net" "os" "pkg/proxy" + "strings" ) func main() { @@ -27,10 +30,50 @@ func main() { } func exposePort(host net.Addr) error { - log.Printf("exposePort %#v\n", host) + name := host.String() + log.Printf("exposePort %s\n", name) + err := os.Mkdir("/port/"+name, 0) + if err != nil { + log.Printf("Failed to mkdir /port/%s: %#v\n", name, err) + return err + } + ctl, err := os.OpenFile("/port/"+name+"/ctl", os.O_RDWR, 0) + if err != nil { + log.Printf("Failed to open /port/%s/ctl: %#v\n", name, err) + return err + } + _, err = ctl.WriteString(fmt.Sprintf("%s:%s", name, name)) + if err != nil { + log.Printf("Failed to open /port/%s/ctl: %#v\n", name, err) + return err + } + _, err = ctl.Seek(0, 0) + if err != nil { + log.Printf("Failed to seek on /port/%s/ctl: %#v\n", name, err) + return err + } + results := make([]byte, 100) + count, err := ctl.Read(results) + if err != nil { + log.Printf("Failed to read from /port/%s/ctl: %#v\n", name, err) + return err + } + response := string(results[0:count]) + if strings.HasPrefix(response, "ERROR ") { + os.Remove("/port/" + name + "/ctl") + response = strings.Trim(response[6:], " \t\r\n") + return errors.New(response) + } + // TODO: consider whether close/clunk of ctl would be a better tear down + // signal return nil } func unexposePort(host net.Addr) { - log.Printf("unexposePort %#v\n", host) + name := host.String() + log.Printf("unexposePort %s\n", name) + err := os.Remove("/port/" + name) + if err != nil { + log.Printf("Failed to remove /port/%s: %#v\n", name, err) + } } From 772bb48010a172a9a872d83089b069cc14752490 Mon Sep 17 00:00:00 2001 From: David Scott Date: Sun, 3 Apr 2016 20:52:12 +0100 Subject: [PATCH 07/12] proxy: determine "my" address by examining the "docker" forward When requesting a port forward we currently need to know the VM's address from the point of view of the port forwarder. The easiest way to discover this is to read it from the existing "docker" port forward. Note this should all be revamped once we have vsock support. Signed-off-by: David Scott --- alpine/packages/proxy/main.go | 45 +++++++++++++++++++++++++++++----- alpine/packages/proxy/proxy.go | 6 +++-- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/alpine/packages/proxy/main.go b/alpine/packages/proxy/main.go index 383a580f2..fda26965d 100644 --- a/alpine/packages/proxy/main.go +++ b/alpine/packages/proxy/main.go @@ -11,9 +11,9 @@ import ( ) func main() { - host, container := parseHostContainerAddrs() + host, port, container := parseHostContainerAddrs() - err := exposePort(host) + err := exposePort(host, port) if err != nil { sendError(err) } @@ -29,7 +29,7 @@ func main() { os.Exit(0) } -func exposePort(host net.Addr) error { +func exposePort(host net.Addr, port int) error { name := host.String() log.Printf("exposePort %s\n", name) err := os.Mkdir("/port/"+name, 0) @@ -42,7 +42,12 @@ func exposePort(host net.Addr) error { log.Printf("Failed to open /port/%s/ctl: %#v\n", name, err) return err } - _, err = ctl.WriteString(fmt.Sprintf("%s:%s", name, name)) + me, err := getMyAddress() + if err != nil { + log.Printf("Failed to determine my local address: %#v\n", err) + return err + } + _, err = ctl.WriteString(fmt.Sprintf("%s:%s:%d", name, me, port)) if err != nil { log.Printf("Failed to open /port/%s/ctl: %#v\n", name, err) return err @@ -58,14 +63,17 @@ func exposePort(host net.Addr) error { log.Printf("Failed to read from /port/%s/ctl: %#v\n", name, err) return err } + // TODO: consider whether close/clunk of ctl would be a better tear down + // signal + ctl.Close() + response := string(results[0:count]) if strings.HasPrefix(response, "ERROR ") { os.Remove("/port/" + name + "/ctl") response = strings.Trim(response[6:], " \t\r\n") return errors.New(response) } - // TODO: consider whether close/clunk of ctl would be a better tear down - // signal + return nil } @@ -77,3 +85,28 @@ func unexposePort(host net.Addr) { log.Printf("Failed to remove /port/%s: %#v\n", name, err) } } + +var myAddress string + +// getMyAddress returns a string representing my address from the host's +// point of view. For now this is an IP address but it soon should be a vsock +// port. +func getMyAddress() (string, error) { + if myAddress != "" { + return myAddress, nil + } + d, err := os.Open("/port/docker") + if err != nil { + return "", err + } + defer d.Close() + bytes := make([]byte, 100) + count, err := d.Read(bytes) + if err != nil { + return "", err + } + s := string(bytes)[0:count] + bits := strings.Split(s, ":") + myAddress = bits[2] + return myAddress, nil +} diff --git a/alpine/packages/proxy/proxy.go b/alpine/packages/proxy/proxy.go index b6fce56b2..f730bc22e 100644 --- a/alpine/packages/proxy/proxy.go +++ b/alpine/packages/proxy/proxy.go @@ -32,7 +32,7 @@ func sendOK() { // parseHostContainerAddrs parses the flags passed on reexec to create the TCP or UDP // net.Addrs to map the host and container ports -func parseHostContainerAddrs() (host net.Addr, container net.Addr) { +func parseHostContainerAddrs() (host net.Addr, port int, container net.Addr) { var ( proto = flag.String("proto", "tcp", "proxy protocol") hostIP = flag.String("host-ip", "", "host ip") @@ -46,15 +46,17 @@ func parseHostContainerAddrs() (host net.Addr, container net.Addr) { switch *proto { case "tcp": host = &net.TCPAddr{IP: net.ParseIP(*hostIP), Port: *hostPort} + port = *hostPort container = &net.TCPAddr{IP: net.ParseIP(*containerIP), Port: *containerPort} case "udp": host = &net.UDPAddr{IP: net.ParseIP(*hostIP), Port: *hostPort} + port = *hostPort container = &net.UDPAddr{IP: net.ParseIP(*containerIP), Port: *containerPort} default: log.Fatalf("unsupported protocol %s", *proto) } - return host, container + return host, port, container } func handleStopSignals(p proxy.Proxy) { From c627c0e6437cbeb7f5f2bc612de126824cded71f Mon Sep 17 00:00:00 2001 From: David Scott Date: Sun, 3 Apr 2016 18:32:04 +0100 Subject: [PATCH 08/12] docker: compile from scratch rather than download binaries A later patch will apply a non-upstream patch to docker. Signed-off-by: David Scott --- alpine/packages/docker/.gitignore | 1 + alpine/packages/docker/Makefile | 17 +++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/alpine/packages/docker/.gitignore b/alpine/packages/docker/.gitignore index e660fd93d..861389dec 100644 --- a/alpine/packages/docker/.gitignore +++ b/alpine/packages/docker/.gitignore @@ -1 +1,2 @@ bin/ +docker.git diff --git a/alpine/packages/docker/Makefile b/alpine/packages/docker/Makefile index 16824ea3e..3af3d4745 100644 --- a/alpine/packages/docker/Makefile +++ b/alpine/packages/docker/Makefile @@ -1,14 +1,19 @@ -all: docker - DOCKER_VERSION=1.11.0-rc3 ARCH?=x86_64 OS?=Linux -DOCKER_HOST=$(shell if echo "$(DOCKER_VERSION)" | grep -q -- '-rc'; then echo "test.docker.com"; else echo "get.docker.com"; fi) -docker: +all: bin + +bin: docker.git + (cd docker.git && git fetch origin --tags && git checkout v${DOCKER_VERSION}) + BIND_DIR=$(shell pwd) make -C docker.git binary mkdir -p bin - if [ "${ARCH}" != "arm" ] ; then curl -f -L -o docker.tgz https://${DOCKER_HOST}/builds/${OS}/${ARCH}/docker-${DOCKER_VERSION}.tgz && tar xzf docker.tgz && mv docker/* bin && rm -rf docker/ docker.tgz; else cp docker-arm bin/docker; fi - chmod +x bin/* + cp docker.git/bundles/${DOCKER_VERSION}/binary/* bin/ + rm -f bin/*.md5 + rm -f bin/*.sha256 + +docker.git: + git clone git://github.com/docker/docker.git docker.git clean: rm -rf bin From 30bf58a223afa43c2cd667da6e2125f4990b7135 Mon Sep 17 00:00:00 2001 From: David Scott Date: Sun, 3 Apr 2016 19:38:59 +0100 Subject: [PATCH 09/12] docker: add a patch to add --userland-proxy-bin argument This will let us override the `docker-proxy` with the enhanced host port-forwarding aware version. Signed-off-by: David Scott --- alpine/packages/docker/Makefile | 7 +- .../001-override-userland-proxy-binary | 207 ++++++++++++++++++ 2 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 alpine/packages/docker/patches/001-override-userland-proxy-binary diff --git a/alpine/packages/docker/Makefile b/alpine/packages/docker/Makefile index 3af3d4745..22fd48912 100644 --- a/alpine/packages/docker/Makefile +++ b/alpine/packages/docker/Makefile @@ -5,7 +5,12 @@ OS?=Linux all: bin bin: docker.git - (cd docker.git && git fetch origin --tags && git checkout v${DOCKER_VERSION}) + (cd docker.git && git fetch origin --tags) + cd docker.git && \ + git checkout v${DOCKER_VERSION} && \ + git branch -D v${DOCKER_VERSION}-patched; \ + git checkout -b v${DOCKER_VERSION}-patched && \ + (for i in $(shell ls -1 patches); do patch -p1 < ../patches/$${i}; git commit -a -s -m "applied $${i}"; done) BIND_DIR=$(shell pwd) make -C docker.git binary mkdir -p bin cp docker.git/bundles/${DOCKER_VERSION}/binary/* bin/ diff --git a/alpine/packages/docker/patches/001-override-userland-proxy-binary b/alpine/packages/docker/patches/001-override-userland-proxy-binary new file mode 100644 index 000000000..63677cba0 --- /dev/null +++ b/alpine/packages/docker/patches/001-override-userland-proxy-binary @@ -0,0 +1,207 @@ +commit dfb1652b51a083ef3285977b4c42ec113480c67b +Author: David Scott +Date: Sun Apr 3 10:39:34 2016 +0100 + + Add a command-line argument --userland-proxy-bin="" + + We will use to prototype a "ports plugin" mechanism which will + ultimately be able to expose container ports on other machines, + via other protocols e.g. + + - as a channel multiplexed within an ssh connection + - as a TCP port exposed on a host running a VM running docker + + The new --userland-proxy-bin argument is only inspected if + --userland-proxy=true. + + If --userland-proxy-bin="" (the default) then the existing built-in + docker-proxy will be used. + + If --userland-proxy-bin<>"" then the named program will be run instead. + + Signed-off-by: David Scott + +diff --git a/daemon/config_unix.go b/daemon/config_unix.go +index 5394949..ce10498 100644 +--- a/daemon/config_unix.go ++++ b/daemon/config_unix.go +@@ -42,6 +42,7 @@ type bridgeConfig struct { + EnableIPForward bool `json:"ip-forward,omitempty"` + EnableIPMasq bool `json:"ip-mask,omitempty"` + EnableUserlandProxy bool `json:"userland-proxy,omitempty"` ++ UserlandProxyBin string `json:"userland-proxy-bin,omitempty"` + DefaultIP net.IP `json:"ip,omitempty"` + Iface string `json:"bridge,omitempty"` + IP string `json:"bip,omitempty"` +@@ -78,6 +79,7 @@ func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) strin + cmd.BoolVar(&config.bridgeConfig.InterContainerCommunication, []string{"#icc", "-icc"}, true, usageFn("Enable inter-container communication")) + cmd.Var(opts.NewIPOpt(&config.bridgeConfig.DefaultIP, "0.0.0.0"), []string{"#ip", "-ip"}, usageFn("Default IP when binding container ports")) + cmd.BoolVar(&config.bridgeConfig.EnableUserlandProxy, []string{"-userland-proxy"}, true, usageFn("Use userland proxy for loopback traffic")) ++ cmd.StringVar(&config.bridgeConfig.UserlandProxyBin, []string{"-userland-proxy-bin"}, "", usageFn("Use specific userland proxy binary if in userland proxy mode")) + cmd.BoolVar(&config.EnableCors, []string{"#api-enable-cors", "#-api-enable-cors"}, false, usageFn("Enable CORS headers in the remote API, this is deprecated by --api-cors-header")) + cmd.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", usageFn("Set CORS headers in the remote API")) + cmd.StringVar(&config.CgroupParent, []string{"-cgroup-parent"}, "", usageFn("Set parent cgroup for all containers")) +diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go +index a152fd0..2ed48e1 100644 +--- a/daemon/daemon_unix.go ++++ b/daemon/daemon_unix.go +@@ -651,7 +651,8 @@ func driverOptions(config *Config) []nwconfig.Option { + bridgeConfig := options.Generic{ + "EnableIPForwarding": config.bridgeConfig.EnableIPForward, + "EnableIPTables": config.bridgeConfig.EnableIPTables, +- "EnableUserlandProxy": config.bridgeConfig.EnableUserlandProxy} ++ "EnableUserlandProxy": config.bridgeConfig.EnableUserlandProxy, ++ "UserlandProxyBin": config.bridgeConfig.UserlandProxyBin} + bridgeOption := options.Generic{netlabel.GenericData: bridgeConfig} + + dOptions := []nwconfig.Option{} +diff --git a/docs/reference/commandline/daemon.md b/docs/reference/commandline/daemon.md +index 5ca0024..61cd369 100644 +--- a/docs/reference/commandline/daemon.md ++++ b/docs/reference/commandline/daemon.md +@@ -67,6 +67,7 @@ weight = -1 + --tlsverify Use TLS and verify the remote + --userns-remap="default" Enable user namespace remapping + --userland-proxy=true Use userland proxy for loopback traffic ++ --userland-proxy-bin="" Use this userland proxy binary, if userland-proxy is set + + Options with [] may be specified multiple times. + +diff --git a/vendor/src/github.com/docker/libnetwork/drivers/bridge/bridge.go b/vendor/src/github.com/docker/libnetwork/drivers/bridge/bridge.go +index 00e16e1..16ad81d 100644 +--- a/vendor/src/github.com/docker/libnetwork/drivers/bridge/bridge.go ++++ b/vendor/src/github.com/docker/libnetwork/drivers/bridge/bridge.go +@@ -50,6 +50,7 @@ type configuration struct { + EnableIPForwarding bool + EnableIPTables bool + EnableUserlandProxy bool ++ UserlandProxyBin string + } + + // networkConfiguration for network specific configuration +@@ -1211,7 +1212,7 @@ func (d *driver) ProgramExternalConnectivity(nid, eid string, options map[string + } + + // Program any required port mapping and store them in the endpoint +- endpoint.portMapping, err = network.allocatePorts(endpoint, network.config.DefaultBindingIP, d.config.EnableUserlandProxy) ++ endpoint.portMapping, err = network.allocatePorts(endpoint, network.config.DefaultBindingIP, d.config.EnableUserlandProxy, d.config.UserlandProxyBin) + if err != nil { + return err + } +diff --git a/vendor/src/github.com/docker/libnetwork/drivers/bridge/port_mapping.go b/vendor/src/github.com/docker/libnetwork/drivers/bridge/port_mapping.go +index 965cc9a..1824f46 100644 +--- a/vendor/src/github.com/docker/libnetwork/drivers/bridge/port_mapping.go ++++ b/vendor/src/github.com/docker/libnetwork/drivers/bridge/port_mapping.go +@@ -14,7 +14,7 @@ var ( + defaultBindingIP = net.IPv4(0, 0, 0, 0) + ) + +-func (n *bridgeNetwork) allocatePorts(ep *bridgeEndpoint, reqDefBindIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) { ++func (n *bridgeNetwork) allocatePorts(ep *bridgeEndpoint, reqDefBindIP net.IP, ulPxyEnabled bool, pxyBin string) ([]types.PortBinding, error) { + if ep.extConnConfig == nil || ep.extConnConfig.PortBindings == nil { + return nil, nil + } +@@ -24,14 +24,14 @@ func (n *bridgeNetwork) allocatePorts(ep *bridgeEndpoint, reqDefBindIP net.IP, u + defHostIP = reqDefBindIP + } + +- return n.allocatePortsInternal(ep.extConnConfig.PortBindings, ep.addr.IP, defHostIP, ulPxyEnabled) ++ return n.allocatePortsInternal(ep.extConnConfig.PortBindings, ep.addr.IP, defHostIP, ulPxyEnabled, pxyBin) + } + +-func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, containerIP, defHostIP net.IP, ulPxyEnabled bool) ([]types.PortBinding, error) { ++func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, containerIP, defHostIP net.IP, ulPxyEnabled bool, pxyBin string) ([]types.PortBinding, error) { + bs := make([]types.PortBinding, 0, len(bindings)) + for _, c := range bindings { + b := c.GetCopy() +- if err := n.allocatePort(&b, containerIP, defHostIP, ulPxyEnabled); err != nil { ++ if err := n.allocatePort(&b, containerIP, defHostIP, ulPxyEnabled, pxyBin); err != nil { + // On allocation failure, release previously allocated ports. On cleanup error, just log a warning message + if cuErr := n.releasePortsInternal(bs); cuErr != nil { + logrus.Warnf("Upon allocation failure for %v, failed to clear previously allocated port bindings: %v", b, cuErr) +@@ -43,7 +43,7 @@ func (n *bridgeNetwork) allocatePortsInternal(bindings []types.PortBinding, cont + return bs, nil + } + +-func (n *bridgeNetwork) allocatePort(bnd *types.PortBinding, containerIP, defHostIP net.IP, ulPxyEnabled bool) error { ++func (n *bridgeNetwork) allocatePort(bnd *types.PortBinding, containerIP, defHostIP net.IP, ulPxyEnabled bool, pxyBin string) error { + var ( + host net.Addr + err error +@@ -70,7 +70,7 @@ func (n *bridgeNetwork) allocatePort(bnd *types.PortBinding, containerIP, defHos + + // Try up to maxAllocatePortAttempts times to get a port that's not already allocated. + for i := 0; i < maxAllocatePortAttempts; i++ { +- if host, err = n.portMapper.MapRange(container, bnd.HostIP, int(bnd.HostPort), int(bnd.HostPortEnd), ulPxyEnabled); err == nil { ++ if host, err = n.portMapper.MapRange(container, bnd.HostIP, int(bnd.HostPort), int(bnd.HostPortEnd), ulPxyEnabled, pxyBin); err == nil { + break + } + // There is no point in immediately retrying to map an explicitly chosen port. +diff --git a/vendor/src/github.com/docker/libnetwork/portmapper/mapper.go b/vendor/src/github.com/docker/libnetwork/portmapper/mapper.go +index d125fa8..e30b88c 100644 +--- a/vendor/src/github.com/docker/libnetwork/portmapper/mapper.go ++++ b/vendor/src/github.com/docker/libnetwork/portmapper/mapper.go +@@ -61,12 +61,12 @@ func (pm *PortMapper) SetIptablesChain(c *iptables.ChainInfo, bridgeName string) + } + + // Map maps the specified container transport address to the host's network address and transport port +-func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int, useProxy bool) (host net.Addr, err error) { +- return pm.MapRange(container, hostIP, hostPort, hostPort, useProxy) ++func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int, useProxy bool, proxyBin string) (host net.Addr, err error) { ++ return pm.MapRange(container, hostIP, hostPort, hostPort, useProxy, proxyBin) + } + + // MapRange maps the specified container transport address to the host's network address and transport port range +-func (pm *PortMapper) MapRange(container net.Addr, hostIP net.IP, hostPortStart, hostPortEnd int, useProxy bool) (host net.Addr, err error) { ++func (pm *PortMapper) MapRange(container net.Addr, hostIP net.IP, hostPortStart, hostPortEnd int, useProxy bool, proxyBin string) (host net.Addr, err error) { + pm.lock.Lock() + defer pm.lock.Unlock() + +@@ -90,7 +90,7 @@ func (pm *PortMapper) MapRange(container net.Addr, hostIP net.IP, hostPortStart, + } + + if useProxy { +- m.userlandProxy = newProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port) ++ m.userlandProxy = newProxy(proxyBin, proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port) + } else { + m.userlandProxy = newDummyProxy(proto, hostIP, allocatedHostPort) + } +@@ -107,7 +107,7 @@ func (pm *PortMapper) MapRange(container net.Addr, hostIP net.IP, hostPortStart, + } + + if useProxy { +- m.userlandProxy = newProxy(proto, hostIP, allocatedHostPort, container.(*net.UDPAddr).IP, container.(*net.UDPAddr).Port) ++ m.userlandProxy = newProxy(proxyBin, proto, hostIP, allocatedHostPort, container.(*net.UDPAddr).IP, container.(*net.UDPAddr).Port) + } else { + m.userlandProxy = newDummyProxy(proto, hostIP, allocatedHostPort) + } +diff --git a/vendor/src/github.com/docker/libnetwork/portmapper/proxy.go b/vendor/src/github.com/docker/libnetwork/portmapper/proxy.go +index ddde274..57bfeb1 100644 +--- a/vendor/src/github.com/docker/libnetwork/portmapper/proxy.go ++++ b/vendor/src/github.com/docker/libnetwork/portmapper/proxy.go +@@ -92,9 +92,14 @@ func handleStopSignals(p proxy.Proxy) { + } + } + +-func newProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int) userlandProxy { ++func newProxyCommand(userlandProxyBin string, proto string, hostIP net.IP, hostPort int, containerIP net.IP, containerPort int) userlandProxy { ++ path := userlandProxyBin ++ if userlandProxyBin == "" { ++ userlandProxyBin = userlandProxyCommandName ++ path = reexec.Self() ++ } + args := []string{ +- userlandProxyCommandName, ++ userlandProxyBin, + "-proto", proto, + "-host-ip", hostIP.String(), + "-host-port", strconv.Itoa(hostPort), +@@ -104,7 +109,7 @@ func newProxyCommand(proto string, hostIP net.IP, hostPort int, containerIP net. + + return &proxyCommand{ + cmd: &exec.Cmd{ +- Path: reexec.Self(), ++ Path: path, + Args: args, + SysProcAttr: &syscall.SysProcAttr{ + Pdeathsig: syscall.SIGTERM, // send a sigterm to the proxy if the daemon process dies From 9982a03f3163721e5c4e8c213b861398355da463 Mon Sep 17 00:00:00 2001 From: David Scott Date: Sun, 3 Apr 2016 20:02:31 +0100 Subject: [PATCH 10/12] 9pinit: mount the port control filesystem under /port This filesystem can be used to add and remove host port forwards e.g. mkdir /port/test echo -n '127.0.0.1:80:127.0.0.1:80' >> /port/test/ctl RESULT=$(cat /port/test/ctl) Signed-off-by: David Scott --- alpine/packages/9pinit/etc/init.d/9pinit | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/alpine/packages/9pinit/etc/init.d/9pinit b/alpine/packages/9pinit/etc/init.d/9pinit index 13a618bef..fe6b74d27 100755 --- a/alpine/packages/9pinit/etc/init.d/9pinit +++ b/alpine/packages/9pinit/etc/init.d/9pinit @@ -24,6 +24,10 @@ start() "db") mkdir -p /Database mount -t 9p -o trans=virtio,dfltuid=1001,dfltgid=50,version=9p2000 db /Database + ;; + "port") + mkdir -p /port + mount -t 9p -o trans=virtio,dfltuid=1001,dfltgid=50,version=9p2000 port /port esac done From 4f6641ff8c51122e4d7d2d8e2602b45ed84874d8 Mon Sep 17 00:00:00 2001 From: David Scott Date: Sun, 3 Apr 2016 19:58:04 +0100 Subject: [PATCH 11/12] docker: if network=slirp or native/port-forwarding=true, forward ports Signed-off-by: David Scott --- alpine/packages/docker/etc/init.d/docker | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/alpine/packages/docker/etc/init.d/docker b/alpine/packages/docker/etc/init.d/docker index aa6ba89d8..d0e77332f 100755 --- a/alpine/packages/docker/etc/init.d/docker +++ b/alpine/packages/docker/etc/init.d/docker @@ -41,8 +41,14 @@ start() if cat /proc/cmdline | grep -q 'com.docker.database' then DATABASE="$(cat /proc/cmdline | sed -e 's/.*com.docker.database="//' -e 's/".*//')" - CONFIG_FILE="/Database/branch/master/ro/${DATABASE}/etc/docker/daemon.json" + MASTER_RO="/Database/branch/master/ro" + CONFIG_FILE="${MASTER_RO}/${DATABASE}/etc/docker/daemon.json" [ -s ${CONFIG_FILE} ] && DOCKER_OPTS="${DOCKER_OPTS} --config-file ${CONFIG_FILE}" + NETWORK_MODE="$(cat ${MASTER_RO}/${DATABASE}/network | tr -d '[[:space:]]')" + NATIVE_PORT_FORWARDING="$(cat ${MASTER_RO}/${DATABASE}/native/port-forwarding | tr -d '[[:space:]]')" + if [ "${NETWORK_MODE}" = "slirp" -o "${NATIVE_PORT_FORWARDING}" = "true" ]; then + DOCKER_OPTS="${DOCKER_OPTS} --userland-proxy-bin /sbin/proxy" + fi fi for d in Users Volumes tmp private From 9f3a73fe041fd53caf1ebea2b62ffc706db83ad9 Mon Sep 17 00:00:00 2001 From: David Scott Date: Mon, 4 Apr 2016 11:19:03 +0100 Subject: [PATCH 12/12] Import docker/docker/pkg/proxy from 18c7c67308bd4a24a41028e63c2603bb74eac85e Signed-off-by: David Scott --- .../proxy/pkg/proxy/network_proxy_test.go | 216 ++++++++++++++++++ alpine/packages/proxy/pkg/proxy/proxy.go | 37 +++ alpine/packages/proxy/pkg/proxy/stub_proxy.go | 31 +++ alpine/packages/proxy/pkg/proxy/tcp_proxy.go | 99 ++++++++ alpine/packages/proxy/pkg/proxy/udp_proxy.go | 169 ++++++++++++++ 5 files changed, 552 insertions(+) create mode 100644 alpine/packages/proxy/pkg/proxy/network_proxy_test.go create mode 100644 alpine/packages/proxy/pkg/proxy/proxy.go create mode 100644 alpine/packages/proxy/pkg/proxy/stub_proxy.go create mode 100644 alpine/packages/proxy/pkg/proxy/tcp_proxy.go create mode 100644 alpine/packages/proxy/pkg/proxy/udp_proxy.go diff --git a/alpine/packages/proxy/pkg/proxy/network_proxy_test.go b/alpine/packages/proxy/pkg/proxy/network_proxy_test.go new file mode 100644 index 000000000..9e382567c --- /dev/null +++ b/alpine/packages/proxy/pkg/proxy/network_proxy_test.go @@ -0,0 +1,216 @@ +package proxy + +import ( + "bytes" + "fmt" + "io" + "net" + "strings" + "testing" + "time" +) + +var testBuf = []byte("Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo") +var testBufSize = len(testBuf) + +type EchoServer interface { + Run() + Close() + LocalAddr() net.Addr +} + +type TCPEchoServer struct { + listener net.Listener + testCtx *testing.T +} + +type UDPEchoServer struct { + conn net.PacketConn + testCtx *testing.T +} + +func NewEchoServer(t *testing.T, proto, address string) EchoServer { + var server EchoServer + if strings.HasPrefix(proto, "tcp") { + listener, err := net.Listen(proto, address) + if err != nil { + t.Fatal(err) + } + server = &TCPEchoServer{listener: listener, testCtx: t} + } else { + socket, err := net.ListenPacket(proto, address) + if err != nil { + t.Fatal(err) + } + server = &UDPEchoServer{conn: socket, testCtx: t} + } + return server +} + +func (server *TCPEchoServer) Run() { + go func() { + for { + client, err := server.listener.Accept() + if err != nil { + return + } + go func(client net.Conn) { + if _, err := io.Copy(client, client); err != nil { + server.testCtx.Logf("can't echo to the client: %v\n", err.Error()) + } + client.Close() + }(client) + } + }() +} + +func (server *TCPEchoServer) LocalAddr() net.Addr { return server.listener.Addr() } +func (server *TCPEchoServer) Close() { server.listener.Addr() } + +func (server *UDPEchoServer) Run() { + go func() { + readBuf := make([]byte, 1024) + for { + read, from, err := server.conn.ReadFrom(readBuf) + if err != nil { + return + } + for i := 0; i != read; { + written, err := server.conn.WriteTo(readBuf[i:read], from) + if err != nil { + break + } + i += written + } + } + }() +} + +func (server *UDPEchoServer) LocalAddr() net.Addr { return server.conn.LocalAddr() } +func (server *UDPEchoServer) Close() { server.conn.Close() } + +func testProxyAt(t *testing.T, proto string, proxy Proxy, addr string) { + defer proxy.Close() + go proxy.Run() + client, err := net.Dial(proto, addr) + if err != nil { + t.Fatalf("Can't connect to the proxy: %v", err) + } + defer client.Close() + client.SetDeadline(time.Now().Add(10 * time.Second)) + if _, err = client.Write(testBuf); err != nil { + t.Fatal(err) + } + recvBuf := make([]byte, testBufSize) + if _, err = client.Read(recvBuf); err != nil { + t.Fatal(err) + } + if !bytes.Equal(testBuf, recvBuf) { + t.Fatal(fmt.Errorf("Expected [%v] but got [%v]", testBuf, recvBuf)) + } +} + +func testProxy(t *testing.T, proto string, proxy Proxy) { + testProxyAt(t, proto, proxy, proxy.FrontendAddr().String()) +} + +func TestTCP4Proxy(t *testing.T) { + backend := NewEchoServer(t, "tcp", "127.0.0.1:0") + defer backend.Close() + backend.Run() + frontendAddr := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} + proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) + if err != nil { + t.Fatal(err) + } + testProxy(t, "tcp", proxy) +} + +func TestTCP6Proxy(t *testing.T) { + backend := NewEchoServer(t, "tcp", "[::1]:0") + defer backend.Close() + backend.Run() + frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0} + proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) + if err != nil { + t.Fatal(err) + } + testProxy(t, "tcp", proxy) +} + +func TestTCPDualStackProxy(t *testing.T) { + // If I understand `godoc -src net favoriteAddrFamily` (used by the + // net.Listen* functions) correctly this should work, but it doesn't. + t.Skip("No support for dual stack yet") + backend := NewEchoServer(t, "tcp", "[::1]:0") + defer backend.Close() + backend.Run() + frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0} + proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) + if err != nil { + t.Fatal(err) + } + ipv4ProxyAddr := &net.TCPAddr{ + IP: net.IPv4(127, 0, 0, 1), + Port: proxy.FrontendAddr().(*net.TCPAddr).Port, + } + testProxyAt(t, "tcp", proxy, ipv4ProxyAddr.String()) +} + +func TestUDP4Proxy(t *testing.T) { + backend := NewEchoServer(t, "udp", "127.0.0.1:0") + defer backend.Close() + backend.Run() + frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} + proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) + if err != nil { + t.Fatal(err) + } + testProxy(t, "udp", proxy) +} + +func TestUDP6Proxy(t *testing.T) { + backend := NewEchoServer(t, "udp", "[::1]:0") + defer backend.Close() + backend.Run() + frontendAddr := &net.UDPAddr{IP: net.IPv6loopback, Port: 0} + proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) + if err != nil { + t.Fatal(err) + } + testProxy(t, "udp", proxy) +} + +func TestUDPWriteError(t *testing.T) { + frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} + // Hopefully, this port will be free: */ + backendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 25587} + proxy, err := NewProxy(frontendAddr, backendAddr) + if err != nil { + t.Fatal(err) + } + defer proxy.Close() + go proxy.Run() + client, err := net.Dial("udp", "127.0.0.1:25587") + if err != nil { + t.Fatalf("Can't connect to the proxy: %v", err) + } + defer client.Close() + // Make sure the proxy doesn't stop when there is no actual backend: + client.Write(testBuf) + client.Write(testBuf) + backend := NewEchoServer(t, "udp", "127.0.0.1:25587") + defer backend.Close() + backend.Run() + client.SetDeadline(time.Now().Add(10 * time.Second)) + if _, err = client.Write(testBuf); err != nil { + t.Fatal(err) + } + recvBuf := make([]byte, testBufSize) + if _, err = client.Read(recvBuf); err != nil { + t.Fatal(err) + } + if !bytes.Equal(testBuf, recvBuf) { + t.Fatal(fmt.Errorf("Expected [%v] but got [%v]", testBuf, recvBuf)) + } +} diff --git a/alpine/packages/proxy/pkg/proxy/proxy.go b/alpine/packages/proxy/pkg/proxy/proxy.go new file mode 100644 index 000000000..4e24e5f6a --- /dev/null +++ b/alpine/packages/proxy/pkg/proxy/proxy.go @@ -0,0 +1,37 @@ +// Package proxy provides a network Proxy interface and implementations for TCP +// and UDP. +package proxy + +import ( + "fmt" + "net" +) + +// Proxy defines the behavior of a proxy. It forwards traffic back and forth +// between two endpoints : the frontend and the backend. +// It can be used to do software port-mapping between two addresses. +// e.g. forward all traffic between the frontend (host) 127.0.0.1:3000 +// to the backend (container) at 172.17.42.108:4000. +type Proxy interface { + // Run starts forwarding traffic back and forth between the front + // and back-end addresses. + Run() + // Close stops forwarding traffic and close both ends of the Proxy. + Close() + // FrontendAddr returns the address on which the proxy is listening. + FrontendAddr() net.Addr + // BackendAddr returns the proxied address. + BackendAddr() net.Addr +} + +// NewProxy creates a Proxy according to the specified frontendAddr and backendAddr. +func NewProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) { + switch frontendAddr.(type) { + case *net.UDPAddr: + return NewUDPProxy(frontendAddr.(*net.UDPAddr), backendAddr.(*net.UDPAddr)) + case *net.TCPAddr: + return NewTCPProxy(frontendAddr.(*net.TCPAddr), backendAddr.(*net.TCPAddr)) + default: + panic(fmt.Errorf("Unsupported protocol")) + } +} diff --git a/alpine/packages/proxy/pkg/proxy/stub_proxy.go b/alpine/packages/proxy/pkg/proxy/stub_proxy.go new file mode 100644 index 000000000..571749e46 --- /dev/null +++ b/alpine/packages/proxy/pkg/proxy/stub_proxy.go @@ -0,0 +1,31 @@ +package proxy + +import ( + "net" +) + +// StubProxy is a proxy that is a stub (does nothing). +type StubProxy struct { + frontendAddr net.Addr + backendAddr net.Addr +} + +// Run does nothing. +func (p *StubProxy) Run() {} + +// Close does nothing. +func (p *StubProxy) Close() {} + +// FrontendAddr returns the frontend address. +func (p *StubProxy) FrontendAddr() net.Addr { return p.frontendAddr } + +// BackendAddr returns the backend address. +func (p *StubProxy) BackendAddr() net.Addr { return p.backendAddr } + +// NewStubProxy creates a new StubProxy +func NewStubProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) { + return &StubProxy{ + frontendAddr: frontendAddr, + backendAddr: backendAddr, + }, nil +} diff --git a/alpine/packages/proxy/pkg/proxy/tcp_proxy.go b/alpine/packages/proxy/pkg/proxy/tcp_proxy.go new file mode 100644 index 000000000..3cd742af7 --- /dev/null +++ b/alpine/packages/proxy/pkg/proxy/tcp_proxy.go @@ -0,0 +1,99 @@ +package proxy + +import ( + "io" + "net" + "syscall" + + "github.com/Sirupsen/logrus" +) + +// TCPProxy is a proxy for TCP connections. It implements the Proxy interface to +// handle TCP traffic forwarding between the frontend and backend addresses. +type TCPProxy struct { + listener *net.TCPListener + frontendAddr *net.TCPAddr + backendAddr *net.TCPAddr +} + +// NewTCPProxy creates a new TCPProxy. +func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr) (*TCPProxy, error) { + listener, err := net.ListenTCP("tcp", frontendAddr) + if err != nil { + return nil, err + } + // If the port in frontendAddr was 0 then ListenTCP will have a picked + // a port to listen on, hence the call to Addr to get that actual port: + return &TCPProxy{ + listener: listener, + frontendAddr: listener.Addr().(*net.TCPAddr), + backendAddr: backendAddr, + }, nil +} + +func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) { + backend, err := net.DialTCP("tcp", nil, proxy.backendAddr) + if err != nil { + logrus.Printf("Can't forward traffic to backend tcp/%v: %s\n", proxy.backendAddr, err) + client.Close() + return + } + + event := make(chan int64) + var broker = func(to, from *net.TCPConn) { + written, err := io.Copy(to, from) + if err != nil { + // If the socket we are writing to is shutdown with + // SHUT_WR, forward it to the other end of the pipe: + if err, ok := err.(*net.OpError); ok && err.Err == syscall.EPIPE { + from.CloseWrite() + } + } + to.CloseRead() + event <- written + } + + go broker(client, backend) + go broker(backend, client) + + var transferred int64 + for i := 0; i < 2; i++ { + select { + case written := <-event: + transferred += written + case <-quit: + // Interrupt the two brokers and "join" them. + client.Close() + backend.Close() + for ; i < 2; i++ { + transferred += <-event + } + return + } + } + client.Close() + backend.Close() +} + +// Run starts forwarding the traffic using TCP. +func (proxy *TCPProxy) Run() { + quit := make(chan bool) + defer close(quit) + for { + client, err := proxy.listener.Accept() + if err != nil { + logrus.Printf("Stopping proxy on tcp/%v for tcp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err) + return + } + go proxy.clientLoop(client.(*net.TCPConn), quit) + } +} + +// Close stops forwarding the traffic. +func (proxy *TCPProxy) Close() { proxy.listener.Close() } + +// FrontendAddr returns the TCP address on which the proxy is listening. +func (proxy *TCPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr } + +// BackendAddr returns the TCP proxied address. +func (proxy *TCPProxy) BackendAddr() net.Addr { return proxy.backendAddr } diff --git a/alpine/packages/proxy/pkg/proxy/udp_proxy.go b/alpine/packages/proxy/pkg/proxy/udp_proxy.go new file mode 100644 index 000000000..b8375c374 --- /dev/null +++ b/alpine/packages/proxy/pkg/proxy/udp_proxy.go @@ -0,0 +1,169 @@ +package proxy + +import ( + "encoding/binary" + "net" + "strings" + "sync" + "syscall" + "time" + + "github.com/Sirupsen/logrus" +) + +const ( + // UDPConnTrackTimeout is the timeout used for UDP connection tracking + UDPConnTrackTimeout = 90 * time.Second + // UDPBufSize is the buffer size for the UDP proxy + UDPBufSize = 65507 +) + +// A net.Addr where the IP is split into two fields so you can use it as a key +// in a map: +type connTrackKey struct { + IPHigh uint64 + IPLow uint64 + Port int +} + +func newConnTrackKey(addr *net.UDPAddr) *connTrackKey { + if len(addr.IP) == net.IPv4len { + return &connTrackKey{ + IPHigh: 0, + IPLow: uint64(binary.BigEndian.Uint32(addr.IP)), + Port: addr.Port, + } + } + return &connTrackKey{ + IPHigh: binary.BigEndian.Uint64(addr.IP[:8]), + IPLow: binary.BigEndian.Uint64(addr.IP[8:]), + Port: addr.Port, + } +} + +type connTrackMap map[connTrackKey]*net.UDPConn + +// UDPProxy is proxy for which handles UDP datagrams. It implements the Proxy +// interface to handle UDP traffic forwarding between the frontend and backend +// addresses. +type UDPProxy struct { + listener *net.UDPConn + frontendAddr *net.UDPAddr + backendAddr *net.UDPAddr + connTrackTable connTrackMap + connTrackLock sync.Mutex +} + +// NewUDPProxy creates a new UDPProxy. +func NewUDPProxy(frontendAddr, backendAddr *net.UDPAddr) (*UDPProxy, error) { + listener, err := net.ListenUDP("udp", frontendAddr) + if err != nil { + return nil, err + } + return &UDPProxy{ + listener: listener, + frontendAddr: listener.LocalAddr().(*net.UDPAddr), + backendAddr: backendAddr, + connTrackTable: make(connTrackMap), + }, nil +} + +func (proxy *UDPProxy) replyLoop(proxyConn *net.UDPConn, clientAddr *net.UDPAddr, clientKey *connTrackKey) { + defer func() { + proxy.connTrackLock.Lock() + delete(proxy.connTrackTable, *clientKey) + proxy.connTrackLock.Unlock() + proxyConn.Close() + }() + + readBuf := make([]byte, UDPBufSize) + for { + proxyConn.SetReadDeadline(time.Now().Add(UDPConnTrackTimeout)) + again: + read, err := proxyConn.Read(readBuf) + if err != nil { + if err, ok := err.(*net.OpError); ok && err.Err == syscall.ECONNREFUSED { + // This will happen if the last write failed + // (e.g: nothing is actually listening on the + // proxied port on the container), ignore it + // and continue until UDPConnTrackTimeout + // expires: + goto again + } + return + } + for i := 0; i != read; { + written, err := proxy.listener.WriteToUDP(readBuf[i:read], clientAddr) + if err != nil { + return + } + i += written + } + } +} + +// Run starts forwarding the traffic using UDP. +func (proxy *UDPProxy) Run() { + readBuf := make([]byte, UDPBufSize) + for { + read, from, err := proxy.listener.ReadFromUDP(readBuf) + if err != nil { + // NOTE: Apparently ReadFrom doesn't return + // ECONNREFUSED like Read do (see comment in + // UDPProxy.replyLoop) + if !isClosedError(err) { + logrus.Printf("Stopping proxy on udp/%v for udp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err) + } + break + } + + fromKey := newConnTrackKey(from) + proxy.connTrackLock.Lock() + proxyConn, hit := proxy.connTrackTable[*fromKey] + if !hit { + proxyConn, err = net.DialUDP("udp", nil, proxy.backendAddr) + if err != nil { + logrus.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err) + proxy.connTrackLock.Unlock() + continue + } + proxy.connTrackTable[*fromKey] = proxyConn + go proxy.replyLoop(proxyConn, from, fromKey) + } + proxy.connTrackLock.Unlock() + for i := 0; i != read; { + written, err := proxyConn.Write(readBuf[i:read]) + if err != nil { + logrus.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err) + break + } + i += written + } + } +} + +// Close stops forwarding the traffic. +func (proxy *UDPProxy) Close() { + proxy.listener.Close() + proxy.connTrackLock.Lock() + defer proxy.connTrackLock.Unlock() + for _, conn := range proxy.connTrackTable { + conn.Close() + } +} + +// FrontendAddr returns the UDP address on which the proxy is listening. +func (proxy *UDPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr } + +// BackendAddr returns the proxied UDP address. +func (proxy *UDPProxy) BackendAddr() net.Addr { return proxy.backendAddr } + +func isClosedError(err error) bool { + /* This comparison is ugly, but unfortunately, net.go doesn't export errClosing. + * See: + * http://golang.org/src/pkg/net/net.go + * https://code.google.com/p/go/issues/detail?id=4337 + * https://groups.google.com/forum/#!msg/golang-nuts/0_aaCvBmOcM/SptmDyX1XJMJ + */ + return strings.HasSuffix(err.Error(), "use of closed network connection") +}