├── .dockerignore ├── .gitignore ├── Dockerfile ├── Godeps ├── Godeps.json ├── Readme └── _workspace │ ├── .gitignore │ └── src │ └── github.com │ ├── codegangsta │ └── cli │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── app.go │ │ ├── autocomplete │ │ ├── bash_autocomplete │ │ └── zsh_autocomplete │ │ ├── cli.go │ │ ├── command.go │ │ ├── context.go │ │ ├── flag.go │ │ └── help.go │ └── miekg │ └── dns │ ├── .gitignore │ ├── .travis.yml │ ├── AUTHORS │ ├── CONTRIBUTORS │ ├── COPYRIGHT │ ├── LICENSE │ ├── README.md │ ├── client.go │ ├── clientconfig.go │ ├── defaults.go │ ├── dns.go │ ├── dnssec.go │ ├── dnssec_keygen.go │ ├── dnssec_keyscan.go │ ├── dnssec_privkey.go │ ├── doc.go │ ├── edns.go │ ├── format.go │ ├── idn │ ├── code_points.go │ └── punycode.go │ ├── labels.go │ ├── msg.go │ ├── nsecx.go │ ├── privaterr.go │ ├── rawmsg.go │ ├── sanitize.go │ ├── scanner.go │ ├── server.go │ ├── sig0.go │ ├── singleinflight.go │ ├── tlsa.go │ ├── tsig.go │ ├── types.go │ ├── types_generate.go │ ├── udp.go │ ├── udp_linux.go │ ├── udp_other.go │ ├── udp_windows.go │ ├── update.go │ ├── xfr.go │ ├── zgenerate.go │ ├── zscan.go │ ├── zscan_rr.go │ └── ztypes.go ├── LICENSE ├── README.md ├── TODO.md ├── _docs ├── 0-Index.md ├── 1-Command-Line.md ├── 2-Deploying.md ├── 3-Service-Naming.md ├── 4-External-DNS.md ├── 5-Best-Practices.md └── Advanced-1-Internals.md ├── clusterdns ├── clusterdns.go ├── clusterdns_test.go └── refresh │ ├── refresh.go │ ├── refresh.o │ └── refresh_test.go ├── main.go ├── rrgen ├── filter.go ├── filter_test.go ├── rrgen.go └── rrgen_test.go ├── rrstore ├── store.go └── store_test.go ├── rrtype ├── rrtype.go └── rrtype_test.go ├── server ├── server.go └── server_test.go ├── swarm ├── swarm.go └── swarm_test.go ├── task └── task.go ├── tlsconfig └── config.go ├── usage.go └── util.go /.dockerignore: -------------------------------------------------------------------------------- 1 | wagl 2 | .git 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | wagl 3 | 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.5 2 | 3 | ENV GOPATH /go:/go/src/github.com/ahmetalpbalkan/wagl/Godeps/_workspace 4 | ADD . /go/src/github.com/ahmetalpbalkan/wagl 5 | 6 | WORKDIR /go/src/github.com/ahmetalpbalkan/wagl 7 | 8 | RUN GOOS=linux GOARCH=amd64 \ 9 | go install 10 | 11 | RUN ["wagl", "--help"] 12 | 13 | # 53: DNS 14 | EXPOSE 53/udp 15 | 16 | CMD ["wagl"] 17 | -------------------------------------------------------------------------------- /Godeps/Godeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "github.com/ahmetalpbalkan/wagl", 3 | "GoVersion": "go1.5", 4 | "Deps": [ 5 | { 6 | "ImportPath": "github.com/codegangsta/cli", 7 | "Comment": "1.2.0-159-gcffab77", 8 | "Rev": "cffab77ecb4f963ced9e30344eb2b9282ef36887" 9 | }, 10 | { 11 | "ImportPath": "github.com/miekg/dns", 12 | "Rev": "adeb323cbc8e73c87181c5ac9d393d66bbc4e165" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Godeps/Readme: -------------------------------------------------------------------------------- 1 | This directory tree is generated automatically by godep. 2 | 3 | Please do not edit. 4 | 5 | See https://github.com/tools/godep for more information. 6 | -------------------------------------------------------------------------------- /Godeps/_workspace/.gitignore: -------------------------------------------------------------------------------- 1 | /pkg 2 | /bin 3 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/cli/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | 4 | go: 5 | - 1.0.3 6 | - 1.1.2 7 | - 1.2.2 8 | - 1.3.3 9 | - 1.4.2 10 | 11 | script: 12 | - go vet ./... 13 | - go test -v ./... 14 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/cli/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 Jeremy Saenz 2 | All Rights Reserved. 3 | 4 | MIT LICENSE 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/cli/README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/codegangsta/cli.png?branch=master)](https://travis-ci.org/codegangsta/cli) 2 | 3 | # cli.go 4 | `cli.go` is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. 5 | 6 | You can view the API docs here: 7 | http://godoc.org/github.com/codegangsta/cli 8 | 9 | ## Overview 10 | Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app. 11 | 12 | **This is where `cli.go` comes into play.** `cli.go` makes command line programming fun, organized, and expressive! 13 | 14 | ## Installation 15 | Make sure you have a working Go environment (go 1.1+ is *required*). [See the install instructions](http://golang.org/doc/install.html). 16 | 17 | To install `cli.go`, simply run: 18 | ``` 19 | $ go get github.com/codegangsta/cli 20 | ``` 21 | 22 | Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used: 23 | ``` 24 | export PATH=$PATH:$GOPATH/bin 25 | ``` 26 | 27 | ## Getting Started 28 | One of the philosophies behind `cli.go` is that an API should be playful and full of discovery. So a `cli.go` app can be as little as one line of code in `main()`. 29 | 30 | ``` go 31 | package main 32 | 33 | import ( 34 | "os" 35 | "github.com/codegangsta/cli" 36 | ) 37 | 38 | func main() { 39 | cli.NewApp().Run(os.Args) 40 | } 41 | ``` 42 | 43 | This app will run and show help text, but is not very useful. Let's give an action to execute and some help documentation: 44 | 45 | ``` go 46 | package main 47 | 48 | import ( 49 | "os" 50 | "github.com/codegangsta/cli" 51 | ) 52 | 53 | func main() { 54 | app := cli.NewApp() 55 | app.Name = "boom" 56 | app.Usage = "make an explosive entrance" 57 | app.Action = func(c *cli.Context) { 58 | println("boom! I say!") 59 | } 60 | 61 | app.Run(os.Args) 62 | } 63 | ``` 64 | 65 | Running this already gives you a ton of functionality, plus support for things like subcommands and flags, which are covered below. 66 | 67 | ## Example 68 | 69 | Being a programmer can be a lonely job. Thankfully by the power of automation that is not the case! Let's create a greeter app to fend off our demons of loneliness! 70 | 71 | Start by creating a directory named `greet`, and within it, add a file, `greet.go` with the following code in it: 72 | 73 | ``` go 74 | package main 75 | 76 | import ( 77 | "os" 78 | "github.com/codegangsta/cli" 79 | ) 80 | 81 | func main() { 82 | app := cli.NewApp() 83 | app.Name = "greet" 84 | app.Usage = "fight the loneliness!" 85 | app.Action = func(c *cli.Context) { 86 | println("Hello friend!") 87 | } 88 | 89 | app.Run(os.Args) 90 | } 91 | ``` 92 | 93 | Install our command to the `$GOPATH/bin` directory: 94 | 95 | ``` 96 | $ go install 97 | ``` 98 | 99 | Finally run our new command: 100 | 101 | ``` 102 | $ greet 103 | Hello friend! 104 | ``` 105 | 106 | `cli.go` also generates neat help text: 107 | 108 | ``` 109 | $ greet help 110 | NAME: 111 | greet - fight the loneliness! 112 | 113 | USAGE: 114 | greet [global options] command [command options] [arguments...] 115 | 116 | VERSION: 117 | 0.0.0 118 | 119 | COMMANDS: 120 | help, h Shows a list of commands or help for one command 121 | 122 | GLOBAL OPTIONS 123 | --version Shows version information 124 | ``` 125 | 126 | ### Arguments 127 | You can lookup arguments by calling the `Args` function on `cli.Context`. 128 | 129 | ``` go 130 | ... 131 | app.Action = func(c *cli.Context) { 132 | println("Hello", c.Args()[0]) 133 | } 134 | ... 135 | ``` 136 | 137 | ### Flags 138 | Setting and querying flags is simple. 139 | ``` go 140 | ... 141 | app.Flags = []cli.Flag { 142 | cli.StringFlag{ 143 | Name: "lang", 144 | Value: "english", 145 | Usage: "language for the greeting", 146 | }, 147 | } 148 | app.Action = func(c *cli.Context) { 149 | name := "someone" 150 | if len(c.Args()) > 0 { 151 | name = c.Args()[0] 152 | } 153 | if c.String("lang") == "spanish" { 154 | println("Hola", name) 155 | } else { 156 | println("Hello", name) 157 | } 158 | } 159 | ... 160 | ``` 161 | 162 | See full list of flags at http://godoc.org/github.com/codegangsta/cli 163 | 164 | #### Alternate Names 165 | 166 | You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g. 167 | 168 | ``` go 169 | app.Flags = []cli.Flag { 170 | cli.StringFlag{ 171 | Name: "lang, l", 172 | Value: "english", 173 | Usage: "language for the greeting", 174 | }, 175 | } 176 | ``` 177 | 178 | That flag can then be set with `--lang spanish` or `-l spanish`. Note that giving two different forms of the same flag in the same command invocation is an error. 179 | 180 | #### Values from the Environment 181 | 182 | You can also have the default value set from the environment via `EnvVar`. e.g. 183 | 184 | ``` go 185 | app.Flags = []cli.Flag { 186 | cli.StringFlag{ 187 | Name: "lang, l", 188 | Value: "english", 189 | Usage: "language for the greeting", 190 | EnvVar: "APP_LANG", 191 | }, 192 | } 193 | ``` 194 | 195 | The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default. 196 | 197 | ``` go 198 | app.Flags = []cli.Flag { 199 | cli.StringFlag{ 200 | Name: "lang, l", 201 | Value: "english", 202 | Usage: "language for the greeting", 203 | EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG", 204 | }, 205 | } 206 | ``` 207 | 208 | ### Subcommands 209 | 210 | Subcommands can be defined for a more git-like command line app. 211 | ```go 212 | ... 213 | app.Commands = []cli.Command{ 214 | { 215 | Name: "add", 216 | Aliases: []string{"a"}, 217 | Usage: "add a task to the list", 218 | Action: func(c *cli.Context) { 219 | println("added task: ", c.Args().First()) 220 | }, 221 | }, 222 | { 223 | Name: "complete", 224 | Aliases: []string{"c"}, 225 | Usage: "complete a task on the list", 226 | Action: func(c *cli.Context) { 227 | println("completed task: ", c.Args().First()) 228 | }, 229 | }, 230 | { 231 | Name: "template", 232 | Aliases: []string{"r"}, 233 | Usage: "options for task templates", 234 | Subcommands: []cli.Command{ 235 | { 236 | Name: "add", 237 | Usage: "add a new template", 238 | Action: func(c *cli.Context) { 239 | println("new task template: ", c.Args().First()) 240 | }, 241 | }, 242 | { 243 | Name: "remove", 244 | Usage: "remove an existing template", 245 | Action: func(c *cli.Context) { 246 | println("removed task template: ", c.Args().First()) 247 | }, 248 | }, 249 | }, 250 | }, 251 | } 252 | ... 253 | ``` 254 | 255 | ### Bash Completion 256 | 257 | You can enable completion commands by setting the `EnableBashCompletion` 258 | flag on the `App` object. By default, this setting will only auto-complete to 259 | show an app's subcommands, but you can write your own completion methods for 260 | the App or its subcommands. 261 | ```go 262 | ... 263 | var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"} 264 | app := cli.NewApp() 265 | app.EnableBashCompletion = true 266 | app.Commands = []cli.Command{ 267 | { 268 | Name: "complete", 269 | Aliases: []string{"c"}, 270 | Usage: "complete a task on the list", 271 | Action: func(c *cli.Context) { 272 | println("completed task: ", c.Args().First()) 273 | }, 274 | BashComplete: func(c *cli.Context) { 275 | // This will complete if no args are passed 276 | if len(c.Args()) > 0 { 277 | return 278 | } 279 | for _, t := range tasks { 280 | fmt.Println(t) 281 | } 282 | }, 283 | } 284 | } 285 | ... 286 | ``` 287 | 288 | #### To Enable 289 | 290 | Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while 291 | setting the `PROG` variable to the name of your program: 292 | 293 | `PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` 294 | 295 | #### To Distribute 296 | 297 | Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename 298 | it to the name of the program you wish to add autocomplete support for (or 299 | automatically install it there if you are distributing a package). Don't forget 300 | to source the file to make it active in the current shell. 301 | 302 | ``` 303 | sudo cp src/bash_autocomplete /etc/bash_completion.d/ 304 | source /etc/bash_completion.d/ 305 | ``` 306 | 307 | Alternatively, you can just document that users should source the generic 308 | `autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set 309 | to the name of their program (as above). 310 | 311 | ## Contribution Guidelines 312 | Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch. 313 | 314 | If you have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together. 315 | 316 | If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out. 317 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/cli/app.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "os" 8 | "time" 9 | ) 10 | 11 | // App is the main structure of a cli application. It is recomended that 12 | // an app be created with the cli.NewApp() function 13 | type App struct { 14 | // The name of the program. Defaults to os.Args[0] 15 | Name string 16 | // Full name of command for help, defaults to Name 17 | HelpName string 18 | // Description of the program. 19 | Usage string 20 | // Description of the program argument format. 21 | ArgsUsage string 22 | // Version of the program 23 | Version string 24 | // List of commands to execute 25 | Commands []Command 26 | // List of flags to parse 27 | Flags []Flag 28 | // Boolean to enable bash completion commands 29 | EnableBashCompletion bool 30 | // Boolean to hide built-in help command 31 | HideHelp bool 32 | // Boolean to hide built-in version flag 33 | HideVersion bool 34 | // An action to execute when the bash-completion flag is set 35 | BashComplete func(context *Context) 36 | // An action to execute before any subcommands are run, but after the context is ready 37 | // If a non-nil error is returned, no subcommands are run 38 | Before func(context *Context) error 39 | // An action to execute after any subcommands are run, but after the subcommand has finished 40 | // It is run even if Action() panics 41 | After func(context *Context) error 42 | // The action to execute when no subcommands are specified 43 | Action func(context *Context) 44 | // Execute this function if the proper command cannot be found 45 | CommandNotFound func(context *Context, command string) 46 | // Compilation date 47 | Compiled time.Time 48 | // List of all authors who contributed 49 | Authors []Author 50 | // Copyright of the binary if any 51 | Copyright string 52 | // Name of Author (Note: Use App.Authors, this is deprecated) 53 | Author string 54 | // Email of Author (Note: Use App.Authors, this is deprecated) 55 | Email string 56 | // Writer writer to write output to 57 | Writer io.Writer 58 | } 59 | 60 | // Tries to find out when this binary was compiled. 61 | // Returns the current time if it fails to find it. 62 | func compileTime() time.Time { 63 | info, err := os.Stat(os.Args[0]) 64 | if err != nil { 65 | return time.Now() 66 | } 67 | return info.ModTime() 68 | } 69 | 70 | // Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action. 71 | func NewApp() *App { 72 | return &App{ 73 | Name: os.Args[0], 74 | HelpName: os.Args[0], 75 | Usage: "A new cli application", 76 | Version: "0.0.0", 77 | BashComplete: DefaultAppComplete, 78 | Action: helpCommand.Action, 79 | Compiled: compileTime(), 80 | Writer: os.Stdout, 81 | } 82 | } 83 | 84 | // Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination 85 | func (a *App) Run(arguments []string) (err error) { 86 | if a.Author != "" || a.Email != "" { 87 | a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) 88 | } 89 | 90 | newCmds := []Command{} 91 | for _, c := range a.Commands { 92 | if c.HelpName == "" { 93 | c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) 94 | } 95 | newCmds = append(newCmds, c) 96 | } 97 | a.Commands = newCmds 98 | 99 | // append help to commands 100 | if a.Command(helpCommand.Name) == nil && !a.HideHelp { 101 | a.Commands = append(a.Commands, helpCommand) 102 | if (HelpFlag != BoolFlag{}) { 103 | a.appendFlag(HelpFlag) 104 | } 105 | } 106 | 107 | //append version/help flags 108 | if a.EnableBashCompletion { 109 | a.appendFlag(BashCompletionFlag) 110 | } 111 | 112 | if !a.HideVersion { 113 | a.appendFlag(VersionFlag) 114 | } 115 | 116 | // parse flags 117 | set := flagSet(a.Name, a.Flags) 118 | set.SetOutput(ioutil.Discard) 119 | err = set.Parse(arguments[1:]) 120 | nerr := normalizeFlags(a.Flags, set) 121 | if nerr != nil { 122 | fmt.Fprintln(a.Writer, nerr) 123 | context := NewContext(a, set, nil) 124 | ShowAppHelp(context) 125 | return nerr 126 | } 127 | context := NewContext(a, set, nil) 128 | 129 | if err != nil { 130 | fmt.Fprintln(a.Writer, "Incorrect Usage.") 131 | fmt.Fprintln(a.Writer) 132 | ShowAppHelp(context) 133 | return err 134 | } 135 | 136 | if checkCompletions(context) { 137 | return nil 138 | } 139 | 140 | if !a.HideHelp && checkHelp(context) { 141 | ShowAppHelp(context) 142 | return nil 143 | } 144 | 145 | if !a.HideVersion && checkVersion(context) { 146 | ShowVersion(context) 147 | return nil 148 | } 149 | 150 | if a.After != nil { 151 | defer func() { 152 | afterErr := a.After(context) 153 | if afterErr != nil { 154 | if err != nil { 155 | err = NewMultiError(err, afterErr) 156 | } else { 157 | err = afterErr 158 | } 159 | } 160 | }() 161 | } 162 | 163 | if a.Before != nil { 164 | err := a.Before(context) 165 | if err != nil { 166 | return err 167 | } 168 | } 169 | 170 | args := context.Args() 171 | if args.Present() { 172 | name := args.First() 173 | c := a.Command(name) 174 | if c != nil { 175 | return c.Run(context) 176 | } 177 | } 178 | 179 | // Run default Action 180 | a.Action(context) 181 | return nil 182 | } 183 | 184 | // Another entry point to the cli app, takes care of passing arguments and error handling 185 | func (a *App) RunAndExitOnError() { 186 | if err := a.Run(os.Args); err != nil { 187 | fmt.Fprintln(os.Stderr, err) 188 | os.Exit(1) 189 | } 190 | } 191 | 192 | // Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags 193 | func (a *App) RunAsSubcommand(ctx *Context) (err error) { 194 | // append help to commands 195 | if len(a.Commands) > 0 { 196 | if a.Command(helpCommand.Name) == nil && !a.HideHelp { 197 | a.Commands = append(a.Commands, helpCommand) 198 | if (HelpFlag != BoolFlag{}) { 199 | a.appendFlag(HelpFlag) 200 | } 201 | } 202 | } 203 | 204 | newCmds := []Command{} 205 | for _, c := range a.Commands { 206 | if c.HelpName == "" { 207 | c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) 208 | } 209 | newCmds = append(newCmds, c) 210 | } 211 | a.Commands = newCmds 212 | 213 | // append flags 214 | if a.EnableBashCompletion { 215 | a.appendFlag(BashCompletionFlag) 216 | } 217 | 218 | // parse flags 219 | set := flagSet(a.Name, a.Flags) 220 | set.SetOutput(ioutil.Discard) 221 | err = set.Parse(ctx.Args().Tail()) 222 | nerr := normalizeFlags(a.Flags, set) 223 | context := NewContext(a, set, ctx) 224 | 225 | if nerr != nil { 226 | fmt.Fprintln(a.Writer, nerr) 227 | fmt.Fprintln(a.Writer) 228 | if len(a.Commands) > 0 { 229 | ShowSubcommandHelp(context) 230 | } else { 231 | ShowCommandHelp(ctx, context.Args().First()) 232 | } 233 | return nerr 234 | } 235 | 236 | if err != nil { 237 | fmt.Fprintln(a.Writer, "Incorrect Usage.") 238 | fmt.Fprintln(a.Writer) 239 | ShowSubcommandHelp(context) 240 | return err 241 | } 242 | 243 | if checkCompletions(context) { 244 | return nil 245 | } 246 | 247 | if len(a.Commands) > 0 { 248 | if checkSubcommandHelp(context) { 249 | return nil 250 | } 251 | } else { 252 | if checkCommandHelp(ctx, context.Args().First()) { 253 | return nil 254 | } 255 | } 256 | 257 | if a.After != nil { 258 | defer func() { 259 | afterErr := a.After(context) 260 | if afterErr != nil { 261 | if err != nil { 262 | err = NewMultiError(err, afterErr) 263 | } else { 264 | err = afterErr 265 | } 266 | } 267 | }() 268 | } 269 | 270 | if a.Before != nil { 271 | err := a.Before(context) 272 | if err != nil { 273 | return err 274 | } 275 | } 276 | 277 | args := context.Args() 278 | if args.Present() { 279 | name := args.First() 280 | c := a.Command(name) 281 | if c != nil { 282 | return c.Run(context) 283 | } 284 | } 285 | 286 | // Run default Action 287 | a.Action(context) 288 | 289 | return nil 290 | } 291 | 292 | // Returns the named command on App. Returns nil if the command does not exist 293 | func (a *App) Command(name string) *Command { 294 | for _, c := range a.Commands { 295 | if c.HasName(name) { 296 | return &c 297 | } 298 | } 299 | 300 | return nil 301 | } 302 | 303 | func (a *App) hasFlag(flag Flag) bool { 304 | for _, f := range a.Flags { 305 | if flag == f { 306 | return true 307 | } 308 | } 309 | 310 | return false 311 | } 312 | 313 | func (a *App) appendFlag(flag Flag) { 314 | if !a.hasFlag(flag) { 315 | a.Flags = append(a.Flags, flag) 316 | } 317 | } 318 | 319 | // Author represents someone who has contributed to a cli project. 320 | type Author struct { 321 | Name string // The Authors name 322 | Email string // The Authors email 323 | } 324 | 325 | // String makes Author comply to the Stringer interface, to allow an easy print in the templating process 326 | func (a Author) String() string { 327 | e := "" 328 | if a.Email != "" { 329 | e = "<" + a.Email + "> " 330 | } 331 | 332 | return fmt.Sprintf("%v %v", a.Name, e) 333 | } 334 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | : ${PROG:=$(basename ${BASH_SOURCE})} 4 | 5 | _cli_bash_autocomplete() { 6 | local cur prev opts base 7 | COMPREPLY=() 8 | cur="${COMP_WORDS[COMP_CWORD]}" 9 | prev="${COMP_WORDS[COMP_CWORD-1]}" 10 | opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) 11 | COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) 12 | return 0 13 | } 14 | 15 | complete -F _cli_bash_autocomplete $PROG -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/zsh_autocomplete: -------------------------------------------------------------------------------- 1 | autoload -U compinit && compinit 2 | autoload -U bashcompinit && bashcompinit 3 | 4 | script_dir=$(dirname $0) 5 | source ${script_dir}/bash_autocomplete 6 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/cli/cli.go: -------------------------------------------------------------------------------- 1 | // Package cli provides a minimal framework for creating and organizing command line 2 | // Go applications. cli is designed to be easy to understand and write, the most simple 3 | // cli application can be written as follows: 4 | // func main() { 5 | // cli.NewApp().Run(os.Args) 6 | // } 7 | // 8 | // Of course this application does not do much, so let's make this an actual application: 9 | // func main() { 10 | // app := cli.NewApp() 11 | // app.Name = "greet" 12 | // app.Usage = "say a greeting" 13 | // app.Action = func(c *cli.Context) { 14 | // println("Greetings") 15 | // } 16 | // 17 | // app.Run(os.Args) 18 | // } 19 | package cli 20 | 21 | import ( 22 | "strings" 23 | ) 24 | 25 | type MultiError struct { 26 | Errors []error 27 | } 28 | 29 | func NewMultiError(err ...error) MultiError { 30 | return MultiError{Errors: err} 31 | } 32 | 33 | func (m MultiError) Error() string { 34 | errs := make([]string, len(m.Errors)) 35 | for i, err := range m.Errors { 36 | errs[i] = err.Error() 37 | } 38 | 39 | return strings.Join(errs, "\n") 40 | } 41 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/cli/command.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "strings" 7 | ) 8 | 9 | // Command is a subcommand for a cli.App. 10 | type Command struct { 11 | // The name of the command 12 | Name string 13 | // short name of the command. Typically one character (deprecated, use `Aliases`) 14 | ShortName string 15 | // A list of aliases for the command 16 | Aliases []string 17 | // A short description of the usage of this command 18 | Usage string 19 | // A longer explanation of how the command works 20 | Description string 21 | // A short description of the arguments of this command 22 | ArgsUsage string 23 | // The function to call when checking for bash command completions 24 | BashComplete func(context *Context) 25 | // An action to execute before any sub-subcommands are run, but after the context is ready 26 | // If a non-nil error is returned, no sub-subcommands are run 27 | Before func(context *Context) error 28 | // An action to execute after any subcommands are run, but after the subcommand has finished 29 | // It is run even if Action() panics 30 | After func(context *Context) error 31 | // The function to call when this command is invoked 32 | Action func(context *Context) 33 | // List of child commands 34 | Subcommands []Command 35 | // List of flags to parse 36 | Flags []Flag 37 | // Treat all flags as normal arguments if true 38 | SkipFlagParsing bool 39 | // Boolean to hide built-in help command 40 | HideHelp bool 41 | 42 | // Full name of command for help, defaults to full command name, including parent commands. 43 | HelpName string 44 | commandNamePath []string 45 | } 46 | 47 | // Returns the full name of the command. 48 | // For subcommands this ensures that parent commands are part of the command path 49 | func (c Command) FullName() string { 50 | if c.commandNamePath == nil { 51 | return c.Name 52 | } 53 | return strings.Join(c.commandNamePath, " ") 54 | } 55 | 56 | // Invokes the command given the context, parses ctx.Args() to generate command-specific flags 57 | func (c Command) Run(ctx *Context) error { 58 | if len(c.Subcommands) > 0 || c.Before != nil || c.After != nil { 59 | return c.startApp(ctx) 60 | } 61 | 62 | if !c.HideHelp && (HelpFlag != BoolFlag{}) { 63 | // append help to flags 64 | c.Flags = append( 65 | c.Flags, 66 | HelpFlag, 67 | ) 68 | } 69 | 70 | if ctx.App.EnableBashCompletion { 71 | c.Flags = append(c.Flags, BashCompletionFlag) 72 | } 73 | 74 | set := flagSet(c.Name, c.Flags) 75 | set.SetOutput(ioutil.Discard) 76 | 77 | firstFlagIndex := -1 78 | terminatorIndex := -1 79 | for index, arg := range ctx.Args() { 80 | if arg == "--" { 81 | terminatorIndex = index 82 | break 83 | } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { 84 | firstFlagIndex = index 85 | } 86 | } 87 | 88 | var err error 89 | if firstFlagIndex > -1 && !c.SkipFlagParsing { 90 | args := ctx.Args() 91 | regularArgs := make([]string, len(args[1:firstFlagIndex])) 92 | copy(regularArgs, args[1:firstFlagIndex]) 93 | 94 | var flagArgs []string 95 | if terminatorIndex > -1 { 96 | flagArgs = args[firstFlagIndex:terminatorIndex] 97 | regularArgs = append(regularArgs, args[terminatorIndex:]...) 98 | } else { 99 | flagArgs = args[firstFlagIndex:] 100 | } 101 | 102 | err = set.Parse(append(flagArgs, regularArgs...)) 103 | } else { 104 | err = set.Parse(ctx.Args().Tail()) 105 | } 106 | 107 | if err != nil { 108 | fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") 109 | fmt.Fprintln(ctx.App.Writer) 110 | ShowCommandHelp(ctx, c.Name) 111 | return err 112 | } 113 | 114 | nerr := normalizeFlags(c.Flags, set) 115 | if nerr != nil { 116 | fmt.Fprintln(ctx.App.Writer, nerr) 117 | fmt.Fprintln(ctx.App.Writer) 118 | ShowCommandHelp(ctx, c.Name) 119 | return nerr 120 | } 121 | context := NewContext(ctx.App, set, ctx) 122 | 123 | if checkCommandCompletions(context, c.Name) { 124 | return nil 125 | } 126 | 127 | if checkCommandHelp(context, c.Name) { 128 | return nil 129 | } 130 | context.Command = c 131 | c.Action(context) 132 | return nil 133 | } 134 | 135 | func (c Command) Names() []string { 136 | names := []string{c.Name} 137 | 138 | if c.ShortName != "" { 139 | names = append(names, c.ShortName) 140 | } 141 | 142 | return append(names, c.Aliases...) 143 | } 144 | 145 | // Returns true if Command.Name or Command.ShortName matches given name 146 | func (c Command) HasName(name string) bool { 147 | for _, n := range c.Names() { 148 | if n == name { 149 | return true 150 | } 151 | } 152 | return false 153 | } 154 | 155 | func (c Command) startApp(ctx *Context) error { 156 | app := NewApp() 157 | 158 | // set the name and usage 159 | app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) 160 | if c.HelpName == "" { 161 | app.HelpName = c.HelpName 162 | } else { 163 | app.HelpName = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) 164 | } 165 | 166 | if c.Description != "" { 167 | app.Usage = c.Description 168 | } else { 169 | app.Usage = c.Usage 170 | } 171 | 172 | // set CommandNotFound 173 | app.CommandNotFound = ctx.App.CommandNotFound 174 | 175 | // set the flags and commands 176 | app.Commands = c.Subcommands 177 | app.Flags = c.Flags 178 | app.HideHelp = c.HideHelp 179 | 180 | app.Version = ctx.App.Version 181 | app.HideVersion = ctx.App.HideVersion 182 | app.Compiled = ctx.App.Compiled 183 | app.Author = ctx.App.Author 184 | app.Email = ctx.App.Email 185 | app.Writer = ctx.App.Writer 186 | 187 | // bash completion 188 | app.EnableBashCompletion = ctx.App.EnableBashCompletion 189 | if c.BashComplete != nil { 190 | app.BashComplete = c.BashComplete 191 | } 192 | 193 | // set the actions 194 | app.Before = c.Before 195 | app.After = c.After 196 | if c.Action != nil { 197 | app.Action = c.Action 198 | } else { 199 | app.Action = helpSubcommand.Action 200 | } 201 | 202 | var newCmds []Command 203 | for _, cc := range app.Commands { 204 | cc.commandNamePath = []string{c.Name, cc.Name} 205 | newCmds = append(newCmds, cc) 206 | } 207 | app.Commands = newCmds 208 | 209 | return app.RunAsSubcommand(ctx) 210 | } 211 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/codegangsta/cli/help.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | "text/tabwriter" 8 | "text/template" 9 | ) 10 | 11 | // The text template for the Default help topic. 12 | // cli.go uses text/template to render templates. You can 13 | // render custom help text by setting this variable. 14 | var AppHelpTemplate = `NAME: 15 | {{.Name}} - {{.Usage}} 16 | 17 | USAGE: 18 | {{.HelpName}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} 19 | {{if .Version}} 20 | VERSION: 21 | {{.Version}} 22 | {{end}}{{if len .Authors}} 23 | AUTHOR(S): 24 | {{range .Authors}}{{ . }}{{end}} 25 | {{end}}{{if .Commands}} 26 | COMMANDS: 27 | {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} 28 | {{end}}{{end}}{{if .Flags}} 29 | GLOBAL OPTIONS: 30 | {{range .Flags}}{{.}} 31 | {{end}}{{end}}{{if .Copyright }} 32 | COPYRIGHT: 33 | {{.Copyright}} 34 | {{end}} 35 | ` 36 | 37 | // The text template for the command help topic. 38 | // cli.go uses text/template to render templates. You can 39 | // render custom help text by setting this variable. 40 | var CommandHelpTemplate = `NAME: 41 | {{.HelpName}} - {{.Usage}} 42 | 43 | USAGE: 44 | {{.HelpName}}{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Description}} 45 | 46 | DESCRIPTION: 47 | {{.Description}}{{end}}{{if .Flags}} 48 | 49 | OPTIONS: 50 | {{range .Flags}}{{.}} 51 | {{end}}{{ end }} 52 | ` 53 | 54 | // The text template for the subcommand help topic. 55 | // cli.go uses text/template to render templates. You can 56 | // render custom help text by setting this variable. 57 | var SubcommandHelpTemplate = `NAME: 58 | {{.HelpName}} - {{.Usage}} 59 | 60 | USAGE: 61 | {{.HelpName}} command{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} 62 | 63 | COMMANDS: 64 | {{range .Commands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} 65 | {{end}}{{if .Flags}} 66 | OPTIONS: 67 | {{range .Flags}}{{.}} 68 | {{end}}{{end}} 69 | ` 70 | 71 | var helpCommand = Command{ 72 | Name: "help", 73 | Aliases: []string{"h"}, 74 | Usage: "Shows a list of commands or help for one command", 75 | ArgsUsage: "[command]", 76 | Action: func(c *Context) { 77 | args := c.Args() 78 | if args.Present() { 79 | ShowCommandHelp(c, args.First()) 80 | } else { 81 | ShowAppHelp(c) 82 | } 83 | }, 84 | } 85 | 86 | var helpSubcommand = Command{ 87 | Name: "help", 88 | Aliases: []string{"h"}, 89 | Usage: "Shows a list of commands or help for one command", 90 | ArgsUsage: "[command]", 91 | Action: func(c *Context) { 92 | args := c.Args() 93 | if args.Present() { 94 | ShowCommandHelp(c, args.First()) 95 | } else { 96 | ShowSubcommandHelp(c) 97 | } 98 | }, 99 | } 100 | 101 | // Prints help for the App or Command 102 | type helpPrinter func(w io.Writer, templ string, data interface{}) 103 | 104 | var HelpPrinter helpPrinter = printHelp 105 | 106 | // Prints version for the App 107 | var VersionPrinter = printVersion 108 | 109 | func ShowAppHelp(c *Context) { 110 | HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) 111 | } 112 | 113 | // Prints the list of subcommands as the default app completion method 114 | func DefaultAppComplete(c *Context) { 115 | for _, command := range c.App.Commands { 116 | for _, name := range command.Names() { 117 | fmt.Fprintln(c.App.Writer, name) 118 | } 119 | } 120 | } 121 | 122 | // Prints help for the given command 123 | func ShowCommandHelp(ctx *Context, command string) { 124 | // show the subcommand help for a command with subcommands 125 | if command == "" { 126 | HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App) 127 | return 128 | } 129 | 130 | for _, c := range ctx.App.Commands { 131 | if c.HasName(command) { 132 | HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c) 133 | return 134 | } 135 | } 136 | 137 | if ctx.App.CommandNotFound != nil { 138 | ctx.App.CommandNotFound(ctx, command) 139 | } else { 140 | fmt.Fprintf(ctx.App.Writer, "No help topic for '%v'\n", command) 141 | } 142 | } 143 | 144 | // Prints help for the given subcommand 145 | func ShowSubcommandHelp(c *Context) { 146 | ShowCommandHelp(c, c.Command.Name) 147 | } 148 | 149 | // Prints the version number of the App 150 | func ShowVersion(c *Context) { 151 | VersionPrinter(c) 152 | } 153 | 154 | func printVersion(c *Context) { 155 | fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version) 156 | } 157 | 158 | // Prints the lists of commands within a given context 159 | func ShowCompletions(c *Context) { 160 | a := c.App 161 | if a != nil && a.BashComplete != nil { 162 | a.BashComplete(c) 163 | } 164 | } 165 | 166 | // Prints the custom completions for a given command 167 | func ShowCommandCompletions(ctx *Context, command string) { 168 | c := ctx.App.Command(command) 169 | if c != nil && c.BashComplete != nil { 170 | c.BashComplete(ctx) 171 | } 172 | } 173 | 174 | func printHelp(out io.Writer, templ string, data interface{}) { 175 | funcMap := template.FuncMap{ 176 | "join": strings.Join, 177 | } 178 | 179 | w := tabwriter.NewWriter(out, 0, 8, 1, '\t', 0) 180 | t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) 181 | err := t.Execute(w, data) 182 | if err != nil { 183 | panic(err) 184 | } 185 | w.Flush() 186 | } 187 | 188 | func checkVersion(c *Context) bool { 189 | found := false 190 | if VersionFlag.Name != "" { 191 | eachName(VersionFlag.Name, func(name string) { 192 | if c.GlobalBool(name) || c.Bool(name) { 193 | found = true 194 | } 195 | }) 196 | } 197 | return found 198 | } 199 | 200 | func checkHelp(c *Context) bool { 201 | found := false 202 | if HelpFlag.Name != "" { 203 | eachName(HelpFlag.Name, func(name string) { 204 | if c.GlobalBool(name) || c.Bool(name) { 205 | found = true 206 | } 207 | }) 208 | } 209 | return found 210 | } 211 | 212 | func checkCommandHelp(c *Context, name string) bool { 213 | if c.Bool("h") || c.Bool("help") { 214 | ShowCommandHelp(c, name) 215 | return true 216 | } 217 | 218 | return false 219 | } 220 | 221 | func checkSubcommandHelp(c *Context) bool { 222 | if c.GlobalBool("h") || c.GlobalBool("help") { 223 | ShowSubcommandHelp(c) 224 | return true 225 | } 226 | 227 | return false 228 | } 229 | 230 | func checkCompletions(c *Context) bool { 231 | if (c.GlobalBool(BashCompletionFlag.Name) || c.Bool(BashCompletionFlag.Name)) && c.App.EnableBashCompletion { 232 | ShowCompletions(c) 233 | return true 234 | } 235 | 236 | return false 237 | } 238 | 239 | func checkCommandCompletions(c *Context, name string) bool { 240 | if c.Bool(BashCompletionFlag.Name) && c.App.EnableBashCompletion { 241 | ShowCommandCompletions(c, name) 242 | return true 243 | } 244 | 245 | return false 246 | } 247 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/.gitignore: -------------------------------------------------------------------------------- 1 | *.6 2 | tags 3 | test.out 4 | a.out 5 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | go: 4 | - 1.4 5 | - 1.5 6 | script: 7 | - go test -race -v -bench=. 8 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/AUTHORS: -------------------------------------------------------------------------------- 1 | Miek Gieben 2 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Alex A. Skinner 2 | Andrew Tunnell-Jones 3 | Ask Bjørn Hansen 4 | Dave Cheney 5 | Dusty Wilson 6 | Marek Majkowski 7 | Peter van Dijk 8 | Omri Bahumi 9 | Alex Sergeyev 10 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/COPYRIGHT: -------------------------------------------------------------------------------- 1 | Copyright 2009 The Go Authors. All rights reserved. Use of this source code 2 | is governed by a BSD-style license that can be found in the LICENSE file. 3 | Extensions of the original work are copyright (c) 2011 Miek Gieben 4 | 5 | Copyright 2011 Miek Gieben. All rights reserved. Use of this source code is 6 | governed by a BSD-style license that can be found in the LICENSE file. 7 | 8 | Copyright 2014 CloudFlare. All rights reserved. Use of this source code is 9 | governed by a BSD-style license that can be found in the LICENSE file. 10 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/LICENSE: -------------------------------------------------------------------------------- 1 | Extensions of the original work are copyright (c) 2011 Miek Gieben 2 | 3 | As this is fork of the official Go code the same license applies: 4 | 5 | Copyright (c) 2009 The Go Authors. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | * Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above 14 | copyright notice, this list of conditions and the following disclaimer 15 | in the documentation and/or other materials provided with the 16 | distribution. 17 | * Neither the name of Google Inc. nor the names of its 18 | contributors may be used to endorse or promote products derived from 19 | this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | 33 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/miekg/dns.svg?branch=master)](https://travis-ci.org/miekg/dns) 2 | 3 | # Alternative (more granular) approach to a DNS library 4 | 5 | > Less is more. 6 | 7 | Complete and usable DNS library. All widely used Resource Records are 8 | supported, including the DNSSEC types. It follows a lean and mean philosophy. 9 | If there is stuff you should know as a DNS programmer there isn't a convenience 10 | function for it. Server side and client side programming is supported, i.e. you 11 | can build servers and resolvers with it. 12 | 13 | If you like this, you may also be interested in: 14 | 15 | * https://github.com/miekg/unbound -- Go wrapper for the Unbound resolver. 16 | 17 | # Goals 18 | 19 | * KISS; 20 | * Fast; 21 | * Small API, if its easy to code in Go, don't make a function for it. 22 | 23 | # Users 24 | 25 | A not-so-up-to-date-list-that-may-be-actually-current: 26 | 27 | * https://cloudflare.com 28 | * https://github.com/abh/geodns 29 | * http://www.statdns.com/ 30 | * http://www.dnsinspect.com/ 31 | * https://github.com/chuangbo/jianbing-dictionary-dns 32 | * http://www.dns-lg.com/ 33 | * https://github.com/fcambus/rrda 34 | * https://github.com/kenshinx/godns 35 | * https://github.com/skynetservices/skydns 36 | * https://github.com/DevelopersPL/godnsagent 37 | * https://github.com/duedil-ltd/discodns 38 | * https://github.com/StalkR/dns-reverse-proxy 39 | * https://github.com/tianon/rawdns 40 | * https://mesosphere.github.io/mesos-dns/ 41 | * https://pulse.turbobytes.com/ 42 | * https://play.google.com/store/apps/details?id=com.turbobytes.dig 43 | * https://github.com/fcambus/statzone 44 | * https://github.com/benschw/dns-clb-go 45 | * https://github.com/corny/dnscheck for http://public-dns.tk/ 46 | * https://namesmith.io 47 | 48 | Send pull request if you want to be listed here. 49 | 50 | # Features 51 | 52 | * UDP/TCP queries, IPv4 and IPv6; 53 | * RFC 1035 zone file parsing ($INCLUDE, $ORIGIN, $TTL and $GENERATE (for all record types) are supported; 54 | * Fast: 55 | * Reply speed around ~ 80K qps (faster hardware results in more qps); 56 | * Parsing RRs ~ 100K RR/s, that's 5M records in about 50 seconds; 57 | * Server side programming (mimicking the net/http package); 58 | * Client side programming; 59 | * DNSSEC: signing, validating and key generation for DSA, RSA and ECDSA; 60 | * EDNS0, NSID; 61 | * AXFR/IXFR; 62 | * TSIG, SIG(0); 63 | * DNS name compression; 64 | * Depends only on the standard library. 65 | 66 | Have fun! 67 | 68 | Miek Gieben - 2010-2012 - 69 | 70 | # Building 71 | 72 | Building is done with the `go` tool. If you have setup your GOPATH 73 | correctly, the following should work: 74 | 75 | go get github.com/miekg/dns 76 | go build github.com/miekg/dns 77 | 78 | ## Examples 79 | 80 | A short "how to use the API" is at the beginning of doc.go (this also will show 81 | when you call `godoc github.com/miekg/dns`). 82 | 83 | Example programs can be found in the `github.com/miekg/exdns` repository. 84 | 85 | ## Supported RFCs 86 | 87 | *all of them* 88 | 89 | * 103{4,5} - DNS standard 90 | * 1348 - NSAP record (removed the record) 91 | * 1982 - Serial Arithmetic 92 | * 1876 - LOC record 93 | * 1995 - IXFR 94 | * 1996 - DNS notify 95 | * 2136 - DNS Update (dynamic updates) 96 | * 2181 - RRset definition - there is no RRset type though, just []RR 97 | * 2537 - RSAMD5 DNS keys 98 | * 2065 - DNSSEC (updated in later RFCs) 99 | * 2671 - EDNS record 100 | * 2782 - SRV record 101 | * 2845 - TSIG record 102 | * 2915 - NAPTR record 103 | * 2929 - DNS IANA Considerations 104 | * 3110 - RSASHA1 DNS keys 105 | * 3225 - DO bit (DNSSEC OK) 106 | * 340{1,2,3} - NAPTR record 107 | * 3445 - Limiting the scope of (DNS)KEY 108 | * 3597 - Unknown RRs 109 | * 4025 - IPSECKEY 110 | * 403{3,4,5} - DNSSEC + validation functions 111 | * 4255 - SSHFP record 112 | * 4343 - Case insensitivity 113 | * 4408 - SPF record 114 | * 4509 - SHA256 Hash in DS 115 | * 4592 - Wildcards in the DNS 116 | * 4635 - HMAC SHA TSIG 117 | * 4701 - DHCID 118 | * 4892 - id.server 119 | * 5001 - NSID 120 | * 5155 - NSEC3 record 121 | * 5205 - HIP record 122 | * 5702 - SHA2 in the DNS 123 | * 5936 - AXFR 124 | * 5966 - TCP implementation recommendations 125 | * 6605 - ECDSA 126 | * 6725 - IANA Registry Update 127 | * 6742 - ILNP DNS 128 | * 6840 - Clarifications and Implementation Notes for DNS Security 129 | * 6844 - CAA record 130 | * 6891 - EDNS0 update 131 | * 6895 - DNS IANA considerations 132 | * 6975 - Algorithm Understanding in DNSSEC 133 | * 7043 - EUI48/EUI64 records 134 | * 7314 - DNS (EDNS) EXPIRE Option 135 | * 7553 - URI record 136 | * xxxx - EDNS0 DNS Update Lease (draft) 137 | 138 | ## Loosely based upon 139 | 140 | * `ldns` 141 | * `NSD` 142 | * `Net::DNS` 143 | * `GRONG` 144 | 145 | ## TODO 146 | 147 | * privatekey.Precompute() when signing? 148 | * Last remaining RRs: APL, ATMA, A6, NSAP and NXT. 149 | * Missing in parsing: ISDN, UNSPEC, NSAP and ATMA. 150 | * NSEC(3) cover/match/closest enclose. 151 | * Replies with TC bit are not parsed to the end. 152 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/clientconfig.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "bufio" 5 | "os" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // ClientConfig wraps the contents of the /etc/resolv.conf file. 11 | type ClientConfig struct { 12 | Servers []string // servers to use 13 | Search []string // suffixes to append to local name 14 | Port string // what port to use 15 | Ndots int // number of dots in name to trigger absolute lookup 16 | Timeout int // seconds before giving up on packet 17 | Attempts int // lost packets before giving up on server, not used in the package dns 18 | } 19 | 20 | // ClientConfigFromFile parses a resolv.conf(5) like file and returns 21 | // a *ClientConfig. 22 | func ClientConfigFromFile(resolvconf string) (*ClientConfig, error) { 23 | file, err := os.Open(resolvconf) 24 | if err != nil { 25 | return nil, err 26 | } 27 | defer file.Close() 28 | c := new(ClientConfig) 29 | scanner := bufio.NewScanner(file) 30 | c.Servers = make([]string, 0) 31 | c.Search = make([]string, 0) 32 | c.Port = "53" 33 | c.Ndots = 1 34 | c.Timeout = 5 35 | c.Attempts = 2 36 | 37 | for scanner.Scan() { 38 | if err := scanner.Err(); err != nil { 39 | return nil, err 40 | } 41 | line := scanner.Text() 42 | f := strings.Fields(line) 43 | if len(f) < 1 { 44 | continue 45 | } 46 | switch f[0] { 47 | case "nameserver": // add one name server 48 | if len(f) > 1 { 49 | // One more check: make sure server name is 50 | // just an IP address. Otherwise we need DNS 51 | // to look it up. 52 | name := f[1] 53 | c.Servers = append(c.Servers, name) 54 | } 55 | 56 | case "domain": // set search path to just this domain 57 | if len(f) > 1 { 58 | c.Search = make([]string, 1) 59 | c.Search[0] = f[1] 60 | } else { 61 | c.Search = make([]string, 0) 62 | } 63 | 64 | case "search": // set search path to given servers 65 | c.Search = make([]string, len(f)-1) 66 | for i := 0; i < len(c.Search); i++ { 67 | c.Search[i] = f[i+1] 68 | } 69 | 70 | case "options": // magic options 71 | for i := 1; i < len(f); i++ { 72 | s := f[i] 73 | switch { 74 | case len(s) >= 6 && s[:6] == "ndots:": 75 | n, _ := strconv.Atoi(s[6:]) 76 | if n < 1 { 77 | n = 1 78 | } 79 | c.Ndots = n 80 | case len(s) >= 8 && s[:8] == "timeout:": 81 | n, _ := strconv.Atoi(s[8:]) 82 | if n < 1 { 83 | n = 1 84 | } 85 | c.Timeout = n 86 | case len(s) >= 8 && s[:9] == "attempts:": 87 | n, _ := strconv.Atoi(s[9:]) 88 | if n < 1 { 89 | n = 1 90 | } 91 | c.Attempts = n 92 | case s == "rotate": 93 | /* not imp */ 94 | } 95 | } 96 | } 97 | } 98 | return c, nil 99 | } 100 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/defaults.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "strconv" 7 | ) 8 | 9 | const hexDigit = "0123456789abcdef" 10 | 11 | // Everything is assumed in ClassINET. 12 | 13 | // SetReply creates a reply message from a request message. 14 | func (dns *Msg) SetReply(request *Msg) *Msg { 15 | dns.Id = request.Id 16 | dns.RecursionDesired = request.RecursionDesired // Copy rd bit 17 | dns.Response = true 18 | dns.Opcode = OpcodeQuery 19 | dns.Rcode = RcodeSuccess 20 | if len(request.Question) > 0 { 21 | dns.Question = make([]Question, 1) 22 | dns.Question[0] = request.Question[0] 23 | } 24 | return dns 25 | } 26 | 27 | // SetQuestion creates a question message, it sets the Question 28 | // section, generates an Id and sets the RecursionDesired (RD) 29 | // bit to true. 30 | func (dns *Msg) SetQuestion(z string, t uint16) *Msg { 31 | dns.Id = Id() 32 | dns.RecursionDesired = true 33 | dns.Question = make([]Question, 1) 34 | dns.Question[0] = Question{z, t, ClassINET} 35 | return dns 36 | } 37 | 38 | // SetNotify creates a notify message, it sets the Question 39 | // section, generates an Id and sets the Authoritative (AA) 40 | // bit to true. 41 | func (dns *Msg) SetNotify(z string) *Msg { 42 | dns.Opcode = OpcodeNotify 43 | dns.Authoritative = true 44 | dns.Id = Id() 45 | dns.Question = make([]Question, 1) 46 | dns.Question[0] = Question{z, TypeSOA, ClassINET} 47 | return dns 48 | } 49 | 50 | // SetRcode creates an error message suitable for the request. 51 | func (dns *Msg) SetRcode(request *Msg, rcode int) *Msg { 52 | dns.SetReply(request) 53 | dns.Rcode = rcode 54 | return dns 55 | } 56 | 57 | // SetRcodeFormatError creates a message with FormError set. 58 | func (dns *Msg) SetRcodeFormatError(request *Msg) *Msg { 59 | dns.Rcode = RcodeFormatError 60 | dns.Opcode = OpcodeQuery 61 | dns.Response = true 62 | dns.Authoritative = false 63 | dns.Id = request.Id 64 | return dns 65 | } 66 | 67 | // SetUpdate makes the message a dynamic update message. It 68 | // sets the ZONE section to: z, TypeSOA, ClassINET. 69 | func (dns *Msg) SetUpdate(z string) *Msg { 70 | dns.Id = Id() 71 | dns.Response = false 72 | dns.Opcode = OpcodeUpdate 73 | dns.Compress = false // BIND9 cannot handle compression 74 | dns.Question = make([]Question, 1) 75 | dns.Question[0] = Question{z, TypeSOA, ClassINET} 76 | return dns 77 | } 78 | 79 | // SetIxfr creates message for requesting an IXFR. 80 | func (dns *Msg) SetIxfr(z string, serial uint32, ns, mbox string) *Msg { 81 | dns.Id = Id() 82 | dns.Question = make([]Question, 1) 83 | dns.Ns = make([]RR, 1) 84 | s := new(SOA) 85 | s.Hdr = RR_Header{z, TypeSOA, ClassINET, defaultTtl, 0} 86 | s.Serial = serial 87 | s.Ns = ns 88 | s.Mbox = mbox 89 | dns.Question[0] = Question{z, TypeIXFR, ClassINET} 90 | dns.Ns[0] = s 91 | return dns 92 | } 93 | 94 | // SetAxfr creates message for requesting an AXFR. 95 | func (dns *Msg) SetAxfr(z string) *Msg { 96 | dns.Id = Id() 97 | dns.Question = make([]Question, 1) 98 | dns.Question[0] = Question{z, TypeAXFR, ClassINET} 99 | return dns 100 | } 101 | 102 | // SetTsig appends a TSIG RR to the message. 103 | // This is only a skeleton TSIG RR that is added as the last RR in the 104 | // additional section. The Tsig is calculated when the message is being send. 105 | func (dns *Msg) SetTsig(z, algo string, fudge, timesigned int64) *Msg { 106 | t := new(TSIG) 107 | t.Hdr = RR_Header{z, TypeTSIG, ClassANY, 0, 0} 108 | t.Algorithm = algo 109 | t.Fudge = 300 110 | t.TimeSigned = uint64(timesigned) 111 | t.OrigId = dns.Id 112 | dns.Extra = append(dns.Extra, t) 113 | return dns 114 | } 115 | 116 | // SetEdns0 appends a EDNS0 OPT RR to the message. 117 | // TSIG should always the last RR in a message. 118 | func (dns *Msg) SetEdns0(udpsize uint16, do bool) *Msg { 119 | e := new(OPT) 120 | e.Hdr.Name = "." 121 | e.Hdr.Rrtype = TypeOPT 122 | e.SetUDPSize(udpsize) 123 | if do { 124 | e.SetDo() 125 | } 126 | dns.Extra = append(dns.Extra, e) 127 | return dns 128 | } 129 | 130 | // IsTsig checks if the message has a TSIG record as the last record 131 | // in the additional section. It returns the TSIG record found or nil. 132 | func (dns *Msg) IsTsig() *TSIG { 133 | if len(dns.Extra) > 0 { 134 | if dns.Extra[len(dns.Extra)-1].Header().Rrtype == TypeTSIG { 135 | return dns.Extra[len(dns.Extra)-1].(*TSIG) 136 | } 137 | } 138 | return nil 139 | } 140 | 141 | // IsEdns0 checks if the message has a EDNS0 (OPT) record, any EDNS0 142 | // record in the additional section will do. It returns the OPT record 143 | // found or nil. 144 | func (dns *Msg) IsEdns0() *OPT { 145 | for _, r := range dns.Extra { 146 | if r.Header().Rrtype == TypeOPT { 147 | return r.(*OPT) 148 | } 149 | } 150 | return nil 151 | } 152 | 153 | // IsDomainName checks if s is a valid domain name, it returns the number of 154 | // labels and true, when a domain name is valid. Note that non fully qualified 155 | // domain name is considered valid, in this case the last label is counted in 156 | // the number of labels. When false is returned the number of labels is not 157 | // defined. Also note that this function is extremely liberal; almost any 158 | // string is a valid domain name as the DNS is 8 bit protocol. It checks if each 159 | // label fits in 63 characters, but there is no length check for the entire 160 | // string s. I.e. a domain name longer than 255 characters is considered valid. 161 | func IsDomainName(s string) (labels int, ok bool) { 162 | _, labels, err := packDomainName(s, nil, 0, nil, false) 163 | return labels, err == nil 164 | } 165 | 166 | // IsSubDomain checks if child is indeed a child of the parent. Both child and 167 | // parent are *not* downcased before doing the comparison. 168 | func IsSubDomain(parent, child string) bool { 169 | // Entire child is contained in parent 170 | return CompareDomainName(parent, child) == CountLabel(parent) 171 | } 172 | 173 | // IsMsg sanity checks buf and returns an error if it isn't a valid DNS packet. 174 | // The checking is performed on the binary payload. 175 | func IsMsg(buf []byte) error { 176 | // Header 177 | if len(buf) < 12 { 178 | return errors.New("dns: bad message header") 179 | } 180 | // Header: Opcode 181 | // TODO(miek): more checks here, e.g. check all header bits. 182 | return nil 183 | } 184 | 185 | // IsFqdn checks if a domain name is fully qualified. 186 | func IsFqdn(s string) bool { 187 | l := len(s) 188 | if l == 0 { 189 | return false 190 | } 191 | return s[l-1] == '.' 192 | } 193 | 194 | // IsRRset checks if a set of RRs is a valid RRset as defined by RFC 2181. 195 | // This means the RRs need to have the same type, name, and class. Returns true 196 | // if the RR set is valid, otherwise false. 197 | func IsRRset(rrset []RR) bool { 198 | if len(rrset) == 0 { 199 | return false 200 | } 201 | if len(rrset) == 1 { 202 | return true 203 | } 204 | rrHeader := rrset[0].Header() 205 | rrType := rrHeader.Rrtype 206 | rrClass := rrHeader.Class 207 | rrName := rrHeader.Name 208 | 209 | for _, rr := range rrset[1:] { 210 | curRRHeader := rr.Header() 211 | if curRRHeader.Rrtype != rrType || curRRHeader.Class != rrClass || curRRHeader.Name != rrName { 212 | // Mismatch between the records, so this is not a valid rrset for 213 | //signing/verifying 214 | return false 215 | } 216 | } 217 | 218 | return true 219 | } 220 | 221 | // Fqdn return the fully qualified domain name from s. 222 | // If s is already fully qualified, it behaves as the identity function. 223 | func Fqdn(s string) string { 224 | if IsFqdn(s) { 225 | return s 226 | } 227 | return s + "." 228 | } 229 | 230 | // Copied from the official Go code. 231 | 232 | // ReverseAddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP 233 | // address suitable for reverse DNS (PTR) record lookups or an error if it fails 234 | // to parse the IP address. 235 | func ReverseAddr(addr string) (arpa string, err error) { 236 | ip := net.ParseIP(addr) 237 | if ip == nil { 238 | return "", &Error{err: "unrecognized address: " + addr} 239 | } 240 | if ip.To4() != nil { 241 | return strconv.Itoa(int(ip[15])) + "." + strconv.Itoa(int(ip[14])) + "." + strconv.Itoa(int(ip[13])) + "." + 242 | strconv.Itoa(int(ip[12])) + ".in-addr.arpa.", nil 243 | } 244 | // Must be IPv6 245 | buf := make([]byte, 0, len(ip)*4+len("ip6.arpa.")) 246 | // Add it, in reverse, to the buffer 247 | for i := len(ip) - 1; i >= 0; i-- { 248 | v := ip[i] 249 | buf = append(buf, hexDigit[v&0xF]) 250 | buf = append(buf, '.') 251 | buf = append(buf, hexDigit[v>>4]) 252 | buf = append(buf, '.') 253 | } 254 | // Append "ip6.arpa." and return (buf already has the final .) 255 | buf = append(buf, "ip6.arpa."...) 256 | return string(buf), nil 257 | } 258 | 259 | // String returns the string representation for the type t. 260 | func (t Type) String() string { 261 | if t1, ok := TypeToString[uint16(t)]; ok { 262 | return t1 263 | } 264 | return "TYPE" + strconv.Itoa(int(t)) 265 | } 266 | 267 | // String returns the string representation for the class c. 268 | func (c Class) String() string { 269 | if c1, ok := ClassToString[uint16(c)]; ok { 270 | return c1 271 | } 272 | return "CLASS" + strconv.Itoa(int(c)) 273 | } 274 | 275 | // String returns the string representation for the name n. 276 | func (n Name) String() string { 277 | return sprintName(string(n)) 278 | } 279 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/dns.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import "strconv" 4 | 5 | const ( 6 | year68 = 1 << 31 // For RFC1982 (Serial Arithmetic) calculations in 32 bits. 7 | // DefaultMsgSize is the standard default for messages larger than 512 bytes. 8 | DefaultMsgSize = 4096 9 | // MinMsgSize is the minimal size of a DNS packet. 10 | MinMsgSize = 512 11 | // MaxMsgSize is the largest possible DNS packet. 12 | MaxMsgSize = 65535 13 | defaultTtl = 3600 // Default internal TTL. 14 | ) 15 | 16 | // Error represents a DNS error 17 | type Error struct{ err string } 18 | 19 | func (e *Error) Error() string { 20 | if e == nil { 21 | return "dns: " 22 | } 23 | return "dns: " + e.err 24 | } 25 | 26 | // An RR represents a resource record. 27 | type RR interface { 28 | // Header returns the header of an resource record. The header contains 29 | // everything up to the rdata. 30 | Header() *RR_Header 31 | // String returns the text representation of the resource record. 32 | String() string 33 | // copy returns a copy of the RR 34 | copy() RR 35 | // len returns the length (in octets) of the uncompressed RR in wire format. 36 | len() int 37 | } 38 | 39 | // RR_Header is the header all DNS resource records share. 40 | type RR_Header struct { 41 | Name string `dns:"cdomain-name"` 42 | Rrtype uint16 43 | Class uint16 44 | Ttl uint32 45 | Rdlength uint16 // length of data after header 46 | } 47 | 48 | // Header returns itself. This is here to make RR_Header implement the RR interface. 49 | func (h *RR_Header) Header() *RR_Header { return h } 50 | 51 | // Just to imlement the RR interface. 52 | func (h *RR_Header) copy() RR { return nil } 53 | 54 | func (h *RR_Header) copyHeader() *RR_Header { 55 | r := new(RR_Header) 56 | r.Name = h.Name 57 | r.Rrtype = h.Rrtype 58 | r.Class = h.Class 59 | r.Ttl = h.Ttl 60 | r.Rdlength = h.Rdlength 61 | return r 62 | } 63 | 64 | func (h *RR_Header) String() string { 65 | var s string 66 | 67 | if h.Rrtype == TypeOPT { 68 | s = ";" 69 | // and maybe other things 70 | } 71 | 72 | s += sprintName(h.Name) + "\t" 73 | s += strconv.FormatInt(int64(h.Ttl), 10) + "\t" 74 | s += Class(h.Class).String() + "\t" 75 | s += Type(h.Rrtype).String() + "\t" 76 | return s 77 | } 78 | 79 | func (h *RR_Header) len() int { 80 | l := len(h.Name) + 1 81 | l += 10 // rrtype(2) + class(2) + ttl(4) + rdlength(2) 82 | return l 83 | } 84 | 85 | // ToRFC3597 converts a known RR to the unknown RR representation 86 | // from RFC 3597. 87 | func (rr *RFC3597) ToRFC3597(r RR) error { 88 | buf := make([]byte, r.len()*2) 89 | off, err := PackStruct(r, buf, 0) 90 | if err != nil { 91 | return err 92 | } 93 | buf = buf[:off] 94 | rawSetRdlength(buf, 0, off) 95 | _, err = UnpackStruct(rr, buf, 0) 96 | if err != nil { 97 | return err 98 | } 99 | return nil 100 | } 101 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/dnssec_keygen.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto" 5 | "crypto/dsa" 6 | "crypto/ecdsa" 7 | "crypto/elliptic" 8 | "crypto/rand" 9 | "crypto/rsa" 10 | "math/big" 11 | ) 12 | 13 | // Generate generates a DNSKEY of the given bit size. 14 | // The public part is put inside the DNSKEY record. 15 | // The Algorithm in the key must be set as this will define 16 | // what kind of DNSKEY will be generated. 17 | // The ECDSA algorithms imply a fixed keysize, in that case 18 | // bits should be set to the size of the algorithm. 19 | func (k *DNSKEY) Generate(bits int) (crypto.PrivateKey, error) { 20 | switch k.Algorithm { 21 | case DSA, DSANSEC3SHA1: 22 | if bits != 1024 { 23 | return nil, ErrKeySize 24 | } 25 | case RSAMD5, RSASHA1, RSASHA256, RSASHA1NSEC3SHA1: 26 | if bits < 512 || bits > 4096 { 27 | return nil, ErrKeySize 28 | } 29 | case RSASHA512: 30 | if bits < 1024 || bits > 4096 { 31 | return nil, ErrKeySize 32 | } 33 | case ECDSAP256SHA256: 34 | if bits != 256 { 35 | return nil, ErrKeySize 36 | } 37 | case ECDSAP384SHA384: 38 | if bits != 384 { 39 | return nil, ErrKeySize 40 | } 41 | } 42 | 43 | switch k.Algorithm { 44 | case DSA, DSANSEC3SHA1: 45 | params := new(dsa.Parameters) 46 | if err := dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160); err != nil { 47 | return nil, err 48 | } 49 | priv := new(dsa.PrivateKey) 50 | priv.PublicKey.Parameters = *params 51 | err := dsa.GenerateKey(priv, rand.Reader) 52 | if err != nil { 53 | return nil, err 54 | } 55 | k.setPublicKeyDSA(params.Q, params.P, params.G, priv.PublicKey.Y) 56 | return priv, nil 57 | case RSAMD5, RSASHA1, RSASHA256, RSASHA512, RSASHA1NSEC3SHA1: 58 | priv, err := rsa.GenerateKey(rand.Reader, bits) 59 | if err != nil { 60 | return nil, err 61 | } 62 | k.setPublicKeyRSA(priv.PublicKey.E, priv.PublicKey.N) 63 | return priv, nil 64 | case ECDSAP256SHA256, ECDSAP384SHA384: 65 | var c elliptic.Curve 66 | switch k.Algorithm { 67 | case ECDSAP256SHA256: 68 | c = elliptic.P256() 69 | case ECDSAP384SHA384: 70 | c = elliptic.P384() 71 | } 72 | priv, err := ecdsa.GenerateKey(c, rand.Reader) 73 | if err != nil { 74 | return nil, err 75 | } 76 | k.setPublicKeyECDSA(priv.PublicKey.X, priv.PublicKey.Y) 77 | return priv, nil 78 | default: 79 | return nil, ErrAlg 80 | } 81 | } 82 | 83 | // Set the public key (the value E and N) 84 | func (k *DNSKEY) setPublicKeyRSA(_E int, _N *big.Int) bool { 85 | if _E == 0 || _N == nil { 86 | return false 87 | } 88 | buf := exponentToBuf(_E) 89 | buf = append(buf, _N.Bytes()...) 90 | k.PublicKey = toBase64(buf) 91 | return true 92 | } 93 | 94 | // Set the public key for Elliptic Curves 95 | func (k *DNSKEY) setPublicKeyECDSA(_X, _Y *big.Int) bool { 96 | if _X == nil || _Y == nil { 97 | return false 98 | } 99 | var intlen int 100 | switch k.Algorithm { 101 | case ECDSAP256SHA256: 102 | intlen = 32 103 | case ECDSAP384SHA384: 104 | intlen = 48 105 | } 106 | k.PublicKey = toBase64(curveToBuf(_X, _Y, intlen)) 107 | return true 108 | } 109 | 110 | // Set the public key for DSA 111 | func (k *DNSKEY) setPublicKeyDSA(_Q, _P, _G, _Y *big.Int) bool { 112 | if _Q == nil || _P == nil || _G == nil || _Y == nil { 113 | return false 114 | } 115 | buf := dsaToBuf(_Q, _P, _G, _Y) 116 | k.PublicKey = toBase64(buf) 117 | return true 118 | } 119 | 120 | // Set the public key (the values E and N) for RSA 121 | // RFC 3110: Section 2. RSA Public KEY Resource Records 122 | func exponentToBuf(_E int) []byte { 123 | var buf []byte 124 | i := big.NewInt(int64(_E)) 125 | if len(i.Bytes()) < 256 { 126 | buf = make([]byte, 1) 127 | buf[0] = uint8(len(i.Bytes())) 128 | } else { 129 | buf = make([]byte, 3) 130 | buf[0] = 0 131 | buf[1] = uint8(len(i.Bytes()) >> 8) 132 | buf[2] = uint8(len(i.Bytes())) 133 | } 134 | buf = append(buf, i.Bytes()...) 135 | return buf 136 | } 137 | 138 | // Set the public key for X and Y for Curve. The two 139 | // values are just concatenated. 140 | func curveToBuf(_X, _Y *big.Int, intlen int) []byte { 141 | buf := intToBytes(_X, intlen) 142 | buf = append(buf, intToBytes(_Y, intlen)...) 143 | return buf 144 | } 145 | 146 | // Set the public key for X and Y for Curve. The two 147 | // values are just concatenated. 148 | func dsaToBuf(_Q, _P, _G, _Y *big.Int) []byte { 149 | t := divRoundUp(divRoundUp(_G.BitLen(), 8)-64, 8) 150 | buf := []byte{byte(t)} 151 | buf = append(buf, intToBytes(_Q, 20)...) 152 | buf = append(buf, intToBytes(_P, 64+t*8)...) 153 | buf = append(buf, intToBytes(_G, 64+t*8)...) 154 | buf = append(buf, intToBytes(_Y, 64+t*8)...) 155 | return buf 156 | } 157 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/dnssec_keyscan.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto" 5 | "crypto/dsa" 6 | "crypto/ecdsa" 7 | "crypto/rsa" 8 | "io" 9 | "math/big" 10 | "strconv" 11 | "strings" 12 | ) 13 | 14 | // NewPrivateKey returns a PrivateKey by parsing the string s. 15 | // s should be in the same form of the BIND private key files. 16 | func (k *DNSKEY) NewPrivateKey(s string) (crypto.PrivateKey, error) { 17 | if s[len(s)-1] != '\n' { // We need a closing newline 18 | return k.ReadPrivateKey(strings.NewReader(s+"\n"), "") 19 | } 20 | return k.ReadPrivateKey(strings.NewReader(s), "") 21 | } 22 | 23 | // ReadPrivateKey reads a private key from the io.Reader q. The string file is 24 | // only used in error reporting. 25 | // The public key must be known, because some cryptographic algorithms embed 26 | // the public inside the privatekey. 27 | func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, error) { 28 | m, e := parseKey(q, file) 29 | if m == nil { 30 | return nil, e 31 | } 32 | if _, ok := m["private-key-format"]; !ok { 33 | return nil, ErrPrivKey 34 | } 35 | if m["private-key-format"] != "v1.2" && m["private-key-format"] != "v1.3" { 36 | return nil, ErrPrivKey 37 | } 38 | // TODO(mg): check if the pubkey matches the private key 39 | algo, err := strconv.Atoi(strings.SplitN(m["algorithm"], " ", 2)[0]) 40 | if err != nil { 41 | return nil, ErrPrivKey 42 | } 43 | switch uint8(algo) { 44 | case DSA: 45 | priv, e := readPrivateKeyDSA(m) 46 | if e != nil { 47 | return nil, e 48 | } 49 | pub := k.publicKeyDSA() 50 | if pub == nil { 51 | return nil, ErrKey 52 | } 53 | priv.PublicKey = *pub 54 | return priv, e 55 | case RSAMD5: 56 | fallthrough 57 | case RSASHA1: 58 | fallthrough 59 | case RSASHA1NSEC3SHA1: 60 | fallthrough 61 | case RSASHA256: 62 | fallthrough 63 | case RSASHA512: 64 | priv, e := readPrivateKeyRSA(m) 65 | if e != nil { 66 | return nil, e 67 | } 68 | pub := k.publicKeyRSA() 69 | if pub == nil { 70 | return nil, ErrKey 71 | } 72 | priv.PublicKey = *pub 73 | return priv, e 74 | case ECCGOST: 75 | return nil, ErrPrivKey 76 | case ECDSAP256SHA256: 77 | fallthrough 78 | case ECDSAP384SHA384: 79 | priv, e := readPrivateKeyECDSA(m) 80 | if e != nil { 81 | return nil, e 82 | } 83 | pub := k.publicKeyECDSA() 84 | if pub == nil { 85 | return nil, ErrKey 86 | } 87 | priv.PublicKey = *pub 88 | return priv, e 89 | default: 90 | return nil, ErrPrivKey 91 | } 92 | } 93 | 94 | // Read a private key (file) string and create a public key. Return the private key. 95 | func readPrivateKeyRSA(m map[string]string) (*rsa.PrivateKey, error) { 96 | p := new(rsa.PrivateKey) 97 | p.Primes = []*big.Int{nil, nil} 98 | for k, v := range m { 99 | switch k { 100 | case "modulus", "publicexponent", "privateexponent", "prime1", "prime2": 101 | v1, err := fromBase64([]byte(v)) 102 | if err != nil { 103 | return nil, err 104 | } 105 | switch k { 106 | case "modulus": 107 | p.PublicKey.N = big.NewInt(0) 108 | p.PublicKey.N.SetBytes(v1) 109 | case "publicexponent": 110 | i := big.NewInt(0) 111 | i.SetBytes(v1) 112 | p.PublicKey.E = int(i.Int64()) // int64 should be large enough 113 | case "privateexponent": 114 | p.D = big.NewInt(0) 115 | p.D.SetBytes(v1) 116 | case "prime1": 117 | p.Primes[0] = big.NewInt(0) 118 | p.Primes[0].SetBytes(v1) 119 | case "prime2": 120 | p.Primes[1] = big.NewInt(0) 121 | p.Primes[1].SetBytes(v1) 122 | } 123 | case "exponent1", "exponent2", "coefficient": 124 | // not used in Go (yet) 125 | case "created", "publish", "activate": 126 | // not used in Go (yet) 127 | } 128 | } 129 | return p, nil 130 | } 131 | 132 | func readPrivateKeyDSA(m map[string]string) (*dsa.PrivateKey, error) { 133 | p := new(dsa.PrivateKey) 134 | p.X = big.NewInt(0) 135 | for k, v := range m { 136 | switch k { 137 | case "private_value(x)": 138 | v1, err := fromBase64([]byte(v)) 139 | if err != nil { 140 | return nil, err 141 | } 142 | p.X.SetBytes(v1) 143 | case "created", "publish", "activate": 144 | /* not used in Go (yet) */ 145 | } 146 | } 147 | return p, nil 148 | } 149 | 150 | func readPrivateKeyECDSA(m map[string]string) (*ecdsa.PrivateKey, error) { 151 | p := new(ecdsa.PrivateKey) 152 | p.D = big.NewInt(0) 153 | // TODO: validate that the required flags are present 154 | for k, v := range m { 155 | switch k { 156 | case "privatekey": 157 | v1, err := fromBase64([]byte(v)) 158 | if err != nil { 159 | return nil, err 160 | } 161 | p.D.SetBytes(v1) 162 | case "created", "publish", "activate": 163 | /* not used in Go (yet) */ 164 | } 165 | } 166 | return p, nil 167 | } 168 | 169 | // parseKey reads a private key from r. It returns a map[string]string, 170 | // with the key-value pairs, or an error when the file is not correct. 171 | func parseKey(r io.Reader, file string) (map[string]string, error) { 172 | s := scanInit(r) 173 | m := make(map[string]string) 174 | c := make(chan lex) 175 | k := "" 176 | // Start the lexer 177 | go klexer(s, c) 178 | for l := range c { 179 | // It should alternate 180 | switch l.value { 181 | case zKey: 182 | k = l.token 183 | case zValue: 184 | if k == "" { 185 | return nil, &ParseError{file, "no private key seen", l} 186 | } 187 | //println("Setting", strings.ToLower(k), "to", l.token, "b") 188 | m[strings.ToLower(k)] = l.token 189 | k = "" 190 | } 191 | } 192 | return m, nil 193 | } 194 | 195 | // klexer scans the sourcefile and returns tokens on the channel c. 196 | func klexer(s *scan, c chan lex) { 197 | var l lex 198 | str := "" // Hold the current read text 199 | commt := false 200 | key := true 201 | x, err := s.tokenText() 202 | defer close(c) 203 | for err == nil { 204 | l.column = s.position.Column 205 | l.line = s.position.Line 206 | switch x { 207 | case ':': 208 | if commt { 209 | break 210 | } 211 | l.token = str 212 | if key { 213 | l.value = zKey 214 | c <- l 215 | // Next token is a space, eat it 216 | s.tokenText() 217 | key = false 218 | str = "" 219 | } else { 220 | l.value = zValue 221 | } 222 | case ';': 223 | commt = true 224 | case '\n': 225 | if commt { 226 | // Reset a comment 227 | commt = false 228 | } 229 | l.value = zValue 230 | l.token = str 231 | c <- l 232 | str = "" 233 | commt = false 234 | key = true 235 | default: 236 | if commt { 237 | break 238 | } 239 | str += string(x) 240 | } 241 | x, err = s.tokenText() 242 | } 243 | if len(str) > 0 { 244 | // Send remainder 245 | l.token = str 246 | l.value = zValue 247 | c <- l 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/dnssec_privkey.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto" 5 | "crypto/dsa" 6 | "crypto/ecdsa" 7 | "crypto/rsa" 8 | "math/big" 9 | "strconv" 10 | ) 11 | 12 | const format = "Private-key-format: v1.3\n" 13 | 14 | // PrivateKeyString converts a PrivateKey to a string. This string has the same 15 | // format as the private-key-file of BIND9 (Private-key-format: v1.3). 16 | // It needs some info from the key (the algorithm), so its a method of the DNSKEY 17 | // It supports rsa.PrivateKey, ecdsa.PrivateKey and dsa.PrivateKey 18 | func (r *DNSKEY) PrivateKeyString(p crypto.PrivateKey) string { 19 | algorithm := strconv.Itoa(int(r.Algorithm)) 20 | algorithm += " (" + AlgorithmToString[r.Algorithm] + ")" 21 | 22 | switch p := p.(type) { 23 | case *rsa.PrivateKey: 24 | modulus := toBase64(p.PublicKey.N.Bytes()) 25 | e := big.NewInt(int64(p.PublicKey.E)) 26 | publicExponent := toBase64(e.Bytes()) 27 | privateExponent := toBase64(p.D.Bytes()) 28 | prime1 := toBase64(p.Primes[0].Bytes()) 29 | prime2 := toBase64(p.Primes[1].Bytes()) 30 | // Calculate Exponent1/2 and Coefficient as per: http://en.wikipedia.org/wiki/RSA#Using_the_Chinese_remainder_algorithm 31 | // and from: http://code.google.com/p/go/issues/detail?id=987 32 | one := big.NewInt(1) 33 | p1 := big.NewInt(0).Sub(p.Primes[0], one) 34 | q1 := big.NewInt(0).Sub(p.Primes[1], one) 35 | exp1 := big.NewInt(0).Mod(p.D, p1) 36 | exp2 := big.NewInt(0).Mod(p.D, q1) 37 | coeff := big.NewInt(0).ModInverse(p.Primes[1], p.Primes[0]) 38 | 39 | exponent1 := toBase64(exp1.Bytes()) 40 | exponent2 := toBase64(exp2.Bytes()) 41 | coefficient := toBase64(coeff.Bytes()) 42 | 43 | return format + 44 | "Algorithm: " + algorithm + "\n" + 45 | "Modulus: " + modulus + "\n" + 46 | "PublicExponent: " + publicExponent + "\n" + 47 | "PrivateExponent: " + privateExponent + "\n" + 48 | "Prime1: " + prime1 + "\n" + 49 | "Prime2: " + prime2 + "\n" + 50 | "Exponent1: " + exponent1 + "\n" + 51 | "Exponent2: " + exponent2 + "\n" + 52 | "Coefficient: " + coefficient + "\n" 53 | 54 | case *ecdsa.PrivateKey: 55 | var intlen int 56 | switch r.Algorithm { 57 | case ECDSAP256SHA256: 58 | intlen = 32 59 | case ECDSAP384SHA384: 60 | intlen = 48 61 | } 62 | private := toBase64(intToBytes(p.D, intlen)) 63 | return format + 64 | "Algorithm: " + algorithm + "\n" + 65 | "PrivateKey: " + private + "\n" 66 | 67 | case *dsa.PrivateKey: 68 | T := divRoundUp(divRoundUp(p.PublicKey.Parameters.G.BitLen(), 8)-64, 8) 69 | prime := toBase64(intToBytes(p.PublicKey.Parameters.P, 64+T*8)) 70 | subprime := toBase64(intToBytes(p.PublicKey.Parameters.Q, 20)) 71 | base := toBase64(intToBytes(p.PublicKey.Parameters.G, 64+T*8)) 72 | priv := toBase64(intToBytes(p.X, 20)) 73 | pub := toBase64(intToBytes(p.PublicKey.Y, 64+T*8)) 74 | return format + 75 | "Algorithm: " + algorithm + "\n" + 76 | "Prime(p): " + prime + "\n" + 77 | "Subprime(q): " + subprime + "\n" + 78 | "Base(g): " + base + "\n" + 79 | "Private_value(x): " + priv + "\n" + 80 | "Public_value(y): " + pub + "\n" 81 | 82 | default: 83 | return "" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/format.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "net" 5 | "reflect" 6 | "strconv" 7 | ) 8 | 9 | // NumField returns the number of rdata fields r has. 10 | func NumField(r RR) int { 11 | return reflect.ValueOf(r).Elem().NumField() - 1 // Remove RR_Header 12 | } 13 | 14 | // Field returns the rdata field i as a string. Fields are indexed starting from 1. 15 | // RR types that holds slice data, for instance the NSEC type bitmap will return a single 16 | // string where the types are concatenated using a space. 17 | // Accessing non existing fields will cause a panic. 18 | func Field(r RR, i int) string { 19 | if i == 0 { 20 | return "" 21 | } 22 | d := reflect.ValueOf(r).Elem().Field(i) 23 | switch k := d.Kind(); k { 24 | case reflect.String: 25 | return d.String() 26 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 27 | return strconv.FormatInt(d.Int(), 10) 28 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 29 | return strconv.FormatUint(d.Uint(), 10) 30 | case reflect.Slice: 31 | switch reflect.ValueOf(r).Elem().Type().Field(i).Tag { 32 | case `dns:"a"`: 33 | // TODO(miek): Hmm store this as 16 bytes 34 | if d.Len() < net.IPv6len { 35 | return net.IPv4(byte(d.Index(0).Uint()), 36 | byte(d.Index(1).Uint()), 37 | byte(d.Index(2).Uint()), 38 | byte(d.Index(3).Uint())).String() 39 | } 40 | return net.IPv4(byte(d.Index(12).Uint()), 41 | byte(d.Index(13).Uint()), 42 | byte(d.Index(14).Uint()), 43 | byte(d.Index(15).Uint())).String() 44 | case `dns:"aaaa"`: 45 | return net.IP{ 46 | byte(d.Index(0).Uint()), 47 | byte(d.Index(1).Uint()), 48 | byte(d.Index(2).Uint()), 49 | byte(d.Index(3).Uint()), 50 | byte(d.Index(4).Uint()), 51 | byte(d.Index(5).Uint()), 52 | byte(d.Index(6).Uint()), 53 | byte(d.Index(7).Uint()), 54 | byte(d.Index(8).Uint()), 55 | byte(d.Index(9).Uint()), 56 | byte(d.Index(10).Uint()), 57 | byte(d.Index(11).Uint()), 58 | byte(d.Index(12).Uint()), 59 | byte(d.Index(13).Uint()), 60 | byte(d.Index(14).Uint()), 61 | byte(d.Index(15).Uint()), 62 | }.String() 63 | case `dns:"nsec"`: 64 | if d.Len() == 0 { 65 | return "" 66 | } 67 | s := Type(d.Index(0).Uint()).String() 68 | for i := 1; i < d.Len(); i++ { 69 | s += " " + Type(d.Index(i).Uint()).String() 70 | } 71 | return s 72 | case `dns:"wks"`: 73 | if d.Len() == 0 { 74 | return "" 75 | } 76 | s := strconv.Itoa(int(d.Index(0).Uint())) 77 | for i := 0; i < d.Len(); i++ { 78 | s += " " + strconv.Itoa(int(d.Index(i).Uint())) 79 | } 80 | return s 81 | default: 82 | // if it does not have a tag its a string slice 83 | fallthrough 84 | case `dns:"txt"`: 85 | if d.Len() == 0 { 86 | return "" 87 | } 88 | s := d.Index(0).String() 89 | for i := 1; i < d.Len(); i++ { 90 | s += " " + d.Index(i).String() 91 | } 92 | return s 93 | } 94 | } 95 | return "" 96 | } 97 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/labels.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | // Holds a bunch of helper functions for dealing with labels. 4 | 5 | // SplitDomainName splits a name string into it's labels. 6 | // www.miek.nl. returns []string{"www", "miek", "nl"} 7 | // The root label (.) returns nil. Note that using 8 | // strings.Split(s) will work in most cases, but does not handle 9 | // escaped dots (\.) for instance. 10 | func SplitDomainName(s string) (labels []string) { 11 | if len(s) == 0 { 12 | return nil 13 | } 14 | fqdnEnd := 0 // offset of the final '.' or the length of the name 15 | idx := Split(s) 16 | begin := 0 17 | if s[len(s)-1] == '.' { 18 | fqdnEnd = len(s) - 1 19 | } else { 20 | fqdnEnd = len(s) 21 | } 22 | 23 | switch len(idx) { 24 | case 0: 25 | return nil 26 | case 1: 27 | // no-op 28 | default: 29 | end := 0 30 | for i := 1; i < len(idx); i++ { 31 | end = idx[i] 32 | labels = append(labels, s[begin:end-1]) 33 | begin = end 34 | } 35 | } 36 | 37 | labels = append(labels, s[begin:fqdnEnd]) 38 | return labels 39 | } 40 | 41 | // CompareDomainName compares the names s1 and s2 and 42 | // returns how many labels they have in common starting from the *right*. 43 | // The comparison stops at the first inequality. The names are not downcased 44 | // before the comparison. 45 | // 46 | // www.miek.nl. and miek.nl. have two labels in common: miek and nl 47 | // www.miek.nl. and www.bla.nl. have one label in common: nl 48 | func CompareDomainName(s1, s2 string) (n int) { 49 | s1 = Fqdn(s1) 50 | s2 = Fqdn(s2) 51 | l1 := Split(s1) 52 | l2 := Split(s2) 53 | 54 | // the first check: root label 55 | if l1 == nil || l2 == nil { 56 | return 57 | } 58 | 59 | j1 := len(l1) - 1 // end 60 | i1 := len(l1) - 2 // start 61 | j2 := len(l2) - 1 62 | i2 := len(l2) - 2 63 | // the second check can be done here: last/only label 64 | // before we fall through into the for-loop below 65 | if s1[l1[j1]:] == s2[l2[j2]:] { 66 | n++ 67 | } else { 68 | return 69 | } 70 | for { 71 | if i1 < 0 || i2 < 0 { 72 | break 73 | } 74 | if s1[l1[i1]:l1[j1]] == s2[l2[i2]:l2[j2]] { 75 | n++ 76 | } else { 77 | break 78 | } 79 | j1-- 80 | i1-- 81 | j2-- 82 | i2-- 83 | } 84 | return 85 | } 86 | 87 | // CountLabel counts the the number of labels in the string s. 88 | func CountLabel(s string) (labels int) { 89 | if s == "." { 90 | return 91 | } 92 | off := 0 93 | end := false 94 | for { 95 | off, end = NextLabel(s, off) 96 | labels++ 97 | if end { 98 | return 99 | } 100 | } 101 | } 102 | 103 | // Split splits a name s into its label indexes. 104 | // www.miek.nl. returns []int{0, 4, 9}, www.miek.nl also returns []int{0, 4, 9}. 105 | // The root name (.) returns nil. Also see SplitDomainName. 106 | func Split(s string) []int { 107 | if s == "." { 108 | return nil 109 | } 110 | idx := make([]int, 1, 3) 111 | off := 0 112 | end := false 113 | 114 | for { 115 | off, end = NextLabel(s, off) 116 | if end { 117 | return idx 118 | } 119 | idx = append(idx, off) 120 | } 121 | } 122 | 123 | // NextLabel returns the index of the start of the next label in the 124 | // string s starting at offset. 125 | // The bool end is true when the end of the string has been reached. 126 | // Also see PrevLabel. 127 | func NextLabel(s string, offset int) (i int, end bool) { 128 | quote := false 129 | for i = offset; i < len(s)-1; i++ { 130 | switch s[i] { 131 | case '\\': 132 | quote = !quote 133 | default: 134 | quote = false 135 | case '.': 136 | if quote { 137 | quote = !quote 138 | continue 139 | } 140 | return i + 1, false 141 | } 142 | } 143 | return i + 1, true 144 | } 145 | 146 | // PrevLabel returns the index of the label when starting from the right and 147 | // jumping n labels to the left. 148 | // The bool start is true when the start of the string has been overshot. 149 | // Also see NextLabel. 150 | func PrevLabel(s string, n int) (i int, start bool) { 151 | if n == 0 { 152 | return len(s), false 153 | } 154 | lab := Split(s) 155 | if lab == nil { 156 | return 0, true 157 | } 158 | if n > len(lab) { 159 | return 0, true 160 | } 161 | return lab[len(lab)-n], false 162 | } 163 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/nsecx.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto/sha1" 5 | "hash" 6 | "io" 7 | "strings" 8 | ) 9 | 10 | type saltWireFmt struct { 11 | Salt string `dns:"size-hex"` 12 | } 13 | 14 | // HashName hashes a string (label) according to RFC 5155. It returns the hashed string in 15 | // uppercase. 16 | func HashName(label string, ha uint8, iter uint16, salt string) string { 17 | saltwire := new(saltWireFmt) 18 | saltwire.Salt = salt 19 | wire := make([]byte, DefaultMsgSize) 20 | n, err := PackStruct(saltwire, wire, 0) 21 | if err != nil { 22 | return "" 23 | } 24 | wire = wire[:n] 25 | name := make([]byte, 255) 26 | off, err := PackDomainName(strings.ToLower(label), name, 0, nil, false) 27 | if err != nil { 28 | return "" 29 | } 30 | name = name[:off] 31 | var s hash.Hash 32 | switch ha { 33 | case SHA1: 34 | s = sha1.New() 35 | default: 36 | return "" 37 | } 38 | 39 | // k = 0 40 | name = append(name, wire...) 41 | io.WriteString(s, string(name)) 42 | nsec3 := s.Sum(nil) 43 | // k > 0 44 | for k := uint16(0); k < iter; k++ { 45 | s.Reset() 46 | nsec3 = append(nsec3, wire...) 47 | io.WriteString(s, string(nsec3)) 48 | nsec3 = s.Sum(nil) 49 | } 50 | return toBase32(nsec3) 51 | } 52 | 53 | // Denialer is an interface that should be implemented by types that are used to denial 54 | // answers in DNSSEC. 55 | type Denialer interface { 56 | // Cover will check if the (unhashed) name is being covered by this NSEC or NSEC3. 57 | Cover(name string) bool 58 | // Match will check if the ownername matches the (unhashed) name for this NSEC3 or NSEC3. 59 | Match(name string) bool 60 | } 61 | 62 | // Cover implements the Denialer interface. 63 | func (rr *NSEC) Cover(name string) bool { 64 | return true 65 | } 66 | 67 | // Match implements the Denialer interface. 68 | func (rr *NSEC) Match(name string) bool { 69 | return true 70 | } 71 | 72 | // Cover implements the Denialer interface. 73 | func (rr *NSEC3) Cover(name string) bool { 74 | // FIXME(miek): check if the zones match 75 | // FIXME(miek): check if we're not dealing with parent nsec3 76 | hname := HashName(name, rr.Hash, rr.Iterations, rr.Salt) 77 | labels := Split(rr.Hdr.Name) 78 | if len(labels) < 2 { 79 | return false 80 | } 81 | hash := strings.ToUpper(rr.Hdr.Name[labels[0] : labels[1]-1]) // -1 to remove the dot 82 | if hash == rr.NextDomain { 83 | return false // empty interval 84 | } 85 | if hash > rr.NextDomain { // last name, points to apex 86 | // hname > hash 87 | // hname > rr.NextDomain 88 | // TODO(miek) 89 | } 90 | if hname <= hash { 91 | return false 92 | } 93 | if hname >= rr.NextDomain { 94 | return false 95 | } 96 | return true 97 | } 98 | 99 | // Match implements the Denialer interface. 100 | func (rr *NSEC3) Match(name string) bool { 101 | // FIXME(miek): Check if we are in the same zone 102 | hname := HashName(name, rr.Hash, rr.Iterations, rr.Salt) 103 | labels := Split(rr.Hdr.Name) 104 | if len(labels) < 2 { 105 | return false 106 | } 107 | hash := strings.ToUpper(rr.Hdr.Name[labels[0] : labels[1]-1]) // -1 to remove the . 108 | if hash == hname { 109 | return true 110 | } 111 | return false 112 | } 113 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/privaterr.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // PrivateRdata is an interface used for implementing "Private Use" RR types, see 9 | // RFC 6895. This allows one to experiment with new RR types, without requesting an 10 | // official type code. Also see dns.PrivateHandle and dns.PrivateHandleRemove. 11 | type PrivateRdata interface { 12 | // String returns the text presentaton of the Rdata of the Private RR. 13 | String() string 14 | // Parse parses the Rdata of the private RR. 15 | Parse([]string) error 16 | // Pack is used when packing a private RR into a buffer. 17 | Pack([]byte) (int, error) 18 | // Unpack is used when unpacking a private RR from a buffer. 19 | // TODO(miek): diff. signature than Pack, see edns0.go for instance. 20 | Unpack([]byte) (int, error) 21 | // Copy copies the Rdata. 22 | Copy(PrivateRdata) error 23 | // Len returns the length in octets of the Rdata. 24 | Len() int 25 | } 26 | 27 | // PrivateRR represents an RR that uses a PrivateRdata user-defined type. 28 | // It mocks normal RRs and implements dns.RR interface. 29 | type PrivateRR struct { 30 | Hdr RR_Header 31 | Data PrivateRdata 32 | } 33 | 34 | func mkPrivateRR(rrtype uint16) *PrivateRR { 35 | // Panics if RR is not an instance of PrivateRR. 36 | rrfunc, ok := TypeToRR[rrtype] 37 | if !ok { 38 | panic(fmt.Sprintf("dns: invalid operation with Private RR type %d", rrtype)) 39 | } 40 | 41 | anyrr := rrfunc() 42 | switch rr := anyrr.(type) { 43 | case *PrivateRR: 44 | return rr 45 | } 46 | panic(fmt.Sprintf("dns: RR is not a PrivateRR, TypeToRR[%d] generator returned %T", rrtype, anyrr)) 47 | } 48 | 49 | // Header return the RR header of r. 50 | func (r *PrivateRR) Header() *RR_Header { return &r.Hdr } 51 | 52 | func (r *PrivateRR) String() string { return r.Hdr.String() + r.Data.String() } 53 | 54 | // Private len and copy parts to satisfy RR interface. 55 | func (r *PrivateRR) len() int { return r.Hdr.len() + r.Data.Len() } 56 | func (r *PrivateRR) copy() RR { 57 | // make new RR like this: 58 | rr := mkPrivateRR(r.Hdr.Rrtype) 59 | newh := r.Hdr.copyHeader() 60 | rr.Hdr = *newh 61 | 62 | err := r.Data.Copy(rr.Data) 63 | if err != nil { 64 | panic("dns: got value that could not be used to copy Private rdata") 65 | } 66 | return rr 67 | } 68 | 69 | // PrivateHandle registers a private resource record type. It requires 70 | // string and numeric representation of private RR type and generator function as argument. 71 | func PrivateHandle(rtypestr string, rtype uint16, generator func() PrivateRdata) { 72 | rtypestr = strings.ToUpper(rtypestr) 73 | 74 | TypeToRR[rtype] = func() RR { return &PrivateRR{RR_Header{}, generator()} } 75 | TypeToString[rtype] = rtypestr 76 | StringToType[rtypestr] = rtype 77 | 78 | setPrivateRR := func(h RR_Header, c chan lex, o, f string) (RR, *ParseError, string) { 79 | rr := mkPrivateRR(h.Rrtype) 80 | rr.Hdr = h 81 | 82 | var l lex 83 | text := make([]string, 0, 2) // could be 0..N elements, median is probably 1 84 | FETCH: 85 | for { 86 | // TODO(miek): we could also be returning _QUOTE, this might or might not 87 | // be an issue (basically parsing TXT becomes hard) 88 | switch l = <-c; l.value { 89 | case zNewline, zEOF: 90 | break FETCH 91 | case zString: 92 | text = append(text, l.token) 93 | } 94 | } 95 | 96 | err := rr.Data.Parse(text) 97 | if err != nil { 98 | return nil, &ParseError{f, err.Error(), l}, "" 99 | } 100 | 101 | return rr, nil, "" 102 | } 103 | 104 | typeToparserFunc[rtype] = parserFunc{setPrivateRR, true} 105 | } 106 | 107 | // PrivateHandleRemove removes defenitions required to support private RR type. 108 | func PrivateHandleRemove(rtype uint16) { 109 | rtypestr, ok := TypeToString[rtype] 110 | if ok { 111 | delete(TypeToRR, rtype) 112 | delete(TypeToString, rtype) 113 | delete(typeToparserFunc, rtype) 114 | delete(StringToType, rtypestr) 115 | } 116 | return 117 | } 118 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/rawmsg.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | // These raw* functions do not use reflection, they directly set the values 4 | // in the buffer. There are faster than their reflection counterparts. 5 | 6 | // RawSetId sets the message id in buf. 7 | func rawSetId(msg []byte, i uint16) bool { 8 | if len(msg) < 2 { 9 | return false 10 | } 11 | msg[0], msg[1] = packUint16(i) 12 | return true 13 | } 14 | 15 | // rawSetQuestionLen sets the length of the question section. 16 | func rawSetQuestionLen(msg []byte, i uint16) bool { 17 | if len(msg) < 6 { 18 | return false 19 | } 20 | msg[4], msg[5] = packUint16(i) 21 | return true 22 | } 23 | 24 | // rawSetAnswerLen sets the lenght of the answer section. 25 | func rawSetAnswerLen(msg []byte, i uint16) bool { 26 | if len(msg) < 8 { 27 | return false 28 | } 29 | msg[6], msg[7] = packUint16(i) 30 | return true 31 | } 32 | 33 | // rawSetsNsLen sets the lenght of the authority section. 34 | func rawSetNsLen(msg []byte, i uint16) bool { 35 | if len(msg) < 10 { 36 | return false 37 | } 38 | msg[8], msg[9] = packUint16(i) 39 | return true 40 | } 41 | 42 | // rawSetExtraLen sets the lenght of the additional section. 43 | func rawSetExtraLen(msg []byte, i uint16) bool { 44 | if len(msg) < 12 { 45 | return false 46 | } 47 | msg[10], msg[11] = packUint16(i) 48 | return true 49 | } 50 | 51 | // rawSetRdlength sets the rdlength in the header of 52 | // the RR. The offset 'off' must be positioned at the 53 | // start of the header of the RR, 'end' must be the 54 | // end of the RR. 55 | func rawSetRdlength(msg []byte, off, end int) bool { 56 | l := len(msg) 57 | Loop: 58 | for { 59 | if off+1 > l { 60 | return false 61 | } 62 | c := int(msg[off]) 63 | off++ 64 | switch c & 0xC0 { 65 | case 0x00: 66 | if c == 0x00 { 67 | // End of the domainname 68 | break Loop 69 | } 70 | if off+c > l { 71 | return false 72 | } 73 | off += c 74 | 75 | case 0xC0: 76 | // pointer, next byte included, ends domainname 77 | off++ 78 | break Loop 79 | } 80 | } 81 | // The domainname has been seen, we at the start of the fixed part in the header. 82 | // Type is 2 bytes, class is 2 bytes, ttl 4 and then 2 bytes for the length. 83 | off += 2 + 2 + 4 84 | if off+2 > l { 85 | return false 86 | } 87 | //off+1 is the end of the header, 'end' is the end of the rr 88 | //so 'end' - 'off+2' is the length of the rdata 89 | rdatalen := end - (off + 2) 90 | if rdatalen > 0xFFFF { 91 | return false 92 | } 93 | msg[off], msg[off+1] = packUint16(uint16(rdatalen)) 94 | return true 95 | } 96 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/sanitize.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | // Dedup removes identical RRs from rrs. It preserves the original ordering. 4 | // The lowest TTL of any duplicates is used in the remaining one. Dedup modifies 5 | // rrs. 6 | // m is used to store the RRs temporay. If it is nil a new map will be allocated. 7 | func Dedup(rrs []RR, m map[string]RR) []RR { 8 | if m == nil { 9 | m = make(map[string]RR) 10 | } 11 | // Save the keys, so we don't have to call normalizedString twice. 12 | keys := make([]*string, 0, len(rrs)) 13 | 14 | for _, r := range rrs { 15 | key := normalizedString(r) 16 | keys = append(keys, &key) 17 | if _, ok := m[key]; ok { 18 | // Shortest TTL wins. 19 | if m[key].Header().Ttl > r.Header().Ttl { 20 | m[key].Header().Ttl = r.Header().Ttl 21 | } 22 | continue 23 | } 24 | 25 | m[key] = r 26 | } 27 | // If the length of the result map equals the amount of RRs we got, 28 | // it means they were all different. We can then just return the original rrset. 29 | if len(m) == len(rrs) { 30 | return rrs 31 | } 32 | 33 | j := 0 34 | for i, r := range rrs { 35 | // If keys[i] lives in the map, we should copy and remove it. 36 | if _, ok := m[*keys[i]]; ok { 37 | delete(m, *keys[i]) 38 | rrs[j] = r 39 | j++ 40 | } 41 | 42 | if len(m) == 0 { 43 | break 44 | } 45 | } 46 | 47 | return rrs[:j] 48 | } 49 | 50 | // normalizedString returns a normalized string from r. The TTL 51 | // is removed and the domain name is lowercased. We go from this: 52 | // DomainNameTTLCLASSTYPERDATA to: 53 | // lowercasenameCLASSTYPE... 54 | func normalizedString(r RR) string { 55 | // A string Go DNS makes has: domainnameTTL... 56 | b := []byte(r.String()) 57 | 58 | // find the first non-escaped tab, then another, so we capture where the TTL lives. 59 | esc := false 60 | ttlStart, ttlEnd := 0, 0 61 | for i := 0; i < len(b) && ttlEnd == 0; i++ { 62 | switch { 63 | case b[i] == '\\': 64 | esc = !esc 65 | case b[i] == '\t' && !esc: 66 | if ttlStart == 0 { 67 | ttlStart = i 68 | continue 69 | } 70 | if ttlEnd == 0 { 71 | ttlEnd = i 72 | } 73 | case b[i] >= 'A' && b[i] <= 'Z' && !esc: 74 | b[i] += 32 75 | default: 76 | esc = false 77 | } 78 | } 79 | 80 | // remove TTL. 81 | copy(b[ttlStart:], b[ttlEnd:]) 82 | cut := ttlEnd - ttlStart 83 | return string(b[:len(b)-cut]) 84 | } 85 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/scanner.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | // Implement a simple scanner, return a byte stream from an io reader. 4 | 5 | import ( 6 | "bufio" 7 | "io" 8 | "text/scanner" 9 | ) 10 | 11 | type scan struct { 12 | src *bufio.Reader 13 | position scanner.Position 14 | eof bool // Have we just seen a eof 15 | } 16 | 17 | func scanInit(r io.Reader) *scan { 18 | s := new(scan) 19 | s.src = bufio.NewReader(r) 20 | s.position.Line = 1 21 | return s 22 | } 23 | 24 | // tokenText returns the next byte from the input 25 | func (s *scan) tokenText() (byte, error) { 26 | c, err := s.src.ReadByte() 27 | if err != nil { 28 | return c, err 29 | } 30 | // delay the newline handling until the next token is delivered, 31 | // fixes off-by-one errors when reporting a parse error. 32 | if s.eof == true { 33 | s.position.Line++ 34 | s.position.Column = 0 35 | s.eof = false 36 | } 37 | if c == '\n' { 38 | s.eof = true 39 | return c, nil 40 | } 41 | s.position.Column++ 42 | return c, nil 43 | } 44 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/sig0.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto" 5 | "crypto/dsa" 6 | "crypto/ecdsa" 7 | "crypto/rsa" 8 | "math/big" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | // Sign signs a dns.Msg. It fills the signature with the appropriate data. 14 | // The SIG record should have the SignerName, KeyTag, Algorithm, Inception 15 | // and Expiration set. 16 | func (rr *SIG) Sign(k crypto.Signer, m *Msg) ([]byte, error) { 17 | if k == nil { 18 | return nil, ErrPrivKey 19 | } 20 | if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 { 21 | return nil, ErrKey 22 | } 23 | rr.Header().Rrtype = TypeSIG 24 | rr.Header().Class = ClassANY 25 | rr.Header().Ttl = 0 26 | rr.Header().Name = "." 27 | rr.OrigTtl = 0 28 | rr.TypeCovered = 0 29 | rr.Labels = 0 30 | 31 | buf := make([]byte, m.Len()+rr.len()) 32 | mbuf, err := m.PackBuffer(buf) 33 | if err != nil { 34 | return nil, err 35 | } 36 | if &buf[0] != &mbuf[0] { 37 | return nil, ErrBuf 38 | } 39 | off, err := PackRR(rr, buf, len(mbuf), nil, false) 40 | if err != nil { 41 | return nil, err 42 | } 43 | buf = buf[:off:cap(buf)] 44 | 45 | hash, ok := AlgorithmToHash[rr.Algorithm] 46 | if !ok { 47 | return nil, ErrAlg 48 | } 49 | 50 | hasher := hash.New() 51 | // Write SIG rdata 52 | hasher.Write(buf[len(mbuf)+1+2+2+4+2:]) 53 | // Write message 54 | hasher.Write(buf[:len(mbuf)]) 55 | 56 | signature, err := sign(k, hasher.Sum(nil), hash, rr.Algorithm) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | rr.Signature = toBase64(signature) 62 | sig := string(signature) 63 | 64 | buf = append(buf, sig...) 65 | if len(buf) > int(^uint16(0)) { 66 | return nil, ErrBuf 67 | } 68 | // Adjust sig data length 69 | rdoff := len(mbuf) + 1 + 2 + 2 + 4 70 | rdlen, _ := unpackUint16(buf, rdoff) 71 | rdlen += uint16(len(sig)) 72 | buf[rdoff], buf[rdoff+1] = packUint16(rdlen) 73 | // Adjust additional count 74 | adc, _ := unpackUint16(buf, 10) 75 | adc++ 76 | buf[10], buf[11] = packUint16(adc) 77 | return buf, nil 78 | } 79 | 80 | // Verify validates the message buf using the key k. 81 | // It's assumed that buf is a valid message from which rr was unpacked. 82 | func (rr *SIG) Verify(k *KEY, buf []byte) error { 83 | if k == nil { 84 | return ErrKey 85 | } 86 | if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 { 87 | return ErrKey 88 | } 89 | 90 | var hash crypto.Hash 91 | switch rr.Algorithm { 92 | case DSA, RSASHA1: 93 | hash = crypto.SHA1 94 | case RSASHA256, ECDSAP256SHA256: 95 | hash = crypto.SHA256 96 | case ECDSAP384SHA384: 97 | hash = crypto.SHA384 98 | case RSASHA512: 99 | hash = crypto.SHA512 100 | default: 101 | return ErrAlg 102 | } 103 | hasher := hash.New() 104 | 105 | buflen := len(buf) 106 | qdc, _ := unpackUint16(buf, 4) 107 | anc, _ := unpackUint16(buf, 6) 108 | auc, _ := unpackUint16(buf, 8) 109 | adc, offset := unpackUint16(buf, 10) 110 | var err error 111 | for i := uint16(0); i < qdc && offset < buflen; i++ { 112 | _, offset, err = UnpackDomainName(buf, offset) 113 | if err != nil { 114 | return err 115 | } 116 | // Skip past Type and Class 117 | offset += 2 + 2 118 | } 119 | for i := uint16(1); i < anc+auc+adc && offset < buflen; i++ { 120 | _, offset, err = UnpackDomainName(buf, offset) 121 | if err != nil { 122 | return err 123 | } 124 | // Skip past Type, Class and TTL 125 | offset += 2 + 2 + 4 126 | if offset+1 >= buflen { 127 | continue 128 | } 129 | var rdlen uint16 130 | rdlen, offset = unpackUint16(buf, offset) 131 | offset += int(rdlen) 132 | } 133 | if offset >= buflen { 134 | return &Error{err: "overflowing unpacking signed message"} 135 | } 136 | 137 | // offset should be just prior to SIG 138 | bodyend := offset 139 | // owner name SHOULD be root 140 | _, offset, err = UnpackDomainName(buf, offset) 141 | if err != nil { 142 | return err 143 | } 144 | // Skip Type, Class, TTL, RDLen 145 | offset += 2 + 2 + 4 + 2 146 | sigstart := offset 147 | // Skip Type Covered, Algorithm, Labels, Original TTL 148 | offset += 2 + 1 + 1 + 4 149 | if offset+4+4 >= buflen { 150 | return &Error{err: "overflow unpacking signed message"} 151 | } 152 | expire := uint32(buf[offset])<<24 | uint32(buf[offset+1])<<16 | uint32(buf[offset+2])<<8 | uint32(buf[offset+3]) 153 | offset += 4 154 | incept := uint32(buf[offset])<<24 | uint32(buf[offset+1])<<16 | uint32(buf[offset+2])<<8 | uint32(buf[offset+3]) 155 | offset += 4 156 | now := uint32(time.Now().Unix()) 157 | if now < incept || now > expire { 158 | return ErrTime 159 | } 160 | // Skip key tag 161 | offset += 2 162 | var signername string 163 | signername, offset, err = UnpackDomainName(buf, offset) 164 | if err != nil { 165 | return err 166 | } 167 | // If key has come from the DNS name compression might 168 | // have mangled the case of the name 169 | if strings.ToLower(signername) != strings.ToLower(k.Header().Name) { 170 | return &Error{err: "signer name doesn't match key name"} 171 | } 172 | sigend := offset 173 | hasher.Write(buf[sigstart:sigend]) 174 | hasher.Write(buf[:10]) 175 | hasher.Write([]byte{ 176 | byte((adc - 1) << 8), 177 | byte(adc - 1), 178 | }) 179 | hasher.Write(buf[12:bodyend]) 180 | 181 | hashed := hasher.Sum(nil) 182 | sig := buf[sigend:] 183 | switch k.Algorithm { 184 | case DSA: 185 | pk := k.publicKeyDSA() 186 | sig = sig[1:] 187 | r := big.NewInt(0) 188 | r.SetBytes(sig[:len(sig)/2]) 189 | s := big.NewInt(0) 190 | s.SetBytes(sig[len(sig)/2:]) 191 | if pk != nil { 192 | if dsa.Verify(pk, hashed, r, s) { 193 | return nil 194 | } 195 | return ErrSig 196 | } 197 | case RSASHA1, RSASHA256, RSASHA512: 198 | pk := k.publicKeyRSA() 199 | if pk != nil { 200 | return rsa.VerifyPKCS1v15(pk, hash, hashed, sig) 201 | } 202 | case ECDSAP256SHA256, ECDSAP384SHA384: 203 | pk := k.publicKeyECDSA() 204 | r := big.NewInt(0) 205 | r.SetBytes(sig[:len(sig)/2]) 206 | s := big.NewInt(0) 207 | s.SetBytes(sig[len(sig)/2:]) 208 | if pk != nil { 209 | if ecdsa.Verify(pk, hashed, r, s) { 210 | return nil 211 | } 212 | return ErrSig 213 | } 214 | } 215 | return ErrKeyAlg 216 | } 217 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/singleinflight.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Adapted for dns package usage by Miek Gieben. 6 | 7 | package dns 8 | 9 | import "sync" 10 | import "time" 11 | 12 | // call is an in-flight or completed singleflight.Do call 13 | type call struct { 14 | wg sync.WaitGroup 15 | val *Msg 16 | rtt time.Duration 17 | err error 18 | dups int 19 | } 20 | 21 | // singleflight represents a class of work and forms a namespace in 22 | // which units of work can be executed with duplicate suppression. 23 | type singleflight struct { 24 | sync.Mutex // protects m 25 | m map[string]*call // lazily initialized 26 | } 27 | 28 | // Do executes and returns the results of the given function, making 29 | // sure that only one execution is in-flight for a given key at a 30 | // time. If a duplicate comes in, the duplicate caller waits for the 31 | // original to complete and receives the same results. 32 | // The return value shared indicates whether v was given to multiple callers. 33 | func (g *singleflight) Do(key string, fn func() (*Msg, time.Duration, error)) (v *Msg, rtt time.Duration, err error, shared bool) { 34 | g.Lock() 35 | if g.m == nil { 36 | g.m = make(map[string]*call) 37 | } 38 | if c, ok := g.m[key]; ok { 39 | c.dups++ 40 | g.Unlock() 41 | c.wg.Wait() 42 | return c.val, c.rtt, c.err, true 43 | } 44 | c := new(call) 45 | c.wg.Add(1) 46 | g.m[key] = c 47 | g.Unlock() 48 | 49 | c.val, c.rtt, c.err = fn() 50 | c.wg.Done() 51 | 52 | g.Lock() 53 | delete(g.m, key) 54 | g.Unlock() 55 | 56 | return c.val, c.rtt, c.err, c.dups > 0 57 | } 58 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/tlsa.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto/sha256" 5 | "crypto/sha512" 6 | "crypto/x509" 7 | "encoding/hex" 8 | "errors" 9 | "io" 10 | "net" 11 | "strconv" 12 | ) 13 | 14 | // CertificateToDANE converts a certificate to a hex string as used in the TLSA record. 15 | func CertificateToDANE(selector, matchingType uint8, cert *x509.Certificate) (string, error) { 16 | switch matchingType { 17 | case 0: 18 | switch selector { 19 | case 0: 20 | return hex.EncodeToString(cert.Raw), nil 21 | case 1: 22 | return hex.EncodeToString(cert.RawSubjectPublicKeyInfo), nil 23 | } 24 | case 1: 25 | h := sha256.New() 26 | switch selector { 27 | case 0: 28 | io.WriteString(h, string(cert.Raw)) 29 | return hex.EncodeToString(h.Sum(nil)), nil 30 | case 1: 31 | io.WriteString(h, string(cert.RawSubjectPublicKeyInfo)) 32 | return hex.EncodeToString(h.Sum(nil)), nil 33 | } 34 | case 2: 35 | h := sha512.New() 36 | switch selector { 37 | case 0: 38 | io.WriteString(h, string(cert.Raw)) 39 | return hex.EncodeToString(h.Sum(nil)), nil 40 | case 1: 41 | io.WriteString(h, string(cert.RawSubjectPublicKeyInfo)) 42 | return hex.EncodeToString(h.Sum(nil)), nil 43 | } 44 | } 45 | return "", errors.New("dns: bad TLSA MatchingType or TLSA Selector") 46 | } 47 | 48 | // Sign creates a TLSA record from an SSL certificate. 49 | func (r *TLSA) Sign(usage, selector, matchingType int, cert *x509.Certificate) (err error) { 50 | r.Hdr.Rrtype = TypeTLSA 51 | r.Usage = uint8(usage) 52 | r.Selector = uint8(selector) 53 | r.MatchingType = uint8(matchingType) 54 | 55 | r.Certificate, err = CertificateToDANE(r.Selector, r.MatchingType, cert) 56 | if err != nil { 57 | return err 58 | } 59 | return nil 60 | } 61 | 62 | // Verify verifies a TLSA record against an SSL certificate. If it is OK 63 | // a nil error is returned. 64 | func (r *TLSA) Verify(cert *x509.Certificate) error { 65 | c, err := CertificateToDANE(r.Selector, r.MatchingType, cert) 66 | if err != nil { 67 | return err // Not also ErrSig? 68 | } 69 | if r.Certificate == c { 70 | return nil 71 | } 72 | return ErrSig // ErrSig, really? 73 | } 74 | 75 | // TLSAName returns the ownername of a TLSA resource record as per the 76 | // rules specified in RFC 6698, Section 3. 77 | func TLSAName(name, service, network string) (string, error) { 78 | if !IsFqdn(name) { 79 | return "", ErrFqdn 80 | } 81 | p, e := net.LookupPort(network, service) 82 | if e != nil { 83 | return "", e 84 | } 85 | return "_" + strconv.Itoa(p) + "_" + network + "." + name, nil 86 | } 87 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/tsig.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/md5" 6 | "crypto/sha1" 7 | "crypto/sha256" 8 | "crypto/sha512" 9 | "encoding/hex" 10 | "hash" 11 | "io" 12 | "strconv" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | // HMAC hashing codes. These are transmitted as domain names. 18 | const ( 19 | HmacMD5 = "hmac-md5.sig-alg.reg.int." 20 | HmacSHA1 = "hmac-sha1." 21 | HmacSHA256 = "hmac-sha256." 22 | HmacSHA512 = "hmac-sha512." 23 | ) 24 | 25 | // TSIG is the RR the holds the transaction signature of a message. 26 | // See RFC 2845 and RFC 4635. 27 | type TSIG struct { 28 | Hdr RR_Header 29 | Algorithm string `dns:"domain-name"` 30 | TimeSigned uint64 `dns:"uint48"` 31 | Fudge uint16 32 | MACSize uint16 33 | MAC string `dns:"size-hex"` 34 | OrigId uint16 35 | Error uint16 36 | OtherLen uint16 37 | OtherData string `dns:"size-hex"` 38 | } 39 | 40 | // TSIG has no official presentation format, but this will suffice. 41 | 42 | func (rr *TSIG) String() string { 43 | s := "\n;; TSIG PSEUDOSECTION:\n" 44 | s += rr.Hdr.String() + 45 | " " + rr.Algorithm + 46 | " " + tsigTimeToString(rr.TimeSigned) + 47 | " " + strconv.Itoa(int(rr.Fudge)) + 48 | " " + strconv.Itoa(int(rr.MACSize)) + 49 | " " + strings.ToUpper(rr.MAC) + 50 | " " + strconv.Itoa(int(rr.OrigId)) + 51 | " " + strconv.Itoa(int(rr.Error)) + // BIND prints NOERROR 52 | " " + strconv.Itoa(int(rr.OtherLen)) + 53 | " " + rr.OtherData 54 | return s 55 | } 56 | 57 | // The following values must be put in wireformat, so that the MAC can be calculated. 58 | // RFC 2845, section 3.4.2. TSIG Variables. 59 | type tsigWireFmt struct { 60 | // From RR_Header 61 | Name string `dns:"domain-name"` 62 | Class uint16 63 | Ttl uint32 64 | // Rdata of the TSIG 65 | Algorithm string `dns:"domain-name"` 66 | TimeSigned uint64 `dns:"uint48"` 67 | Fudge uint16 68 | // MACSize, MAC and OrigId excluded 69 | Error uint16 70 | OtherLen uint16 71 | OtherData string `dns:"size-hex"` 72 | } 73 | 74 | // If we have the MAC use this type to convert it to wiredata. 75 | // Section 3.4.3. Request MAC 76 | type macWireFmt struct { 77 | MACSize uint16 78 | MAC string `dns:"size-hex"` 79 | } 80 | 81 | // 3.3. Time values used in TSIG calculations 82 | type timerWireFmt struct { 83 | TimeSigned uint64 `dns:"uint48"` 84 | Fudge uint16 85 | } 86 | 87 | // TsigGenerate fills out the TSIG record attached to the message. 88 | // The message should contain 89 | // a "stub" TSIG RR with the algorithm, key name (owner name of the RR), 90 | // time fudge (defaults to 300 seconds) and the current time 91 | // The TSIG MAC is saved in that Tsig RR. 92 | // When TsigGenerate is called for the first time requestMAC is set to the empty string and 93 | // timersOnly is false. 94 | // If something goes wrong an error is returned, otherwise it is nil. 95 | func TsigGenerate(m *Msg, secret, requestMAC string, timersOnly bool) ([]byte, string, error) { 96 | if m.IsTsig() == nil { 97 | panic("dns: TSIG not last RR in additional") 98 | } 99 | // If we barf here, the caller is to blame 100 | rawsecret, err := fromBase64([]byte(secret)) 101 | if err != nil { 102 | return nil, "", err 103 | } 104 | 105 | rr := m.Extra[len(m.Extra)-1].(*TSIG) 106 | m.Extra = m.Extra[0 : len(m.Extra)-1] // kill the TSIG from the msg 107 | mbuf, err := m.Pack() 108 | if err != nil { 109 | return nil, "", err 110 | } 111 | buf := tsigBuffer(mbuf, rr, requestMAC, timersOnly) 112 | 113 | t := new(TSIG) 114 | var h hash.Hash 115 | switch rr.Algorithm { 116 | case HmacMD5: 117 | h = hmac.New(md5.New, []byte(rawsecret)) 118 | case HmacSHA1: 119 | h = hmac.New(sha1.New, []byte(rawsecret)) 120 | case HmacSHA256: 121 | h = hmac.New(sha256.New, []byte(rawsecret)) 122 | case HmacSHA512: 123 | h = hmac.New(sha512.New, []byte(rawsecret)) 124 | default: 125 | return nil, "", ErrKeyAlg 126 | } 127 | io.WriteString(h, string(buf)) 128 | t.MAC = hex.EncodeToString(h.Sum(nil)) 129 | t.MACSize = uint16(len(t.MAC) / 2) // Size is half! 130 | 131 | t.Hdr = RR_Header{Name: rr.Hdr.Name, Rrtype: TypeTSIG, Class: ClassANY, Ttl: 0} 132 | t.Fudge = rr.Fudge 133 | t.TimeSigned = rr.TimeSigned 134 | t.Algorithm = rr.Algorithm 135 | t.OrigId = m.Id 136 | 137 | tbuf := make([]byte, t.len()) 138 | if off, err := PackRR(t, tbuf, 0, nil, false); err == nil { 139 | tbuf = tbuf[:off] // reset to actual size used 140 | } else { 141 | return nil, "", err 142 | } 143 | mbuf = append(mbuf, tbuf...) 144 | rawSetExtraLen(mbuf, uint16(len(m.Extra)+1)) 145 | return mbuf, t.MAC, nil 146 | } 147 | 148 | // TsigVerify verifies the TSIG on a message. 149 | // If the signature does not validate err contains the 150 | // error, otherwise it is nil. 151 | func TsigVerify(msg []byte, secret, requestMAC string, timersOnly bool) error { 152 | rawsecret, err := fromBase64([]byte(secret)) 153 | if err != nil { 154 | return err 155 | } 156 | // Strip the TSIG from the incoming msg 157 | stripped, tsig, err := stripTsig(msg) 158 | if err != nil { 159 | return err 160 | } 161 | 162 | msgMAC, err := hex.DecodeString(tsig.MAC) 163 | if err != nil { 164 | return err 165 | } 166 | 167 | buf := tsigBuffer(stripped, tsig, requestMAC, timersOnly) 168 | 169 | // Fudge factor works both ways. A message can arrive before it was signed because 170 | // of clock skew. 171 | now := uint64(time.Now().Unix()) 172 | ti := now - tsig.TimeSigned 173 | if now < tsig.TimeSigned { 174 | ti = tsig.TimeSigned - now 175 | } 176 | if uint64(tsig.Fudge) < ti { 177 | return ErrTime 178 | } 179 | 180 | var h hash.Hash 181 | switch tsig.Algorithm { 182 | case HmacMD5: 183 | h = hmac.New(md5.New, rawsecret) 184 | case HmacSHA1: 185 | h = hmac.New(sha1.New, rawsecret) 186 | case HmacSHA256: 187 | h = hmac.New(sha256.New, rawsecret) 188 | case HmacSHA512: 189 | h = hmac.New(sha512.New, rawsecret) 190 | default: 191 | return ErrKeyAlg 192 | } 193 | h.Write(buf) 194 | if !hmac.Equal(h.Sum(nil), msgMAC) { 195 | return ErrSig 196 | } 197 | return nil 198 | } 199 | 200 | // Create a wiredata buffer for the MAC calculation. 201 | func tsigBuffer(msgbuf []byte, rr *TSIG, requestMAC string, timersOnly bool) []byte { 202 | var buf []byte 203 | if rr.TimeSigned == 0 { 204 | rr.TimeSigned = uint64(time.Now().Unix()) 205 | } 206 | if rr.Fudge == 0 { 207 | rr.Fudge = 300 // Standard (RFC) default. 208 | } 209 | 210 | if requestMAC != "" { 211 | m := new(macWireFmt) 212 | m.MACSize = uint16(len(requestMAC) / 2) 213 | m.MAC = requestMAC 214 | buf = make([]byte, len(requestMAC)) // long enough 215 | n, _ := PackStruct(m, buf, 0) 216 | buf = buf[:n] 217 | } 218 | 219 | tsigvar := make([]byte, DefaultMsgSize) 220 | if timersOnly { 221 | tsig := new(timerWireFmt) 222 | tsig.TimeSigned = rr.TimeSigned 223 | tsig.Fudge = rr.Fudge 224 | n, _ := PackStruct(tsig, tsigvar, 0) 225 | tsigvar = tsigvar[:n] 226 | } else { 227 | tsig := new(tsigWireFmt) 228 | tsig.Name = strings.ToLower(rr.Hdr.Name) 229 | tsig.Class = ClassANY 230 | tsig.Ttl = rr.Hdr.Ttl 231 | tsig.Algorithm = strings.ToLower(rr.Algorithm) 232 | tsig.TimeSigned = rr.TimeSigned 233 | tsig.Fudge = rr.Fudge 234 | tsig.Error = rr.Error 235 | tsig.OtherLen = rr.OtherLen 236 | tsig.OtherData = rr.OtherData 237 | n, _ := PackStruct(tsig, tsigvar, 0) 238 | tsigvar = tsigvar[:n] 239 | } 240 | 241 | if requestMAC != "" { 242 | x := append(buf, msgbuf...) 243 | buf = append(x, tsigvar...) 244 | } else { 245 | buf = append(msgbuf, tsigvar...) 246 | } 247 | return buf 248 | } 249 | 250 | // Strip the TSIG from the raw message. 251 | func stripTsig(msg []byte) ([]byte, *TSIG, error) { 252 | // Copied from msg.go's Unpack() 253 | // Header. 254 | var dh Header 255 | var err error 256 | dns := new(Msg) 257 | rr := new(TSIG) 258 | off := 0 259 | tsigoff := 0 260 | if off, err = UnpackStruct(&dh, msg, off); err != nil { 261 | return nil, nil, err 262 | } 263 | if dh.Arcount == 0 { 264 | return nil, nil, ErrNoSig 265 | } 266 | // Rcode, see msg.go Unpack() 267 | if int(dh.Bits&0xF) == RcodeNotAuth { 268 | return nil, nil, ErrAuth 269 | } 270 | 271 | // Arrays. 272 | dns.Question = make([]Question, dh.Qdcount) 273 | dns.Answer = make([]RR, dh.Ancount) 274 | dns.Ns = make([]RR, dh.Nscount) 275 | dns.Extra = make([]RR, dh.Arcount) 276 | 277 | for i := 0; i < len(dns.Question); i++ { 278 | off, err = UnpackStruct(&dns.Question[i], msg, off) 279 | if err != nil { 280 | return nil, nil, err 281 | } 282 | } 283 | for i := 0; i < len(dns.Answer); i++ { 284 | dns.Answer[i], off, err = UnpackRR(msg, off) 285 | if err != nil { 286 | return nil, nil, err 287 | } 288 | } 289 | for i := 0; i < len(dns.Ns); i++ { 290 | dns.Ns[i], off, err = UnpackRR(msg, off) 291 | if err != nil { 292 | return nil, nil, err 293 | } 294 | } 295 | for i := 0; i < len(dns.Extra); i++ { 296 | tsigoff = off 297 | dns.Extra[i], off, err = UnpackRR(msg, off) 298 | if err != nil { 299 | return nil, nil, err 300 | } 301 | if dns.Extra[i].Header().Rrtype == TypeTSIG { 302 | rr = dns.Extra[i].(*TSIG) 303 | // Adjust Arcount. 304 | arcount, _ := unpackUint16(msg, 10) 305 | msg[10], msg[11] = packUint16(arcount - 1) 306 | break 307 | } 308 | } 309 | if rr == nil { 310 | return nil, nil, ErrNoSig 311 | } 312 | return msg[:tsigoff], rr, nil 313 | } 314 | 315 | // Translate the TSIG time signed into a date. There is no 316 | // need for RFC1982 calculations as this date is 48 bits. 317 | func tsigTimeToString(t uint64) string { 318 | ti := time.Unix(int64(t), 0).UTC() 319 | return ti.Format("20060102150405") 320 | } 321 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/types_generate.go: -------------------------------------------------------------------------------- 1 | //+build ignore 2 | 3 | // types_generate.go is meant to run with go generate. It will use 4 | // go/{importer,types} to track down all the RR struct types. Then for each type 5 | // it will generate conversion tables (TypeToRR and TypeToString) and banal 6 | // methods (len, Header, copy) based on the struct tags. The generated source is 7 | // written to ztypes.go, and is meant to be checked into git. 8 | package main 9 | 10 | import ( 11 | "bytes" 12 | "fmt" 13 | "go/format" 14 | "go/importer" 15 | "go/types" 16 | "log" 17 | "os" 18 | "strings" 19 | "text/template" 20 | ) 21 | 22 | var skipLen = map[string]struct{}{ 23 | "NSEC": struct{}{}, 24 | "NSEC3": struct{}{}, 25 | "OPT": struct{}{}, 26 | "WKS": struct{}{}, 27 | "IPSECKEY": struct{}{}, 28 | } 29 | 30 | var packageHdr = ` 31 | // *** DO NOT MODIFY *** 32 | // AUTOGENERATED BY go generate 33 | 34 | package dns 35 | 36 | import ( 37 | "encoding/base64" 38 | "net" 39 | ) 40 | 41 | ` 42 | 43 | var TypeToRR = template.Must(template.New("TypeToRR").Parse(` 44 | // TypeToRR is a map of constructors for each RR type. 45 | var TypeToRR = map[uint16]func() RR{ 46 | {{range .}}{{if ne . "RFC3597"}} Type{{.}}: func() RR { return new({{.}}) }, 47 | {{end}}{{end}} } 48 | 49 | `)) 50 | 51 | var typeToString = template.Must(template.New("typeToString").Parse(` 52 | // TypeToString is a map of strings for each RR type. 53 | var TypeToString = map[uint16]string{ 54 | {{range .}}{{if ne . "NSAPPTR"}} Type{{.}}: "{{.}}", 55 | {{end}}{{end}} TypeNSAPPTR: "NSAP-PTR", 56 | } 57 | 58 | `)) 59 | 60 | var headerFunc = template.Must(template.New("headerFunc").Parse(` 61 | // Header() functions 62 | {{range .}} func (rr *{{.}}) Header() *RR_Header { return &rr.Hdr } 63 | {{end}} 64 | 65 | `)) 66 | 67 | // getTypeStruct will take a type and the package scope, and return the 68 | // (innermost) struct if the type is considered a RR type (currently defined as 69 | // those structs beginning with a RR_Header, could be redefined as implementing 70 | // the RR interface). The bool return value indicates if embedded structs were 71 | // resolved. 72 | func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) { 73 | st, ok := t.Underlying().(*types.Struct) 74 | if !ok { 75 | return nil, false 76 | } 77 | if st.Field(0).Type() == scope.Lookup("RR_Header").Type() { 78 | return st, false 79 | } 80 | if st.Field(0).Anonymous() { 81 | st, _ := getTypeStruct(st.Field(0).Type(), scope) 82 | return st, true 83 | } 84 | return nil, false 85 | } 86 | 87 | func main() { 88 | // Import and type-check the package 89 | pkg, err := importer.Default().Import("github.com/miekg/dns") 90 | fatalIfErr(err) 91 | scope := pkg.Scope() 92 | 93 | // Collect constants like TypeX 94 | var numberedTypes []string 95 | for _, name := range scope.Names() { 96 | o := scope.Lookup(name) 97 | if o == nil || !o.Exported() { 98 | continue 99 | } 100 | b, ok := o.Type().(*types.Basic) 101 | if !ok || b.Kind() != types.Uint16 { 102 | continue 103 | } 104 | if !strings.HasPrefix(o.Name(), "Type") { 105 | continue 106 | } 107 | name := strings.TrimPrefix(o.Name(), "Type") 108 | if name == "PrivateRR" { 109 | continue 110 | } 111 | numberedTypes = append(numberedTypes, name) 112 | } 113 | 114 | // Collect actual types (*X) 115 | var namedTypes []string 116 | for _, name := range scope.Names() { 117 | o := scope.Lookup(name) 118 | if o == nil || !o.Exported() { 119 | continue 120 | } 121 | if st, _ := getTypeStruct(o.Type(), scope); st == nil { 122 | continue 123 | } 124 | if name == "PrivateRR" { 125 | continue 126 | } 127 | 128 | // Check if corresponding TypeX exists 129 | if scope.Lookup("Type"+o.Name()) == nil && o.Name() != "RFC3597" { 130 | log.Fatalf("Constant Type%s does not exist.", o.Name()) 131 | } 132 | 133 | namedTypes = append(namedTypes, o.Name()) 134 | } 135 | 136 | b := &bytes.Buffer{} 137 | b.WriteString(packageHdr) 138 | 139 | // Generate TypeToRR 140 | fatalIfErr(TypeToRR.Execute(b, namedTypes)) 141 | 142 | // Generate typeToString 143 | fatalIfErr(typeToString.Execute(b, numberedTypes)) 144 | 145 | // Generate headerFunc 146 | fatalIfErr(headerFunc.Execute(b, namedTypes)) 147 | 148 | // Generate len() 149 | fmt.Fprint(b, "// len() functions\n") 150 | for _, name := range namedTypes { 151 | if _, ok := skipLen[name]; ok { 152 | continue 153 | } 154 | o := scope.Lookup(name) 155 | st, isEmbedded := getTypeStruct(o.Type(), scope) 156 | if isEmbedded { 157 | continue 158 | } 159 | fmt.Fprintf(b, "func (rr *%s) len() int {\n", name) 160 | fmt.Fprintf(b, "l := rr.Hdr.len()\n") 161 | for i := 1; i < st.NumFields(); i++ { 162 | o := func(s string) { fmt.Fprintf(b, s, st.Field(i).Name()) } 163 | 164 | if _, ok := st.Field(i).Type().(*types.Slice); ok { 165 | switch st.Tag(i) { 166 | case `dns:"-"`: 167 | // ignored 168 | case `dns:"cdomain-name"`, `dns:"domain-name"`, `dns:"txt"`: 169 | o("for _, x := range rr.%s { l += len(x) + 1 }\n") 170 | default: 171 | log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) 172 | } 173 | continue 174 | } 175 | 176 | switch st.Tag(i) { 177 | case `dns:"-"`: 178 | // ignored 179 | case `dns:"cdomain-name"`, `dns:"domain-name"`: 180 | o("l += len(rr.%s) + 1\n") 181 | case `dns:"octet"`: 182 | o("l += len(rr.%s)\n") 183 | case `dns:"base64"`: 184 | o("l += base64.StdEncoding.DecodedLen(len(rr.%s))\n") 185 | case `dns:"size-hex"`, `dns:"hex"`: 186 | o("l += len(rr.%s)/2 + 1\n") 187 | case `dns:"a"`: 188 | o("l += net.IPv4len // %s\n") 189 | case `dns:"aaaa"`: 190 | o("l += net.IPv6len // %s\n") 191 | case `dns:"txt"`: 192 | o("for _, t := range rr.%s { l += len(t) + 1 }\n") 193 | case `dns:"uint48"`: 194 | o("l += 6 // %s\n") 195 | case "": 196 | switch st.Field(i).Type().(*types.Basic).Kind() { 197 | case types.Uint8: 198 | o("l += 1 // %s\n") 199 | case types.Uint16: 200 | o("l += 2 // %s\n") 201 | case types.Uint32: 202 | o("l += 4 // %s\n") 203 | case types.Uint64: 204 | o("l += 8 // %s\n") 205 | case types.String: 206 | o("l += len(rr.%s) + 1\n") 207 | default: 208 | log.Fatalln(name, st.Field(i).Name()) 209 | } 210 | default: 211 | log.Fatalln(name, st.Field(i).Name(), st.Tag(i)) 212 | } 213 | } 214 | fmt.Fprintf(b, "return l }\n") 215 | } 216 | 217 | // Generate copy() 218 | fmt.Fprint(b, "// copy() functions\n") 219 | for _, name := range namedTypes { 220 | o := scope.Lookup(name) 221 | st, isEmbedded := getTypeStruct(o.Type(), scope) 222 | if isEmbedded { 223 | continue 224 | } 225 | fmt.Fprintf(b, "func (rr *%s) copy() RR {\n", name) 226 | fields := []string{"*rr.Hdr.copyHeader()"} 227 | for i := 1; i < st.NumFields(); i++ { 228 | f := st.Field(i).Name() 229 | if sl, ok := st.Field(i).Type().(*types.Slice); ok { 230 | t := sl.Underlying().String() 231 | t = strings.TrimPrefix(t, "[]") 232 | t = strings.TrimPrefix(t, "github.com/miekg/dns.") 233 | fmt.Fprintf(b, "%s := make([]%s, len(rr.%s)); copy(%s, rr.%s)\n", 234 | f, t, f, f, f) 235 | fields = append(fields, f) 236 | continue 237 | } 238 | if st.Field(i).Type().String() == "net.IP" { 239 | fields = append(fields, "copyIP(rr."+f+")") 240 | continue 241 | } 242 | fields = append(fields, "rr."+f) 243 | } 244 | fmt.Fprintf(b, "return &%s{%s}\n", name, strings.Join(fields, ",")) 245 | fmt.Fprintf(b, "}\n") 246 | } 247 | 248 | // gofmt 249 | res, err := format.Source(b.Bytes()) 250 | if err != nil { 251 | b.WriteTo(os.Stderr) 252 | log.Fatal(err) 253 | } 254 | 255 | // write result 256 | f, err := os.Create("ztypes.go") 257 | fatalIfErr(err) 258 | defer f.Close() 259 | f.Write(res) 260 | } 261 | 262 | func fatalIfErr(err error) { 263 | if err != nil { 264 | log.Fatal(err) 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/udp.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package dns 4 | 5 | import ( 6 | "net" 7 | "syscall" 8 | ) 9 | 10 | // SessionUDP holds the remote address and the associated 11 | // out-of-band data. 12 | type SessionUDP struct { 13 | raddr *net.UDPAddr 14 | context []byte 15 | } 16 | 17 | // RemoteAddr returns the remote network address. 18 | func (s *SessionUDP) RemoteAddr() net.Addr { return s.raddr } 19 | 20 | // setUDPSocketOptions sets the UDP socket options. 21 | // This function is implemented on a per platform basis. See udp_*.go for more details 22 | func setUDPSocketOptions(conn *net.UDPConn) error { 23 | sa, err := getUDPSocketName(conn) 24 | if err != nil { 25 | return err 26 | } 27 | switch sa.(type) { 28 | case *syscall.SockaddrInet6: 29 | v6only, err := getUDPSocketOptions6Only(conn) 30 | if err != nil { 31 | return err 32 | } 33 | setUDPSocketOptions6(conn) 34 | if !v6only { 35 | setUDPSocketOptions4(conn) 36 | } 37 | case *syscall.SockaddrInet4: 38 | setUDPSocketOptions4(conn) 39 | } 40 | return nil 41 | } 42 | 43 | // ReadFromSessionUDP acts just like net.UDPConn.ReadFrom(), but returns a session object instead of a 44 | // net.UDPAddr. 45 | func ReadFromSessionUDP(conn *net.UDPConn, b []byte) (int, *SessionUDP, error) { 46 | oob := make([]byte, 40) 47 | n, oobn, _, raddr, err := conn.ReadMsgUDP(b, oob) 48 | if err != nil { 49 | return n, nil, err 50 | } 51 | return n, &SessionUDP{raddr, oob[:oobn]}, err 52 | } 53 | 54 | // WriteToSessionUDP acts just like net.UDPConn.WritetTo(), but uses a *SessionUDP instead of a net.Addr. 55 | func WriteToSessionUDP(conn *net.UDPConn, b []byte, session *SessionUDP) (int, error) { 56 | n, _, err := conn.WriteMsgUDP(b, session.context, session.raddr) 57 | return n, err 58 | } 59 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/udp_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package dns 4 | 5 | // See: 6 | // * http://stackoverflow.com/questions/3062205/setting-the-source-ip-for-a-udp-socket and 7 | // * http://blog.powerdns.com/2012/10/08/on-binding-datagram-udp-sockets-to-the-any-addresses/ 8 | // 9 | // Why do we need this: When listening on 0.0.0.0 with UDP so kernel decides what is the outgoing 10 | // interface, this might not always be the correct one. This code will make sure the egress 11 | // packet's interface matched the ingress' one. 12 | 13 | import ( 14 | "net" 15 | "syscall" 16 | ) 17 | 18 | // setUDPSocketOptions4 prepares the v4 socket for sessions. 19 | func setUDPSocketOptions4(conn *net.UDPConn) error { 20 | file, err := conn.File() 21 | if err != nil { 22 | return err 23 | } 24 | if err := syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IP, syscall.IP_PKTINFO, 1); err != nil { 25 | return err 26 | } 27 | return nil 28 | } 29 | 30 | // setUDPSocketOptions6 prepares the v6 socket for sessions. 31 | func setUDPSocketOptions6(conn *net.UDPConn) error { 32 | file, err := conn.File() 33 | if err != nil { 34 | return err 35 | } 36 | if err := syscall.SetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IPV6_RECVPKTINFO, 1); err != nil { 37 | return err 38 | } 39 | return nil 40 | } 41 | 42 | // getUDPSocketOption6Only return true if the socket is v6 only and false when it is v4/v6 combined 43 | // (dualstack). 44 | func getUDPSocketOptions6Only(conn *net.UDPConn) (bool, error) { 45 | file, err := conn.File() 46 | if err != nil { 47 | return false, err 48 | } 49 | // dual stack. See http://stackoverflow.com/questions/1618240/how-to-support-both-ipv4-and-ipv6-connections 50 | v6only, err := syscall.GetsockoptInt(int(file.Fd()), syscall.IPPROTO_IPV6, syscall.IPV6_V6ONLY) 51 | if err != nil { 52 | return false, err 53 | } 54 | return v6only == 1, nil 55 | } 56 | 57 | func getUDPSocketName(conn *net.UDPConn) (syscall.Sockaddr, error) { 58 | file, err := conn.File() 59 | if err != nil { 60 | return nil, err 61 | } 62 | return syscall.Getsockname(int(file.Fd())) 63 | } 64 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/udp_other.go: -------------------------------------------------------------------------------- 1 | // +build !linux 2 | 3 | package dns 4 | 5 | import ( 6 | "net" 7 | "syscall" 8 | ) 9 | 10 | // These do nothing. See udp_linux.go for an example of how to implement this. 11 | 12 | // We tried to adhire to some kind of naming scheme. 13 | 14 | func setUDPSocketOptions4(conn *net.UDPConn) error { return nil } 15 | func setUDPSocketOptions6(conn *net.UDPConn) error { return nil } 16 | func getUDPSocketOptions6Only(conn *net.UDPConn) (bool, error) { return false, nil } 17 | func getUDPSocketName(conn *net.UDPConn) (syscall.Sockaddr, error) { return nil, nil } 18 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/udp_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package dns 4 | 5 | import "net" 6 | 7 | type SessionUDP struct { 8 | raddr *net.UDPAddr 9 | } 10 | 11 | // ReadFromSessionUDP acts just like net.UDPConn.ReadFrom(), but returns a session object instead of a 12 | // net.UDPAddr. 13 | func ReadFromSessionUDP(conn *net.UDPConn, b []byte) (int, *SessionUDP, error) { 14 | n, raddr, err := conn.ReadFrom(b) 15 | if err != nil { 16 | return n, nil, err 17 | } 18 | session := &SessionUDP{raddr.(*net.UDPAddr)} 19 | return n, session, err 20 | } 21 | 22 | // WriteToSessionUDP acts just like net.UDPConn.WritetTo(), but uses a *SessionUDP instead of a net.Addr. 23 | func WriteToSessionUDP(conn *net.UDPConn, b []byte, session *SessionUDP) (int, error) { 24 | n, err := conn.WriteTo(b, session.raddr) 25 | return n, err 26 | } 27 | 28 | func (s *SessionUDP) RemoteAddr() net.Addr { return s.raddr } 29 | 30 | // setUDPSocketOptions sets the UDP socket options. 31 | // This function is implemented on a per platform basis. See udp_*.go for more details 32 | func setUDPSocketOptions(conn *net.UDPConn) error { 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/update.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | // NameUsed sets the RRs in the prereq section to 4 | // "Name is in use" RRs. RFC 2136 section 2.4.4. 5 | func (u *Msg) NameUsed(rr []RR) { 6 | u.Answer = make([]RR, len(rr)) 7 | for i, r := range rr { 8 | u.Answer[i] = &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassANY}} 9 | } 10 | } 11 | 12 | // NameNotUsed sets the RRs in the prereq section to 13 | // "Name is in not use" RRs. RFC 2136 section 2.4.5. 14 | func (u *Msg) NameNotUsed(rr []RR) { 15 | u.Answer = make([]RR, len(rr)) 16 | for i, r := range rr { 17 | u.Answer[i] = &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassNONE}} 18 | } 19 | } 20 | 21 | // Used sets the RRs in the prereq section to 22 | // "RRset exists (value dependent -- with rdata)" RRs. RFC 2136 section 2.4.2. 23 | func (u *Msg) Used(rr []RR) { 24 | if len(u.Question) == 0 { 25 | panic("dns: empty question section") 26 | } 27 | u.Answer = make([]RR, len(rr)) 28 | for i, r := range rr { 29 | u.Answer[i] = r 30 | u.Answer[i].Header().Class = u.Question[0].Qclass 31 | } 32 | } 33 | 34 | // RRsetUsed sets the RRs in the prereq section to 35 | // "RRset exists (value independent -- no rdata)" RRs. RFC 2136 section 2.4.1. 36 | func (u *Msg) RRsetUsed(rr []RR) { 37 | u.Answer = make([]RR, len(rr)) 38 | for i, r := range rr { 39 | u.Answer[i] = r 40 | u.Answer[i].Header().Class = ClassANY 41 | u.Answer[i].Header().Ttl = 0 42 | u.Answer[i].Header().Rdlength = 0 43 | } 44 | } 45 | 46 | // RRsetNotUsed sets the RRs in the prereq section to 47 | // "RRset does not exist" RRs. RFC 2136 section 2.4.3. 48 | func (u *Msg) RRsetNotUsed(rr []RR) { 49 | u.Answer = make([]RR, len(rr)) 50 | for i, r := range rr { 51 | u.Answer[i] = r 52 | u.Answer[i].Header().Class = ClassNONE 53 | u.Answer[i].Header().Rdlength = 0 54 | u.Answer[i].Header().Ttl = 0 55 | } 56 | } 57 | 58 | // Insert creates a dynamic update packet that adds an complete RRset, see RFC 2136 section 2.5.1. 59 | func (u *Msg) Insert(rr []RR) { 60 | if len(u.Question) == 0 { 61 | panic("dns: empty question section") 62 | } 63 | u.Ns = make([]RR, len(rr)) 64 | for i, r := range rr { 65 | u.Ns[i] = r 66 | u.Ns[i].Header().Class = u.Question[0].Qclass 67 | } 68 | } 69 | 70 | // RemoveRRset creates a dynamic update packet that deletes an RRset, see RFC 2136 section 2.5.2. 71 | func (u *Msg) RemoveRRset(rr []RR) { 72 | u.Ns = make([]RR, len(rr)) 73 | for i, r := range rr { 74 | u.Ns[i] = &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: r.Header().Rrtype, Class: ClassANY}} 75 | } 76 | } 77 | 78 | // RemoveName creates a dynamic update packet that deletes all RRsets of a name, see RFC 2136 section 2.5.3 79 | func (u *Msg) RemoveName(rr []RR) { 80 | u.Ns = make([]RR, len(rr)) 81 | for i, r := range rr { 82 | u.Ns[i] = &ANY{Hdr: RR_Header{Name: r.Header().Name, Ttl: 0, Rrtype: TypeANY, Class: ClassANY}} 83 | } 84 | } 85 | 86 | // Remove creates a dynamic update packet deletes RR from the RRSset, see RFC 2136 section 2.5.4 87 | func (u *Msg) Remove(rr []RR) { 88 | u.Ns = make([]RR, len(rr)) 89 | for i, r := range rr { 90 | u.Ns[i] = r 91 | u.Ns[i].Header().Class = ClassNONE 92 | u.Ns[i].Header().Ttl = 0 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/xfr.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Envelope is used when doing a zone transfer with a remote server. 8 | type Envelope struct { 9 | RR []RR // The set of RRs in the answer section of the xfr reply message. 10 | Error error // If something went wrong, this contains the error. 11 | } 12 | 13 | // A Transfer defines parameters that are used during a zone transfer. 14 | type Transfer struct { 15 | *Conn 16 | DialTimeout time.Duration // net.DialTimeout, defaults to 2 seconds 17 | ReadTimeout time.Duration // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds 18 | WriteTimeout time.Duration // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds 19 | TsigSecret map[string]string // Secret(s) for Tsig map[], zonename must be fully qualified 20 | tsigTimersOnly bool 21 | } 22 | 23 | // Think we need to away to stop the transfer 24 | 25 | // In performs an incoming transfer with the server in a. 26 | // If you would like to set the source IP, or some other attribute 27 | // of a Dialer for a Transfer, you can do so by specifying the attributes 28 | // in the Transfer.Conn: 29 | // 30 | // d := net.Dialer{LocalAddr: transfer_source} 31 | // con, err := d.Dial("tcp", master) 32 | // dnscon := &dns.Conn{Conn:con} 33 | // transfer = &dns.Transfer{Conn: dnscon} 34 | // channel, err := transfer.In(message, master) 35 | // 36 | func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) { 37 | timeout := dnsTimeout 38 | if t.DialTimeout != 0 { 39 | timeout = t.DialTimeout 40 | } 41 | if t.Conn == nil { 42 | t.Conn, err = DialTimeout("tcp", a, timeout) 43 | if err != nil { 44 | return nil, err 45 | } 46 | } 47 | if err := t.WriteMsg(q); err != nil { 48 | return nil, err 49 | } 50 | env = make(chan *Envelope) 51 | go func() { 52 | if q.Question[0].Qtype == TypeAXFR { 53 | go t.inAxfr(q.Id, env) 54 | return 55 | } 56 | if q.Question[0].Qtype == TypeIXFR { 57 | go t.inIxfr(q.Id, env) 58 | return 59 | } 60 | }() 61 | return env, nil 62 | } 63 | 64 | func (t *Transfer) inAxfr(id uint16, c chan *Envelope) { 65 | first := true 66 | defer t.Close() 67 | defer close(c) 68 | timeout := dnsTimeout 69 | if t.ReadTimeout != 0 { 70 | timeout = t.ReadTimeout 71 | } 72 | for { 73 | t.Conn.SetReadDeadline(time.Now().Add(timeout)) 74 | in, err := t.ReadMsg() 75 | if err != nil { 76 | c <- &Envelope{nil, err} 77 | return 78 | } 79 | if id != in.Id { 80 | c <- &Envelope{in.Answer, ErrId} 81 | return 82 | } 83 | if first { 84 | if !isSOAFirst(in) { 85 | c <- &Envelope{in.Answer, ErrSoa} 86 | return 87 | } 88 | first = !first 89 | // only one answer that is SOA, receive more 90 | if len(in.Answer) == 1 { 91 | t.tsigTimersOnly = true 92 | c <- &Envelope{in.Answer, nil} 93 | continue 94 | } 95 | } 96 | 97 | if !first { 98 | t.tsigTimersOnly = true // Subsequent envelopes use this. 99 | if isSOALast(in) { 100 | c <- &Envelope{in.Answer, nil} 101 | return 102 | } 103 | c <- &Envelope{in.Answer, nil} 104 | } 105 | } 106 | } 107 | 108 | func (t *Transfer) inIxfr(id uint16, c chan *Envelope) { 109 | serial := uint32(0) // The first serial seen is the current server serial 110 | first := true 111 | defer t.Close() 112 | defer close(c) 113 | timeout := dnsTimeout 114 | if t.ReadTimeout != 0 { 115 | timeout = t.ReadTimeout 116 | } 117 | for { 118 | t.SetReadDeadline(time.Now().Add(timeout)) 119 | in, err := t.ReadMsg() 120 | if err != nil { 121 | c <- &Envelope{nil, err} 122 | return 123 | } 124 | if id != in.Id { 125 | c <- &Envelope{in.Answer, ErrId} 126 | return 127 | } 128 | if first { 129 | // A single SOA RR signals "no changes" 130 | if len(in.Answer) == 1 && isSOAFirst(in) { 131 | c <- &Envelope{in.Answer, nil} 132 | return 133 | } 134 | 135 | // Check if the returned answer is ok 136 | if !isSOAFirst(in) { 137 | c <- &Envelope{in.Answer, ErrSoa} 138 | return 139 | } 140 | // This serial is important 141 | serial = in.Answer[0].(*SOA).Serial 142 | first = !first 143 | } 144 | 145 | // Now we need to check each message for SOA records, to see what we need to do 146 | if !first { 147 | t.tsigTimersOnly = true 148 | // If the last record in the IXFR contains the servers' SOA, we should quit 149 | if v, ok := in.Answer[len(in.Answer)-1].(*SOA); ok { 150 | if v.Serial == serial { 151 | c <- &Envelope{in.Answer, nil} 152 | return 153 | } 154 | } 155 | c <- &Envelope{in.Answer, nil} 156 | } 157 | } 158 | } 159 | 160 | // Out performs an outgoing transfer with the client connecting in w. 161 | // Basic use pattern: 162 | // 163 | // ch := make(chan *dns.Envelope) 164 | // tr := new(dns.Transfer) 165 | // tr.Out(w, r, ch) 166 | // c <- &dns.Envelope{RR: []dns.RR{soa, rr1, rr2, rr3, soa}} 167 | // close(ch) 168 | // w.Hijack() 169 | // // w.Close() // Client closes connection 170 | // 171 | // The server is responsible for sending the correct sequence of RRs through the 172 | // channel ch. 173 | func (t *Transfer) Out(w ResponseWriter, q *Msg, ch chan *Envelope) error { 174 | for x := range ch { 175 | r := new(Msg) 176 | // Compress? 177 | r.SetReply(q) 178 | r.Authoritative = true 179 | // assume it fits TODO(miek): fix 180 | r.Answer = append(r.Answer, x.RR...) 181 | if err := w.WriteMsg(r); err != nil { 182 | return err 183 | } 184 | } 185 | w.TsigTimersOnly(true) 186 | return nil 187 | } 188 | 189 | // ReadMsg reads a message from the transfer connection t. 190 | func (t *Transfer) ReadMsg() (*Msg, error) { 191 | m := new(Msg) 192 | p := make([]byte, MaxMsgSize) 193 | n, err := t.Read(p) 194 | if err != nil && n == 0 { 195 | return nil, err 196 | } 197 | p = p[:n] 198 | if err := m.Unpack(p); err != nil { 199 | return nil, err 200 | } 201 | if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil { 202 | if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok { 203 | return m, ErrSecret 204 | } 205 | // Need to work on the original message p, as that was used to calculate the tsig. 206 | err = TsigVerify(p, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly) 207 | t.tsigRequestMAC = ts.MAC 208 | } 209 | return m, err 210 | } 211 | 212 | // WriteMsg writes a message through the transfer connection t. 213 | func (t *Transfer) WriteMsg(m *Msg) (err error) { 214 | var out []byte 215 | if ts := m.IsTsig(); ts != nil && t.TsigSecret != nil { 216 | if _, ok := t.TsigSecret[ts.Hdr.Name]; !ok { 217 | return ErrSecret 218 | } 219 | out, t.tsigRequestMAC, err = TsigGenerate(m, t.TsigSecret[ts.Hdr.Name], t.tsigRequestMAC, t.tsigTimersOnly) 220 | } else { 221 | out, err = m.Pack() 222 | } 223 | if err != nil { 224 | return err 225 | } 226 | if _, err = t.Write(out); err != nil { 227 | return err 228 | } 229 | return nil 230 | } 231 | 232 | func isSOAFirst(in *Msg) bool { 233 | if len(in.Answer) > 0 { 234 | return in.Answer[0].Header().Rrtype == TypeSOA 235 | } 236 | return false 237 | } 238 | 239 | func isSOALast(in *Msg) bool { 240 | if len(in.Answer) > 0 { 241 | return in.Answer[len(in.Answer)-1].Header().Rrtype == TypeSOA 242 | } 243 | return false 244 | } 245 | -------------------------------------------------------------------------------- /Godeps/_workspace/src/github.com/miekg/dns/zgenerate.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // Parse the $GENERATE statement as used in BIND9 zones. 11 | // See http://www.zytrax.com/books/dns/ch8/generate.html for instance. 12 | // We are called after '$GENERATE '. After which we expect: 13 | // * the range (12-24/2) 14 | // * lhs (ownername) 15 | // * [[ttl][class]] 16 | // * type 17 | // * rhs (rdata) 18 | // But we are lazy here, only the range is parsed *all* occurences 19 | // of $ after that are interpreted. 20 | // Any error are returned as a string value, the empty string signals 21 | // "no error". 22 | func generate(l lex, c chan lex, t chan *Token, o string) string { 23 | step := 1 24 | if i := strings.IndexAny(l.token, "/"); i != -1 { 25 | if i+1 == len(l.token) { 26 | return "bad step in $GENERATE range" 27 | } 28 | if s, e := strconv.Atoi(l.token[i+1:]); e == nil { 29 | if s < 0 { 30 | return "bad step in $GENERATE range" 31 | } 32 | step = s 33 | } else { 34 | return "bad step in $GENERATE range" 35 | } 36 | l.token = l.token[:i] 37 | } 38 | sx := strings.SplitN(l.token, "-", 2) 39 | if len(sx) != 2 { 40 | return "bad start-stop in $GENERATE range" 41 | } 42 | start, err := strconv.Atoi(sx[0]) 43 | if err != nil { 44 | return "bad start in $GENERATE range" 45 | } 46 | end, err := strconv.Atoi(sx[1]) 47 | if err != nil { 48 | return "bad stop in $GENERATE range" 49 | } 50 | if end < 0 || start < 0 || end < start { 51 | return "bad range in $GENERATE range" 52 | } 53 | 54 | <-c // _BLANK 55 | // Create a complete new string, which we then parse again. 56 | s := "" 57 | BuildRR: 58 | l = <-c 59 | if l.value != zNewline && l.value != zEOF { 60 | s += l.token 61 | goto BuildRR 62 | } 63 | for i := start; i <= end; i += step { 64 | var ( 65 | escape bool 66 | dom bytes.Buffer 67 | mod string 68 | err string 69 | offset int 70 | ) 71 | 72 | for j := 0; j < len(s); j++ { // No 'range' because we need to jump around 73 | switch s[j] { 74 | case '\\': 75 | if escape { 76 | dom.WriteByte('\\') 77 | escape = false 78 | continue 79 | } 80 | escape = true 81 | case '$': 82 | mod = "%d" 83 | offset = 0 84 | if escape { 85 | dom.WriteByte('$') 86 | escape = false 87 | continue 88 | } 89 | escape = false 90 | if j+1 >= len(s) { // End of the string 91 | dom.WriteString(fmt.Sprintf(mod, i+offset)) 92 | continue 93 | } else { 94 | if s[j+1] == '$' { 95 | dom.WriteByte('$') 96 | j++ 97 | continue 98 | } 99 | } 100 | // Search for { and } 101 | if s[j+1] == '{' { // Modifier block 102 | sep := strings.Index(s[j+2:], "}") 103 | if sep == -1 { 104 | return "bad modifier in $GENERATE" 105 | } 106 | mod, offset, err = modToPrintf(s[j+2 : j+2+sep]) 107 | if err != "" { 108 | return err 109 | } 110 | j += 2 + sep // Jump to it 111 | } 112 | dom.WriteString(fmt.Sprintf(mod, i+offset)) 113 | default: 114 | if escape { // Pretty useless here 115 | escape = false 116 | continue 117 | } 118 | dom.WriteByte(s[j]) 119 | } 120 | } 121 | // Re-parse the RR and send it on the current channel t 122 | rx, e := NewRR("$ORIGIN " + o + "\n" + dom.String()) 123 | if e != nil { 124 | return e.(*ParseError).err 125 | } 126 | t <- &Token{RR: rx} 127 | // Its more efficient to first built the rrlist and then parse it in 128 | // one go! But is this a problem? 129 | } 130 | return "" 131 | } 132 | 133 | // Convert a $GENERATE modifier 0,0,d to something Printf can deal with. 134 | func modToPrintf(s string) (string, int, string) { 135 | xs := strings.SplitN(s, ",", 3) 136 | if len(xs) != 3 { 137 | return "", 0, "bad modifier in $GENERATE" 138 | } 139 | // xs[0] is offset, xs[1] is width, xs[2] is base 140 | if xs[2] != "o" && xs[2] != "d" && xs[2] != "x" && xs[2] != "X" { 141 | return "", 0, "bad base in $GENERATE" 142 | } 143 | offset, err := strconv.Atoi(xs[0]) 144 | if err != nil || offset > 255 { 145 | return "", 0, "bad offset in $GENERATE" 146 | } 147 | width, err := strconv.Atoi(xs[1]) 148 | if err != nil || width > 255 { 149 | return "", offset, "bad width in $GENERATE" 150 | } 151 | switch { 152 | case width < 0: 153 | return "", offset, "bad width in $GENERATE" 154 | case width == 0: 155 | return "%" + xs[1] + xs[2], offset, "" 156 | } 157 | return "%0" + xs[1] + xs[2], offset, "" 158 | } 159 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > :warning: **As of Docker v1.12 `wagl` is now obsolete**, as Swarm is integrated into 2 | > the Docker Engine and has built in service discovery and routing 3 | > features. You should not be using `wagl` anymore. :warning: 4 | 5 | ## wagl: DNS Service Discovery for Docker Swarm v1.0 6 | 7 | `wagl` runs inside your [Docker Swarm][sw] cluster and provides 8 | DNS-based service discovery (using DNS A and SRV records) and 9 | simple load balancing by rotating the list of IP addresses in 10 | DNS records. 11 | 12 | For instance, if you run your API container with command: 13 | 14 | docker run -d -l dns.service=api -p 80:80 nginx 15 | 16 | other containers in the cluster will be able to reach this container using URL 17 | **`http://api.swarm`**. It is a **minimalist** solution, yet handles most of the 18 | basic DNS service discovery functionality well ––but we're open to pull 19 | requests. 20 | 21 | `wagl` runs inside a container in the Swarm cluster (preferably on manager 22 | nodes) and is easy to deploy. 23 | 24 | 25 | ### [User Guide](_docs/0-Index.md) 26 | 27 | 0. [**wagl Command-Line Interface**](_docs/1-Command-Line.md) 28 | 0. [**Deploying wagl**](_docs/2-Deploying.md) 29 | 0. [**Service Naming for DNS**](_docs/3-Service-Naming.md) 30 | 0. [**DNS Forwarding for External Domains**](_docs/4-External-DNS.md) 31 | 0. [**Best Practices**](_docs/5-Best-Practices.md) 32 | 33 | ### Demo 34 | 35 | Watch the demo at: https://www.youtube.com/watch?v=H7dr6lZqw6I 36 | [![](http://cl.ly/image/330U0j280J27/Image%202015-10-15%20at%201.03.49%20PM.png)](https://www.youtube.com/watch?v=H7dr6lZqw6I) 37 | 38 | ### 5-Minute Tutorial 39 | 40 | Let's create a Docker Swarm cluster with [`docker-machine`][machine] and deploy 41 | a `wagl` container to serve as a DNS server to this cluster: 42 | 43 | **Step 0:** Download docker client and docker-swarm on your machine. 44 | 45 | **Step 1:** Obtain a Swarm discovery token: 46 | 47 | ```sh 48 | $ docker run --rm swarm create 49 | 9746027c20071fdabf9347203fc380fa 50 | ``` 51 | 52 | **Step 2:** Create a single master and 3-node Swarm cluster with docker-machine 53 | 54 | ```sh 55 | TOKEN=9746027c20071fdabf9347203fc380fa # <-- paste your token 56 | docker-machine create -d virtualbox --swarm --swarm-master --swarm-discovery token://$TOKEN swarm-m && \ 57 | for i in {0..2}; do docker-machine create -d virtualbox --swarm --swarm-discovery token://$TOKEN swarm-$i; done 58 | ``` 59 | 60 | **Step 3:** Deploy the `wagl` DNS container to the Swarm master node: 61 | 62 | ```sh 63 | docker-machine ssh swarm-m 64 | ``` 65 | 66 | and then run: 67 | 68 | ```sh 69 | docker run -d --restart=always \ 70 | --link=swarm-agent-master:swarm \ 71 | -v /var/lib/boot2docker/ca.pem:/certs/ca.pem \ 72 | -v /var/lib/boot2docker/server.pem:/certs/cert.pem \ 73 | -v /var/lib/boot2docker/server-key.pem:/certs/key.pem \ 74 | -p 53:53/udp \ 75 | --name=dns \ 76 | ahmet/wagl \ 77 | wagl --swarm tcp://swarm:3376 \ 78 | --swarm-cert-path /certs 79 | ``` 80 | 81 | The following command deploys a `wagl` container (named `dns`) pointing it to a 82 | “Swarm manager” running on the same node on `:3376` and starts listening for DNS 83 | queries on port 53. 84 | 85 | After the container is working (verify with `docker ps`), exit the SSH prompt. 86 | 87 | **Step 4:** Schedule some web server containers on your cluster. 88 | 89 | Pay attention to how we use Docker labels (`-l` argument) to name our services: 90 | 91 | ```sh 92 | $ eval $(docker-machine env --swarm swarm-m) 93 | $ docker run -d -l dns.service=blog -p 80:80 nginx 94 | $ docker run -d -l dns.service=api -l dns.domain=billing -p 80:80 nginx 95 | $ docker run -d -l dns.service=api -l dns.domain=billing -p 80:80 nginx 96 | ``` 97 | 98 | **Step 5:** Verify the DNS works! Start a container in the cluster 99 | with `--dns` argument as IP address where the `dns` container running (in this 100 | case, master node) and make a request for `http://blog.swarm`: 101 | 102 | ```sh 103 | $ master=$(docker-machine ip swarm-m) 104 | $ docker run -it --dns $master busybox 105 | / # wget -qO- http://blog.swarm 106 | Connecting to blog.swarm (192.168.99.101:80) 107 | 108 | 109 | 110 | Welcome to nginx!... 111 | ``` 112 | Let's quit this container and launch a Debian container in the cluster to make DNS lookups 113 | to these `api` containers for A/SRV records: 114 | 115 | ```sh 116 | $ docker run -it --dns $master debian 117 | /# apt-get -q update && apt-get -qqy install dnsutils 118 | ... 119 | 120 | /# dig +short A api.billing.swarm 121 | 192.168.99.103 122 | 192.168.99.102 123 | 124 | /# dig +short SRV _api._tcp.billing.swarm 125 | 1 1 80 192.168.99.102. 126 | 1 1 80 192.168.99.103. 127 | ``` 128 | 129 | As you can notice the IP addresses are returned in random order for very naive 130 | load-balancing via the DNS records. 131 | 132 | **This is `wagl` in a nutshell. Play and experiment with it!** 133 | 134 | 135 | 136 | ### Not Implemented Properties 137 | 138 | Some features are not implemented for the sake of minimalism. Please be aware of 139 | these before using. 140 | 141 | * DNSSEC 142 | * IPv6 records (such as type AAAA) 143 | * Not-so-needed record types (NS, SOA, MX etc) 144 | * HTTP REST API to query records 145 | * Proper and configurable DNS message exchange timeouts 146 | * DNS over TCP: currently we only do UDP, I have no idea what happens to large 147 | DNS queries or answers. 148 | * Recursion on external nameservers: We just randomly pick an external NS to 149 | forward the request and if that fails we don't try others, we just call it 150 | failed. 151 | * Staleness checks are fragile to system clock changes because Go language does 152 | not have monotonically increasing clock implementation. 153 | 154 | For the not implemented features, we return `NOTIMP` status code in DNS answers 155 | and any server failure returns `SERVFAIL` status code. 156 | 157 | 158 | 159 | ### Authors 160 | 161 | * [Ahmet Alp Balkan](http://www.ahmetalpbalkan.com/) 162 | 163 | 164 | 165 | ### License 166 | 167 | This project is licensed under Apache License Version 2.0. Please refer to 168 | [LICENSE](LICENSE). 169 | 170 | 171 | 172 | ### Disclaimer 173 | 174 | This project is affiliated neither with Microsoft Corporation nor Docker Inc. 175 | 176 | 177 | 178 | #### Why the name? 179 | 180 | It turns out the scientists obvserved that the honeybees coming back from a food 181 | source to the bee hive, they tended to waggle about excitedly in a figure 8 182 | pattern which **shares the location of the food source** with other bees. This 183 | is called **“The Waggle Dance”**. It is actually pretty amazing, you should just 184 | [watch the video][waggle-dance]. 185 | 186 | [![](http://cl.ly/image/1b3B3q410e0z/Image%202015-08-27%20at%204.01.12%20PM.png)][waggle-dance] 187 | 188 | [sw]: https://github.com/docker/swarm 189 | [machine]: https://github.com/docker/machine 190 | [waggle-dance]: https://www.youtube.com/watch?v=bFDGPgXtK-U 191 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | * Use monotonic clocks to prevent old RRStore writes initiated finishing later 4 | on to be effective. 5 | * Use `gb` or `Godeps` to vendor dependencies. -------------------------------------------------------------------------------- /_docs/0-Index.md: -------------------------------------------------------------------------------- 1 | # wagl User Guide 2 | 3 | ### Table of contents 4 | 5 | 0. [**wagl Command-Line Interface**](1-Command-Line.md) 6 | 0. [**Deploying wagl**](2-Deploying.md) 7 | 0. [**Service Naming for DNS**](3-Service-Naming.md) 8 | 0. [**DNS Forwarding for External Domains**](4-External-DNS.md) 9 | 0. [**Best Practices**](5-Best-Practices.md) 10 | 11 | ### Advanced Topics 12 | 13 | 0. [**wagl Internals**](Advanced-1-Internals.md) 14 | -------------------------------------------------------------------------------- /_docs/1-Command-Line.md: -------------------------------------------------------------------------------- 1 | # Command Line Interface 2 | 3 | Good thing about `wagl` is you almost never need to know about the command-line 4 | arguments. It has good defaults. 5 | 6 | By running `wagl --help` (or `docker run --rm ahmet/wagl --help`) you 7 | can see the command line arguments: 8 | 9 | ``` 10 | $ wagl --help 11 | NAME: 12 | wagl - DNS service discovery for Docker Swarm clusters 13 | 14 | USAGE: 15 | wagl [options] command [command options] 16 | 17 | VERSION: 18 | 0.1 19 | 20 | OPTIONS: 21 | --bind ":53" IP:port on which the server shoud listen 22 | --swarm "127.0.0.1:2376" address of the Swarm manager 23 | --swarm-cert-path directory TLS certs for Swarm manager is stored [$DOCKER_CERT_PATH] 24 | --swarm-tlsverify verify remote Swarm's identity using TLS [$DOCKER_TLS_VERIFY] 25 | --domain "swarm." DNS domain (FQDN suffix) for which this server is authoritative 26 | --external use external nameservers to resolve DNS requests outside the domain (true by default) 27 | --ns [--ns option --ns option] external nameserver(s) to forward requests (default: nameservers in /etc/resolv.conf) 28 | --refresh "15s" how frequently refresh DNS table from cluster records 29 | --refresh-timeout "10s" time alotted for Swarm to list containers in the cluster 30 | --staleness "1m0s" how long to serve stale DNS records before exiting 31 | --help, -h show help 32 | --version, -v print the version 33 | ``` 34 | 35 | Some of these arguments can be also picked up from the environment (those 36 | specified as [$ENV] above). -------------------------------------------------------------------------------- /_docs/2-Deploying.md: -------------------------------------------------------------------------------- 1 | # Deploying and Running wagl 2 | 3 | Recommended way to deploy wagl is to use its Docker image 4 | (`ahmet/wagl`) and **run it in a container**. 5 | 6 | Also, it is easier to deploy `wagl` to the machines where the Swarm managers are 7 | running at (co-locating). Rest of this guide will assume you will be practicing 8 | as such. 9 | 10 | If your Docker Swarm cluster is running without TLS authentication, deploying 11 | and running `wagl` is as easy as the following: 12 | 13 | ```sh 14 | $ docker run -d --restart=always \ 15 | --link=:swarm \ 16 | -p 53:53/udp \ 17 | --name=dns \ 18 | ahmet/wagl \ 19 | --swarm tcp://swarm:3376 \ 20 | ``` 21 | 22 | In the example above, replace `` with the name of your 23 | Swarm manager and then by using linking `wagl` can now talk to the Swarm manager 24 | and query the containers to create DNS records. 25 | 26 | 27 | ### Using TLS 28 | 29 | If your Swarm manager is secured with TLS certificates, `wagl` needs to access 30 | these certificates. In order to do that, you can mount the certs into the 31 | container by using Docker Volumes feature and specify `--swarm-cert-path` 32 | argument. This assumes the specified directory has `key.pem`, `cert.pem`, 33 | `ca.pem` files: 34 | 35 | For instance when you set up a cluster with `docker-machine` the configuration 36 | looks like the following: 37 | 38 | ```sh 39 | $ docker run -d --restart=always \ 40 | --link=swarm-agent-master:swarm \ 41 | -v /var/lib/boot2docker/ca.pem:/certs/ca.pem \ 42 | -v /var/lib/boot2docker/server.pem:/certs/cert.pem \ 43 | -v /var/lib/boot2docker/server-key.pem:/certs/key.pem \ 44 | -p 53:53/udp \ 45 | --name=dns \ 46 | ahmet/wagl \ 47 | --swarm tcp://swarm:3376 \ 48 | --swarm-cert-path /certs 49 | ``` 50 | 51 | (Replace certificate paths and `swarm-agent-master` name according to your set 52 | up.) -------------------------------------------------------------------------------- /_docs/3-Service-Naming.md: -------------------------------------------------------------------------------- 1 | # Service Naming for DNS 2 | 3 | `wagl` discovers containers in your Swarm cluster using 4 | [Docker container labels][docker-labels]: 5 | 6 | * `dns.service` 7 | * `dns.domain` (optional) 8 | 9 | Labels can be specified with `-l` option to `docker run` command. 10 | 11 | If you run a container with `dns.service` label such as: 12 | 13 | docker run -d -l dns.service=web -p 5000:80 [image] 14 | 15 | The following DNS resource records will be created: 16 | 17 | > | Class | Domain | 18 | > |-------|---------| 19 | > | A | `web.swarm.` | 20 | > | SRV | `_web._tcp._swarm.` | 21 | 22 | If you add `dns.domain` label with `-l dns.domain=a.b` the generated DNS records 23 | will be: 24 | 25 | > | Class | Domain | 26 | > |-------|---------| 27 | > | A | `web.a.b.swarm.` | 28 | > | SRV | `_web._tcp.a.b.swarm.` | 29 | 30 | ### Port and Protocol for SRV records 31 | 32 | If the container has multiple ports open, only the port **appearing first** in 33 | the list is used to generate DNS SRV records. 34 | 35 | If it is an UDP port then the protocol segment in the SRV record generated would 36 | have an `_udp` segment instead of `_tcp`, such as 37 | `_servicename._udp[.domain.name].swarm`. 38 | 39 | [docker-labels]: https://docs.docker.com/userguide/labels-custom-metadata/ -------------------------------------------------------------------------------- /_docs/4-External-DNS.md: -------------------------------------------------------------------------------- 1 | # DNS Forwarding for External Domains 2 | 3 | `wagl` is by default enabled for resolving external domain names (outside 4 | `swarm.` domain), such as `google.com.` 5 | 6 | This is done forwarding the DNS query to the external nameservers. If no 7 | external nameserver is configured (with `--ns` argument), then the nameservers 8 | in `/etc/resolv.conf` are automatically picked up. 9 | 10 | You can specify different nameservers manually with `--ns` argument, such as: 11 | 12 | $ wagl [...options] --ns 8.8.8.8 --ns 8.8.4.4 13 | 14 | 15 | ### Disabling External Queries 16 | 17 | You can entirely disable the external forwarding with `--external=false` 18 | argument. In this case, wagl would return `SERVFAIL` code to external domain 19 | queries. 20 | 21 | This can be especially useful if you would like to handle external DNS queries 22 | with a complete and more robust DNS server implementation such as **BIND**. 23 | 24 | ### Tips for using with BIND 25 | 26 | In order to use BIND with `wagl`, you should start `wagl` on a different port 27 | such as: 28 | 29 | $ wagl [...options] --external=false --bind=:8053 30 | 31 | and then configure BIND as follows for the `swarm.` zone (assuming `wagl` runs 32 | on the specified IP 192.168.0.4): 33 | 34 | ``` 35 | zone "swarm" { 36 | type forward; 37 | forward only; 38 | forwarders { 192.168.0.4 port 8053; }; 39 | }; 40 | ``` -------------------------------------------------------------------------------- /_docs/5-Best-Practices.md: -------------------------------------------------------------------------------- 1 | # Best Practices 2 | 3 | 4 | ### Where to run wagl container? 5 | 6 | Ideally, you might want to co-locate the `wagl` container with the Swarm 7 | manager(s) so that it is not affected by IP changes etc. 8 | 9 | Alternatively, if your Swarm manager(s) have hostnames in the Virtual Network 10 | that can resolve into IP addresses (such as `swarm-master-0`). 11 | 12 | 13 | ### Running multiple instances of wagl 14 | 15 | Should be safe. 16 | 17 | Deploying multiple `wagl` containers in the case of multiple Swarm managers 18 | should just work fine even though `wagl` queries different Swarm managers. This 19 | is because Swarm manager nodes in the *follower* state should be proxying the 20 | query to the Swarm *leader* already. 21 | 22 | 23 | ### Locating wagl with `docker run --dns` 24 | 25 | It's suggested to have a fixed private IP address for Swarm managers in your 26 | cluster (such as 10.0.0.1/2/3). This way you can specify multiple `--dns` 27 | arguments to `docker run` such as: 28 | 29 | docker run --dns 10.0.0.1 --dns 10.0.0.2 --dns 10.0.0.3 [image] 30 | 31 | Also, instead of passing `--dns` argument to `docker run` every time, you might 32 | want to provide it directly to the docker daemons on Swarm nodes: 33 | 34 | docker daemon --dns 10.0.0.1 --dns 10.0.0.0.2 [...] 35 | 36 | This way the container can use redundancy of having multiple DNS servers (`wagl` 37 | containers) and tolerate failures. 38 | 39 | ### Handling external DNS queries 40 | 41 | If you really care about external DNS queries, you should use BIND to route 42 | `swarm.` queries to `wagl` and the rest to a proper DNS server rather than using 43 | `wagl`'s primitive forwarding feature. 44 | 45 | Read [External DNS topic](4-External-DNS.md) about this. 46 | 47 | 48 | ### Starting wagl with `--restart=always` 49 | 50 | `wagl` prefers consistency over liveness. 51 | 52 | This means `wagl` will exit if the DNS records go stale a lot (see `--staleness` 53 | argument). 54 | 55 | This could be because of a problem in the network or communicating with Swarm 56 | manager. 57 | 58 | If `wagl` is running inside a Docker container, then it's suggested to start 59 | `wagl` with `docker run --restart=always` 60 | 61 | If it is running standalone, it is suggested to run `wagl` on top of an init 62 | system such as supervisor, runit. 63 | 64 | ### Running on the host or in a container 65 | 66 | There is not much difference running wagl directly on a host or inside a 67 | container. 68 | 69 | If your docker daemon is not running as root, you may not bind DNS server to 70 | port 53 (requires sudo). 71 | 72 | Also managing the container and handling crashes is easier within a container. 73 | Therefore it is the recommended way. -------------------------------------------------------------------------------- /_docs/Advanced-1-Internals.md: -------------------------------------------------------------------------------- 1 | # Advanced: wagl Internals 2 | 3 | ### wagl Polls Records 4 | 5 | `wagl` refreshes the DNS records by polling the Swarm API periodically (see the 6 | `--refresh` option). 7 | 8 | In that sense `wagl` does not rely on Docker Events API as they could be tricky 9 | and could easily end up with message losses. In the future, a combination of 10 | both polling and the events API are planned to be used in the future. -------------------------------------------------------------------------------- /clusterdns/clusterdns.go: -------------------------------------------------------------------------------- 1 | package clusterdns 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | "github.com/ahmetalpbalkan/wagl/clusterdns/refresh" 9 | "github.com/ahmetalpbalkan/wagl/rrgen" 10 | "github.com/ahmetalpbalkan/wagl/rrstore" 11 | "github.com/ahmetalpbalkan/wagl/task" 12 | ) 13 | 14 | // ClusterDriver describes a distributed task execution environment. 15 | type ClusterDriver interface { 16 | // Tasks gives the active tasks in the cluster which may or may not be 17 | // eligible for load balancing due to various reasons such as having no 18 | // ports exposed or invalid characters in service/domain names. 19 | Tasks() (task.ClusterState, error) 20 | } 21 | 22 | // ClusterDNS keeps the DNS records in sync with Cluster state. 23 | type ClusterDNS struct { 24 | domain string 25 | rr rrstore.RRWriter 26 | cl ClusterDriver 27 | } 28 | 29 | func New(domain string, rr rrstore.RRWriter, cl ClusterDriver) *ClusterDNS { 30 | return &ClusterDNS{domain, rr, cl} 31 | } 32 | 33 | // SyncRecords syncs the DNS records in the RR table with the cluster by 34 | // querying the cluster and updating the RR table. 35 | func (c *ClusterDNS) SyncRecords() error { 36 | state, err := c.cl.Tasks() 37 | if err != nil { 38 | return fmt.Errorf("error fetching cluster state: %v", err) 39 | } 40 | c.rr.Set(rrgen.RRs(c.domain, state)) 41 | return nil 42 | } 43 | 44 | func (c *ClusterDNS) StartRefreshing(interval, timeout time.Duration, cancel <-chan struct{}) (<-chan error, <-chan struct{}) { 45 | t := time.NewTicker(interval) 46 | go func() { // garbage collect the ticker 47 | <-cancel 48 | t.Stop() 49 | }() 50 | 51 | log.Printf("Starting to refresh DNS records every %v...", interval) 52 | return refresh.New(func(cancel <-chan struct{}) error { 53 | // TODO see if we can plumb the cancellation to SyncRecords 54 | log.Println("Refreshing DNS records...") 55 | return c.SyncRecords() 56 | }, t.C, timeout, cancel) 57 | } 58 | -------------------------------------------------------------------------------- /clusterdns/clusterdns_test.go: -------------------------------------------------------------------------------- 1 | package clusterdns 2 | 3 | // TODO implement tests 4 | -------------------------------------------------------------------------------- /clusterdns/refresh/refresh.go: -------------------------------------------------------------------------------- 1 | package refresh 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | ) 7 | 8 | type RefreshFunc func(cancel <-chan struct{}) error 9 | 10 | // New starts a new loop to call f and returns two channels, one for errors that 11 | // come out of the calls to f or the refresh loop, and one for signaling every 12 | // time f returns successfully. 13 | func New(f RefreshFunc, tickCh <-chan time.Time, timeout time.Duration, cancelCh <-chan struct{}) (<-chan error, <-chan struct{}) { 14 | errCh := make(chan error, 1) // buffered so that we don't block on error 15 | okCh := make(chan struct{}, 1) 16 | 17 | go func() { // in the background 18 | for _ = range tickCh { 19 | // handle starting, timeout, cancellation or completion of call 20 | // to f in the background 21 | go func() { 22 | cancelF := make(chan struct{}) 23 | doneF := make(chan struct{}, 1) 24 | errF := make(chan error, 1) 25 | go func() { // Kick "f" off in the background 26 | err := f(cancelF) 27 | if err != nil { 28 | errF <- err 29 | } else { 30 | doneF <- struct{}{} // never blocks sending b/c buffered 31 | } 32 | }() 33 | select { 34 | case <-time.After(timeout): // task timed out 35 | errCh <- errors.New("refreshing timed out") 36 | close(cancelF) 37 | case <-cancelCh: // cancellation from upstream 38 | close(cancelF) 39 | case err := <-errF: // f errored 40 | errCh <- err 41 | case <-doneF: // task done 42 | okCh <- struct{}{} 43 | } 44 | 45 | }() 46 | } 47 | }() 48 | return errCh, okCh 49 | } 50 | -------------------------------------------------------------------------------- /clusterdns/refresh/refresh.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmetb/wagl/4592af09a91aec48dc4af100ab6cd49beb67718b/clusterdns/refresh/refresh.o -------------------------------------------------------------------------------- /clusterdns/refresh/refresh_test.go: -------------------------------------------------------------------------------- 1 | package refresh 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | var ( 11 | dummyError = errors.New("dummy error") 12 | ) 13 | 14 | func TestRefresh_simple(t *testing.T) { 15 | i := 0 16 | f := func(cancel <-chan struct{}) error { 17 | i++ 18 | return nil 19 | } 20 | tick := make(chan time.Time) 21 | done := make(chan struct{}) 22 | errCh, okCh := New(f, tick, time.Millisecond*15, done) 23 | 24 | go func() { 25 | for err := range errCh { 26 | t.Fatalf("err received: %v", err) 27 | } 28 | }() 29 | 30 | // (tick + sleep 10 ms) * 5 31 | n := 5 32 | go func() { 33 | for j := 0; j < n; j++ { 34 | tick <- time.Now() 35 | time.Sleep(time.Millisecond * 10) 36 | } 37 | }() 38 | 39 | // validate ok signals 40 | for j := 0; j < n; j++ { 41 | <-okCh 42 | } 43 | select { 44 | case <-okCh: 45 | t.Fatal("okCh still has values") 46 | default: 47 | } 48 | 49 | // validate calls to f are made 50 | if expected := n; i != expected { 51 | t.Fatalf("wrong # of calls to f, expected: %d got: %d", expected, i) 52 | } 53 | } 54 | 55 | func TestRefresh_timeout(t *testing.T) { 56 | // orchestrate predefined sleeps on each call to f 57 | sleeps := []int{1, 5, 15, 20, 25, 2, 3, 2} 58 | var m sync.Mutex 59 | cur := 0 60 | 61 | var ww sync.WaitGroup 62 | 63 | f := func(cancel <-chan struct{}) error { 64 | defer ww.Done() 65 | m.Lock() 66 | d := time.Duration(sleeps[cur]) * time.Millisecond 67 | cur++ 68 | num := cur 69 | m.Unlock() 70 | 71 | t.Logf("f%d sleep for: %v -- %v", num, d, time.Now()) 72 | select { 73 | case <-cancel: 74 | t.Logf("f%d is cancelled", num) 75 | case <-time.After(d): 76 | t.Logf("f%d done: %v", num, time.Now()) 77 | } 78 | return nil 79 | } 80 | 81 | tick := make(chan time.Time) 82 | done := make(chan struct{}) 83 | errCh, okCh := New(f, tick, time.Millisecond*10, done) //TODO use okch 84 | 85 | // send all ticks at once (won't block b/c buffered ch) 86 | ww.Add(len(sleeps)) 87 | for _ = range sleeps { 88 | tick <- time.Now() 89 | } 90 | ww.Wait() 91 | 92 | expectedErrs := 3 93 | for i := 0; i < expectedErrs; i++ { 94 | <-errCh 95 | } 96 | select { 97 | case <-errCh: 98 | t.Fatal("there are more errors") 99 | default: 100 | } 101 | 102 | expectedOks := len(sleeps) - expectedErrs 103 | for i := 0; i < expectedOks; i++ { 104 | <-okCh 105 | } 106 | select { 107 | case <-okCh: 108 | t.Fatal("there are more OKs") 109 | default: 110 | } 111 | close(done) 112 | } 113 | 114 | func TestRefresh_errGoesToErrCh(t *testing.T) { 115 | f := func(cancel <-chan struct{}) error { 116 | return dummyError 117 | } 118 | tick := make(chan time.Time) 119 | done := make(chan struct{}) 120 | defer close(done) 121 | errCh, _ := New(f, tick, time.Millisecond*15, done) //TODO use okch 122 | 123 | n := 5 124 | go func() { 125 | for i := 0; i < n; i++ { 126 | tick <- time.Now() 127 | } 128 | }() 129 | 130 | for i := 0; i < n; i++ { 131 | err := <-errCh 132 | if err != dummyError { 133 | t.Fatalf("got wrong error: %v", err) 134 | } 135 | } 136 | } 137 | 138 | func TestRefresh_cancelsTaskOnTimeout(t *testing.T) { 139 | timeout := time.Millisecond * 20 140 | 141 | f := func(cancel <-chan struct{}) error { 142 | select { 143 | case <-cancel: 144 | t.Log("successfully cancelled") 145 | case <-time.After(timeout * 2): 146 | t.Fatal("did not cancel the task on timeout") 147 | } 148 | return nil 149 | } 150 | tick := make(chan time.Time) 151 | done := make(chan struct{}) 152 | defer close(done) 153 | 154 | _, _ = New(f, tick, timeout, done) //TODO use okch 155 | 156 | // send a tick 157 | var wg sync.WaitGroup 158 | wg.Add(1) 159 | go func() { 160 | defer wg.Done() 161 | select { 162 | case tick <- time.Now(): 163 | case <-done: 164 | t.Fatal("could not send tick") 165 | } 166 | }() 167 | 168 | wg.Wait() 169 | } 170 | 171 | func TestRefresh_cancellation(t *testing.T) { 172 | timeout := time.Second 173 | 174 | fCancelled := make(chan struct{}, 1) 175 | f := func(cancel <-chan struct{}) error { 176 | select { 177 | case fCancelled <- <-cancel: 178 | t.Log("got cancellation") 179 | case <-time.After(time.Second): 180 | t.Fatalf("f not cancelled") 181 | } 182 | return nil 183 | } 184 | tick := make(chan time.Time, 1) 185 | done := make(chan struct{}) 186 | 187 | _, _ = New(f, tick, timeout, done) 188 | tick <- time.Now() // won't block 189 | 190 | <-time.After(time.Millisecond * 10) 191 | t.Log("closing cancel ch") 192 | close(done) // cancel! 193 | 194 | time.Sleep(time.Millisecond * 20) // give f some time to cancel 195 | 196 | select { 197 | case <-fCancelled: 198 | t.Log("cancellation happened") 199 | default: 200 | t.Fatal("f did not do cancellation") 201 | } 202 | } 203 | 204 | func TestRefresh_fTakesMoreTimeThanTicks_soThatTasksInterleave(t *testing.T) { 205 | started := 0 206 | n := 10 // tick count 207 | finished := make(chan struct{}, n) 208 | 209 | sleep := 200 * time.Millisecond 210 | 211 | var m sync.Mutex 212 | f := func(cancel <-chan struct{}) error { 213 | m.Lock() 214 | started++ 215 | m.Unlock() 216 | t.Log("task started") 217 | 218 | select { 219 | case <-time.After(sleep): 220 | t.Log("task done") 221 | finished <- struct{}{} 222 | case <-cancel: 223 | } 224 | return nil 225 | } 226 | 227 | done := make(chan struct{}) 228 | defer close(done) 229 | tick := make(chan time.Time) 230 | _, _ = New(f, tick, time.Second, done) 231 | 232 | var wg sync.WaitGroup 233 | wg.Add(n) 234 | 235 | start := time.Now() 236 | 237 | // send ticks 238 | for i := 0; i < n; i++ { 239 | t.Logf("--> scheduling (%d): %v", i, time.Now()) 240 | go func(num int) { 241 | defer wg.Done() 242 | 243 | select { 244 | case tick <- time.Now(): 245 | t.Logf("--- scheduled (%d)...: %v", num, time.Now()) 246 | case <-done: 247 | t.Fatalf("retryloop is canceled before sending tick") 248 | } 249 | }(i) 250 | } 251 | wg.Wait() 252 | 253 | // prove calls to f interleave and not serialized 254 | d := time.Since(start) 255 | if expMax := sleep; d > expMax { 256 | t.Fatalf("sending ticks took longer than expected (%v > %v). are they blocked on f to return? ", d, expMax) 257 | } 258 | 259 | var wg2 sync.WaitGroup 260 | wg2.Add(n) 261 | for i := 0; i < n; i++ { 262 | go func() { 263 | defer wg2.Done() 264 | <-finished 265 | }() 266 | } 267 | wg2.Wait() 268 | } 269 | 270 | func TestRefresh_fSometimesReturnsErr(t *testing.T) { 271 | vals := []bool{false, true, false, false, true, false} 272 | cur := 0 273 | var m sync.Mutex 274 | f := func(c <-chan struct{}) error { 275 | m.Lock() 276 | v := vals[cur] 277 | cur++ 278 | m.Unlock() 279 | 280 | if !v { 281 | return dummyError 282 | } 283 | return nil 284 | } 285 | 286 | tick := make(chan time.Time) 287 | done := make(chan struct{}) 288 | errCh, okCh := New(f, tick, time.Second, done) 289 | defer close(done) 290 | 291 | // send ticks 292 | go func() { 293 | for _ = range vals { 294 | tick <- time.Now() 295 | } 296 | }() 297 | 298 | expectedErrs := 4 299 | expectedOKs := len(vals) - expectedErrs 300 | 301 | oks := 0 302 | errs := 0 303 | 304 | for { 305 | if oks+errs == len(vals) { 306 | break 307 | } 308 | select { 309 | case <-okCh: 310 | oks++ 311 | case <-errCh: 312 | errs++ 313 | } 314 | } 315 | 316 | if errs != expectedErrs { 317 | t.Fatalf("wrong errs count: %d, expected: %d", errs, expectedErrs) 318 | } 319 | if oks != expectedOKs { 320 | t.Fatalf("wrong OKs count: %d, expected: %d", oks, expectedOKs) 321 | } 322 | 323 | t.Log("alright... see if chans still have value") 324 | select { 325 | case <-errCh: 326 | t.Fatal("errCh still has value") 327 | default: 328 | } 329 | select { 330 | case <-okCh: 331 | t.Fatal("okCh still has value") 332 | default: 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "net" 8 | "os" 9 | "strings" 10 | "time" 11 | 12 | "github.com/ahmetalpbalkan/wagl/clusterdns" 13 | "github.com/ahmetalpbalkan/wagl/rrstore" 14 | "github.com/ahmetalpbalkan/wagl/server" 15 | "github.com/ahmetalpbalkan/wagl/swarm" 16 | 17 | "github.com/codegangsta/cli" 18 | ) 19 | 20 | const ( 21 | defaultDnsDomain = "swarm." 22 | defaultAddr = ":53" 23 | defaultSwarm = "127.0.0.1:2376" 24 | defaultRefreshInterval = time.Second * 15 25 | defaultRefreshTimeout = time.Second * 10 26 | defaultStalenessPeriod = time.Second * 60 27 | ) 28 | 29 | type Options struct { 30 | // User input 31 | domain string 32 | bindAddr string 33 | swarmAddr string 34 | tlsDir string 35 | tlsVerify bool 36 | recurse bool 37 | nameservers []string 38 | refreshInterval time.Duration 39 | refreshTimeout time.Duration 40 | stalenessPeriod time.Duration 41 | } 42 | 43 | func (o *Options) String() string { 44 | return fmt.Sprintf(`Configuration: 45 | - Domain: "%s" 46 | - Listen: "%s" 47 | - Swarm: %s 48 | - TLS: %s (verify: %v) 49 | - External: %v (ns: [%s]) 50 | - Refresh: Every %v (timeout: %v) (staleness: %v) 51 | -------------------`, 52 | o.domain, 53 | o.bindAddr, 54 | o.swarmAddr, 55 | o.tlsDir, 56 | o.tlsVerify, 57 | o.recurse, strings.Join(o.nameservers, ","), 58 | o.refreshInterval, o.refreshTimeout, o.stalenessPeriod) 59 | } 60 | 61 | func main() { 62 | cmd := cli.NewApp() 63 | cmd.Name = "wagl" 64 | cmd.Version = "0.1" 65 | cmd.Usage = "DNS service discovery for Docker Swarm clusters" 66 | cli.AppHelpTemplate = usageTemplate 67 | cmd.Flags = []cli.Flag{ 68 | cli.StringFlag{ 69 | Name: "bind", 70 | Value: defaultAddr, 71 | Usage: "IP:port on which the server shoud listen", 72 | }, 73 | cli.StringFlag{ 74 | Name: "swarm", 75 | Value: defaultSwarm, 76 | Usage: "address of the Swarm manager", // TODO accept multiple 77 | }, 78 | cli.StringFlag{ 79 | Name: "swarm-cert-path", 80 | Value: "", 81 | Usage: "directory TLS certs for Swarm manager is stored", 82 | EnvVar: "DOCKER_CERT_PATH", 83 | }, 84 | cli.BoolFlag{ 85 | Name: "swarm-tlsverify", 86 | Usage: "verify remote Swarm's identity using TLS", 87 | EnvVar: "DOCKER_TLS_VERIFY", 88 | }, 89 | cli.StringFlag{ 90 | Name: "domain", 91 | Value: defaultDnsDomain, 92 | Usage: "DNS domain (FQDN suffix) for which this server is authoritative", 93 | }, 94 | cli.BoolTFlag{ 95 | Name: "external", 96 | Usage: "use external nameservers to resolve DNS requests outside the domain (true by default)", 97 | }, 98 | cli.StringSliceFlag{ 99 | Name: "ns", 100 | Usage: "external nameserver(s) to forward requests (default: nameservers in /etc/resolv.conf)", 101 | }, 102 | cli.DurationFlag{ 103 | Name: "refresh", 104 | Value: defaultRefreshInterval, 105 | Usage: "how frequently refresh DNS table from cluster records", 106 | }, 107 | cli.DurationFlag{ 108 | Name: "refresh-timeout", 109 | Value: defaultRefreshTimeout, 110 | Usage: "time alotted for Swarm to list containers in the cluster", 111 | }, 112 | cli.DurationFlag{ 113 | Name: "staleness", 114 | Value: defaultStalenessPeriod, 115 | Usage: "how long to serve stale DNS records before exiting", 116 | }, 117 | } 118 | cmd.Action = func(c *cli.Context) { 119 | opts := &Options{ 120 | domain: c.String("domain"), 121 | bindAddr: c.String("bind"), 122 | swarmAddr: c.String("swarm"), 123 | tlsDir: c.String("swarm-cert-path"), 124 | tlsVerify: c.Bool("swarm-tlsverify"), 125 | recurse: c.BoolT("external"), 126 | nameservers: c.StringSlice("ns"), 127 | refreshInterval: c.Duration("refresh"), 128 | refreshTimeout: c.Duration("refresh-timeout"), 129 | stalenessPeriod: c.Duration("staleness"), 130 | } 131 | if err := validate(opts); err != nil { 132 | log.Fatalf("Error: %v", err) 133 | } 134 | log.Printf("%s", opts) 135 | serve(opts) 136 | } 137 | cmd.Run(os.Args) 138 | } 139 | 140 | // validate looks for logical correctness and consistency of the input arguments 141 | // to the program. 142 | func validate(opt *Options) error { 143 | // No NS must be specified if recursion is off 144 | if !opt.recurse && len(opt.nameservers) > 0 { 145 | return errors.New("External querying disabled, but external nameservers specified") 146 | } 147 | 148 | // TLS verify can be used only if certs are specified 149 | if opt.tlsVerify && opt.tlsDir == "" { 150 | return errors.New("TLS verify specified; but not TLS cert path") 151 | } 152 | 153 | // No nameservers speficied, check resolv.conf, add it. 154 | if opt.recurse && len(opt.nameservers) == 0 { 155 | if ns, err := localNameservers(); err != nil { 156 | return fmt.Errorf("Failed to load nameservers list: %v", err) 157 | } else if len(ns) == 0 { 158 | return fmt.Errorf("No nameservers found in /etc/resolv.conf") 159 | } else { 160 | opt.nameservers = ns 161 | } 162 | } 163 | 164 | // Nameserver validations: 165 | // - make sure nameservers are IP[:port] 166 | // - add default DNS port to nameservers if missing 167 | for i, v := range opt.nameservers { 168 | host := v 169 | if h, _, err := net.SplitHostPort(v); err != nil { // Missing port 170 | opt.nameservers[i] = v + ":53" 171 | } else { 172 | host = h 173 | } 174 | // Make sure hostname is IP (do not support domain names as nameservers) 175 | if ip := net.ParseIP(host); ip == nil { 176 | return fmt.Errorf("Nameserver is not an IP address: '%s'", host) 177 | } 178 | } 179 | 180 | // Refresh timeout < refresh interval 181 | if opt.refreshTimeout >= opt.refreshInterval { 182 | return fmt.Errorf("Refresh timeout (%v) should be less than refresh interval (%v)", opt.refreshTimeout, opt.refreshInterval) 183 | } 184 | 185 | return nil 186 | } 187 | 188 | // serve starts the DNS server and blocks. 189 | func serve(opt *Options) { 190 | dockerTLS, err := tlsConfig(opt.tlsDir, opt.tlsVerify) 191 | if err != nil { 192 | log.Fatalf("Error establishing TLS config: %v", err) 193 | } 194 | 195 | rrs := rrstore.New() 196 | cluster, err := swarm.New(opt.swarmAddr, dockerTLS) 197 | if err != nil { 198 | log.Fatalf("Error initializing Swarm: %v", err) 199 | } 200 | dns := clusterdns.New(opt.domain, rrs, cluster) 201 | 202 | cancel := make(chan struct{}) 203 | defer close(cancel) 204 | errCh, okCh := dns.StartRefreshing(opt.refreshInterval, opt.refreshTimeout, 205 | cancel) 206 | 207 | go func() { 208 | var lastSuccess time.Time 209 | var start = time.Now() 210 | var errs = 0 211 | var ok = 0 212 | 213 | for { 214 | // Exit if records are stale. Here we prefer consistency over 215 | // liveliness/availability. 216 | if (ok > 0 && time.Since(lastSuccess) > opt.stalenessPeriod) || 217 | (ok == 0 && time.Since(start) > opt.stalenessPeriod) { 218 | close(cancel) 219 | 220 | var last string 221 | if lastSuccess.IsZero() { 222 | last = "never" 223 | } else { 224 | last = lastSuccess.String() 225 | } 226 | log.Fatalf("Fatal: exiting rather than serving stale records. Staleness period: %v, last success: %s", opt.stalenessPeriod, last) 227 | } 228 | 229 | select { 230 | case err := <-errCh: 231 | errs++ 232 | log.Printf("Refresh error (#%d): %v ", errs, err) 233 | case <-okCh: 234 | errs = 0 // reset errs 235 | ok++ 236 | lastSuccess = time.Now() 237 | log.Printf("Successfully refreshed records.") 238 | case <-cancel: 239 | log.Fatal("Fatal: Refreshing records cancelled.") 240 | } 241 | } 242 | }() 243 | 244 | srv := server.New(opt.domain, opt.bindAddr, rrs, opt.recurse, opt.nameservers) 245 | log.Fatal(srv.ListenAndServe()) 246 | } 247 | -------------------------------------------------------------------------------- /rrgen/filter.go: -------------------------------------------------------------------------------- 1 | package rrgen 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/ahmetalpbalkan/wagl/task" 7 | ) 8 | 9 | // FilterFunc determines if a Task can be used, if not provides a reason. 10 | type FilterFunc func(t task.Task) (bool, string) 11 | 12 | type Filters []FilterFunc 13 | 14 | // BadTask describes a Task that is not eligible. 15 | type BadTask struct { 16 | task.Task 17 | Reason string 18 | } 19 | 20 | // DnsFilters are list of filters applied in order to determine DNS eligibility 21 | // of tasks. The end result of the filters are Tasks (containers) that can have 22 | // DNS RRs. 23 | var DnsFilters = Filters([]FilterFunc{ 24 | HasDnsName, 25 | HasPorts, 26 | PortsHaveProtos, 27 | }) 28 | 29 | // filterTasks filters tasks based on their eligibility for having DNS records 30 | // and returns the list of good tasks and bad ones along with their reasons. 31 | func (f Filters) FilterTasks(ll []task.Task) ([]task.Task, []BadTask) { 32 | badTasks := make([]BadTask, 0) 33 | goodTasks := make([]task.Task, 0) 34 | 35 | for _, t := range ll { 36 | bad := false 37 | for _, ff := range f { 38 | if ok, reason := ff(t); !ok { 39 | badTasks = append(badTasks, BadTask{t, reason}) 40 | bad = true 41 | break 42 | } 43 | } 44 | if !bad { 45 | goodTasks = append(goodTasks, t) 46 | } 47 | } 48 | return goodTasks, badTasks 49 | } 50 | 51 | // Filters 52 | 53 | func HasPorts(t task.Task) (bool, string) { 54 | return len(t.Ports) > 0, "has no port mappings" 55 | } 56 | 57 | func HasDnsName(t task.Task) (bool, string) { 58 | return t.Service != "", "has no DNS name specified (or not configured for DNS)" 59 | } 60 | 61 | func PortsHaveProtos(t task.Task) (bool, string) { 62 | for _, p := range t.Ports { 63 | if p.Proto == "" { 64 | return false, fmt.Sprintf("no network protocol specified for port mapping '%s'", p) 65 | } 66 | } 67 | return true, "" 68 | } 69 | 70 | // TODO implement DNS name checks (length, valid characters and such) 71 | -------------------------------------------------------------------------------- /rrgen/filter_test.go: -------------------------------------------------------------------------------- 1 | package rrgen 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ahmetalpbalkan/wagl/task" 7 | ) 8 | 9 | func TestFilterTasks(t *testing.T) { 10 | var ( 11 | allGood = func(t task.Task) (bool, string) { return true, "" } 12 | allBad = func(t task.Task) (bool, string) { return false, "nope" } 13 | hasPort = func(t task.Task) (bool, string) { 14 | if len(t.Ports) > 0 { 15 | return true, "" 16 | } 17 | return false, "task has no ports" 18 | } 19 | ) 20 | ll := make([]task.Task, 5) 21 | 22 | // 0th task has a port 23 | ll[0] = task.Task{Ports: []task.Port{ 24 | {HostPort: 80}}} 25 | 26 | cases := []struct { 27 | fs Filters 28 | good int 29 | bad int 30 | }{ 31 | {[]FilterFunc{allGood}, len(ll), 0}, 32 | {[]FilterFunc{allGood, allBad}, 0, len(ll)}, 33 | {[]FilterFunc{allGood, hasPort}, 1, len(ll) - 1}, 34 | } 35 | for i, c := range cases { 36 | if o, b := c.fs.FilterTasks(ll); len(o) != c.good { 37 | t.Fatalf("case %d: wrong good task count", i) 38 | } else if len(b) != c.bad { 39 | t.Fatalf("case %d: wrong bad task count", i) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /rrgen/rrgen.go: -------------------------------------------------------------------------------- 1 | // Package rrgen finds out tasks eligible for having DNS records and generates 2 | // th DNS Resource Records for these. 3 | package rrgen 4 | 5 | import ( 6 | "fmt" 7 | "log" 8 | 9 | "github.com/ahmetalpbalkan/wagl/rrstore" 10 | "github.com/ahmetalpbalkan/wagl/task" 11 | "github.com/miekg/dns" 12 | ) 13 | 14 | type rrEntry struct { 15 | rrType uint16 16 | domain string 17 | record string 18 | } 19 | 20 | func (r *rrEntry) String() string { 21 | return fmt.Sprintf("%s %s %s", dns.TypeToString[r.rrType], r.domain, r.record) 22 | } 23 | 24 | // RRs determines the tasks which can have DNS Resource Records and returns the 25 | // RRs based on the given cluster state. 26 | func RRs(domain string, state task.ClusterState) rrstore.RRs { 27 | goodTasks, badTasks := DnsFilters.FilterTasks(state) 28 | if len(badTasks) > 0 { 29 | log.Printf("Found %d tasks are not eligible for DNS records:", len(badTasks)) 30 | for _, v := range badTasks { 31 | log.Printf("\t- %s: %s", v.Id, v.Reason) 32 | } 33 | } 34 | log.Printf("Tasks with DNS records: %d", len(goodTasks)) 35 | return getRRs(domain, goodTasks) 36 | } 37 | 38 | // getRRs generates all DNS Resource Record table for the given tasks by 39 | // generating records for each task individually and then grouping them by their 40 | // service[.domain] name. 41 | func getRRs(domain string, ll []task.Task) rrstore.RRs { 42 | rr := make(rrstore.RRs) 43 | for _, t := range ll { 44 | for _, r := range getTaskRRs(domain, t) { 45 | log.Printf("\t+RR: %s", r.String()) 46 | insertRR(rr, r) 47 | } 48 | } 49 | return rr 50 | } 51 | 52 | // getTaskRRs returns all DNS RRs of a Task as a list 53 | func getTaskRRs(domain string, t task.Task) []rrEntry { 54 | l := make([]rrEntry, 0) 55 | 56 | // Prepend task domain to DNS domain 57 | tail := dns.Fqdn(domain) 58 | if t.Domain != "" { 59 | tail = dns.Fqdn(t.Domain) + tail 60 | } 61 | 62 | // A record ("A service.domain. IP") 63 | ip := t.Ports[0].HostIP.String() // use first port mapping's IP addr 64 | l = append(l, rrEntry{dns.TypeA, fmt.Sprintf("%s.%s", t.Service, tail), ip}) 65 | 66 | // SRV records for each port mapping ("SRV _service._tcp.domain. IP PORT") 67 | for _, p := range t.Ports { 68 | val := fmt.Sprintf("%s:%d", p.HostIP, p.HostPort) 69 | l = append(l, rrEntry{dns.TypeSRV, fmt.Sprintf("_%s._%s.%s", t.Service, p.Proto, tail), val}) 70 | } 71 | return l 72 | } 73 | 74 | // insertRR adds the specified RR entry into the RR table. 75 | func insertRR(rr rrstore.RRs, entry rrEntry) { 76 | if rr[entry.rrType] == nil { 77 | rr[entry.rrType] = make(map[string][]string) 78 | } 79 | rr[entry.rrType][entry.domain] = append(rr[entry.rrType][entry.domain], entry.record) 80 | } 81 | -------------------------------------------------------------------------------- /rrgen/rrgen_test.go: -------------------------------------------------------------------------------- 1 | package rrgen 2 | 3 | import ( 4 | "net" 5 | "reflect" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/ahmetalpbalkan/wagl/rrstore" 10 | "github.com/ahmetalpbalkan/wagl/task" 11 | "github.com/miekg/dns" 12 | ) 13 | 14 | func Test_insertRR(t *testing.T) { 15 | rr := make(rrstore.RRs) 16 | insertRR(rr, rrEntry{dns.TypeA, "foo.domain.", "10.0.0.1"}) 17 | insertRR(rr, rrEntry{dns.TypeA, "foo.domain.", "10.0.0.2"}) 18 | insertRR(rr, rrEntry{dns.TypeSRV, "_foo._tcp.domain.", "10.0.0.3:3000"}) 19 | 20 | expected := rrstore.RRs(map[uint16]map[string][]string{ 21 | dns.TypeA: {"foo.domain.": []string{"10.0.0.1", "10.0.0.2"}}, 22 | dns.TypeSRV: {"_foo._tcp.domain.": []string{"10.0.0.3:3000"}}}) 23 | 24 | if !reflect.DeepEqual(expected, rr) { 25 | t.Fatalf("wrong value.\nexpected=%#v\ngot=%#v", expected, rr) 26 | } 27 | } 28 | 29 | func Test_getTaskRRs(t *testing.T) { 30 | cases := []struct { 31 | t task.Task 32 | rs []string 33 | }{ 34 | // Task with no domain 35 | {task.Task{ 36 | Service: "foo", 37 | Ports: []task.Port{ 38 | { 39 | HostIP: net.IPv4(10, 0, 0, 1), 40 | HostPort: 8000, 41 | Proto: "tcp", 42 | }, 43 | }}, 44 | []string{ 45 | "A foo.domain. 10.0.0.1", 46 | "SRV _foo._tcp.domain. 10.0.0.1:8000", 47 | }}, 48 | 49 | // Task with project domain and multiple ports 50 | {task.Task{ 51 | Service: "api", 52 | Domain: "billing", 53 | Ports: []task.Port{ 54 | { 55 | HostIP: net.IPv4(10, 0, 0, 2), 56 | HostPort: 8001, 57 | Proto: "tcp", 58 | }, 59 | { 60 | HostIP: net.IPv4(10, 0, 0, 2), 61 | HostPort: 8002, 62 | Proto: "udp", 63 | }, 64 | }}, 65 | []string{ 66 | "A api.billing.domain. 10.0.0.2", 67 | "SRV _api._tcp.billing.domain. 10.0.0.2:8001", 68 | "SRV _api._udp.billing.domain. 10.0.0.2:8002", 69 | }}, 70 | } 71 | 72 | for _, c := range cases { 73 | rrs := getTaskRRs("domain", c.t) 74 | ll := make([]string, len(rrs)) 75 | for i := range rrs { 76 | ll[i] = rrs[i].String() 77 | } 78 | 79 | in := strings.Join(c.rs, "\n") 80 | out := strings.Join(ll, "\n") 81 | 82 | if in != out { 83 | t.Fatalf("wrong RRs.\nexpected: '%s'\ngot: '%s'", in, out) 84 | } 85 | } 86 | } 87 | 88 | func Test_RRs_empty(t *testing.T) { 89 | rr := getRRs("domain", nil) 90 | if len(rr) > 0 { 91 | t.Fatal("output has records") 92 | } 93 | 94 | rr = RRs("domain", task.ClusterState([]task.Task{ 95 | { 96 | Id: "no-ports", 97 | Service: "api", 98 | Ports: []task.Port{}, 99 | }, 100 | { 101 | Id: "no-service-name", 102 | Ports: []task.Port{{net.IPv4(10, 0, 0, 2), 8001, "tcp"}}, 103 | }, 104 | })) 105 | if len(rr) > 0 { 106 | t.Fatal("output has records") 107 | } 108 | } 109 | 110 | func Test_RRs_actualWorkload(t *testing.T) { 111 | rr := RRs("domain", task.ClusterState([]task.Task{ 112 | { 113 | Id: "bind", 114 | Service: "dns", 115 | Domain: "infra", 116 | Ports: []task.Port{{net.IPv4(192, 168, 0, 3), 53, "udp"}}, 117 | }, 118 | { 119 | Id: "web1", 120 | Service: "api", 121 | Ports: []task.Port{{net.IPv4(192, 168, 0, 1), 8000, "tcp"}}, 122 | }, 123 | { 124 | Id: "web2", 125 | Service: "api", 126 | Ports: []task.Port{ 127 | {net.IPv4(192, 168, 0, 2), 8000, "tcp"}, 128 | {net.IPv4(192, 168, 0, 2), 5000, "udp"}, 129 | }, 130 | }, 131 | { 132 | Id: "nginx", 133 | Service: "frontend", 134 | Domain: "blog", 135 | Ports: []task.Port{ 136 | {net.IPv4(192, 168, 0, 3), 8000, "tcp"}, 137 | }, 138 | }, 139 | { // no proto on port 140 | Id: "debian", 141 | Service: "test", 142 | Ports: []task.Port{{net.IPv4(192, 168, 0, 3), 500, ""}}, 143 | }, 144 | { // no service name 145 | Id: "debian", 146 | Ports: []task.Port{{net.IPv4(192, 168, 0, 3), 500, "udp"}}, 147 | }, 148 | })) 149 | 150 | expected := rrstore.RRs(map[uint16]map[string][]string{ 151 | dns.TypeA: { 152 | "dns.infra.domain.": []string{"192.168.0.3"}, 153 | "api.domain.": []string{"192.168.0.1", "192.168.0.2"}, 154 | "frontend.blog.domain.": []string{"192.168.0.3"}, 155 | }, 156 | dns.TypeSRV: { 157 | "_dns._udp.infra.domain.": []string{"192.168.0.3:53"}, 158 | "_api._tcp.domain.": []string{"192.168.0.1:8000", "192.168.0.2:8000"}, 159 | "_api._udp.domain.": []string{"192.168.0.2:5000"}, 160 | "_frontend._tcp.blog.domain.": []string{"192.168.0.3:8000"}, 161 | }}) 162 | 163 | if !reflect.DeepEqual(rr, expected) { 164 | t.Fatalf("wrong value.\nexp: %#v\ngot: %#v", expected, rr) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /rrstore/store.go: -------------------------------------------------------------------------------- 1 | // Package rrstore provides concurrency-safe storage for DNS Resource Records 2 | // (RRs). 3 | package rrstore 4 | 5 | import ( 6 | "sync" 7 | ) 8 | 9 | // RRs stores FQDN RR answer for various RR Types. 10 | // Example: 11 | // { 12 | // dns.TypeA: {"a.b." : ["10.0.0.3"]}, 13 | // dns.TypeSRV: {"a.b." : ["10.0.0.3:23481","10.0.0.7:11215"]} 14 | // } 15 | type RRs map[uint16]map[string][]string 16 | 17 | type RRReader interface { 18 | Get(fqdn string, rrType uint16) (rrs []string, ok bool) 19 | } 20 | 21 | type RRWriter interface { 22 | Set(rl RRs) 23 | } 24 | 25 | type RRStore interface { 26 | RRReader 27 | RRWriter 28 | } 29 | 30 | type rrStore struct { 31 | rrs RRs 32 | m sync.RWMutex 33 | } 34 | 35 | // New creates a new record table to store DNS Resource Records. 36 | func New() RRStore { 37 | return &rrStore{} 38 | } 39 | 40 | func (r *rrStore) Get(fqdn string, rrType uint16) (rrs []string, ok bool) { 41 | r.m.RLock() 42 | defer r.m.RUnlock() 43 | rrs, ok = r.rrs[rrType][fqdn] 44 | return 45 | } 46 | 47 | func (r *rrStore) Set(rl RRs) { 48 | r.m.Lock() 49 | defer r.m.Unlock() 50 | r.rrs = rl 51 | } 52 | -------------------------------------------------------------------------------- /rrstore/store_test.go: -------------------------------------------------------------------------------- 1 | package rrstore 2 | 3 | import ( 4 | "reflect" 5 | "sync" 6 | "testing" 7 | ) 8 | 9 | func TestRRStore(t *testing.T) { 10 | s := New() 11 | s.Get("foo", 0) 12 | 13 | in := map[uint16]map[string][]string{ 14 | 0: {"1": []string{"2"}}, 15 | 3: {"4": []string{"5"}}, 16 | } 17 | s.Set(in) 18 | if v, _ := s.Get("1", 0); !reflect.DeepEqual(v, in[0]["1"]) { 19 | t.Fatal("wrong value") 20 | } 21 | if v, _ := s.Get("4", 3); !reflect.DeepEqual(v, in[3]["4"]) { 22 | t.Fatal("wrong value") 23 | } 24 | if _, ok := s.Get("1", 1); ok { 25 | t.Fatal("wrong value") 26 | } 27 | } 28 | 29 | func TestRRStore_RaceCond(t *testing.T) { 30 | s := New() 31 | var wg sync.WaitGroup 32 | for i := 0; i < 1000; i++ { 33 | wg.Add(2) 34 | go func() { 35 | s.Get("1", 0) 36 | wg.Done() 37 | }() 38 | go func() { 39 | s.Set(make(map[uint16]map[string][]string)) 40 | wg.Done() 41 | }() 42 | } 43 | wg.Wait() 44 | } 45 | -------------------------------------------------------------------------------- /rrtype/rrtype.go: -------------------------------------------------------------------------------- 1 | package rrtype 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strconv" 7 | 8 | "github.com/miekg/dns" 9 | ) 10 | 11 | type formatterFunc func(name, rr string) (dns.RR, error) 12 | 13 | var rrFormatters = map[uint16]formatterFunc{ 14 | dns.TypeA: formatA, 15 | dns.TypeSRV: formatSRV, 16 | } 17 | 18 | // IsSupported returns if the system supports answering to questions 19 | // for specified DNS RR Type. 20 | func IsSupported(rrType uint16) bool { 21 | _, ok := rrFormatters[rrType] 22 | return ok 23 | } 24 | 25 | // ToRR converts stored RR info to an appropriate DNS RR based on rrType 26 | // specified (e.g. A, SRV). 27 | func ToRR(rrType uint16, name, rec string) (dns.RR, error) { 28 | f, ok := rrFormatters[rrType] 29 | if !ok { 30 | return nil, fmt.Errorf("Formatting RR to %s(%d) REC not implemented", dns.TypeToString[rrType], rrType) 31 | } 32 | return f(name, rec) 33 | } 34 | 35 | // formatA formats an IP address record for a into A record. 36 | func formatA(name, rec string) (dns.RR, error) { 37 | return &dns.A{ 38 | Hdr: dns.RR_Header{ 39 | Name: name, 40 | Rrtype: dns.TypeA, 41 | Class: dns.ClassINET, 42 | Ttl: 0}, 43 | A: net.ParseIP(rec), 44 | }, nil 45 | } 46 | 47 | // formatSRV formats an IP:port record into a SRV record. 48 | 49 | func formatSRV(name, rec string) (dns.RR, error) { 50 | host, port, err := net.SplitHostPort(rec) 51 | if err != nil { 52 | return nil, fmt.Errorf("cannot format addr %s to SRV record: %v", rec, err) 53 | } 54 | host = dns.Fqdn(host) // have . suffix per SRV RFC 55 | 56 | portNum, err := strconv.ParseUint(port, 10, 16) 57 | if err != nil { 58 | return nil, fmt.Errorf("cannot parse port number in %s: %v", rec, err) 59 | } 60 | 61 | return &dns.SRV{ 62 | Hdr: dns.RR_Header{ 63 | Name: name, 64 | Rrtype: dns.TypeSRV, 65 | Class: dns.ClassINET, 66 | Ttl: 0, 67 | }, 68 | Target: host, 69 | Port: uint16(portNum), 70 | Priority: 1, // keep all records equal 71 | Weight: 1, // keep all records equal 72 | }, nil 73 | } 74 | -------------------------------------------------------------------------------- /rrtype/rrtype_test.go: -------------------------------------------------------------------------------- 1 | package rrtype 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/miekg/dns" 7 | ) 8 | 9 | func TestIsSupported(t *testing.T) { 10 | cases := []struct { 11 | rrType uint16 12 | supported bool 13 | }{ 14 | // supported 15 | {dns.TypeA, true}, 16 | {dns.TypeSRV, true}, 17 | 18 | // some others 19 | {dns.TypeCNAME, false}, 20 | {dns.TypeNS, false}, 21 | {dns.TypeMX, false}, 22 | } 23 | for _, c := range cases { 24 | out := IsSupported(c.rrType) 25 | if out != c.supported { 26 | t.Fatal("wrong value for %s", dns.TypeToString[c.rrType]) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "log" 5 | "math/rand" 6 | "strings" 7 | "time" 8 | 9 | "github.com/ahmetalpbalkan/wagl/rrstore" 10 | "github.com/ahmetalpbalkan/wagl/rrtype" 11 | "github.com/miekg/dns" 12 | ) 13 | 14 | var ( 15 | rnd = rand.New(rand.NewSource(time.Now().UnixNano())) 16 | ) 17 | 18 | type DnsServer struct { 19 | *dns.Server 20 | rr rrstore.RRReader 21 | 22 | recurse bool 23 | nameservers []string 24 | } 25 | 26 | // New creates a DnsServer ready to serve queries for the specified domain on 27 | // the given host:port using the specified DNS Resource Record table as the 28 | // source of truth. 29 | func New(domain, addr string, rr rrstore.RRReader, recurse bool, nameservers []string) *DnsServer { 30 | d := &DnsServer{rr: rr, 31 | recurse: recurse, 32 | nameservers: nameservers} 33 | 34 | mux := dns.NewServeMux() 35 | mux.HandleFunc(".", d.handleExternal) 36 | mux.HandleFunc(dns.Fqdn(domain), d.handleDomain) 37 | d.Server = &dns.Server{ 38 | Addr: addr, 39 | Net: "udp", 40 | Handler: mux, 41 | } 42 | d.Server.NotifyStartedFunc = func() { 43 | log.Printf("DNS server started listening at %s", d.Server.Addr) 44 | } 45 | return d 46 | } 47 | 48 | // handleExternal handles DNS queries that are outside the cluster's domain such 49 | // as the Public Internet. 50 | func (d *DnsServer) handleExternal(w dns.ResponseWriter, r *dns.Msg) { 51 | dom, qType := parseQuestion(r) 52 | q := dns.TypeToString[qType] + " " + dom 53 | log.Printf("--> External: %s", q) 54 | 55 | if !d.recurse { 56 | log.Printf("<-x %s: SERVFAIL: recursion disabled", q) 57 | m := new(dns.Msg) 58 | m.SetReply(r) 59 | m.SetRcode(r, dns.RcodeServerFailure) 60 | m.Authoritative = false 61 | m.RecursionAvailable = false 62 | w.WriteMsg(m) 63 | } else { 64 | in, ns, err := d.queryExternal(r) 65 | if err != nil { 66 | log.Printf("<-x %s (@%s): SERVFAIL: %v", q, ns, err) 67 | m := new(dns.Msg) 68 | m.SetReply(r) 69 | m.SetRcode(r, dns.RcodeServerFailure) 70 | w.WriteMsg(m) 71 | } else { 72 | log.Printf("<-- %s (@%s): %d answers, %d extra, %d ns", q, ns, len(in.Answer), len(in.Extra), len(in.Ns)) 73 | in.Compress = true 74 | w.WriteMsg(in) 75 | } 76 | } 77 | } 78 | 79 | // handleDomain handles DNS queries that come to the cluster 80 | func (d *DnsServer) handleDomain(w dns.ResponseWriter, r *dns.Msg) { 81 | dom, qType := parseQuestion(r) 82 | q := dns.TypeToString[qType] + " " + dom 83 | log.Printf("--> Internal: %s", q) 84 | 85 | m := new(dns.Msg) 86 | m.SetReply(r) 87 | m.Authoritative = true 88 | 89 | supported, found, recs := d.queryRR(qType, dom) 90 | if !supported { 91 | log.Printf("<-x %s: NOTIMP", q) 92 | m.SetRcode(r, dns.RcodeNotImplemented) // NOTIMP 93 | } else if !found { 94 | log.Printf("<-x %s: NXDOMAIN", q) 95 | m.SetRcode(r, dns.RcodeNameError) // NXDOMAIN 96 | } else { 97 | for _, rec := range recs { 98 | rr, err := rrtype.ToRR(qType, dom, rec) 99 | if err != nil { 100 | log.Printf("<-x %s SERVFAIL: record conv err: %v", q, err) 101 | m.SetRcode(r, dns.RcodeServerFailure) 102 | break 103 | } else { 104 | log.Printf("<-- %s: %s", q, rr.String()) 105 | m.Answer = append(m.Answer, rr) 106 | } 107 | } 108 | } 109 | w.WriteMsg(m) 110 | } 111 | 112 | // queryExternal makes an external DNS query to a randomly picked external 113 | // nameserver. 114 | func (d *DnsServer) queryExternal(req *dns.Msg) (*dns.Msg, string, error) { 115 | // TODO use other nameservers in case of failure? 116 | ns := d.nameservers[rnd.Intn(len(d.nameservers))] 117 | c := new(dns.Client) 118 | in, _, err := c.Exchange(req, ns) 119 | return in, ns, err 120 | } 121 | 122 | // queryRR queries the DNS Resource Records for given record type. If the record 123 | // type is not supported or record is not found, false is returned from return 124 | // values, respectively. If records are found, they are returned in a shuffled 125 | // manner. 126 | func (d *DnsServer) queryRR(qType uint16, domain string) (supported bool, found bool, records []string) { 127 | if !rrtype.IsSupported(qType) { 128 | return false, false, nil 129 | } 130 | recs, ok := d.rr.Get(domain, qType) 131 | if !ok { 132 | return true, false, nil 133 | } 134 | shuffle(recs) 135 | return true, true, recs 136 | } 137 | 138 | // parseQuestion parses the first question in the DNS message into domain name 139 | // and DNS RR Type. 140 | func parseQuestion(r *dns.Msg) (domain string, qType uint16) { 141 | q := r.Question[0] 142 | return strings.TrimSpace(strings.ToLower(q.Name)), q.Qtype 143 | } 144 | 145 | // shuffle is an implementation of Modern Fisher–Yates shuffle algortihm. 146 | func shuffle(a []string) { 147 | for i := len(a) - 1; i > 0; i-- { 148 | r := rnd.Intn(i) 149 | a[i], a[r] = a[r], a[i] 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /server/server_test.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/ahmetalpbalkan/wagl/rrstore" 9 | "github.com/miekg/dns" 10 | ) 11 | 12 | func TestHandleExternalOff(t *testing.T) { 13 | srv, ready := testServer(t, rrstore.New()) 14 | <-ready 15 | defer srv.Shutdown() 16 | 17 | if r, err := query(srv.Addr, "example.com", dns.TypeA); err != nil { 18 | t.Fatalf("exchange failed: %v", err) 19 | } else if r.Rcode != dns.RcodeServerFailure { 20 | t.Fatalf("unexpected rcode. expected=%s got=%s", 21 | dns.TypeToString[dns.RcodeServerFailure], 22 | dns.TypeToString[uint16(r.Rcode)]) 23 | } 24 | } 25 | 26 | func TestHandleExternalOn(t *testing.T) { 27 | srv, ready := testServerExternal(t) 28 | <-ready 29 | defer srv.Shutdown() 30 | 31 | cases := []struct { 32 | fqdn string 33 | qType uint16 34 | expectedRCode int 35 | }{ 36 | // Uppercase vs lowercase 37 | {"example.com", dns.TypeA, dns.RcodeSuccess}, 38 | {"eXaMpLe.com", dns.TypeA, dns.RcodeSuccess}, 39 | {"EXAMPLE.COM", dns.TypeA, dns.RcodeSuccess}, 40 | 41 | // Various DNS RR classes 42 | {"google.com", dns.TypeA, dns.RcodeSuccess}, 43 | {"google.com", dns.TypeNS, dns.RcodeSuccess}, 44 | {"google.com", dns.TypeSOA, dns.RcodeSuccess}, 45 | {"google.com", dns.TypeMX, dns.RcodeSuccess}, 46 | 47 | // Tribute 48 | {"bilkent.edu.tr", dns.TypeA, dns.RcodeSuccess}, 49 | 50 | // Non-existing domains 51 | {"booyakashahearmenowrepresentkeepitreal.com", dns.TypeA, dns.RcodeNameError}, 52 | } 53 | 54 | for _, c := range cases { 55 | q := fmt.Sprintf("%s %s", dns.TypeToString[c.qType], c.fqdn) 56 | 57 | if r, err := query(srv.Addr, c.fqdn, c.qType); err != nil { 58 | t.Fatalf("exchange failed (%s): %v", q, err) 59 | } else if r.Rcode != c.expectedRCode { 60 | t.Fatalf("unexpected rcode (%s). expected=%s got=%s", q, 61 | dns.RcodeToString[c.expectedRCode], dns.RcodeToString[r.Rcode]) 62 | if len(r.Answer) == 0 { 63 | t.Fatalf("No answers for %q", q) 64 | } 65 | } 66 | } 67 | } 68 | 69 | func TestHandleDomain(t *testing.T) { 70 | rr := rrstore.New() 71 | rr.Set(map[uint16]map[string][]string{ 72 | dns.TypeA: { 73 | "api.domain.": []string{"10.0.0.1", "10.0.0.2"}, 74 | "blog.domain.": []string{"10.0.1.1", "10.0.1.2", "10.0.1.3"}, 75 | }, 76 | dns.TypeSRV: { 77 | "_web._tcp.domain.": []string{"10.0.0.1:80"}, 78 | "_web._udp.domain.": []string{"10.0.0.1:5001", 79 | "10.0.0.2:5002", 80 | "10.0.0.3:5003"}, 81 | }, 82 | }) 83 | 84 | srv, ready := testServer(t, rr) 85 | <-ready 86 | defer srv.Shutdown() 87 | 88 | cases := []struct { 89 | fqdn string 90 | qType uint16 91 | expectedRCode int 92 | expectedAnswers int 93 | }{ 94 | // List all test cases for all possible DNS questions here within the 95 | // domain. 96 | {"nonexistent.domain.", dns.TypeA, dns.RcodeNameError, 0}, 97 | {"nonexistent.domain.", dns.TypeSRV, dns.RcodeNameError, 0}, 98 | {"api.domain", dns.TypeA, dns.RcodeSuccess, 2}, 99 | {"_web._tcp.domain", dns.TypeSRV, dns.RcodeSuccess, 1}, 100 | {"_WEB._UDP.domain", dns.TypeSRV, dns.RcodeSuccess, 3}, 101 | } 102 | 103 | for _, c := range cases { 104 | q := fmt.Sprintf("%s %s", dns.TypeToString[c.qType], c.fqdn) 105 | 106 | if r, err := query(srv.Addr, c.fqdn, c.qType); err != nil { 107 | t.Fatalf("exchange failed (%s): %v", q, err) 108 | } else if r.Rcode != c.expectedRCode { 109 | t.Fatalf("unexpected rcode (%s). expected=%s got=%s", q, 110 | dns.RcodeToString[c.expectedRCode], dns.RcodeToString[r.Rcode]) 111 | } else if len(r.Answer) != c.expectedAnswers { 112 | t.Fatalf("unexpected answers count. expected=%d got=%d", q, 113 | len(r.Answer), c.expectedAnswers) 114 | } 115 | } 116 | } 117 | 118 | func TestRRShuffling(t *testing.T) { 119 | rr := rrstore.New() 120 | recs := []string{"10.0.0.1", "10.0.0.2", "10.0.0.3"} 121 | rr.Set(map[uint16]map[string][]string{ 122 | dns.TypeA: {"a.domain.": []string{"10.0.0.1", "10.0.0.2", "10.0.0.3"}}}) 123 | 124 | srv, ready := testServer(t, rr) 125 | <-ready 126 | defer srv.Shutdown() 127 | 128 | n := 50 129 | same := true 130 | for i := 0; i < n; i++ { 131 | r, err := query(srv.Addr, "a.domain.", dns.TypeA) 132 | if err != nil { 133 | t.Fatal(err) 134 | } 135 | as := make([]string, len(r.Answer)) 136 | for i, _ := range r.Answer { 137 | as[i] = r.Answer[i].(*dns.A).A.String() 138 | if len(as) != len(recs) { 139 | t.Fatalf("wrong answer count: %d", len(as)) 140 | } 141 | } 142 | if !reflect.DeepEqual(as, recs) { 143 | same = false 144 | break 145 | } 146 | } 147 | if same { 148 | t.Fatalf("same RR ordering occurred even after %d requests", n) 149 | } 150 | } 151 | 152 | func query(addr string, domain string, qType uint16) (*dns.Msg, error) { 153 | c, m := new(dns.Client), new(dns.Msg) 154 | m.SetQuestion(dns.Fqdn(domain), qType) 155 | r, _, err := c.Exchange(m, addr) 156 | return r, err 157 | } 158 | 159 | // testServer gives a test server capable of serving only internal requests. 160 | func testServer(t *testing.T, rr rrstore.RRReader) (*DnsServer, <-chan struct{}) { 161 | srv := New("domain", ":8053", rr, false, []string{}) 162 | 163 | ready := make(chan struct{}, 1) 164 | srv.NotifyStartedFunc = func() { 165 | close(ready) 166 | } 167 | go srv.ListenAndServe() 168 | return srv, ready 169 | } 170 | 171 | // testServerExternal gives a test server capable of serving only external 172 | // requests. 173 | func testServerExternal(t *testing.T) (*DnsServer, <-chan struct{}) { 174 | ns := []string{"8.8.8.8:53", "8.8.4.4:53"} 175 | srv := New("dontcare", ":8053", rrstore.New(), true, ns) 176 | ready := make(chan struct{}, 1) 177 | srv.NotifyStartedFunc = func() { 178 | close(ready) 179 | } 180 | go srv.ListenAndServe() 181 | return srv, ready 182 | } 183 | -------------------------------------------------------------------------------- /swarm/swarm.go: -------------------------------------------------------------------------------- 1 | // Package swarm provides the cluster state and tasks that are going to be 2 | // load balanced in the cluster. 3 | package swarm 4 | 5 | import ( 6 | "crypto/tls" 7 | "encoding/json" 8 | "fmt" 9 | "io/ioutil" 10 | "net" 11 | "net/http" 12 | "net/url" 13 | "strings" 14 | "time" 15 | 16 | "github.com/ahmetalpbalkan/wagl/task" 17 | ) 18 | 19 | const ( 20 | dnsLabel = "dns.service" 21 | dnsDomain = "dns.domain" 22 | ) 23 | 24 | var ( 25 | defaultTimeout = time.Second * 30 26 | ) 27 | 28 | type Swarm struct { 29 | client *http.Client 30 | url *url.URL 31 | } 32 | 33 | // container represents a container item in /containers/json Endpoint of Docker 34 | // Remote API 35 | type container struct { 36 | Id string `json:"Id"` 37 | Ports []containerPort `json:"Ports"` 38 | Names []string `json:"Names"` 39 | Labels map[string]string `json:"Labels"` 40 | } 41 | 42 | // containerPort represents a port declaration item as it appears in Docker 43 | // Remote API /containers/json. 44 | type containerPort struct { 45 | IP string `json:"IP"` 46 | PrivatePort int `json:"PrivatePort"` 47 | PublicPort int `json:"PublicPort"` 48 | Type string `json:"Type"` 49 | } 50 | 51 | // New constructs a client to access a Docker Swarm cluster state. If the cluster 52 | // does not use TLS, tlsConfig must be nil. 53 | func New(swarmUrl string, tlsConfig *tls.Config) (*Swarm, error) { 54 | u, err := url.Parse(swarmUrl) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | // Convert unix:// to http(s):// 60 | if u.Scheme == "" || u.Scheme == "tcp" { 61 | if tlsConfig == nil { 62 | u.Scheme = "http" 63 | } else { 64 | u.Scheme = "https" 65 | } 66 | } 67 | 68 | cl, err := httpClient(u, tlsConfig) 69 | if err != nil { 70 | return nil, fmt.Errorf("error initializing HTTP client for Docker: %v", err) 71 | } 72 | 73 | return &Swarm{ 74 | client: cl, 75 | url: u, 76 | }, nil 77 | } 78 | 79 | // httpClient provides an HTTP client to make requests to the Docker API. 80 | // The code is mostly copied from https://github.com/samalba/dockerclient/ 81 | // instead of copying the entire package for one method. Please check the 82 | // project's license at: https://github.com/samalba/dockerclient/blob/master/LICENSE 83 | func httpClient(u *url.URL, tlsConfig *tls.Config) (*http.Client, error) { 84 | httpTransport := &http.Transport{TLSClientConfig: tlsConfig} 85 | 86 | // Choose between Unix and TCP clients 87 | switch u.Scheme { 88 | default: 89 | httpTransport.Dial = func(proto, addr string) (net.Conn, error) { 90 | return net.DialTimeout(proto, addr, defaultTimeout) 91 | } 92 | case "unix": 93 | socketPath := u.Path 94 | unixDial := func(proto, addr string) (net.Conn, error) { 95 | return net.DialTimeout("unix", socketPath, defaultTimeout) 96 | } 97 | httpTransport.Dial = unixDial 98 | // Override the main URL object so the HTTP lib won't complain 99 | u.Scheme = "http" 100 | u.Host = "unix.sock" 101 | u.Path = "" 102 | } 103 | return &http.Client{Transport: httpTransport}, nil 104 | } 105 | 106 | // Tasks provides running containers in a Swarm cluster. 107 | func (s *Swarm) Tasks() (task.ClusterState, error) { 108 | ll, err := s.listContainers() 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | out, err := containersToTasks(ll) 114 | if err != nil { 115 | return nil, err 116 | } 117 | return out, err 118 | } 119 | 120 | // listContainers returns list of running containers from Docker API 121 | func (s *Swarm) listContainers() ([]container, error) { 122 | url := strings.TrimSuffix(s.url.String(), "/") 123 | req, err := http.NewRequest("GET", url+"/containers/json?all=false", nil) 124 | if err != nil { 125 | return nil, fmt.Errorf("error creating the HTTP request: %v", err) 126 | } 127 | resp, err := s.client.Do(req) 128 | if err != nil { 129 | return nil, err 130 | } 131 | defer resp.Body.Close() 132 | data, err := ioutil.ReadAll(resp.Body) 133 | if err != nil { 134 | return nil, fmt.Errorf("error reading response body: %v", err) 135 | } 136 | 137 | if resp.StatusCode >= 400 { 138 | return nil, fmt.Errorf("Docker API error (Status: %d) Body: %q", resp.Status, data) 139 | } 140 | 141 | var ll []container 142 | if err := json.Unmarshal(data, &ll); err != nil { 143 | return nil, fmt.Errorf("Error unmarshaling response: %v", err) 144 | } 145 | return ll, nil 146 | } 147 | 148 | // containersToTasks strips out unnecessary info from Container type and 149 | // makes task.Task instances out of given list. 150 | func containersToTasks(ll []container) ([]task.Task, error) { 151 | out := make([]task.Task, len(ll)) 152 | for i, c := range ll { 153 | ports, err := mappedPorts(c.Ports) 154 | if err != nil { 155 | return nil, fmt.Errorf("error parsing ports for container %s (%v): %v", c.Id, c.Names, err) 156 | } 157 | srv, dom := dnsPartsFromLabels(c.Labels) 158 | out[i] = task.Task{ 159 | Id: c.Id, 160 | Ports: ports, 161 | Service: srv, 162 | Domain: dom, 163 | } 164 | } 165 | return out, nil 166 | } 167 | 168 | // dnsPartsFromLabels gives service name and domain name (if 169 | // applicable) based which are going to be used in the DNS Resource Records as 170 | // part of the FQDN. If the container is not configured or does not have enough 171 | // info to resolve the service name, both return values will be empty string. 172 | // 173 | // Domain name can be used as project name to categorize many services that 174 | // belong to a project in a FQDN like service.domain.swarm. 175 | // 176 | // In Docker, container labels dns.service and dns.domain are used to come up 177 | // with DNS records for FQDNs like api.swarm., _api._tcp.swarm (no dns.domain 178 | // specified), api.billing.swarm. and _api._tcp.billing.swarm. where dns.domain 179 | // is specified as "billing" and dns.service is specified as "api". 180 | // 181 | // These labels are case insensitive and invalid characters (per DNS spec) 182 | // would cause no DNS records to be generated for these services. 183 | func dnsPartsFromLabels(labels map[string]string) (string, string) { 184 | var ( 185 | service = strings.ToLower(labels[dnsLabel]) 186 | project = strings.ToLower(labels[dnsDomain]) 187 | ) 188 | if service == "" { // does not make sense to have a project name w/o service 189 | project = "" 190 | } 191 | return service, project 192 | } 193 | 194 | // mappedPorts returns only list of ports mapped to the host from a list of 195 | // port mappings. 196 | func mappedPorts(l []containerPort) ([]task.Port, error) { 197 | out := make([]task.Port, 0) 198 | for _, v := range l { 199 | if isMappedPort(v) { 200 | p, err := toPort(v) 201 | if err != nil { 202 | return nil, err 203 | } 204 | out = append(out, p) 205 | } 206 | } 207 | return out, nil 208 | } 209 | 210 | // isMappedPort determines if a port listing is actually mapped to the host. 211 | func isMappedPort(l containerPort) bool { 212 | return l.IP != "" && l.PublicPort != 0 213 | } 214 | 215 | // toPort converts Docker port mapping to task.Port. p must be mapped to host. 216 | func toPort(p containerPort) (task.Port, error) { 217 | ip := net.ParseIP(p.IP) 218 | if ip == nil { 219 | return task.Port{}, fmt.Errorf("cannot parse IP '%s'", p.IP) 220 | } 221 | return task.Port{ 222 | HostIP: ip, 223 | HostPort: p.PublicPort, 224 | Proto: p.Type, 225 | }, nil 226 | } 227 | -------------------------------------------------------------------------------- /swarm/swarm_test.go: -------------------------------------------------------------------------------- 1 | package swarm 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "net/http/httptest" 7 | "reflect" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/ahmetalpbalkan/wagl/task" 12 | ) 13 | 14 | func TestGetTasks(t *testing.T) { 15 | srv := testServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 16 | // implement partial /info endpoint 17 | b := `[ 18 | { 19 | "Id": "nginx", 20 | "Labels": { 21 | "dns.domain": "bilLING", 22 | "dns.service": "API" 23 | }, 24 | "Ports": [ 25 | { 26 | "IP": "192.168.99.103", 27 | "PrivatePort": 80, 28 | "PublicPort": 8000, 29 | "Type": "tcp" 30 | }, 31 | { 32 | "IP": "", 33 | "PrivatePort": 443, 34 | "PublicPort": 0, 35 | "Type": "tcp" 36 | } 37 | ] 38 | }, 39 | { 40 | "Id": "no-ports-but-has-labels", 41 | "Labels": { 42 | "dns.domain": "billing", 43 | "dns.service": "db" 44 | } 45 | } 46 | ]` 47 | w.Write([]byte(b)) 48 | })) 49 | defer srv.Close() 50 | 51 | sw, err := New(srv.URL, nil) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | 56 | out, err := sw.Tasks() 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | 61 | expected := task.ClusterState([]task.Task{ 62 | { 63 | Id: "nginx", 64 | Service: "api", 65 | Domain: "billing", 66 | Ports: []task.Port{{ 67 | HostIP: net.IPv4(192, 168, 99, 103), 68 | HostPort: 8000, 69 | Proto: "tcp", 70 | }}, 71 | }, 72 | { 73 | Id: "no-ports-but-has-labels", 74 | Service: "db", 75 | Domain: "billing", 76 | Ports: []task.Port{}, 77 | }, 78 | }) 79 | if !reflect.DeepEqual(out, expected) { 80 | t.Fatalf("got wrong value.\nexpected: %#v\ngot:%#v", expected, out) 81 | } 82 | } 83 | 84 | func Test_isMappedPort(t *testing.T) { 85 | cases := []struct { 86 | in containerPort 87 | out bool 88 | }{ 89 | {containerPort{ 90 | IP: "192.168.99.103", 91 | PrivatePort: 80, 92 | PublicPort: 80, 93 | Type: "tcp", 94 | }, true}, 95 | {containerPort{ 96 | IP: "", 97 | PrivatePort: 443, 98 | PublicPort: 0, 99 | Type: "tcp", 100 | }, false}, 101 | } 102 | 103 | for i, c := range cases { 104 | if o := isMappedPort(c.in); o != c.out { 105 | t.Fatal("wrong value for case %d", i) 106 | } 107 | } 108 | } 109 | 110 | func Test_mappedPorts(t *testing.T) { 111 | in := []containerPort{ 112 | { 113 | IP: "192.168.99.103", 114 | PrivatePort: 80, 115 | PublicPort: 8000, 116 | Type: "tcp", 117 | }, 118 | { 119 | IP: "", 120 | PrivatePort: 443, 121 | PublicPort: 0, 122 | Type: "tcp", 123 | }, 124 | } 125 | 126 | o, err := mappedPorts(in) 127 | if err != nil { 128 | t.Fatal(err) 129 | } 130 | if !reflect.DeepEqual(o, []task.Port{{ 131 | HostIP: net.IPv4(192, 168, 99, 103), 132 | HostPort: 8000, 133 | Proto: "tcp", 134 | }}) { 135 | t.Fatal("got wrong mappings: %#v", o) 136 | } 137 | } 138 | 139 | func Test_toPort(t *testing.T) { 140 | p, err := toPort(containerPort{ 141 | IP: "192.168.99.103", 142 | PrivatePort: 80, 143 | PublicPort: 8001, 144 | Type: "tcp", 145 | }) 146 | if err != nil { 147 | t.Fatal(err) 148 | } 149 | 150 | expected := task.Port{ 151 | HostIP: net.IPv4(192, 168, 99, 103), 152 | HostPort: 8001, 153 | Proto: "tcp", 154 | } 155 | 156 | if !reflect.DeepEqual(p, expected) { // deep equal required: net.IP is []byte 157 | t.Fatal("got wrong value: %#v", p) 158 | } 159 | } 160 | 161 | func Test_toPortFails(t *testing.T) { 162 | badIP := "not-an-ip" 163 | _, err := toPort(containerPort{ 164 | IP: badIP, 165 | PrivatePort: 80, 166 | PublicPort: 8000, 167 | Type: "tcp", 168 | }) 169 | if err == nil { 170 | t.Fatal("IP parsing did not fail") 171 | } else if !strings.Contains(err.Error(), badIP) { 172 | t.Fatalf("error message does not contain faulty IP value: %v", err) 173 | } 174 | } 175 | 176 | func Test_containerSrvNames(t *testing.T) { 177 | cases := []struct { 178 | labels map[string]string 179 | srv string 180 | prj string 181 | }{ 182 | {map[string]string{}, "", ""}, // empty 183 | {map[string]string{ // no service name 184 | "dns.domain": "billing"}, "", ""}, 185 | {map[string]string{ // no framework 186 | "dns.service": "API"}, "api", ""}, 187 | {map[string]string{ // both values, case-insensitivity test 188 | "dns.service": "API", 189 | "dns.domain": "Billing"}, "api", "billing"}, 190 | } 191 | 192 | for _, c := range cases { 193 | srv, prj := dnsPartsFromLabels(c.labels) 194 | if srv != c.srv { 195 | t.Fatalf("wrong service name. expected: '%s', got: '%s'", c.srv, srv) 196 | } 197 | if prj != c.prj { 198 | t.Fatalf("wrong service name. expected: '%s', got: '%s'", c.prj, prj) 199 | } 200 | } 201 | 202 | } 203 | 204 | func testServer(handler http.Handler) *httptest.Server { 205 | s := httptest.NewServer(handler) 206 | return s 207 | } 208 | -------------------------------------------------------------------------------- /task/task.go: -------------------------------------------------------------------------------- 1 | // Package task describes a task resource in the cluster 2 | package task 3 | 4 | import ( 5 | "fmt" 6 | "net" 7 | ) 8 | 9 | // ClusterState describes the current state of the cluster. 10 | type ClusterState []Task 11 | 12 | // Task describes a running (active) container in the cluster. 13 | type Task struct { 14 | Id string // Identifies container in the cluster 15 | Ports []Port // List of container ports mapped to host 16 | Service string // Name of the service that groups tasks under the same DNS record 17 | Domain string // Optional, a domain name describing the project name the task belongs to, or the launcher framework/orchestrator. 18 | } 19 | 20 | // Port describes network port of a service on the host machine. 21 | type Port struct { 22 | HostIP net.IP 23 | HostPort int 24 | Proto string 25 | } 26 | 27 | func (p Port) String() string { 28 | return fmt.Sprintf("%s:%d/%s", p.HostIP, p.HostPort, p.Proto) 29 | } 30 | -------------------------------------------------------------------------------- /tlsconfig/config.go: -------------------------------------------------------------------------------- 1 | // ----wagl note---- 2 | // This package has been copied from https://github.com/docker/docker/blob/master/pkg/tlsconfig/config.go 3 | // for convenience reasons. Please refer to Docker project's license at: 4 | // https://github.com/docker/docker/blob/master/LICENSE 5 | // ----wagl note---- 6 | 7 | // Package tlsconfig provides primitives to retrieve secure-enough TLS configurations for both clients and servers. 8 | // 9 | // As a reminder from https://golang.org/pkg/crypto/tls/#Config: 10 | // A Config structure is used to configure a TLS client or server. After one has been passed to a TLS function it must not be modified. 11 | // A Config may be reused; the tls package will also not modify it. 12 | package tlsconfig 13 | 14 | import ( 15 | "crypto/tls" 16 | "crypto/x509" 17 | "fmt" 18 | "io/ioutil" 19 | "os" 20 | ) 21 | 22 | // Options represents the information needed to create client and server TLS configurations. 23 | type Options struct { 24 | CAFile string 25 | 26 | // If either CertFile or KeyFile is empty, Client() will not load them 27 | // preventing the client from authenticating to the server. 28 | // However, Server() requires them and will error out if they are empty. 29 | CertFile string 30 | KeyFile string 31 | 32 | // client-only option 33 | InsecureSkipVerify bool 34 | // server-only option 35 | ClientAuth tls.ClientAuthType 36 | } 37 | 38 | // Extra (server-side) accepted CBC cipher suites - will phase out in the future 39 | var acceptedCBCCiphers = []uint16{ 40 | tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, 41 | tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, 42 | tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, 43 | tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 44 | tls.TLS_RSA_WITH_AES_256_CBC_SHA, 45 | tls.TLS_RSA_WITH_AES_128_CBC_SHA, 46 | } 47 | 48 | // Client TLS cipher suites (dropping CBC ciphers for client preferred suite set) 49 | var clientCipherSuites = []uint16{ 50 | tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 51 | tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 52 | } 53 | 54 | // For use by code which already has a crypto/tls options struct but wants to 55 | // use a commonly accepted set of TLS cipher suites, with known weak algorithms removed 56 | var DefaultServerAcceptedCiphers = append(clientCipherSuites, acceptedCBCCiphers...) 57 | 58 | // ServerDefault is a secure-enough TLS configuration for the server TLS configuration. 59 | var ServerDefault = tls.Config{ 60 | // Avoid fallback to SSL protocols < TLS1.0 61 | MinVersion: tls.VersionTLS10, 62 | PreferServerCipherSuites: true, 63 | CipherSuites: DefaultServerAcceptedCiphers, 64 | } 65 | 66 | // ClientDefault is a secure-enough TLS configuration for the client TLS configuration. 67 | var ClientDefault = tls.Config{ 68 | // Prefer TLS1.2 as the client minimum 69 | MinVersion: tls.VersionTLS12, 70 | CipherSuites: clientCipherSuites, 71 | } 72 | 73 | // certPool returns an X.509 certificate pool from `caFile`, the certificate file. 74 | func certPool(caFile string) (*x509.CertPool, error) { 75 | // If we should verify the server, we need to load a trusted ca 76 | certPool := x509.NewCertPool() 77 | pem, err := ioutil.ReadFile(caFile) 78 | if err != nil { 79 | return nil, fmt.Errorf("Could not read CA certificate %q: %v", caFile, err) 80 | } 81 | if !certPool.AppendCertsFromPEM(pem) { 82 | return nil, fmt.Errorf("failed to append certificates from PEM file: %q", caFile) 83 | } 84 | s := certPool.Subjects() 85 | subjects := make([]string, len(s)) 86 | for i, subject := range s { 87 | subjects[i] = string(subject) 88 | } 89 | return certPool, nil 90 | } 91 | 92 | // Client returns a TLS configuration meant to be used by a client. 93 | func Client(options Options) (*tls.Config, error) { 94 | tlsConfig := ClientDefault 95 | tlsConfig.InsecureSkipVerify = options.InsecureSkipVerify 96 | if !options.InsecureSkipVerify { 97 | CAs, err := certPool(options.CAFile) 98 | if err != nil { 99 | return nil, err 100 | } 101 | tlsConfig.RootCAs = CAs 102 | } 103 | 104 | if options.CertFile != "" && options.KeyFile != "" { 105 | tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile) 106 | if err != nil { 107 | return nil, fmt.Errorf("Could not load X509 key pair: %v. Make sure the key is not encrypted", err) 108 | } 109 | tlsConfig.Certificates = []tls.Certificate{tlsCert} 110 | } 111 | 112 | return &tlsConfig, nil 113 | } 114 | 115 | // Server returns a TLS configuration meant to be used by a server. 116 | func Server(options Options) (*tls.Config, error) { 117 | tlsConfig := ServerDefault 118 | tlsConfig.ClientAuth = options.ClientAuth 119 | tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile) 120 | if err != nil { 121 | if os.IsNotExist(err) { 122 | return nil, fmt.Errorf("Could not load X509 key pair (cert: %q, key: %q): %v", options.CertFile, options.KeyFile, err) 123 | } 124 | return nil, fmt.Errorf("Error reading X509 key pair (cert: %q, key: %q): %v. Make sure the key is not encrypted.", options.CertFile, options.KeyFile, err) 125 | } 126 | tlsConfig.Certificates = []tls.Certificate{tlsCert} 127 | if options.ClientAuth >= tls.VerifyClientCertIfGiven { 128 | CAs, err := certPool(options.CAFile) 129 | if err != nil { 130 | return nil, err 131 | } 132 | tlsConfig.ClientCAs = CAs 133 | } 134 | return &tlsConfig, nil 135 | } 136 | -------------------------------------------------------------------------------- /usage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Changes: 4 | // - removed '[argument...]' at the end of USAGE line 5 | // - changed '[global options]' with '[options]' 6 | // - changed 'GLOBAL OPTIONS' with 'OPTIONS' 7 | // - removed 'COMMANDS' section entirely (was showing only 'help,h' anyway) 8 | const usageTemplate = `NAME: 9 | {{.Name}} - {{.Usage}} 10 | 11 | USAGE: 12 | {{.Name}} {{if .Flags}}[options]{{end}}{{if .Commands}} command [command options]{{end}} 13 | {{if .Version}} 14 | VERSION: 15 | {{.Version}} 16 | {{end}}{{if len .Authors}} 17 | AUTHOR(S): 18 | {{range .Authors}}{{ . }}{{end}} 19 | {{end}}{{if .Flags}} 20 | OPTIONS: 21 | {{range .Flags}}{{.}} 22 | {{end}}{{end}}{{if .Copyright }} 23 | COPYRIGHT: 24 | {{.Copyright}} 25 | {{end}} 26 | ` 27 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "path/filepath" 6 | 7 | "github.com/ahmetalpbalkan/wagl/tlsconfig" 8 | "github.com/miekg/dns" 9 | ) 10 | 11 | // tlsConfig constructs a Docker TLS configuration using the certs in the 12 | // specified directory. 13 | func tlsConfig(certDir string, verify bool) (*tls.Config, error) { 14 | if certDir == "" { 15 | return nil, nil 16 | } 17 | 18 | return tlsconfig.Client(tlsconfig.Options{ 19 | CAFile: filepath.Join(certDir, "ca.pem"), 20 | CertFile: filepath.Join(certDir, "cert.pem"), 21 | KeyFile: filepath.Join(certDir, "key.pem"), 22 | InsecureSkipVerify: !verify, 23 | }) 24 | } 25 | 26 | // localNameservers returns list of local nameservers. 27 | func localNameservers() ([]string, error) { 28 | c, err := dns.ClientConfigFromFile("/etc/resolv.conf") 29 | if err != nil { 30 | return nil, err 31 | } 32 | return c.Servers, nil 33 | } 34 | --------------------------------------------------------------------------------