350 lines
9.8 KiB
Go
350 lines
9.8 KiB
Go
|
|
package eth
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"encoding/json"
|
|||
|
|
"fmt"
|
|||
|
|
"gopkg.in/yaml.v3"
|
|||
|
|
"io"
|
|||
|
|
"log"
|
|||
|
|
"net"
|
|||
|
|
"net/http"
|
|||
|
|
"os"
|
|||
|
|
"os/exec"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// NetplanConfig структура для представления YAML файла
|
|||
|
|
type NetplanConfig struct {
|
|||
|
|
Network Network `yaml:"network" json:"network"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type Network struct {
|
|||
|
|
Version int `yaml:"version" json:"version"`
|
|||
|
|
Renderer string `yaml:"renderer" json:"renderer"`
|
|||
|
|
Ethernets Ethernets `yaml:"ethernets" json:"ethernets"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type Ethernets struct {
|
|||
|
|
Eth0 Eth0Config `yaml:"eth0" json:"eth0"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type Eth0Config struct {
|
|||
|
|
DHCP4 *bool `yaml:"dhcp4,omitempty" json:"dhcp4"` // Используем указатель, чтобы различать "false" и отсутствие ключа
|
|||
|
|
Addresses []string `yaml:"addresses,omitempty" json:"addresses"`
|
|||
|
|
Gateway4 string `yaml:"gateway4,omitempty" json:"gateway4,omitempty"`
|
|||
|
|
Nameservers Nameservers `yaml:"nameservers,omitempty" json:"nameservers"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type Nameservers struct {
|
|||
|
|
Addresses []string `yaml:"addresses,omitempty" json:"addresses"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const netplanFilePath = "/etc/netplan/eth.yaml" // Путь к файлу netplan (для примера, лучше использовать /etc/netplan/eth.yaml)
|
|||
|
|
|
|||
|
|
func NetplanHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
|
switch r.Method {
|
|||
|
|
case http.MethodGet:
|
|||
|
|
getNetplanConfig(w, r)
|
|||
|
|
case http.MethodPut:
|
|||
|
|
updateNetplanConfig(w, r)
|
|||
|
|
default:
|
|||
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func getNetplanConfig(w http.ResponseWriter, r *http.Request) {
|
|||
|
|
config, err := readNetplanConfig()
|
|||
|
|
if err != nil {
|
|||
|
|
http.Error(w, fmt.Sprintf("Failed to read config: %v", err), http.StatusInternalServerError)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
json.NewEncoder(w).Encode(config)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func updateNetplanConfig(w http.ResponseWriter, r *http.Request) {
|
|||
|
|
var updatedConfig NetplanConfig
|
|||
|
|
err := json.NewDecoder(r.Body).Decode(&updatedConfig)
|
|||
|
|
if err != nil {
|
|||
|
|
http.Error(w, fmt.Sprintf("Failed to decode request body: %v", err), http.StatusBadRequest)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
err = writeNetplanConfig(updatedConfig)
|
|||
|
|
if err != nil {
|
|||
|
|
http.Error(w, fmt.Sprintf("Failed to write config: %v", err), http.StatusInternalServerError)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
w.WriteHeader(http.StatusOK)
|
|||
|
|
w.Write([]byte("Netplan configuration updated successfully"))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func readNetplanConfig() (*NetplanConfig, error) {
|
|||
|
|
yamlFile, err := os.ReadFile(netplanFilePath)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, fmt.Errorf("failed to read netplan file: %w", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var config NetplanConfig
|
|||
|
|
err = yaml.Unmarshal(yamlFile, &config)
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, fmt.Errorf("failed to unmarshal yaml: %w", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return &config, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func writeNetplanConfig(config NetplanConfig) error {
|
|||
|
|
savedConfig, err := readNetplanConfig()
|
|||
|
|
if err != nil {
|
|||
|
|
return fmt.Errorf("failed to read eth yaml: %w", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
yamlData, err := yaml.Marshal(config)
|
|||
|
|
if err != nil {
|
|||
|
|
return fmt.Errorf("failed to marshal yaml: %w", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
err = os.WriteFile(netplanFilePath, yamlData, 0644)
|
|||
|
|
if err != nil {
|
|||
|
|
return fmt.Errorf("failed to write netplan file: %w", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
cmd := exec.Command("netplan", "apply")
|
|||
|
|
output, err := cmd.CombinedOutput()
|
|||
|
|
if err != nil {
|
|||
|
|
|
|||
|
|
yamlData, err := yaml.Marshal(*savedConfig)
|
|||
|
|
if err != nil {
|
|||
|
|
return fmt.Errorf("failed to marshal yaml: %w", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
err = os.WriteFile(netplanFilePath, yamlData, 0644)
|
|||
|
|
if err != nil {
|
|||
|
|
return fmt.Errorf("failed to write netplan file: %w", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return fmt.Errorf("Ошибка при выполнении команды: %s\n Вывод команды: %s\n", err, output)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func RawNetplanHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
|
switch r.Method {
|
|||
|
|
case http.MethodGet:
|
|||
|
|
getRawNetplanConfig(w, r)
|
|||
|
|
case http.MethodPut:
|
|||
|
|
updateRawNetplanConfig(w, r)
|
|||
|
|
default:
|
|||
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func getRawNetplanConfig(w http.ResponseWriter, r *http.Request) {
|
|||
|
|
content, err := readRawNetplanConfig()
|
|||
|
|
if err != nil {
|
|||
|
|
http.Error(w, fmt.Sprintf("Failed to read raw config: %v", err), http.StatusInternalServerError)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
w.Header().Set("Content-Type", "text/plain")
|
|||
|
|
w.Write([]byte(content))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func updateRawNetplanConfig(w http.ResponseWriter, r *http.Request) {
|
|||
|
|
body, err := io.ReadAll(r.Body)
|
|||
|
|
if err != nil {
|
|||
|
|
http.Error(w, fmt.Sprintf("Failed to read request body: %v", err), http.StatusBadRequest)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
rawYamlContent := string(body)
|
|||
|
|
|
|||
|
|
// Валидация YAML перед записью
|
|||
|
|
if !isValidYAML(rawYamlContent) {
|
|||
|
|
http.Error(w, "Invalid YAML content", http.StatusBadRequest)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
err = writeRawNetplanConfig(rawYamlContent)
|
|||
|
|
if err != nil {
|
|||
|
|
http.Error(w, fmt.Sprintf("Failed to write raw config: %v", err), http.StatusInternalServerError)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
w.WriteHeader(http.StatusOK)
|
|||
|
|
w.Write([]byte("Raw Netplan configuration updated successfully"))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func readRawNetplanConfig() (string, error) {
|
|||
|
|
yamlFile, err := os.ReadFile(netplanFilePath)
|
|||
|
|
if err != nil {
|
|||
|
|
return "", fmt.Errorf("failed to read netplan file: %w", err)
|
|||
|
|
}
|
|||
|
|
return string(yamlFile), nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func writeRawNetplanConfig(content string) error {
|
|||
|
|
err := os.WriteFile(netplanFilePath, []byte(content), 0644)
|
|||
|
|
if err != nil {
|
|||
|
|
return fmt.Errorf("failed to write netplan file: %w", err)
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// isValidYAML проверяет, является ли строка валидным YAML
|
|||
|
|
func isValidYAML(yamlString string) bool {
|
|||
|
|
var temp map[interface{}]interface{}
|
|||
|
|
err := yaml.Unmarshal([]byte(yamlString), &temp)
|
|||
|
|
return err == nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Функция для создания тестового файла eth.yaml (для демонстрации)
|
|||
|
|
func createTestNetplanFile() {
|
|||
|
|
initialYaml := `
|
|||
|
|
network:
|
|||
|
|
version: 2
|
|||
|
|
renderer: networkd
|
|||
|
|
ethernets:
|
|||
|
|
eth0:
|
|||
|
|
dhcp4: yes
|
|||
|
|
addresses: [192.168.8.111/24]
|
|||
|
|
nameservers:
|
|||
|
|
addresses: [8.8.8.8, 8.8.4.4]
|
|||
|
|
`
|
|||
|
|
err := os.WriteFile(netplanFilePath, []byte(initialYaml), 0644)
|
|||
|
|
if err != nil {
|
|||
|
|
log.Fatalf("Failed to create test netplan file: %v", err)
|
|||
|
|
}
|
|||
|
|
fmt.Println("Test netplan file created at:", netplanFilePath)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func init() {
|
|||
|
|
// Создаем тестовый файл при запуске сервера, если его нет (для демонстрации)
|
|||
|
|
if _, err := os.Stat(netplanFilePath); os.IsNotExist(err) {
|
|||
|
|
createTestNetplanFile()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// MacAddressResponse структура для JSON ответа
|
|||
|
|
type MacAddressResponse struct {
|
|||
|
|
MacAddress string `json:"mac_address"`
|
|||
|
|
Interface string `json:"interface"`
|
|||
|
|
Error string `json:"error,omitempty"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// MacAdres HTTP handler для получения MAC адреса интерфейса eth0
|
|||
|
|
func MacAdres(w http.ResponseWriter, r *http.Request) {
|
|||
|
|
interfaces, err := net.Interfaces()
|
|||
|
|
if err != nil {
|
|||
|
|
response := MacAddressResponse{
|
|||
|
|
Error: fmt.Sprintf("Failed to get network interfaces: %v", err),
|
|||
|
|
}
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
|
|||
|
|
json.NewEncoder(w).Encode(response)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var eth0Interface *net.Interface
|
|||
|
|
for _, iface := range interfaces {
|
|||
|
|
if iface.Name == "eth0" {
|
|||
|
|
eth0Interface = &iface
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if eth0Interface == nil {
|
|||
|
|
response := MacAddressResponse{
|
|||
|
|
Error: "Interface eth0 not found",
|
|||
|
|
Interface: "eth0",
|
|||
|
|
}
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.WriteHeader(http.StatusNotFound) // 404 Not Found
|
|||
|
|
json.NewEncoder(w).Encode(response)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
macAddress := eth0Interface.HardwareAddr.String()
|
|||
|
|
|
|||
|
|
response := MacAddressResponse{
|
|||
|
|
MacAddress: macAddress,
|
|||
|
|
Interface: "eth0",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.WriteHeader(http.StatusOK)
|
|||
|
|
json.NewEncoder(w).Encode(response)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// InterfaceIPsResponse структура для JSON ответа со списком IP адресов интерфейса
|
|||
|
|
type InterfaceIPsResponse struct {
|
|||
|
|
Interface string `json:"interface"`
|
|||
|
|
IPAddresses []string `json:"ip_addresses"`
|
|||
|
|
Error string `json:"error,omitempty"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// InterfaceIPs HTTP handler для получения списка IP адресов интерфейса eth0
|
|||
|
|
func InterfaceIPs(w http.ResponseWriter, r *http.Request) {
|
|||
|
|
interfaces, err := net.Interfaces()
|
|||
|
|
if err != nil {
|
|||
|
|
response := InterfaceIPsResponse{
|
|||
|
|
Error: fmt.Sprintf("Failed to get network interfaces: %v", err),
|
|||
|
|
}
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
|
|||
|
|
json.NewEncoder(w).Encode(response)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var eth0Interface *net.Interface
|
|||
|
|
for _, iface := range interfaces {
|
|||
|
|
if iface.Name == "eth0" {
|
|||
|
|
eth0Interface = &iface
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if eth0Interface == nil {
|
|||
|
|
response := InterfaceIPsResponse{
|
|||
|
|
Interface: "eth0",
|
|||
|
|
Error: "Interface eth0 not found",
|
|||
|
|
}
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.WriteHeader(http.StatusNotFound) // 404 Not Found
|
|||
|
|
json.NewEncoder(w).Encode(response)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
addrs, err := eth0Interface.Addrs()
|
|||
|
|
if err != nil {
|
|||
|
|
response := InterfaceIPsResponse{
|
|||
|
|
Interface: "eth0",
|
|||
|
|
Error: fmt.Sprintf("Failed to get addresses for eth0: %v", err),
|
|||
|
|
}
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.WriteHeader(http.StatusInternalServerError) // 500 Internal Server Error
|
|||
|
|
json.NewEncoder(w).Encode(response)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ipAddresses := make([]string, 0)
|
|||
|
|
for _, addr := range addrs {
|
|||
|
|
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { // Проверяем, что это IPNet и не loopback
|
|||
|
|
if ipnet.IP.To4() != nil || ipnet.IP.To16() != nil { // Проверяем, что это IPv4 или IPv6
|
|||
|
|
ipAddresses = append(ipAddresses, ipnet.IP.String())
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
response := InterfaceIPsResponse{
|
|||
|
|
Interface: "eth0",
|
|||
|
|
IPAddresses: ipAddresses,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.WriteHeader(http.StatusOK) // 200 OK
|
|||
|
|
json.NewEncoder(w).Encode(response)
|
|||
|
|
}
|