diff --git a/cmd/pixiecore-gpl/LICENSE b/cmd/pixiecore-gpl/LICENSE new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/cmd/pixiecore-gpl/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/cmd/pixiecore-gpl/main.go b/cmd/pixiecore-gpl/main.go new file mode 100644 index 0000000..cf43563 --- /dev/null +++ b/cmd/pixiecore-gpl/main.go @@ -0,0 +1,32 @@ +// Copyright 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "go.universe.tf/netboot/pixiecore" + "go.universe.tf/netboot/third_party/ipxe" +) + +func main() { + pxe := ipxe.MustAsset("undionly.kpxe") + efi32 := ipxe.MustAsset("ipxe-i386.efi") + efi64 := ipxe.MustAsset("ipxe-x86_64.efi") + + pixiecore.CLI(map[pixiecore.Firmware][]byte{ + pixiecore.FirmwareX86PC: pxe, + pixiecore.FirmwareEFI32: efi32, + pixiecore.FirmwareEFI64: efi64, + }) +} diff --git a/cmd/pixiecore/main.go b/cmd/pixiecore/main.go new file mode 100644 index 0000000..43e5971 --- /dev/null +++ b/cmd/pixiecore/main.go @@ -0,0 +1,21 @@ +// Copyright 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import "go.universe.tf/netboot/pixiecore" + +func main() { + pixiecore.CLI(nil) +} diff --git a/pixiecore/cli.go b/pixiecore/cli.go new file mode 100644 index 0000000..eb849c6 --- /dev/null +++ b/pixiecore/cli.go @@ -0,0 +1,70 @@ +// Copyright 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pixiecore + +import ( + "fmt" + "io" + "net/http" + + "go.universe.tf/netboot/pixiecore/cmd" +) + +type hellyeah struct{} + +func (hellyeah) BootSpec(Machine) (*Spec, error) { + return &Spec{ + Kernel: ID("k"), + Initrd: []ID{"0", "1"}, + }, nil +} + +func (hellyeah) ReadBootFile(p ID) (io.ReadCloser, error) { + var url string + switch p { + case "k": + url = "http://tinycorelinux.net/7.x/x86/release/distribution_files/vmlinuz64" + case "0": + url = "http://tinycorelinux.net/7.x/x86/release/distribution_files/rootfs.gz" + case "1": + url = "http://tinycorelinux.net/7.x/x86/release/distribution_files/modules64.gz" + } + + resp, err := http.Get(url) + if err != nil { + return nil, err + } + if resp.StatusCode != 200 { + return nil, fmt.Errorf("GET %q failed: %s", url, resp.Status) + } + return resp.Body, nil +} + +func (hellyeah) WriteBootFile(ID, io.Reader) error { + return nil +} + +// CLI runs the Pixiecore commandline. +// +// Takes a map of ipxe bootloader binaries for various architectures. +func CLI(ipxe map[Firmware][]byte) { + s := &Server{ + Booter: hellyeah{}, + Ipxe: ipxe, + } + fmt.Println(s.Serve()) + + cmd.Execute() +} diff --git a/pixiecore/cmd/api.go b/pixiecore/cmd/api.go new file mode 100644 index 0000000..1019bd5 --- /dev/null +++ b/pixiecore/cmd/api.go @@ -0,0 +1,34 @@ +// Copyright © 2016 David Anderson +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import "github.com/spf13/cobra" + +var apiCmd = &cobra.Command{ + Use: "api server", + Short: "Boot machines using instructions from one or more API servers", + Long: `API mode is a "PXE to HTTP" translator. Whenever Pixiecore sees a +machine trying to PXE boot, it will ask a remote HTTP(S) API server +what to do. The API server can tell Pixiecore to ignore the machine, +or tell it what to boot. + +It is your responsibility to implement or run a server that implements +the Pixiecore boot API. The specification can be found at .`, + Run: func(cmd *cobra.Command, args []string) { todo("api called") }} + +func init() { + RootCmd.AddCommand(apiCmd) + // TODO: SSL cert flags for both client and server auth. +} diff --git a/pixiecore/cmd/boot.go b/pixiecore/cmd/boot.go new file mode 100644 index 0000000..f057e35 --- /dev/null +++ b/pixiecore/cmd/boot.go @@ -0,0 +1,39 @@ +// Copyright 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import "github.com/spf13/cobra" + +var bootCmd = &cobra.Command{ + Use: "boot kernel [initrd...]", + Short: "Boot a kernel and optional init ramdisks", + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 1 { + fatalf("you must specify at least a kernel") + } + kernel := args[0] + initrd := args[1:] + cmdline, err := cmd.Flags().GetString("cmdline") + if err != nil { + fatalf("Error reading flag: %s", err) + } + todo("run in static mode with kernel=%s, initrd=%v, cmdline=%q", kernel, initrd, cmdline) + }, +} + +func init() { + RootCmd.AddCommand(bootCmd) + bootCmd.Flags().StringP("cmdline", "c", "", "Kernel commandline arguments") +} diff --git a/pixiecore/cmd/quick.go b/pixiecore/cmd/quick.go new file mode 100644 index 0000000..27ba0ee --- /dev/null +++ b/pixiecore/cmd/quick.go @@ -0,0 +1,42 @@ +// Copyright © 2016 David Anderson +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import "github.com/spf13/cobra" + +var quickCmd = &cobra.Command{ + Use: "quick recipe [settings...]", + Short: "Boot an OS from a list", + Long: `This ends up working the same as the simple boot command, but saves +you having to find the kernels and ramdisks for popular OSes. + +TODO: better help here +`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 1 { + fatalf("you must specify at least a recipe") + } + recipe := args[0] + todo("run in quick mode with recipe=%s", recipe) + }, +} + +func init() { + RootCmd.AddCommand(quickCmd) + + // TODO: some kind of caching support where quick OSes get + // downloaded locally, so you don't have to fetch from a remote + // server on every boot attempt. +} diff --git a/pixiecore/cmd/root.go b/pixiecore/cmd/root.go new file mode 100644 index 0000000..24eff94 --- /dev/null +++ b/pixiecore/cmd/root.go @@ -0,0 +1,72 @@ +// Copyright © 2016 David Anderson +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var cfgFile string + +// This represents the base command when called without any subcommands +var RootCmd = &cobra.Command{ + Use: "pixiecore", + Short: "All-in-one network booting", + Long: `Pixiecore is a tool to make network booting easy.`, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +// Execute adds all child commands to the root command sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := RootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(-1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file") +} + +func initConfig() { + if cfgFile != "" { // enable ability to specify config file via flag + viper.SetConfigFile(cfgFile) + if err := viper.ReadInConfig(); err != nil { + fmt.Printf("Error reading configuration file %q: %s\n", viper.ConfigFileUsed(), err) + os.Exit(1) + } + fmt.Println("Using config file:", viper.ConfigFileUsed()) + } + + viper.SetEnvPrefix("pixiecore") + viper.AutomaticEnv() // read in environment variables that match +} + +func fatalf(msg string, args ...interface{}) { + fmt.Printf(msg+"\n", args...) + os.Exit(1) +} + +func todo(msg string, args ...interface{}) { + fatalf("TODO: "+msg, args...) +} diff --git a/pixiecore/dhcp.go b/pixiecore/dhcp.go new file mode 100644 index 0000000..e12eb15 --- /dev/null +++ b/pixiecore/dhcp.go @@ -0,0 +1,214 @@ +// Copyright 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pixiecore + +import ( + "errors" + "fmt" + "net" + + "go.universe.tf/netboot/dhcp4" +) + +func (s *Server) serveDHCP(conn *dhcp4.Conn) { + for { + pkt, intf, err := conn.RecvDHCP() + if err != nil { + fmt.Println(err) + return + } + if intf == nil { + fmt.Println("no interface") + continue + } + + mach, isIpxe, fwtype, err := s.validateDHCP(pkt) + if err != nil { + fmt.Println(err) + continue + } + + spec, err := s.Booter.BootSpec(mach) + if err != nil { + fmt.Println(err) + continue + } + if spec == nil { + fmt.Println("no spec") + continue + } + + // Machine should be booted. + serverIP, err := interfaceIP(intf) + if err != nil { + fmt.Println(err) + continue + } + + resp, err := s.offerDHCP(pkt, mach, serverIP, isIpxe, fwtype) + if err != nil { + fmt.Println(err) + continue + } + + if err = conn.SendDHCP(resp, intf); err != nil { + fmt.Println(err) + continue + } + } +} + +func (s *Server) validateDHCP(pkt *dhcp4.Packet) (mach Machine, isIpxe bool, fwtype Firmware, err error) { + if pkt.Type != dhcp4.MsgDiscover { + return mach, false, 0, errors.New("not DHCPDISCOVER") + } + + fwt, err := pkt.Options.Uint16(93) + if err != nil { + return mach, false, 0, fmt.Errorf("malformed arch: %s", err) + } + fwtype = Firmware(fwt) + if s.Ipxe[fwtype] == nil { + return mach, false, 0, fmt.Errorf("unsupported firmware type %d", fwtype) + } + + guid := pkt.Options[97] + switch len(guid) { + case 0: + // A missing GUID is invalid according to the spec, however + // there are PXE ROMs in the wild that omit the GUID and still + // expect to boot. + case 17: + if guid[0] != 0 { + return mach, false, 0, errors.New("malformed GUID (leading byte must be zero)") + } + default: + return mach, false, 0, errors.New("malformed GUID (wrong size)") + } + + // iPXE options + if len(pkt.Options[175]) > 0 { + bs := pkt.Options[175] + for len(bs) > 0 { + if len(bs) < 2 || len(bs)-2 < int(bs[1]) { + return mach, false, 0, errors.New("Malformed iPXE option") + } + switch bs[0] { + case 19: + // This iPXE build supports HTTP, so is appropriate + // for going straight into the OS kernel, no need to + // chainload our own. + isIpxe = true + } + bs = bs[2+int(bs[1]):] + } + } + + mach.MAC = pkt.HardwareAddr + mach.Arch = fwToArch[fwtype] + return mach, isIpxe, fwtype, nil +} + +func (s *Server) offerDHCP(pkt *dhcp4.Packet, mach Machine, serverIP net.IP, isIpxe bool, fwtype Firmware) (*dhcp4.Packet, error) { + resp := &dhcp4.Packet{ + Type: dhcp4.MsgOffer, + TransactionID: pkt.TransactionID, + Broadcast: true, + HardwareAddr: mach.MAC, + RelayAddr: pkt.RelayAddr, + ServerAddr: serverIP, + Options: make(dhcp4.Options), + } + resp.Options[dhcp4.OptServerIdentifier] = serverIP + // says the server should identify itself as a PXEClient vendor + // type, even though it's a server. Strange. + resp.Options[dhcp4.OptVendorIdentifier] = []byte("PXEClient") + if pkt.Options[97] != nil { + resp.Options[97] = pkt.Options[97] + } + + // TODO: for maximum support, need to also send a BINL response to + // UEFI clients, or they might ignore this ProxyDHCP response. + + if isIpxe { + resp.BootFilename = fmt.Sprintf("http://%s:%d/_/ipxe?arch=%d&mac=%s", serverIP, s.HTTPPort, mach.Arch, mach.MAC) + } else { + resp.BootServerName = serverIP.String() + resp.BootFilename = fmt.Sprintf("%d", fwtype) + } + + if fwtype == FirmwareX86PC { + // In theory, the PXE boot options are required by PXE + // clients. However, some UEFI firmwares don't actually + // support PXE properly, and will ignore ProxyDHCP responses + // that include the option. + // + // On the other hand, seemingly all firmwares support a + // variant of the protocol where option 43 is not + // provided. They behave as if option 43 had pointed them to a + // PXE boot server on port 4011 of the machine sending the + // ProxyDHCP response. Looking at TianoCore sources, I believe + // this is the BINL protocol, which is Microsoft-specific and + // lacks a specification. However, empirically, this code + // seems to work. + // + // But this code block is for classic old BIOS, which does + // behave and want option 43 to tell it how to boot. + + pxe := dhcp4.Options{ + // PXE Boot Server Discovery Control - bypass, just boot from filename. + 6: []byte{8}, + } + bs, err := pxe.Marshal() + if err != nil { + return nil, fmt.Errorf("failed to serialize PXE vendor options: %s", err) + } + resp.Options[43] = bs + } + return resp, nil +} + +func interfaceIP(intf *net.Interface) (net.IP, error) { + addrs, err := intf.Addrs() + if err != nil { + return nil, err + } + + // Try to find an IPv4 address to use, in the following order: + // global unicast (includes rfc1918), link-local unicast, + // loopback. + fs := [](func(net.IP) bool){ + net.IP.IsGlobalUnicast, + net.IP.IsLinkLocalUnicast, + net.IP.IsLoopback, + } + for _, f := range fs { + for _, a := range addrs { + ipaddr, ok := a.(*net.IPNet) + if !ok { + continue + } + ip := ipaddr.IP.To4() + if ip == nil { + continue + } + if f(ip) { + return ip, nil + } + } + } + + return nil, fmt.Errorf("interface %s has no usable server addresses", intf.Name) +} diff --git a/pixiecore/http.go b/pixiecore/http.go new file mode 100644 index 0000000..483b14a --- /dev/null +++ b/pixiecore/http.go @@ -0,0 +1,115 @@ +// Copyright 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pixiecore + +import ( + "bytes" + "fmt" + "io" + "net" + "net/http" + "net/url" + "strconv" +) + +func (s *Server) handleIpxe(w http.ResponseWriter, r *http.Request) { + args := r.URL.Query() + mac, err := net.ParseMAC(args.Get("mac")) + if err != nil { + http.Error(w, fmt.Sprintf("Invalid MAC address %q: %s\n", args.Get("mac"), err), http.StatusBadRequest) + return + } + + i, err := strconv.Atoi(args.Get("arch")) + if err != nil { + http.Error(w, fmt.Sprintf("Invalid architecture %q: %s\n", args.Get("arch"), err), http.StatusBadRequest) + return + + } + arch := Architecture(i) + switch arch { + case ArchIA32, ArchX64: + default: + http.Error(w, fmt.Sprintf("Unknown architecture %q\n", arch), http.StatusBadRequest) + return + } + + mach := Machine{ + MAC: mac, + Arch: arch, + } + spec, err := s.Booter.BootSpec(mach) + if err != nil { + // TODO: maybe don't send this error over the network? + http.Error(w, fmt.Sprintf("Error getting bootspec for %#v: %s\n", mach, err), http.StatusInternalServerError) + return + } + if spec == nil { + // TODO: consider making ipxe abort netbooting so it can fall + // through to other boot options - unsure if that's possible. + http.Error(w, fmt.Sprintf("Should not boot %q\n", mach.MAC), http.StatusServiceUnavailable) + return + } + if spec.Kernel == "" { + // TODO: maybe don't send this error over the network? + http.Error(w, fmt.Sprintf("Invalid bootspec for %q: missing kernel\n", mach.MAC), http.StatusInternalServerError) + return + } + + urlPrefix := fmt.Sprintf("http://%s/_/file?name=", r.Host) + + var b bytes.Buffer + b.WriteString("#!ipxe\n") + fmt.Fprintf(&b, "kernel --name kernel %s%s\n", urlPrefix, url.QueryEscape(string(spec.Kernel))) + for i, initrd := range spec.Initrd { + fmt.Fprintf(&b, "initrd --name initrd%d %s%s\n", i, urlPrefix, url.QueryEscape(string(initrd))) + } + b.WriteString("boot kernel ") + for i := range spec.Initrd { + fmt.Fprintf(&b, "initrd=initrd%d ", i) + } + for k, v := range spec.Cmdline { + switch val := v.(type) { + case string: + fmt.Fprintf(&b, "%s=%s ", k, val) + case int, int32, int64, uint32, uint64: + fmt.Fprintf(&b, "%s=%d ", k, v) + case ID: + fmt.Fprintf(&b, "%s=%s%s ", k, urlPrefix, url.QueryEscape(string(val))) + default: + // TODO: maybe don't send this error over the network? + http.Error(w, fmt.Sprintf("Invalid bootspec for %q: unknown cmdline type\n", mach.MAC), http.StatusInternalServerError) + return + } + } + b.WriteByte('\n') + w.Header().Set("Content-Type", "text/plain") + b.WriteTo(w) +} + +func (s *Server) handleFile(w http.ResponseWriter, r *http.Request) { + name := r.URL.Query().Get("name") + f, err := s.Booter.ReadBootFile(ID(name)) + if err != nil { + // TODO: maybe don't send this error over the network? + http.Error(w, fmt.Sprintf("%s\n", err), http.StatusInternalServerError) + return + } + defer f.Close() + if _, err = io.Copy(w, f); err != nil { + // TODO: logger + fmt.Println("Copy failed:", err) + } +} diff --git a/pixiecore/pixiecore.go b/pixiecore/pixiecore.go new file mode 100644 index 0000000..4273d70 --- /dev/null +++ b/pixiecore/pixiecore.go @@ -0,0 +1,172 @@ +// Copyright 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pixiecore + +import ( + "fmt" + "io" + "net" + "net/http" + + "go.universe.tf/netboot/dhcp4" +) + +const ( + portDHCP = 67 + portTFTP = 69 + portHTTP = 81 + portPXE = 4011 +) + +// An ID is an identifier used by Booters to reference files. +type ID string + +// Architecture describes a kind of CPU architecture. +type Architecture int + +// Architecture types that Pixiecore knows how to boot. +// +// These architectures are self-reported by the booting machine. The +// machine may support additional execution modes. For example, legacy +// PC BIOS reports itself as an ArchIA32, but may also support ArchX64 +// execution. +const ( + // ArchIA32 is a 32-bit x86 machine. It _may_ also support X64 + // execution, but Pixiecore has no way of knowing. + ArchIA32 Architecture = iota + // ArchX64 is a 64-bit x86 machine (aka amd64 aka X64). + ArchX64 +) + +// A Machine describes a machine that is attempting to boot. +type Machine struct { + MAC net.HardwareAddr + Arch Architecture +} + +// A Spec describes a kernel and associated configuration. +type Spec struct { + // The kernel to boot + Kernel ID + // Optional init ramdisks for linux kernels + Initrd []ID + // Optional kernel commandline arguments. Values can be strings, + // numbers, or IDs. IDs get translated to an HTTP(S) URL. + Cmdline map[string]interface{} + // Message to print on the client machine before booting. + Message string +} + +// A Booter provides boot instructions and files for machines. +// +// Due to the stateless nature of various boot protocols, BootSpec() +// will be called multiple times in the course of a single boot +// attempt. +type Booter interface { + // The given MAC address wants to know what it should + // boot. What should Pixiecore make it boot? + // + // Returning an error or a nil BootSpec will make Pixiecore ignore + // the client machine's request. + BootSpec(m Machine) (*Spec, error) + // Get the bytes corresponding to an ID given in Spec. + ReadBootFile(id ID) (io.ReadCloser, error) + // Write the given Reader to an ID given in Spec. + WriteBootFile(id ID, body io.Reader) error +} + +// Firmware describes a kind of firmware attempting to boot. +// +// This should only be used for selecting the right bootloader within +// Pixiecore, kernel selection should key off the more generic +// Architecture. +type Firmware int + +// The bootloaders that Pixiecore knows how to handle. +const ( + // Note the values match the values from RFC4578. + FirmwareX86PC Firmware = 0 // "Classic" x86 BIOS with PXE/UNDI support. + FirmwareEFI32 = 6 // 32-bit x86 processor running EFI + FirmwareEFI64 = 7 // 64-bit x86 processor running EFI +) + +var fwToArch = map[Firmware]Architecture{ + FirmwareX86PC: ArchIA32, + FirmwareEFI32: ArchIA32, + FirmwareEFI64: ArchX64, +} + +// A Server boots machines using a Booter. +type Server struct { + Booter Booter + + // Address to listen on, or empty for all interfaces. + Address string + HTTPPort int + + // Ipxe lists the supported bootable Firmwares, and their + // associated ipxe binary. + Ipxe map[Firmware][]byte + + // These ports can technically be set for testing, but the + // protocols burned in firmware on the client side hardcode these, + // so if you change them in production, nothing will work. + DHCPPort int + TFTPPort int + PXEPort int +} + +// Serve listens for machines attempting to boot, and uses Booter to +// help them. +func (s *Server) Serve() error { + if s.DHCPPort == 0 { + s.DHCPPort = portDHCP + } + if s.TFTPPort == 0 { + s.TFTPPort = portTFTP + } + if s.PXEPort == 0 { + s.PXEPort = portPXE + } + if s.HTTPPort == 0 { + s.HTTPPort = portHTTP + } + + dhcp, err := dhcp4.NewConn(fmt.Sprintf("%s:%d", s.Address, s.DHCPPort)) + if err != nil { + return err + } + tftp, err := net.ListenPacket("udp", fmt.Sprintf("%s:%d", s.Address, s.TFTPPort)) + if err != nil { + dhcp.Close() + return err + } + pxe, err := net.ListenPacket("udp", fmt.Sprintf("%s:%d", s.Address, s.PXEPort)) + if err != nil { + dhcp.Close() + tftp.Close() + return err + } + + go s.serveDHCP(dhcp) + go s.servePXE(pxe) + go s.serveTFTP(tftp) + fmt.Println("Ready!") + + http.HandleFunc("/_/ipxe", s.handleIpxe) + http.HandleFunc("/_/file", s.handleFile) + http.ListenAndServe(fmt.Sprintf("%s:%d", s.Address, s.HTTPPort), nil) + select {} +} diff --git a/pixiecore/pxe.go b/pixiecore/pxe.go new file mode 100644 index 0000000..bb280d2 --- /dev/null +++ b/pixiecore/pxe.go @@ -0,0 +1,147 @@ +// Copyright 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pixiecore + +import ( + "errors" + "fmt" + "net" + + "go.universe.tf/netboot/dhcp4" + "golang.org/x/net/ipv4" +) + +func (s *Server) servePXE(conn net.PacketConn) { + buf := make([]byte, 1024) + l := ipv4.NewPacketConn(conn) + if err := l.SetControlMessage(ipv4.FlagInterface, true); err != nil { + fmt.Println(err) + return + } + + for { + n, msg, addr, err := l.ReadFrom(buf) + if err != nil { + fmt.Println(err) + return + } + + pkt, err := dhcp4.Unmarshal(buf[:n]) + if err != nil { + fmt.Println("not a DHCP packet") + continue + } + + fwtype, err := s.validatePXE(pkt) + if err != nil { + fmt.Println(err) + continue + } + + intf, err := net.InterfaceByIndex(msg.IfIndex) + if err != nil { + fmt.Println(err) + continue + } + + serverIP, err := interfaceIP(intf) + if err != nil { + fmt.Printf("Couldn't find an IP address to use to reply to %s: %s\n", addr, err) + continue + } + + resp, err := s.offerPXE(pkt, serverIP, fwtype) + if err != nil { + fmt.Println(err) + continue + } + + bs, err := resp.Marshal() + if err != nil { + fmt.Println(err) + } + + if _, err := l.WriteTo(bs, &ipv4.ControlMessage{ + IfIndex: msg.IfIndex, + }, addr); err != nil { + fmt.Println(err) + } + } +} + +func (s *Server) validatePXE(pkt *dhcp4.Packet) (fwtype Firmware, err error) { + if pkt.Type != dhcp4.MsgRequest { + return 0, errors.New("not DHCPREQUEST") + } + + fwt, err := pkt.Options.Uint16(93) + if err != nil { + return 0, fmt.Errorf("malformed arch: %s", err) + } + fwtype = Firmware(fwt) + if s.Ipxe[fwtype] == nil { + return 0, fmt.Errorf("unsupported firmware type %d", fwtype) + } + + guid := pkt.Options[97] + switch len(guid) { + case 0: + // A missing GUID is invalid according to the spec, however + // there are PXE ROMs in the wild that omit the GUID and still + // expect to boot. + case 17: + if guid[0] != 0 { + return 0, errors.New("malformed GUID (leading byte must be zero)") + } + default: + return 0, errors.New("malformed GUID (wrong size)") + } + + return fwtype, nil +} + +func (s *Server) offerPXE(pkt *dhcp4.Packet, serverIP net.IP, fwtype Firmware) (resp *dhcp4.Packet, err error) { + resp = &dhcp4.Packet{ + Type: dhcp4.MsgAck, + TransactionID: pkt.TransactionID, + //Broadcast: true, + HardwareAddr: pkt.HardwareAddr, + ClientAddr: pkt.ClientAddr, + RelayAddr: pkt.RelayAddr, + ServerAddr: serverIP, + BootServerName: serverIP.String(), + BootFilename: fmt.Sprintf("%d", fwtype), + Options: dhcp4.Options{ + dhcp4.OptServerIdentifier: serverIP, + dhcp4.OptVendorIdentifier: []byte("PXEClient"), + //dhcp4.OptTFTPServer: []byte(serverIP.String()), + //dhcp4.OptBootFile: []byte(fmt.Sprintf("%d", fwtype)), + }, + } + if pkt.Options[97] != nil { + resp.Options[97] = pkt.Options[97] + } + // pxe := dhcp4.Options{ + // // PXE Boot Server Discovery Control - bypass, just boot from filename. + // 6: []byte{8}, + // } + // bs, err := pxe.Marshal() + // if err != nil { + // return nil, fmt.Errorf("failed to serialize PXE vendor options: %s", err) + // } + // resp.Options[43] = bs + + return resp, nil +} diff --git a/pixiecore/tftp.go b/pixiecore/tftp.go new file mode 100644 index 0000000..60d3f92 --- /dev/null +++ b/pixiecore/tftp.go @@ -0,0 +1,50 @@ +// Copyright 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pixiecore + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "strconv" + + "go.universe.tf/netboot/tftp" +) + +func (s *Server) serveTFTP(l net.PacketConn) { + ts := tftp.Server{ + Handler: s.handleTFTP, + InfoLog: func(msg string) { fmt.Println(msg) }, + } + err := ts.Serve(l) + fmt.Printf("Serving TFTP failed: %s\n", err) +} + +func (s *Server) handleTFTP(path string, clientAddr net.Addr) (io.ReadCloser, int64, error) { + i, err := strconv.Atoi(path) + if err != nil { + return nil, 0, errors.New("not found") + } + + bs, ok := s.Ipxe[Firmware(i)] + if !ok { + return nil, 0, errors.New("not found") + } + + return ioutil.NopCloser(bytes.NewBuffer(bs)), int64(len(bs)), nil +}