mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-11-05 07:21:36 +08:00
152 lines
4.3 KiB
Go
152 lines
4.3 KiB
Go
// Copyright 2019 Yunion
|
|
//
|
|
// 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 pxe
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
|
|
"yunion.io/x/log"
|
|
|
|
"yunion.io/x/onecloud/pkg/baremetal/options"
|
|
"yunion.io/x/onecloud/pkg/util/tftp"
|
|
)
|
|
|
|
var (
|
|
PxeLinuxCfgPattern = `^pxelinux.cfg/01-(?P<mac>([0-9a-f]{2}-){5}[0-9a-f]{2})$`
|
|
)
|
|
|
|
type TFTPHandler struct {
|
|
RootDir string
|
|
BaremetalManager IBaremetalManager
|
|
}
|
|
|
|
func NewTFTPHandler(rootDir string, baremetalManager IBaremetalManager) (*TFTPHandler, error) {
|
|
if _, err := os.Stat(rootDir); err != nil {
|
|
return nil, fmt.Errorf("TFTP root dir %q stat error: %v", rootDir, err)
|
|
}
|
|
return &TFTPHandler{
|
|
RootDir: rootDir,
|
|
BaremetalManager: baremetalManager,
|
|
}, nil
|
|
}
|
|
|
|
// Handle is called when client starts file download from server
|
|
func (h *TFTPHandler) Handle(filename string, clientAddr net.Addr) (io.ReadCloser, int64, error) {
|
|
regEx := regexp.MustCompile(PxeLinuxCfgPattern)
|
|
matches := regEx.FindStringSubmatch(filename)
|
|
|
|
if len(matches) != 0 {
|
|
paramsMap := make(map[string]string)
|
|
// pxelinux config matched
|
|
for i, name := range regEx.SubexpNames() {
|
|
if i > 0 && i <= len(matches) {
|
|
paramsMap[name] = matches[i]
|
|
}
|
|
}
|
|
mac, ok := paramsMap["mac"]
|
|
if !ok {
|
|
return nil, 0, fmt.Errorf("request filename %q not found mac pattern", filename)
|
|
}
|
|
macAddr, err := net.ParseMAC(mac)
|
|
if err != nil {
|
|
return nil, 0, fmt.Errorf("Parse mac string %q error: %v", mac, err)
|
|
}
|
|
return h.sendPxeLinuxCfgResponse(macAddr, clientAddr)
|
|
}
|
|
return h.sendFile(filename, clientAddr)
|
|
}
|
|
|
|
func (h *TFTPHandler) sendPxeLinuxCfgResponse(mac net.HardwareAddr, _ net.Addr) (io.ReadCloser, int64, error) {
|
|
log.Debugf("[TFTP] client mac: %s", mac)
|
|
bmInstance := h.BaremetalManager.GetBaremetalByMac(mac)
|
|
if bmInstance == nil {
|
|
err := fmt.Errorf("Not found baremetal instance by mac: %s", mac)
|
|
log.Errorf("Get baremetal error: %v", err)
|
|
return nil, 0, err
|
|
}
|
|
respStr := bmInstance.GetTFTPResponse()
|
|
log.Debugf("[TFTP] get tftp response config: %s", respStr)
|
|
bs := []byte(respStr)
|
|
size := int64(len(bs))
|
|
buffer := bytes.NewBufferString(respStr)
|
|
|
|
return ioutil.NopCloser(buffer), size, nil
|
|
}
|
|
|
|
func (h *TFTPHandler) sendFile(filename string, _ net.Addr) (io.ReadCloser, int64, error) {
|
|
filename = h.getFilePath(filename)
|
|
|
|
st, err := os.Stat(filename)
|
|
if err != nil {
|
|
log.Errorf("TFTP stat file %q error: %v", filename, err)
|
|
return nil, 0, err
|
|
}
|
|
if !st.Mode().IsRegular() {
|
|
return nil, 0, fmt.Errorf("requested path %q is not a file", filename)
|
|
}
|
|
|
|
file, err := os.Open(filename)
|
|
if err != nil {
|
|
log.Errorf("TFTP open file %q error: %v", filename, err)
|
|
return nil, 0, err
|
|
}
|
|
return file, st.Size(), err
|
|
}
|
|
|
|
func (h *TFTPHandler) getFilePath(fileName string) string {
|
|
return filepath.Join(h.RootDir, fileName)
|
|
}
|
|
|
|
func (h *TFTPHandler) transferLog(clientAddr net.Addr, path string, err error) {
|
|
log.Debugf("TFTP transfer log clientAddr: %s, path: %s, error: %v", clientAddr, path, err)
|
|
}
|
|
|
|
func (s *Server) serveTFTP(l net.PacketConn, handler *TFTPHandler) error {
|
|
bindDial := func(network, addr string) (net.Conn, error) {
|
|
localIp := l.LocalAddr().(*net.UDPAddr).IP
|
|
remoteAddr, err := net.ResolveUDPAddr("udp", addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
log.Debugf("[TFTP] dial udp localAddr %s, remoteAddr: %s", localIp, remoteAddr.String())
|
|
return net.DialUDP("udp", &net.UDPAddr{
|
|
IP: localIp,
|
|
Port: 0, // random free port
|
|
}, remoteAddr)
|
|
}
|
|
|
|
ts := tftp.Server{
|
|
Handler: handler.Handle,
|
|
InfoLog: func(msg string) { log.Debugf("TFTP msg: %s", msg) },
|
|
TransferLog: handler.transferLog,
|
|
Dial: bindDial,
|
|
|
|
MaxBlockSize: int64(options.Options.TftpBlockSizeInBytes),
|
|
WriteAttempts: options.Options.TftpMaxTimeoutRetries,
|
|
}
|
|
err := ts.Serve(l)
|
|
if err != nil {
|
|
return fmt.Errorf("TFTP server shut down: %v", err)
|
|
}
|
|
return nil
|
|
}
|